Merge "Snap for 5410605 from ede286b12e0f253fde1f22758b1c6b62f81434a7 to androidx-master-release" into androidx-master-release
diff --git a/OWNERS b/OWNERS
index e4a4a30..091f1e7 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,7 @@
adamp@google.com
alanv@google.com
+asfalcone@google.com
+ashleyrose@google.com
aurimas@google.com
ccraik@google.com
clarabayarri@google.com
@@ -7,11 +9,12 @@
jeffrygaston@google.com
kirillg@google.com
mount@google.com
+nickanthony@google.com
+obenabde@google.com
pavlis@google.com
romainguy@android.com
sergeyv@google.com
shepshapard@google.com
+sjgilbert@google.com
sumir@google.com
yboyar@google.com
-obenabde@google.com
-nickanthony@google.com
diff --git a/README.md b/README.md
index d824742..17f8fed 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,12 @@
## Using Android Studio
-Open `path/to/checkout/frameworks/support/` in Android Studio. Now you're ready edit, run, and test!
+To open the project with the specific version of Android Studio recommended for developing:
+
+ cd path/to/checkout/frameworks/support/
+ ./studiow
+
+and accept the license agreement when prompted. Now you're ready edit, run, and test!
If you get “Unregistered VCS root detected” click “Add root” to enable git integration for Android Studio.
diff --git a/activity/api/1.0.0-alpha06.txt b/activity/api/1.0.0-alpha06.txt
new file mode 100644
index 0000000..16e509e0
--- /dev/null
+++ b/activity/api/1.0.0-alpha06.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.activity {
+
+ public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
+ ctor public ComponentActivity();
+ ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ method @Deprecated public Object? getLastCustomNonConfigurationInstance();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+ method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
+ method public final Object? onRetainNonConfigurationInstance();
+ method @Deprecated public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ }
+
+ public interface OnBackPressedCallback {
+ method public boolean handleOnBackPressed();
+ }
+
+ public final class OnBackPressedDispatcher {
+ method public androidx.arch.core.util.Cancellable addCallback(androidx.activity.OnBackPressedCallback);
+ method public androidx.arch.core.util.Cancellable addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ method public boolean onBackPressed();
+ }
+
+}
+
diff --git a/activity/api/current.txt b/activity/api/current.txt
index 1d5064e..16e509e0 100644
--- a/activity/api/current.txt
+++ b/activity/api/current.txt
@@ -3,20 +3,28 @@
public class ComponentActivity extends androidx.core.app.ComponentActivity implements androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner androidx.lifecycle.ViewModelStoreOwner {
ctor public ComponentActivity();
- method public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
- method public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ ctor @ContentView public ComponentActivity(@LayoutRes int);
+ method @Deprecated public void addOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ method @Deprecated public void addOnBackPressedCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
method @Deprecated public Object? getLastCustomNonConfigurationInstance();
method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
method public androidx.lifecycle.ViewModelStore getViewModelStore();
method @Deprecated public Object? onRetainCustomNonConfigurationInstance();
method public final Object? onRetainNonConfigurationInstance();
- method public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
+ method @Deprecated public void removeOnBackPressedCallback(androidx.activity.OnBackPressedCallback);
}
public interface OnBackPressedCallback {
method public boolean handleOnBackPressed();
}
+ public final class OnBackPressedDispatcher {
+ method public androidx.arch.core.util.Cancellable addCallback(androidx.activity.OnBackPressedCallback);
+ method public androidx.arch.core.util.Cancellable addCallback(androidx.lifecycle.LifecycleOwner, androidx.activity.OnBackPressedCallback);
+ method public boolean onBackPressed();
+ }
+
}
diff --git a/activity/api/res-1.0.0-alpha06.txt b/activity/api/res-1.0.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/api/res-1.0.0-alpha06.txt
diff --git a/activity/api/restricted_1.0.0-alpha06.txt b/activity/api/restricted_1.0.0-alpha06.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/activity/api/restricted_1.0.0-alpha06.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/activity/build.gradle b/activity/build.gradle
index f687bc8..96270d9 100644
--- a/activity/build.gradle
+++ b/activity/build.gradle
@@ -15,6 +15,7 @@
dependencies {
api(project(":annotation"))
+ api(project(":arch:core-common"))
api(project(":core")) {
exclude group: 'androidx.annotation'
exclude group: 'com.google.guava', module: 'listenablefuture'
diff --git a/activity/ktx/api/1.0.0-alpha06.txt b/activity/ktx/api/1.0.0-alpha06.txt
new file mode 100644
index 0000000..be35c63
--- /dev/null
+++ b/activity/ktx/api/1.0.0-alpha06.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.activity {
+
+ public final class ActivityViewModelLazyKt {
+ ctor public ActivityViewModelLazyKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! viewModels(androidx.activity.ComponentActivity, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
+ }
+
+}
+
diff --git a/activity/ktx/api/res-1.0.0-alpha06.txt b/activity/ktx/api/res-1.0.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/activity/ktx/api/res-1.0.0-alpha06.txt
diff --git a/activity/ktx/api/restricted_1.0.0-alpha06.txt b/activity/ktx/api/restricted_1.0.0-alpha06.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/activity/ktx/api/restricted_1.0.0-alpha06.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/activity/ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt b/activity/ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
index fe8d0b4..2b7b97f 100644
--- a/activity/ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
+++ b/activity/ktx/src/main/java/androidx/activity/ActivityViewModelLazy.kt
@@ -48,5 +48,5 @@
AndroidViewModelFactory.getInstance(application)
}
- return ViewModelLazy(VM::class, { this }, factoryPromise)
+ return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise)
}
diff --git a/activity/src/androidTest/AndroidManifest.xml b/activity/src/androidTest/AndroidManifest.xml
index 9eb99d5..4d2f379 100644
--- a/activity/src/androidTest/AndroidManifest.xml
+++ b/activity/src/androidTest/AndroidManifest.xml
@@ -23,7 +23,6 @@
<activity android:name="androidx.activity.EagerOverrideLifecycleComponentActivity"/>
<activity android:name="androidx.activity.LazyOverrideLifecycleComponentActivity"/>
<activity android:name="androidx.activity.ViewModelActivity"/>
- <activity android:name="androidx.activity.OnBackPressedComponentActivity"/>
<activity android:name="androidx.activity.SavedStateActivity"/>
<activity android:name="androidx.activity.ContentViewActivity"/>
<activity android:name="androidx.activity.AutoRestarterActivity"/>
diff --git a/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt b/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt
deleted file mode 100644
index 396947f..0000000
--- a/activity/src/androidTest/java/androidx/activity/ComponentActivityOnBackPressedTest.kt
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.activity
-
-import androidx.lifecycle.GenericLifecycleObserver
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
-import androidx.test.annotation.UiThreadTest
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
-import java.util.concurrent.CountDownLatch
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ComponentActivityOnBackPressedTest {
-
- @get:Rule
- val activityRule = ActivityTestRule(OnBackPressedComponentActivity::class.java)
-
- @UiThreadTest
- @Test
- fun testAddOnBackPressedListener() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.onBackPressed()
- assertWithMessage("Count should be incremented after handleOnBackPressed")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
- }
-
- @UiThreadTest
- @Test
- fun testRemoveOnBackPressedListener() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.onBackPressed()
- assertWithMessage("Count should be incremented after handleOnBackPressed")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
-
- activity.removeOnBackPressedCallback(onBackPressedCallback)
- activity.onBackPressed()
- // Check that the count still equals 1
- assertWithMessage("Count shouldn't be incremented after removal")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
- }
-
- @UiThreadTest
- @Test
- fun testMultipleCalls() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.onBackPressed()
- activity.onBackPressed()
- assertWithMessage("Count should be incremented after each handleOnBackPressed")
- .that(onBackPressedCallback.count)
- .isEqualTo(2)
- }
-
- @UiThreadTest
- @Test
- fun testMostRecentGetsPriority() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
- val mostRecentOnBackPressedCallback = CountingOnBackPressedCallback()
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.addOnBackPressedCallback(mostRecentOnBackPressedCallback)
- activity.onBackPressed()
- assertWithMessage("Most recent callback should be incremented")
- .that(mostRecentOnBackPressedCallback.count)
- .isEqualTo(1)
- assertWithMessage("Only the most recent callback should be incremented")
- .that(onBackPressedCallback.count)
- .isEqualTo(0)
- }
-
- @UiThreadTest
- @Test
- fun testPassthroughListener() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
- val passThroughOnBackPressedCallback = CountingOnBackPressedCallback(returnValue = false)
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.addOnBackPressedCallback(passThroughOnBackPressedCallback)
- activity.onBackPressed()
- assertWithMessage("Most recent callback should be incremented")
- .that(passThroughOnBackPressedCallback.count)
- .isEqualTo(1)
- assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
- "return false")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
- }
-
- @UiThreadTest
- @Test
- fun testLifecycleCallback() {
- val activity = activityRule.activity
-
- val onBackPressedCallback = CountingOnBackPressedCallback()
- val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
- val lifecycleOwner = object : LifecycleOwner {
- val lifecycleRegistry = LifecycleRegistry(this)
-
- override fun getLifecycle() = lifecycleRegistry
- }
-
- activity.addOnBackPressedCallback(onBackPressedCallback)
- activity.addOnBackPressedCallback(lifecycleOwner, lifecycleOnBackPressedCallback)
- activity.onBackPressed()
- assertWithMessage("Non-started callbacks shouldn't have their count incremented")
- .that(lifecycleOnBackPressedCallback.count)
- .isEqualTo(0)
- assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
- "aren't started")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
-
- // Now start the Lifecycle
- lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
- activity.onBackPressed()
- assertWithMessage("Once the callbacks is started, the count should increment")
- .that(lifecycleOnBackPressedCallback.count)
- .isEqualTo(1)
- assertWithMessage("Only the most recent callback should be incremented")
- .that(onBackPressedCallback.count)
- .isEqualTo(1)
-
- // Now stop the Lifecycle
- lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
- activity.onBackPressed()
- assertWithMessage("Non-started callbacks shouldn't have their count incremented")
- .that(lifecycleOnBackPressedCallback.count)
- .isEqualTo(1)
- assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
- "aren't started")
- .that(onBackPressedCallback.count)
- .isEqualTo(2)
-
- // Now destroy the Lifecycle
- lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- @Suppress("INACCESSIBLE_TYPE")
- assertWithMessage("onDestroy should trigger the removal of any associated callbacks")
- .that(activity.mOnBackPressedCallbacks)
- .hasSize(1)
- activity.onBackPressed()
- assertWithMessage("Non-started callbacks shouldn't have their count incremented")
- .that(lifecycleOnBackPressedCallback.count)
- .isEqualTo(1)
- assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
- "aren't started")
- .that(onBackPressedCallback.count)
- .isEqualTo(3)
- }
-}
-
-class CountingOnBackPressedCallback(val returnValue: Boolean = true) :
- OnBackPressedCallback {
- var count = 0
-
- override fun handleOnBackPressed(): Boolean {
- count++
- return returnValue
- }
-}
-
-class OnBackPressedComponentActivity : ComponentActivity() {
- val activityCallbackLifecycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java)
- val lifecycleObserver: GenericLifecycleObserver = mock(GenericLifecycleObserver::class.java)
- val destroyCountDownLatch = CountDownLatch(1)
-
- init {
- lifecycle.addObserver(lifecycleObserver)
- }
-
- override fun onDestroy() {
- lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner,
- Lifecycle.Event.ON_DESTROY)
- super.onDestroy()
- destroyCountDownLatch.countDown()
- }
-}
diff --git a/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt b/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
index be43231..eff215c 100644
--- a/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
+++ b/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
@@ -18,7 +18,6 @@
import android.widget.TextView
import androidx.activity.test.R
-import androidx.annotation.ContentView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.rule.ActivityTestRule
@@ -43,5 +42,4 @@
}
}
-@ContentView(R.layout.activity_inflates_res)
-class ContentViewActivity : ComponentActivity()
+class ContentViewActivity : ComponentActivity(R.layout.activity_inflates_res)
diff --git a/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt b/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt
new file mode 100644
index 0000000..ca8fcdc
--- /dev/null
+++ b/activity/src/androidTest/java/androidx/activity/OnBackPressedDispatcherTest.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.activity
+
+import androidx.arch.core.util.Cancellable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class OnBackPressedHandlerTest {
+
+ lateinit var dispatcher: OnBackPressedDispatcher
+
+ @Before
+ fun setup() {
+ dispatcher = OnBackPressedDispatcher()
+ }
+
+ @UiThreadTest
+ @Test
+ fun testAddCallback() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+
+ dispatcher.addCallback(onBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Count should be incremented after onBackPressed")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testCancelSubscription() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+
+ val subscription = dispatcher.addCallback(onBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Count should be incremented after onBackPressed")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+
+ subscription.cancel()
+ assertWithMessage("Cancellable should be cancelled after cancel()")
+ .that(subscription.isCancelled)
+ .isTrue()
+ assertWithMessage("Handler should return false when no OnBackPressedCallbacks " +
+ "are registered")
+ .that(dispatcher.onBackPressed())
+ .isFalse()
+ // Check that the count still equals 1
+ assertWithMessage("Count shouldn't be incremented after removal")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testMultipleCalls() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+
+ dispatcher.addCallback(onBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Count should be incremented after each onBackPressed")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(2)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testMostRecentGetsPriority() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+ val mostRecentOnBackPressedCallback = CountingOnBackPressedCallback()
+
+ dispatcher.addCallback(onBackPressedCallback)
+ dispatcher.addCallback(mostRecentOnBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Most recent callback should be incremented")
+ .that(mostRecentOnBackPressedCallback.count)
+ .isEqualTo(1)
+ assertWithMessage("Only the most recent callback should be incremented")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(0)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testPassthroughListener() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+ val passThroughOnBackPressedCallback = CountingOnBackPressedCallback(returnValue = false)
+
+ dispatcher.addCallback(onBackPressedCallback)
+ dispatcher.addCallback(passThroughOnBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Most recent callback should be incremented")
+ .that(passThroughOnBackPressedCallback.count)
+ .isEqualTo(1)
+ assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+ "return false")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testLifecycleCallback() {
+ val onBackPressedCallback = CountingOnBackPressedCallback()
+ val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
+ val lifecycleOwner = object : LifecycleOwner {
+ val lifecycleRegistry = LifecycleRegistry(this)
+
+ override fun getLifecycle() = lifecycleRegistry
+ }
+
+ dispatcher.addCallback(onBackPressedCallback)
+ val observeSubscription = dispatcher.addCallback(lifecycleOwner,
+ lifecycleOnBackPressedCallback)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+ .that(lifecycleOnBackPressedCallback.count)
+ .isEqualTo(0)
+ assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+ "aren't started")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+
+ // Now start the Lifecycle
+ lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Once the callbacks is started, the count should increment")
+ .that(lifecycleOnBackPressedCallback.count)
+ .isEqualTo(1)
+ assertWithMessage("Only the most recent callback should be incremented")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(1)
+
+ // Now stop the Lifecycle
+ lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+ .that(lifecycleOnBackPressedCallback.count)
+ .isEqualTo(1)
+ assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+ "aren't started")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(2)
+
+ // Now destroy the Lifecycle
+ lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+ @Suppress("INACCESSIBLE_TYPE")
+ assertWithMessage("onDestroy should trigger the removal of any associated callbacks")
+ .that(observeSubscription.isCancelled)
+ .isTrue()
+ assertWithMessage("Handler should return true when handling onBackPressed")
+ .that(dispatcher.onBackPressed())
+ .isTrue()
+ assertWithMessage("Non-started callbacks shouldn't have their count incremented")
+ .that(lifecycleOnBackPressedCallback.count)
+ .isEqualTo(1)
+ assertWithMessage("Previous callbacks should be incremented if more recent callbacks " +
+ "aren't started")
+ .that(onBackPressedCallback.count)
+ .isEqualTo(3)
+ }
+
+ @UiThreadTest
+ @Test
+ fun testLifecycleCallback_whenDestroyed() {
+ val lifecycleOnBackPressedCallback = CountingOnBackPressedCallback()
+ val lifecycleOwner = object : LifecycleOwner {
+ val lifecycleRegistry = LifecycleRegistry(this)
+
+ override fun getLifecycle() = lifecycleRegistry
+ }
+ // Start the Lifecycle as DESTROYED
+ lifecycleOwner.lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+
+ val subscription = dispatcher.addCallback(lifecycleOwner,
+ lifecycleOnBackPressedCallback)
+ assertWithMessage("Dispatcher should return Cancellable.CANCELLED if the Lifecycle is " +
+ "DESTROYED")
+ .that(subscription)
+ .isSameAs(Cancellable.CANCELLED)
+ assertWithMessage("Cancellable should be immediately cancelled if the Lifecycle is " +
+ "DESTROYED")
+ .that(subscription.isCancelled)
+ .isTrue()
+ assertWithMessage("Handler should return false when no OnBackPressedCallbacks " +
+ "are registered")
+ .that(dispatcher.onBackPressed())
+ .isFalse()
+ assertWithMessage("Count shouldn't be incremented when the Cancellable is cancelled")
+ .that(lifecycleOnBackPressedCallback.count)
+ .isEqualTo(0)
+ }
+}
+
+class CountingOnBackPressedCallback(val returnValue: Boolean = true) :
+ OnBackPressedCallback {
+ var count = 0
+
+ override fun handleOnBackPressed(): Boolean {
+ count++
+ return returnValue
+ }
+}
diff --git a/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/src/main/java/androidx/activity/ComponentActivity.java
index a3bbef7..2a3e3e9 100644
--- a/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -25,8 +25,10 @@
import androidx.annotation.CallSuper;
import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.arch.core.util.Cancellable;
import androidx.lifecycle.GenericLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
@@ -38,9 +40,7 @@
import androidx.savedstate.SavedStateRegistryController;
import androidx.savedstate.SavedStateRegistryOwner;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.WeakHashMap;
/**
* Base class for activities that enables composition of higher level components.
@@ -66,13 +66,21 @@
// Lazily recreated from NonConfigurationInstances by getViewModelStore()
private ViewModelStore mViewModelStore;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final CopyOnWriteArrayList<LifecycleAwareOnBackPressedCallback> mOnBackPressedCallbacks =
- new CopyOnWriteArrayList<>();
+ private final OnBackPressedDispatcher mOnBackPressedDispatcher = new OnBackPressedDispatcher();
+ /**
+ * Used for the deprecated {@link #removeOnBackPressedCallback(OnBackPressedCallback)}.
+ */
+ private final WeakHashMap<OnBackPressedCallback, Cancellable>
+ mOnBackPressedCallbackCancellables = new WeakHashMap<>();
- // Cache the ContentView layoutIds for Activities.
- private static final HashMap<Class, Integer> sAnnotationIds = new HashMap<>();
+ @LayoutRes
+ private int mContentLayoutId;
+ /**
+ * Default constructor for ComponentActivity. All Activities must have a default constructor
+ * for API 27 and lower devices or when using the default
+ * {@link android.app.AppComponentFactory}.
+ */
public ComponentActivity() {
Lifecycle lifecycle = getLifecycle();
//noinspection ConstantConditions
@@ -113,6 +121,22 @@
}
/**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>.
+ *
+ * <p>This should generally be called from your constructor that takes no parameters,
+ * as is required for API 27 and lower or when using the default
+ * {@link android.app.AppComponentFactory}.
+ *
+ * @see #ComponentActivity()
+ */
+ @ContentView
+ public ComponentActivity(@LayoutRes int contentLayoutId) {
+ this();
+ mContentLayoutId = contentLayoutId;
+ }
+
+ /**
* {@inheritDoc}
*
* If your ComponentActivity is annotated with {@link ContentView}, this will
@@ -123,18 +147,8 @@
super.onCreate(savedInstanceState);
mSavedStateRegistryController.performRestore(savedInstanceState);
ReportFragment.injectIfNeededIn(this);
- Class<? extends ComponentActivity> clazz = getClass();
- if (!sAnnotationIds.containsKey(clazz)) {
- ContentView annotation = clazz.getAnnotation(ContentView.class);
- if (annotation != null) {
- sAnnotationIds.put(clazz, annotation.value());
- } else {
- sAnnotationIds.put(clazz, null);
- }
- }
- Integer layoutId = sAnnotationIds.get(clazz);
- if (layoutId != null && layoutId != 0) {
- setContentView(layoutId);
+ if (mContentLayoutId != 0) {
+ setContentView(mContentLayoutId);
}
}
@@ -258,26 +272,33 @@
/**
* Called when the activity has detected the user's press of the back
- * key. Any {@link OnBackPressedCallback} added via
- * {@link #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)} will be given a
+ * key. The {@link #getOnBackPressedDispatcher() OnBackPressedDispatcher} will be given a
* chance to handle the back button before the default behavior of
* {@link android.app.Activity#onBackPressed()} is invoked.
*
- * @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
+ * @see #getOnBackPressedDispatcher()
*/
@Override
public void onBackPressed() {
- for (OnBackPressedCallback onBackPressedCallback : mOnBackPressedCallbacks) {
- if (onBackPressedCallback.handleOnBackPressed()) {
- return;
- }
+ if (mOnBackPressedDispatcher.onBackPressed()) {
+ return;
}
- // If none of the registered OnBackPressedCallbacks handled the back button,
+ // If the OnBackPressedDispatcher doesn't handle the back button,
// delegate to the super implementation
super.onBackPressed();
}
/**
+ * Retrieve the {@link OnBackPressedDispatcher} that will be triggered when
+ * {@link #onBackPressed()} is called.
+ * @return The {@link OnBackPressedDispatcher} associated with this ComponentActivity.
+ */
+ @NonNull
+ public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
+ return mOnBackPressedDispatcher;
+ }
+
+ /**
* Add a new {@link OnBackPressedCallback}. Callbacks are invoked in order of recency, so
* this newly added {@link OnBackPressedCallback} will be the first callback to receive a
* callback if {@link #onBackPressed()} is called. Only if this callback returns
@@ -295,9 +316,15 @@
*
* @see #onBackPressed()
* @see #removeOnBackPressedCallback(OnBackPressedCallback)
+ * @deprecated Use {@link #getOnBackPressedDispatcher() and
+ * {@link OnBackPressedDispatcher#addCallback(LifecycleOwner, OnBackPressedCallback)}},
+ * explicitly passing in this Activity object as the {@link LifecycleOwner}.
*/
+ @Deprecated
public void addOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
- addOnBackPressedCallback(this, onBackPressedCallback);
+ mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
+ getOnBackPressedDispatcher()
+ .addCallback(this, onBackPressedCallback));
}
/**
@@ -319,18 +346,15 @@
*
* @see #onBackPressed()
* @see #removeOnBackPressedCallback(OnBackPressedCallback)
+ * @deprecated Use {@link #getOnBackPressedDispatcher() and
+ * {@link OnBackPressedDispatcher#addCallback(LifecycleOwner, OnBackPressedCallback)}}.
*/
+ @Deprecated
public void addOnBackPressedCallback(@NonNull LifecycleOwner owner,
@NonNull OnBackPressedCallback onBackPressedCallback) {
- Lifecycle lifecycle = owner.getLifecycle();
- if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
- // Already destroyed, nothing to do
- return;
- }
- // Add new callbacks to the front of the list so that
- // the most recently added callbacks get priority
- mOnBackPressedCallbacks.add(0, new LifecycleAwareOnBackPressedCallback(
- lifecycle, onBackPressedCallback));
+ mOnBackPressedCallbackCancellables.put(onBackPressedCallback,
+ getOnBackPressedDispatcher()
+ .addCallback(owner, onBackPressedCallback));
}
/**
@@ -345,21 +369,16 @@
*
* @param onBackPressedCallback The callback to remove
* @see #addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
+ * @deprecated Use {@link Cancellable#cancel()} on the
+ * {@link Cancellable} returned by {@link #getOnBackPressedDispatcher() and
+ * {@link OnBackPressedDispatcher#addCallback }}.
*/
+ @SuppressWarnings("DeprecatedIsStillUsed") /* See mOnBackPressedCallbackCancellables */
+ @Deprecated
public void removeOnBackPressedCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
- Iterator<LifecycleAwareOnBackPressedCallback> iterator =
- mOnBackPressedCallbacks.iterator();
- LifecycleAwareOnBackPressedCallback callbackToRemove = null;
- while (iterator.hasNext()) {
- LifecycleAwareOnBackPressedCallback callback = iterator.next();
- if (callback.getOnBackPressedCallback().equals(onBackPressedCallback)) {
- callbackToRemove = callback;
- break;
- }
- }
- if (callbackToRemove != null) {
- callbackToRemove.onRemoved();
- mOnBackPressedCallbacks.remove(callbackToRemove);
+ Cancellable cancellable = mOnBackPressedCallbackCancellables.remove(onBackPressedCallback);
+ if (cancellable != null) {
+ cancellable.cancel();
}
}
@@ -368,48 +387,4 @@
public final SavedStateRegistry getSavedStateRegistry() {
return mSavedStateRegistryController.getSavedStateRegistry();
}
-
- private class LifecycleAwareOnBackPressedCallback implements
- OnBackPressedCallback,
- GenericLifecycleObserver {
- private final Lifecycle mLifecycle;
- private final OnBackPressedCallback mOnBackPressedCallback;
-
- LifecycleAwareOnBackPressedCallback(@NonNull Lifecycle lifecycle,
- @NonNull OnBackPressedCallback onBackPressedCallback) {
- mLifecycle = lifecycle;
- mOnBackPressedCallback = onBackPressedCallback;
- mLifecycle.addObserver(this);
- }
-
- Lifecycle getLifecycle() {
- return mLifecycle;
- }
-
- OnBackPressedCallback getOnBackPressedCallback() {
- return mOnBackPressedCallback;
- }
-
- @Override
- public boolean handleOnBackPressed() {
- if (mLifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
- return mOnBackPressedCallback.handleOnBackPressed();
- }
- return false;
- }
-
- @Override
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (event == Lifecycle.Event.ON_DESTROY) {
- synchronized (mOnBackPressedCallbacks) {
- mLifecycle.removeObserver(this);
- mOnBackPressedCallbacks.remove(this);
- }
- }
- }
-
- public void onRemoved() {
- mLifecycle.removeObserver(this);
- }
- }
}
diff --git a/activity/src/main/java/androidx/activity/OnBackPressedCallback.java b/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
index 3eed965..fff6476 100644
--- a/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
+++ b/activity/src/main/java/androidx/activity/OnBackPressedCallback.java
@@ -16,20 +16,17 @@
package androidx.activity;
-import androidx.lifecycle.LifecycleOwner;
-
/**
- * Interface for handling {@link ComponentActivity#onBackPressed()} callbacks without
+ * Interface for handling {@link OnBackPressedDispatcher#onBackPressed()} callbacks without
* strongly coupling that implementation to a subclass of {@link ComponentActivity}.
*
- * @see ComponentActivity#addOnBackPressedCallback(LifecycleOwner, OnBackPressedCallback)
- * @see ComponentActivity#removeOnBackPressedCallback(OnBackPressedCallback)
+ * @see ComponentActivity#getOnBackPressedDispatcher()
*/
public interface OnBackPressedCallback {
/**
- * Callback for handling the {@link ComponentActivity#onBackPressed()} event.
+ * Callback for handling the {@link OnBackPressedDispatcher#onBackPressed()} event.
*
- * @return True if you handled the {@link ComponentActivity#onBackPressed()} event. No
+ * @return True if you handled the {@link OnBackPressedDispatcher#onBackPressed()} event. No
* further {@link OnBackPressedCallback} instances will be called if you return true.
*/
boolean handleOnBackPressed();
diff --git a/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java b/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
new file mode 100644
index 0000000..b11e8d2
--- /dev/null
+++ b/activity/src/main/java/androidx/activity/OnBackPressedDispatcher.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.activity;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Cancellable;
+import androidx.lifecycle.GenericLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+
+/**
+ * Dispatcher that can be used to register {@link OnBackPressedCallback} instances for handling
+ * the {@link ComponentActivity#onBackPressed()} callback via composition.
+ * <pre>
+ * public class FormEntryFragment extends Fragment {
+ * {@literal @}Override
+ * public void onAttach({@literal @}NonNull Context context) {
+ * super.onAttach(context);
+ * requireActivity().getOnBackPressedDispatcher().addCallback(this,
+ * new OnBackPressedCallback() {
+ * {@literal @}Override
+ * public boolean handleOnBackPressed() {
+ * showAreYouSureDialog();
+ * return true;
+ * }
+ * });
+ * }
+ * }
+ * </pre>
+ */
+public final class OnBackPressedDispatcher {
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();
+
+ OnBackPressedDispatcher() {
+ }
+
+ /**
+ * Add a new {@link OnBackPressedCallback}. Callbacks are invoked in the reverse order in which
+ * they are added, so this newly added {@link OnBackPressedCallback} will be the first
+ * callback to receive a callback if {@link #onBackPressed()} is called.
+ * <p>
+ * This method is <strong>not</strong> {@link Lifecycle} aware - if you'd like to ensure that
+ * you only get callbacks when at least {@link Lifecycle.State#STARTED started}, use
+ * {@link #addCallback(LifecycleOwner, OnBackPressedCallback)}.
+ *
+ * @param onBackPressedCallback The callback to add
+ * @return a {@link Cancellable} which can be used to {@link Cancellable#cancel() cancel}
+ * the callback and remove it from the set of OnBackPressedCallbacks. The callback won't be
+ * called for any future {@link #onBackPressed()} calls, but may still receive a
+ * callback if {@link Cancellable#cancel()} is called during the dispatch of an ongoing
+ * {@link #onBackPressed()} call.
+ *
+ * @see #onBackPressed()
+ */
+ @NonNull
+ public Cancellable addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
+ synchronized (mOnBackPressedCallbacks) {
+ mOnBackPressedCallbacks.add(onBackPressedCallback);
+ }
+ return new OnBackPressedCancellable(onBackPressedCallback);
+ }
+
+ /**
+ * Receive callbacks to a new {@link OnBackPressedCallback} when the given
+ * {@link LifecycleOwner} is at least {@link Lifecycle.State#STARTED started}.
+ * <p>
+ * This will automatically call {@link #addCallback(OnBackPressedCallback)} and
+ * {@link Cancellable#cancel()} as the lifecycle state changes.
+ * As a corollary, if your lifecycle is already at least
+ * {@link Lifecycle.State#STARTED started}, calling this method will result in an immediate
+ * call to {@link #addCallback(OnBackPressedCallback)}.
+ * <p>
+ * When the {@link LifecycleOwner} is {@link Lifecycle.State#DESTROYED destroyed}, it will
+ * automatically be removed from the list of callbacks. The only time you would need to
+ * manually call {@link Cancellable#cancel()} on the returned {@link Cancellable} is if
+ * you'd like to remove the callback prior to destruction of the associated lifecycle.
+ *
+ * <p>If the Lifecycle is already
+ * {@link Lifecycle.State#DESTROYED destroyed} when this method is called, this will
+ * return{@link Cancellable#CANCELLED} and the callback will not be added.
+ *
+ * @param owner The LifecycleOwner which controls when the callback should be invoked
+ * @param onBackPressedCallback The callback to add
+ * @return a {@link Cancellable} which can be used to {@link Cancellable#cancel() cancel}
+ * the callback and remove the associated {@link androidx.lifecycle.LifecycleObserver}
+ * and the OnBackPressedCallback. The callback won't be called for any future
+ * {@link #onBackPressed()} calls, but may still receive a callback if
+ * {@link Cancellable#cancel()} is called during the dispatch of an ongoing
+ * {@link #onBackPressed()} call.
+ *
+ * @see #onBackPressed()
+ */
+ @NonNull
+ public Cancellable addCallback(@NonNull LifecycleOwner owner,
+ @NonNull OnBackPressedCallback onBackPressedCallback) {
+ Lifecycle lifecycle = owner.getLifecycle();
+ if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
+ return Cancellable.CANCELLED;
+ }
+ return new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback);
+ }
+
+ /**
+ * Trigger a call to the currently added {@link OnBackPressedCallback callbacks} in reverse
+ * order in which they were added. Only if the most recently added callback returns
+ * <code>false</code> from its {@link OnBackPressedCallback#handleOnBackPressed()}
+ * will any previously added callback be called.
+ *
+ * @return True if an added {@link OnBackPressedCallback} handled the back button.
+ */
+ public boolean onBackPressed() {
+ synchronized (mOnBackPressedCallbacks) {
+ Iterator<OnBackPressedCallback> iterator =
+ mOnBackPressedCallbacks.descendingIterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().handleOnBackPressed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private class OnBackPressedCancellable implements Cancellable {
+ private final OnBackPressedCallback mOnBackPressedCallback;
+ private boolean mCancelled;
+
+ OnBackPressedCancellable(OnBackPressedCallback onBackPressedCallback) {
+ mOnBackPressedCallback = onBackPressedCallback;
+ }
+
+ @Override
+ public void cancel() {
+ synchronized (mOnBackPressedCallbacks) {
+ mOnBackPressedCallbacks.remove(mOnBackPressedCallback);
+ mCancelled = true;
+ }
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+ }
+
+ private class LifecycleOnBackPressedCancellable implements GenericLifecycleObserver,
+ Cancellable {
+ private final Lifecycle mLifecycle;
+ private final OnBackPressedCallback mOnBackPressedCallback;
+
+ @Nullable
+ private Cancellable mCurrentCancellable;
+ private boolean mCancelled = false;
+
+ LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
+ @NonNull OnBackPressedCallback onBackPressedCallback) {
+ mLifecycle = lifecycle;
+ mOnBackPressedCallback = onBackPressedCallback;
+ lifecycle.addObserver(this);
+ }
+
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner source,
+ @NonNull Lifecycle.Event event) {
+ if (event == Lifecycle.Event.ON_START) {
+ mCurrentCancellable = addCallback(mOnBackPressedCallback);
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ // Should always be non-null
+ if (mCurrentCancellable != null) {
+ mCurrentCancellable.cancel();
+ }
+ } else if (event == Lifecycle.Event.ON_DESTROY) {
+ cancel();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ mLifecycle.removeObserver(this);
+ if (mCurrentCancellable != null) {
+ mCurrentCancellable.cancel();
+ mCurrentCancellable = null;
+ }
+ mCancelled = true;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mCancelled;
+ }
+ }
+}
diff --git a/annotations/api/1.1.0-alpha03.txt b/annotations/api/1.1.0-alpha03.txt
new file mode 100644
index 0000000..491ffea
--- /dev/null
+++ b/annotations/api/1.1.0-alpha03.txt
@@ -0,0 +1,247 @@
+// Signature format: 3.0
+package androidx.annotation {
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnimRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnimatorRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnyRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface AnyThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ArrayRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AttrRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface BinderThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface BoolRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface CallSuper {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface CheckResult {
+ method public abstract String suggest() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface ColorInt {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface ColorLong {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ColorRes {
+ }
+
+ @java.lang.annotation.Inherited @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public @interface ContentView {
+ method @LayoutRes public abstract int value();
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DimenRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Dimension {
+ method @DimensionUnit public abstract int unit() default androidx.annotation.Dimension.PX;
+ field public static final int DP = 0; // 0x0
+ field public static final int PX = 1; // 0x1
+ field public static final int SP = 2; // 0x2
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DrawableRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface FloatRange {
+ method public abstract double from() default java.lang.Double.NEGATIVE_INFINITY;
+ method public abstract boolean fromInclusive() default true;
+ method public abstract double to() default java.lang.Double.POSITIVE_INFINITY;
+ method public abstract boolean toInclusive() default true;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface FontRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface FractionRes {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface GuardedBy {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface HalfFloat {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface IdRes {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface InspectableProperty {
+ method public abstract int attributeId() default 0;
+ method public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping() default {};
+ method public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping() default {};
+ method public abstract boolean hasAttributeId() default true;
+ method public abstract String name() default "";
+ method public abstract androidx.annotation.InspectableProperty.ValueType valueType() default androidx.annotation.InspectableProperty.ValueType.INFERRED;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.EnumEntry {
+ method public abstract String name();
+ method public abstract int value();
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.FlagEntry {
+ method public abstract int mask() default 0;
+ method public abstract String name();
+ method public abstract int target();
+ }
+
+ public enum InspectableProperty.ValueType {
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType COLOR;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_ENUM;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_FLAG;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType NONE;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType RESOURCE_ID;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface IntDef {
+ method public abstract boolean flag() default false;
+ method public abstract boolean open() default false;
+ method public abstract int[] value() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface IntRange {
+ method public abstract long from() default java.lang.Long.MIN_VALUE;
+ method public abstract long to() default java.lang.Long.MAX_VALUE;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface IntegerRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface InterpolatorRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PACKAGE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface Keep {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface LayoutRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface LongDef {
+ method public abstract boolean flag() default false;
+ method public abstract boolean open() default false;
+ method public abstract long[] value() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface MainThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface MenuRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface NavigationRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) public @interface NonNull {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) public @interface Nullable {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface PluralsRes {
+ }
+
+ @Dimension(unit=androidx.annotation.Dimension.PX) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface Px {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface RawRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RequiresApi {
+ method @IntRange(from=1) public abstract int api() default 1;
+ method @IntRange(from=1) public abstract int value() default 1;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface RequiresFeature {
+ method public abstract String enforcement();
+ method public abstract String name();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PARAMETER}) public @interface RequiresPermission {
+ method public abstract String[] allOf() default {};
+ method public abstract String[] anyOf() default {};
+ method public abstract boolean conditional() default false;
+ method public abstract String value() default "";
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface RequiresPermission.Read {
+ method public abstract androidx.annotation.RequiresPermission value() default @androidx.annotation.RequiresPermission;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface RequiresPermission.Write {
+ method public abstract androidx.annotation.RequiresPermission value() default @androidx.annotation.RequiresPermission;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo {
+ method public abstract androidx.annotation.RestrictTo.Scope[] value();
+ }
+
+ public enum RestrictTo.Scope {
+ enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP_PREFIX;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Size {
+ method public abstract long max() default java.lang.Long.MAX_VALUE;
+ method public abstract long min() default java.lang.Long.MIN_VALUE;
+ method public abstract long multiple() default 1;
+ method public abstract long value() default -1;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface StringDef {
+ method public abstract boolean open() default false;
+ method public abstract String[] value() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StringRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StyleRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StyleableRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public @interface TransitionRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface UiThread {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface VisibleForTesting {
+ method @ProductionVisibility public abstract int otherwise() default androidx.annotation.VisibleForTesting.PRIVATE;
+ field public static final int NONE = 5; // 0x5
+ field public static final int PACKAGE_PRIVATE = 3; // 0x3
+ field public static final int PRIVATE = 2; // 0x2
+ field public static final int PROTECTED = 4; // 0x4
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface WorkerThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface XmlRes {
+ }
+
+}
+
diff --git a/annotations/api/1.1.0-beta01.txt b/annotations/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..7e260ad
--- /dev/null
+++ b/annotations/api/1.1.0-beta01.txt
@@ -0,0 +1,246 @@
+// Signature format: 3.0
+package androidx.annotation {
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnimRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnimatorRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AnyRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface AnyThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ArrayRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface AttrRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface BinderThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface BoolRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface CallSuper {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface CheckResult {
+ method public abstract String suggest() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface ColorInt {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface ColorLong {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ColorRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface ContentView {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DimenRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Dimension {
+ method @DimensionUnit public abstract int unit() default androidx.annotation.Dimension.PX;
+ field public static final int DP = 0; // 0x0
+ field public static final int PX = 1; // 0x1
+ field public static final int SP = 2; // 0x2
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DrawableRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface FloatRange {
+ method public abstract double from() default java.lang.Double.NEGATIVE_INFINITY;
+ method public abstract boolean fromInclusive() default true;
+ method public abstract double to() default java.lang.Double.POSITIVE_INFINITY;
+ method public abstract boolean toInclusive() default true;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface FontRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface FractionRes {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface GuardedBy {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.FIELD}) public @interface HalfFloat {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface IdRes {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface InspectableProperty {
+ method public abstract int attributeId() default 0;
+ method public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping() default {};
+ method public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping() default {};
+ method public abstract boolean hasAttributeId() default true;
+ method public abstract String name() default "";
+ method public abstract androidx.annotation.InspectableProperty.ValueType valueType() default androidx.annotation.InspectableProperty.ValueType.INFERRED;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.EnumEntry {
+ method public abstract String name();
+ method public abstract int value();
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.FlagEntry {
+ method public abstract int mask() default 0;
+ method public abstract String name();
+ method public abstract int target();
+ }
+
+ public enum InspectableProperty.ValueType {
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType COLOR;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType GRAVITY;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INFERRED;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_ENUM;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_FLAG;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType NONE;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType RESOURCE_ID;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface IntDef {
+ method public abstract boolean flag() default false;
+ method public abstract boolean open() default false;
+ method public abstract int[] value() default {};
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface IntRange {
+ method public abstract long from() default java.lang.Long.MIN_VALUE;
+ method public abstract long to() default java.lang.Long.MAX_VALUE;
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface IntegerRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface InterpolatorRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PACKAGE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD}) public @interface Keep {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface LayoutRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface LongDef {
+ method public abstract boolean flag() default false;
+ method public abstract boolean open() default false;
+ method public abstract long[] value() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface MainThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface MenuRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface NavigationRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) public @interface NonNull {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.PACKAGE}) public @interface Nullable {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface PluralsRes {
+ }
+
+ @Dimension(unit=androidx.annotation.Dimension.PX) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface Px {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface RawRes {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RequiresApi {
+ method @IntRange(from=1) public abstract int api() default 1;
+ method @IntRange(from=1) public abstract int value() default 1;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface RequiresFeature {
+ method public abstract String enforcement();
+ method public abstract String name();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PARAMETER}) public @interface RequiresPermission {
+ method public abstract String[] allOf() default {};
+ method public abstract String[] anyOf() default {};
+ method public abstract boolean conditional() default false;
+ method public abstract String value() default "";
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface RequiresPermission.Read {
+ method public abstract androidx.annotation.RequiresPermission value() default @androidx.annotation.RequiresPermission;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface RequiresPermission.Write {
+ method public abstract androidx.annotation.RequiresPermission value() default @androidx.annotation.RequiresPermission;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PACKAGE}) public @interface RestrictTo {
+ method public abstract androidx.annotation.RestrictTo.Scope[] value();
+ }
+
+ public enum RestrictTo.Scope {
+ enum_constant @Deprecated public static final androidx.annotation.RestrictTo.Scope GROUP_ID;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope LIBRARY_GROUP_PREFIX;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope SUBCLASSES;
+ enum_constant public static final androidx.annotation.RestrictTo.Scope TESTS;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface Size {
+ method public abstract long max() default java.lang.Long.MAX_VALUE;
+ method public abstract long min() default java.lang.Long.MIN_VALUE;
+ method public abstract long multiple() default 1;
+ method public abstract long value() default -1;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface StringDef {
+ method public abstract boolean open() default false;
+ method public abstract String[] value() default {};
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StringRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StyleRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface StyleableRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public @interface TransitionRes {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface UiThread {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface VisibleForTesting {
+ method @ProductionVisibility public abstract int otherwise() default androidx.annotation.VisibleForTesting.PRIVATE;
+ field public static final int NONE = 5; // 0x5
+ field public static final int PACKAGE_PRIVATE = 3; // 0x3
+ field public static final int PRIVATE = 2; // 0x2
+ field public static final int PROTECTED = 4; // 0x4
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.CONSTRUCTOR, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.PARAMETER}) public @interface WorkerThread {
+ }
+
+ @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface XmlRes {
+ }
+
+}
+
diff --git a/annotations/api/current.txt b/annotations/api/current.txt
index 7226b13..7e260ad 100644
--- a/annotations/api/current.txt
+++ b/annotations/api/current.txt
@@ -41,8 +41,7 @@
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface ColorRes {
}
- @java.lang.annotation.Inherited @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public @interface ContentView {
- method @LayoutRes public abstract int value();
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.CONSTRUCTOR}) public @interface ContentView {
}
@java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.LOCAL_VARIABLE}) public @interface DimenRes {
@@ -83,19 +82,19 @@
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public @interface InspectableProperty {
method public abstract int attributeId() default 0;
- method public abstract androidx.annotation.InspectableProperty.EnumMap[] enumMapping() default {};
- method public abstract androidx.annotation.InspectableProperty.FlagMap[] flagMapping() default {};
+ method public abstract androidx.annotation.InspectableProperty.EnumEntry[] enumMapping() default {};
+ method public abstract androidx.annotation.InspectableProperty.FlagEntry[] flagMapping() default {};
method public abstract boolean hasAttributeId() default true;
method public abstract String name() default "";
method public abstract androidx.annotation.InspectableProperty.ValueType valueType() default androidx.annotation.InspectableProperty.ValueType.INFERRED;
}
- @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.EnumMap {
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.EnumEntry {
method public abstract String name();
method public abstract int value();
}
- @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.FlagMap {
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface InspectableProperty.FlagEntry {
method public abstract int mask() default 0;
method public abstract String name();
method public abstract int target();
@@ -108,6 +107,7 @@
enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_ENUM;
enum_constant public static final androidx.annotation.InspectableProperty.ValueType INT_FLAG;
enum_constant public static final androidx.annotation.InspectableProperty.ValueType NONE;
+ enum_constant public static final androidx.annotation.InspectableProperty.ValueType RESOURCE_ID;
}
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface IntDef {
diff --git a/annotations/api/restricted_1.1.0-alpha03.txt b/annotations/api/restricted_1.1.0-alpha03.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/annotations/api/restricted_1.1.0-alpha03.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/annotations/api/restricted_1.1.0-beta01.txt b/annotations/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/annotations/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 7e20eac..12307ad 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -30,6 +30,9 @@
if (options.doclet == null) {
options.addBooleanOption('Xdoclint:none', true)
}
+ // We set the group to null so it doesn't show if a user runs `./gradlew tasks`
+ // because this task is specific to this project
+ group = null
}
}
@@ -57,4 +60,4 @@
mavenGroup = LibraryGroups.ANNOTATION
inceptionYear = "2013"
description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs."
-}
\ No newline at end of file
+}
diff --git a/annotations/src/main/java/androidx/annotation/ContentView.java b/annotations/src/main/java/androidx/annotation/ContentView.java
index 407763a..f2f098d 100644
--- a/annotations/src/main/java/androidx/annotation/ContentView.java
+++ b/annotations/src/main/java/androidx/annotation/ContentView.java
@@ -16,31 +16,31 @@
package androidx.annotation;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.RetentionPolicy.CLASS;
-import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
- * Annotation that can be attached to a component such as an
- * {@link androidx.activity.ComponentActivity} or {@link androidx.fragment.app.Fragment}
+ * Annotation that can be attached to a constructor with a single {@link LayoutRes} parameter
* to denote what layout the component intends to inflate and set as its content.
* <p>
- * This annotation is marked as {@link Inherited} and will therefore apply to subclasses
- * automatically.
- * <p>
* It is strongly recommended that components that support this annotation specifically call
* it out in their documentation.
+ * <pre>
+ * public class MainFragment extends Fragment {
+ * public MainFragment() {
+ * // This constructor is annotated with @ContentView
+ * super(R.layout.main);
+ * }
+ * }
+ * </pre>
*
- * @see androidx.activity.ComponentActivity#onCreate(android.os.Bundle)
- * @see androidx.fragment.app.Fragment#onCreateView
+ * @see androidx.activity.ComponentActivity#ComponentActivity(int)
+ * @see androidx.fragment.app.Fragment#Fragment(int)
*/
-@Retention(RUNTIME)
-@Target({TYPE})
-@Inherited
+@Retention(CLASS)
+@Target({CONSTRUCTOR})
public @interface ContentView {
- @LayoutRes
- int value();
}
diff --git a/annotations/src/main/java/androidx/annotation/InspectableProperty.java b/annotations/src/main/java/androidx/annotation/InspectableProperty.java
index 634210f..e5848d0 100644
--- a/annotations/src/main/java/androidx/annotation/InspectableProperty.java
+++ b/annotations/src/main/java/androidx/annotation/InspectableProperty.java
@@ -73,18 +73,18 @@
*
* Note that {@code #enumMapping()} cannot be used simultaneously with {@link #flagMapping()}.
*
- * @return An array of {@link EnumMap}, empty if not applicable
+ * @return An array of {@link EnumEntry}, empty if not applicable
*/
- EnumMap[] enumMapping() default {};
+ EnumEntry[] enumMapping() default {};
/**
* For flags packed into primitive {int} properties, model the string names of the flags.
*
* Note that {@code #flagMapping()} cannot be used simultaneously with {@link #enumMapping()}.
*
- * @return An array of {@link FlagMap}, empty if not applicable
+ * @return An array of {@link FlagEntry}, empty if not applicable
*/
- FlagMap[] flagMapping() default {};
+ FlagEntry[] flagMapping() default {};
/**
@@ -92,7 +92,7 @@
*/
@Target({TYPE})
@Retention(SOURCE)
- @interface EnumMap {
+ @interface EnumEntry {
/**
* The string name of this enumeration value.
*
@@ -113,7 +113,7 @@
*/
@Target({TYPE})
@Retention(SOURCE)
- @interface FlagMap {
+ @interface FlagEntry {
/**
* The string name of this flag.
*
@@ -161,7 +161,7 @@
*
* This is inferred if {@link #enumMapping()} is specified.
*
- * @see EnumMap
+ * @see EnumEntry
*/
INT_ENUM,
@@ -170,7 +170,7 @@
*
* This is inferred if {@link #flagMapping()} is specified.
*
- * @see FlagMap
+ * @see FlagEntry
*/
INT_FLAG,
@@ -184,8 +184,16 @@
/**
* Value packs gravity information.
*
- * This type is not inferred and is non-trivial to represent using {@link FlagMap}.
+ * This type is not inferred and is non-trivial to represent using {@link FlagEntry}.
*/
- GRAVITY
+ GRAVITY,
+
+ /**
+ * Value is a resource ID
+ *
+ * This type is inferred from the presence of a resource ID annotation such as
+ * {@link AnyRes}.
+ */
+ RESOURCE_ID
}
}
diff --git a/appcompat/api/1.1.0-alpha04.ignore b/appcompat/api/1.1.0-alpha04.ignore
new file mode 100644
index 0000000..f8fc8c9
--- /dev/null
+++ b/appcompat/api/1.1.0-alpha04.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedPackage: androidx.appcompat.content.res:
+ Removed package androidx.appcompat.content.res
+
+
diff --git a/appcompat/api/1.1.0-alpha04.txt b/appcompat/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..03862a1
--- /dev/null
+++ b/appcompat/api/1.1.0-alpha04.txt
@@ -0,0 +1,981 @@
+// Signature format: 3.0
+package androidx.appcompat.app {
+
+ public abstract class ActionBar {
+ ctor public ActionBar();
+ method public abstract void addOnMenuVisibilityListener(androidx.appcompat.app.ActionBar.OnMenuVisibilityListener!);
+ method @Deprecated public abstract void addTab(androidx.appcompat.app.ActionBar.Tab!);
+ method @Deprecated public abstract void addTab(androidx.appcompat.app.ActionBar.Tab!, boolean);
+ method @Deprecated public abstract void addTab(androidx.appcompat.app.ActionBar.Tab!, int);
+ method @Deprecated public abstract void addTab(androidx.appcompat.app.ActionBar.Tab!, int, boolean);
+ method public abstract android.view.View! getCustomView();
+ method public abstract int getDisplayOptions();
+ method public float getElevation();
+ method public abstract int getHeight();
+ method public int getHideOffset();
+ method @Deprecated public abstract int getNavigationItemCount();
+ method @Deprecated public abstract int getNavigationMode();
+ method @Deprecated public abstract int getSelectedNavigationIndex();
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab? getSelectedTab();
+ method public abstract CharSequence? getSubtitle();
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! getTabAt(int);
+ method @Deprecated public abstract int getTabCount();
+ method public android.content.Context! getThemedContext();
+ method public abstract CharSequence? getTitle();
+ method public abstract void hide();
+ method public boolean isHideOnContentScrollEnabled();
+ method public abstract boolean isShowing();
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! newTab();
+ method @Deprecated public abstract void removeAllTabs();
+ method public abstract void removeOnMenuVisibilityListener(androidx.appcompat.app.ActionBar.OnMenuVisibilityListener!);
+ method @Deprecated public abstract void removeTab(androidx.appcompat.app.ActionBar.Tab!);
+ method @Deprecated public abstract void removeTabAt(int);
+ method @Deprecated public abstract void selectTab(androidx.appcompat.app.ActionBar.Tab!);
+ method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable?);
+ method public abstract void setCustomView(android.view.View!);
+ method public abstract void setCustomView(android.view.View!, androidx.appcompat.app.ActionBar.LayoutParams!);
+ method public abstract void setCustomView(int);
+ method public abstract void setDisplayHomeAsUpEnabled(boolean);
+ method public abstract void setDisplayOptions(int);
+ method public abstract void setDisplayOptions(int, int);
+ method public abstract void setDisplayShowCustomEnabled(boolean);
+ method public abstract void setDisplayShowHomeEnabled(boolean);
+ method public abstract void setDisplayShowTitleEnabled(boolean);
+ method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setElevation(float);
+ method public void setHideOffset(int);
+ method public void setHideOnContentScrollEnabled(boolean);
+ method public void setHomeActionContentDescription(CharSequence?);
+ method public void setHomeActionContentDescription(@StringRes int);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable?);
+ method public void setHomeAsUpIndicator(@DrawableRes int);
+ method public void setHomeButtonEnabled(boolean);
+ method public abstract void setIcon(@DrawableRes int);
+ method public abstract void setIcon(android.graphics.drawable.Drawable!);
+ method @Deprecated public abstract void setListNavigationCallbacks(android.widget.SpinnerAdapter!, androidx.appcompat.app.ActionBar.OnNavigationListener!);
+ method public abstract void setLogo(@DrawableRes int);
+ method public abstract void setLogo(android.graphics.drawable.Drawable!);
+ method @Deprecated public abstract void setNavigationMode(int);
+ method @Deprecated public abstract void setSelectedNavigationItem(int);
+ method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method public abstract void setSubtitle(CharSequence!);
+ method public abstract void setSubtitle(int);
+ method public abstract void setTitle(CharSequence!);
+ method public abstract void setTitle(@StringRes int);
+ method public abstract void show();
+ field public static final int DISPLAY_HOME_AS_UP = 4; // 0x4
+ field public static final int DISPLAY_SHOW_CUSTOM = 16; // 0x10
+ field public static final int DISPLAY_SHOW_HOME = 2; // 0x2
+ field public static final int DISPLAY_SHOW_TITLE = 8; // 0x8
+ field public static final int DISPLAY_USE_LOGO = 1; // 0x1
+ field @Deprecated public static final int NAVIGATION_MODE_LIST = 1; // 0x1
+ field @Deprecated public static final int NAVIGATION_MODE_STANDARD = 0; // 0x0
+ field @Deprecated public static final int NAVIGATION_MODE_TABS = 2; // 0x2
+ }
+
+ public static class ActionBar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public ActionBar.LayoutParams(android.content.Context, android.util.AttributeSet!);
+ ctor public ActionBar.LayoutParams(int, int);
+ ctor public ActionBar.LayoutParams(int, int, int);
+ ctor public ActionBar.LayoutParams(int);
+ ctor public ActionBar.LayoutParams(androidx.appcompat.app.ActionBar.LayoutParams!);
+ ctor public ActionBar.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ field public int gravity;
+ }
+
+ public static interface ActionBar.OnMenuVisibilityListener {
+ method public void onMenuVisibilityChanged(boolean);
+ }
+
+ @Deprecated public static interface ActionBar.OnNavigationListener {
+ method @Deprecated public boolean onNavigationItemSelected(int, long);
+ }
+
+ @Deprecated public abstract static class ActionBar.Tab {
+ ctor @Deprecated public ActionBar.Tab();
+ method @Deprecated public abstract CharSequence! getContentDescription();
+ method @Deprecated public abstract android.view.View! getCustomView();
+ method @Deprecated public abstract android.graphics.drawable.Drawable! getIcon();
+ method @Deprecated public abstract int getPosition();
+ method @Deprecated public abstract Object! getTag();
+ method @Deprecated public abstract CharSequence! getText();
+ method @Deprecated public abstract void select();
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setContentDescription(@StringRes int);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setContentDescription(CharSequence!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setCustomView(android.view.View!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setCustomView(int);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setIcon(android.graphics.drawable.Drawable!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setIcon(@DrawableRes int);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setTabListener(androidx.appcompat.app.ActionBar.TabListener!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setTag(Object!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setText(CharSequence!);
+ method @Deprecated public abstract androidx.appcompat.app.ActionBar.Tab! setText(int);
+ field @Deprecated public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ @Deprecated public static interface ActionBar.TabListener {
+ method @Deprecated public void onTabReselected(androidx.appcompat.app.ActionBar.Tab!, androidx.fragment.app.FragmentTransaction!);
+ method @Deprecated public void onTabSelected(androidx.appcompat.app.ActionBar.Tab!, androidx.fragment.app.FragmentTransaction!);
+ method @Deprecated public void onTabUnselected(androidx.appcompat.app.ActionBar.Tab!, androidx.fragment.app.FragmentTransaction!);
+ }
+
+ public class ActionBarDrawerToggle implements androidx.drawerlayout.widget.DrawerLayout.DrawerListener {
+ ctor public ActionBarDrawerToggle(android.app.Activity!, androidx.drawerlayout.widget.DrawerLayout!, @StringRes int, @StringRes int);
+ ctor public ActionBarDrawerToggle(android.app.Activity!, androidx.drawerlayout.widget.DrawerLayout!, androidx.appcompat.widget.Toolbar!, @StringRes int, @StringRes int);
+ method public androidx.appcompat.graphics.drawable.DrawerArrowDrawable getDrawerArrowDrawable();
+ method public android.view.View.OnClickListener! getToolbarNavigationClickListener();
+ method public boolean isDrawerIndicatorEnabled();
+ method public boolean isDrawerSlideAnimationEnabled();
+ method public void onConfigurationChanged(android.content.res.Configuration!);
+ method public void onDrawerClosed(android.view.View!);
+ method public void onDrawerOpened(android.view.View!);
+ method public void onDrawerSlide(android.view.View!, float);
+ method public void onDrawerStateChanged(int);
+ method public boolean onOptionsItemSelected(android.view.MenuItem!);
+ method public void setDrawerArrowDrawable(androidx.appcompat.graphics.drawable.DrawerArrowDrawable);
+ method public void setDrawerIndicatorEnabled(boolean);
+ method public void setDrawerSlideAnimationEnabled(boolean);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable!);
+ method public void setHomeAsUpIndicator(int);
+ method public void setToolbarNavigationClickListener(android.view.View.OnClickListener!);
+ method public void syncState();
+ }
+
+ public static interface ActionBarDrawerToggle.Delegate {
+ method public android.content.Context! getActionBarThemedContext();
+ method public android.graphics.drawable.Drawable! getThemeUpIndicator();
+ method public boolean isNavigationVisible();
+ method public void setActionBarDescription(@StringRes int);
+ method public void setActionBarUpIndicator(android.graphics.drawable.Drawable!, @StringRes int);
+ }
+
+ public static interface ActionBarDrawerToggle.DelegateProvider {
+ method public androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate();
+ }
+
+ public class AlertDialog extends androidx.appcompat.app.AppCompatDialog implements android.content.DialogInterface {
+ ctor protected AlertDialog(android.content.Context);
+ ctor protected AlertDialog(android.content.Context, @StyleRes int);
+ ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener?);
+ method public android.widget.Button! getButton(int);
+ method public android.widget.ListView! getListView();
+ method public void setButton(int, CharSequence!, android.os.Message!);
+ method public void setButton(int, CharSequence!, android.content.DialogInterface.OnClickListener!);
+ method public void setButton(int, CharSequence!, android.graphics.drawable.Drawable!, android.content.DialogInterface.OnClickListener!);
+ method public void setCustomTitle(android.view.View!);
+ method public void setIcon(int);
+ method public void setIcon(android.graphics.drawable.Drawable!);
+ method public void setIconAttribute(int);
+ method public void setMessage(CharSequence!);
+ method public void setView(android.view.View!);
+ method public void setView(android.view.View!, int, int, int, int);
+ }
+
+ public static class AlertDialog.Builder {
+ ctor public AlertDialog.Builder(android.content.Context);
+ ctor public AlertDialog.Builder(android.content.Context, @StyleRes int);
+ method public androidx.appcompat.app.AlertDialog create();
+ method public android.content.Context getContext();
+ method public androidx.appcompat.app.AlertDialog.Builder! setAdapter(android.widget.ListAdapter!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setCancelable(boolean);
+ method public androidx.appcompat.app.AlertDialog.Builder! setCursor(android.database.Cursor!, android.content.DialogInterface.OnClickListener!, String!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setCustomTitle(android.view.View?);
+ method public androidx.appcompat.app.AlertDialog.Builder! setIcon(@DrawableRes int);
+ method public androidx.appcompat.app.AlertDialog.Builder! setIcon(android.graphics.drawable.Drawable?);
+ method public androidx.appcompat.app.AlertDialog.Builder! setIconAttribute(@AttrRes int);
+ method @Deprecated public androidx.appcompat.app.AlertDialog.Builder! setInverseBackgroundForced(boolean);
+ method public androidx.appcompat.app.AlertDialog.Builder! setItems(@ArrayRes int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setItems(CharSequence[]!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setMessage(@StringRes int);
+ method public androidx.appcompat.app.AlertDialog.Builder! setMessage(CharSequence?);
+ method public androidx.appcompat.app.AlertDialog.Builder! setMultiChoiceItems(@ArrayRes int, boolean[]!, android.content.DialogInterface.OnMultiChoiceClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setMultiChoiceItems(CharSequence[]!, boolean[]!, android.content.DialogInterface.OnMultiChoiceClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setMultiChoiceItems(android.database.Cursor!, String!, String!, android.content.DialogInterface.OnMultiChoiceClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNegativeButton(@StringRes int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNegativeButton(CharSequence!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNegativeButtonIcon(android.graphics.drawable.Drawable!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNeutralButton(@StringRes int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNeutralButton(CharSequence!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setNeutralButtonIcon(android.graphics.drawable.Drawable!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setOnCancelListener(android.content.DialogInterface.OnCancelListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setOnDismissListener(android.content.DialogInterface.OnDismissListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setOnKeyListener(android.content.DialogInterface.OnKeyListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setPositiveButton(@StringRes int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setPositiveButton(CharSequence!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setPositiveButtonIcon(android.graphics.drawable.Drawable!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setSingleChoiceItems(@ArrayRes int, int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setSingleChoiceItems(android.database.Cursor!, int, String!, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setSingleChoiceItems(CharSequence[]!, int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setSingleChoiceItems(android.widget.ListAdapter!, int, android.content.DialogInterface.OnClickListener!);
+ method public androidx.appcompat.app.AlertDialog.Builder! setTitle(@StringRes int);
+ method public androidx.appcompat.app.AlertDialog.Builder! setTitle(CharSequence?);
+ method public androidx.appcompat.app.AlertDialog.Builder! setView(int);
+ method public androidx.appcompat.app.AlertDialog.Builder! setView(android.view.View!);
+ method public androidx.appcompat.app.AlertDialog! show();
+ }
+
+ public class AppCompatActivity extends androidx.fragment.app.FragmentActivity implements androidx.appcompat.app.ActionBarDrawerToggle.DelegateProvider androidx.appcompat.app.AppCompatCallback androidx.core.app.TaskStackBuilder.SupportParentable {
+ ctor public AppCompatActivity();
+ ctor @ContentView public AppCompatActivity(@LayoutRes int);
+ method public androidx.appcompat.app.AppCompatDelegate getDelegate();
+ method public androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate();
+ method public androidx.appcompat.app.ActionBar? getSupportActionBar();
+ method public android.content.Intent? getSupportParentActivityIntent();
+ method public void onCreateSupportNavigateUpTaskStack(androidx.core.app.TaskStackBuilder);
+ method public final boolean onMenuItemSelected(int, android.view.MenuItem);
+ method protected void onNightModeChanged(int);
+ method public void onPrepareSupportNavigateUpTaskStack(androidx.core.app.TaskStackBuilder);
+ method @CallSuper public void onSupportActionModeFinished(androidx.appcompat.view.ActionMode);
+ method @CallSuper public void onSupportActionModeStarted(androidx.appcompat.view.ActionMode);
+ method @Deprecated public void onSupportContentChanged();
+ method public boolean onSupportNavigateUp();
+ method public androidx.appcompat.view.ActionMode? onWindowStartingSupportActionMode(androidx.appcompat.view.ActionMode.Callback);
+ method public void setSupportActionBar(androidx.appcompat.widget.Toolbar?);
+ method @Deprecated public void setSupportProgress(int);
+ method @Deprecated public void setSupportProgressBarIndeterminate(boolean);
+ method @Deprecated public void setSupportProgressBarIndeterminateVisibility(boolean);
+ method @Deprecated public void setSupportProgressBarVisibility(boolean);
+ method public androidx.appcompat.view.ActionMode? startSupportActionMode(androidx.appcompat.view.ActionMode.Callback);
+ method public void supportInvalidateOptionsMenu();
+ method public void supportNavigateUpTo(android.content.Intent);
+ method public boolean supportRequestWindowFeature(int);
+ method public boolean supportShouldUpRecreateTask(android.content.Intent);
+ }
+
+ public interface AppCompatCallback {
+ method public void onSupportActionModeFinished(androidx.appcompat.view.ActionMode!);
+ method public void onSupportActionModeStarted(androidx.appcompat.view.ActionMode!);
+ method public androidx.appcompat.view.ActionMode? onWindowStartingSupportActionMode(androidx.appcompat.view.ActionMode.Callback!);
+ }
+
+ public abstract class AppCompatDelegate {
+ method public abstract void addContentView(android.view.View!, android.view.ViewGroup.LayoutParams!);
+ method public abstract boolean applyDayNight();
+ method public void attachBaseContext(android.content.Context!);
+ method public static androidx.appcompat.app.AppCompatDelegate create(android.app.Activity, androidx.appcompat.app.AppCompatCallback?);
+ method public static androidx.appcompat.app.AppCompatDelegate create(android.app.Dialog, androidx.appcompat.app.AppCompatCallback?);
+ method public static androidx.appcompat.app.AppCompatDelegate create(android.content.Context, android.view.Window, androidx.appcompat.app.AppCompatCallback?);
+ method public abstract android.view.View! createView(android.view.View?, String!, android.content.Context, android.util.AttributeSet);
+ method public abstract <T extends android.view.View> T! findViewById(@IdRes int);
+ method public static int getDefaultNightMode();
+ method public abstract androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate();
+ method public int getLocalNightMode();
+ method public abstract android.view.MenuInflater! getMenuInflater();
+ method public abstract androidx.appcompat.app.ActionBar? getSupportActionBar();
+ method public abstract boolean hasWindowFeature(int);
+ method public abstract void installViewFactory();
+ method public abstract void invalidateOptionsMenu();
+ method public static boolean isCompatVectorFromResourcesEnabled();
+ method public abstract boolean isHandleNativeActionModesEnabled();
+ method public abstract void onConfigurationChanged(android.content.res.Configuration!);
+ method public abstract void onCreate(android.os.Bundle!);
+ method public abstract void onDestroy();
+ method public abstract void onPostCreate(android.os.Bundle!);
+ method public abstract void onPostResume();
+ method public abstract void onSaveInstanceState(android.os.Bundle!);
+ method public abstract void onStart();
+ method public abstract void onStop();
+ method public abstract boolean requestWindowFeature(int);
+ method public static void setCompatVectorFromResourcesEnabled(boolean);
+ method public abstract void setContentView(android.view.View!);
+ method public abstract void setContentView(@LayoutRes int);
+ method public abstract void setContentView(android.view.View!, android.view.ViewGroup.LayoutParams!);
+ method public static void setDefaultNightMode(int);
+ method public abstract void setHandleNativeActionModesEnabled(boolean);
+ method public abstract void setLocalNightMode(int);
+ method public abstract void setSupportActionBar(androidx.appcompat.widget.Toolbar?);
+ method public void setTheme(@StyleRes int);
+ method public abstract void setTitle(CharSequence?);
+ method public abstract androidx.appcompat.view.ActionMode? startSupportActionMode(androidx.appcompat.view.ActionMode.Callback);
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ field public static final int FEATURE_SUPPORT_ACTION_BAR = 108; // 0x6c
+ field public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 109; // 0x6d
+ field @Deprecated public static final int MODE_NIGHT_AUTO = 0; // 0x0
+ field public static final int MODE_NIGHT_AUTO_BATTERY = 3; // 0x3
+ field @Deprecated public static final int MODE_NIGHT_AUTO_TIME = 0; // 0x0
+ field public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1; // 0xffffffff
+ field public static final int MODE_NIGHT_NO = 1; // 0x1
+ field public static final int MODE_NIGHT_UNSPECIFIED = -100; // 0xffffff9c
+ field public static final int MODE_NIGHT_YES = 2; // 0x2
+ }
+
+ public class AppCompatDialog extends android.app.Dialog implements androidx.appcompat.app.AppCompatCallback {
+ ctor public AppCompatDialog(android.content.Context!);
+ ctor public AppCompatDialog(android.content.Context!, int);
+ ctor protected AppCompatDialog(android.content.Context!, boolean, android.content.DialogInterface.OnCancelListener!);
+ method public androidx.appcompat.app.AppCompatDelegate! getDelegate();
+ method public androidx.appcompat.app.ActionBar! getSupportActionBar();
+ method public void onSupportActionModeFinished(androidx.appcompat.view.ActionMode!);
+ method public void onSupportActionModeStarted(androidx.appcompat.view.ActionMode!);
+ method public androidx.appcompat.view.ActionMode? onWindowStartingSupportActionMode(androidx.appcompat.view.ActionMode.Callback!);
+ method public boolean supportRequestWindowFeature(int);
+ }
+
+ public class AppCompatDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public AppCompatDialogFragment();
+ }
+
+ public class AppCompatViewInflater {
+ ctor public AppCompatViewInflater();
+ method protected androidx.appcompat.widget.AppCompatAutoCompleteTextView createAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatButton createButton(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatCheckBox createCheckBox(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatCheckedTextView createCheckedTextView(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatEditText createEditText(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatImageButton createImageButton(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatImageView createImageView(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatRadioButton createRadioButton(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatRatingBar createRatingBar(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatSeekBar createSeekBar(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatSpinner createSpinner(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatTextView createTextView(android.content.Context!, android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.AppCompatToggleButton createToggleButton(android.content.Context!, android.util.AttributeSet!);
+ method protected android.view.View? createView(android.content.Context!, String!, android.util.AttributeSet!);
+ }
+
+}
+
+package androidx.appcompat.graphics.drawable {
+
+ public class DrawerArrowDrawable extends android.graphics.drawable.Drawable {
+ ctor public DrawerArrowDrawable(android.content.Context!);
+ method public void draw(android.graphics.Canvas!);
+ method public float getArrowHeadLength();
+ method public float getArrowShaftLength();
+ method public float getBarLength();
+ method public float getBarThickness();
+ method @ColorInt public int getColor();
+ method public int getDirection();
+ method public float getGapSize();
+ method public int getOpacity();
+ method public final android.graphics.Paint! getPaint();
+ method @FloatRange(from=0.0, to=1.0) public float getProgress();
+ method public boolean isSpinEnabled();
+ method public void setAlpha(int);
+ method public void setArrowHeadLength(float);
+ method public void setArrowShaftLength(float);
+ method public void setBarLength(float);
+ method public void setBarThickness(float);
+ method public void setColor(@ColorInt int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setDirection(int);
+ method public void setGapSize(float);
+ method public void setProgress(@FloatRange(from=0.0, to=1.0) float);
+ method public void setSpinEnabled(boolean);
+ method public void setVerticalMirror(boolean);
+ field public static final int ARROW_DIRECTION_END = 3; // 0x3
+ field public static final int ARROW_DIRECTION_LEFT = 0; // 0x0
+ field public static final int ARROW_DIRECTION_RIGHT = 1; // 0x1
+ field public static final int ARROW_DIRECTION_START = 2; // 0x2
+ }
+
+}
+
+package androidx.appcompat.view {
+
+ public abstract class ActionMode {
+ ctor public ActionMode();
+ method public abstract void finish();
+ method public abstract android.view.View! getCustomView();
+ method public abstract android.view.Menu! getMenu();
+ method public abstract android.view.MenuInflater! getMenuInflater();
+ method public abstract CharSequence! getSubtitle();
+ method public Object! getTag();
+ method public abstract CharSequence! getTitle();
+ method public boolean getTitleOptionalHint();
+ method public abstract void invalidate();
+ method public boolean isTitleOptional();
+ method public abstract void setCustomView(android.view.View!);
+ method public abstract void setSubtitle(CharSequence!);
+ method public abstract void setSubtitle(int);
+ method public void setTag(Object!);
+ method public abstract void setTitle(CharSequence!);
+ method public abstract void setTitle(int);
+ method public void setTitleOptionalHint(boolean);
+ }
+
+ public static interface ActionMode.Callback {
+ method public boolean onActionItemClicked(androidx.appcompat.view.ActionMode!, android.view.MenuItem!);
+ method public boolean onCreateActionMode(androidx.appcompat.view.ActionMode!, android.view.Menu!);
+ method public void onDestroyActionMode(androidx.appcompat.view.ActionMode!);
+ method public boolean onPrepareActionMode(androidx.appcompat.view.ActionMode!, android.view.Menu!);
+ }
+
+ public interface CollapsibleActionView {
+ method public void onActionViewCollapsed();
+ method public void onActionViewExpanded();
+ }
+
+ public class ContextThemeWrapper extends android.content.ContextWrapper {
+ ctor public ContextThemeWrapper();
+ ctor public ContextThemeWrapper(android.content.Context!, @StyleRes int);
+ ctor public ContextThemeWrapper(android.content.Context!, android.content.res.Resources.Theme!);
+ method public void applyOverrideConfiguration(android.content.res.Configuration!);
+ method public int getThemeResId();
+ method protected void onApplyThemeResource(android.content.res.Resources.Theme!, int, boolean);
+ }
+
+}
+
+package androidx.appcompat.widget {
+
+ public class ActionMenuView extends androidx.appcompat.widget.LinearLayoutCompat {
+ ctor public ActionMenuView(android.content.Context!);
+ ctor public ActionMenuView(android.content.Context!, android.util.AttributeSet!);
+ method public void dismissPopupMenus();
+ method protected androidx.appcompat.widget.ActionMenuView.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.appcompat.widget.ActionMenuView.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.ActionMenuView.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public android.view.Menu! getMenu();
+ method public android.graphics.drawable.Drawable? getOverflowIcon();
+ method public int getPopupTheme();
+ method public boolean hideOverflowMenu();
+ method public boolean isOverflowMenuShowing();
+ method public void onConfigurationChanged(android.content.res.Configuration!);
+ method public void onDetachedFromWindow();
+ method public void setOnMenuItemClickListener(androidx.appcompat.widget.ActionMenuView.OnMenuItemClickListener!);
+ method public void setOverflowIcon(android.graphics.drawable.Drawable?);
+ method public void setPopupTheme(@StyleRes int);
+ method public boolean showOverflowMenu();
+ }
+
+ public static class ActionMenuView.LayoutParams extends androidx.appcompat.widget.LinearLayoutCompat.LayoutParams {
+ ctor public ActionMenuView.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public ActionMenuView.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public ActionMenuView.LayoutParams(androidx.appcompat.widget.ActionMenuView.LayoutParams!);
+ ctor public ActionMenuView.LayoutParams(int, int);
+ field @android.view.ViewDebug.ExportedProperty public int cellsUsed;
+ field @android.view.ViewDebug.ExportedProperty public boolean expandable;
+ field @android.view.ViewDebug.ExportedProperty public int extraPixels;
+ field @android.view.ViewDebug.ExportedProperty public boolean isOverflowButton;
+ field @android.view.ViewDebug.ExportedProperty public boolean preventEdgeOffset;
+ }
+
+ public static interface ActionMenuView.OnMenuItemClickListener {
+ method public boolean onMenuItemClick(android.view.MenuItem!);
+ }
+
+ public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
+ ctor public AppCompatAutoCompleteTextView(android.content.Context!);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTextAppearance(android.content.Context!, int);
+ }
+
+ public class AppCompatButton extends android.widget.Button implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView {
+ ctor public AppCompatButton(android.content.Context!);
+ ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method public void setSupportAllCaps(boolean);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTextAppearance(android.content.Context!, int);
+ }
+
+ public class AppCompatCheckBox extends android.widget.CheckBox implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
+ ctor public AppCompatCheckBox(android.content.Context!);
+ ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatCheckBox(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportButtonTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportButtonTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public class AppCompatCheckedTextView extends android.widget.CheckedTextView {
+ ctor public AppCompatCheckedTextView(android.content.Context!);
+ ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatCheckedTextView(android.content.Context!, android.util.AttributeSet!, int);
+ method public void setTextAppearance(android.content.Context!, int);
+ }
+
+ public class AppCompatEditText extends android.widget.EditText implements androidx.core.view.TintableBackgroundView {
+ ctor public AppCompatEditText(android.content.Context!);
+ ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatEditText(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTextAppearance(android.content.Context!, int);
+ }
+
+ public class AppCompatImageButton extends android.widget.ImageButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
+ ctor public AppCompatImageButton(android.content.Context!);
+ ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatImageButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportImageTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportImageTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportImageTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public class AppCompatImageView extends android.widget.ImageView implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableImageSourceView {
+ ctor public AppCompatImageView(android.content.Context!);
+ ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatImageView(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportImageTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportImageTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportImageTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportImageTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView implements androidx.core.view.TintableBackgroundView {
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTextAppearance(android.content.Context!, int);
+ }
+
+ public class AppCompatRadioButton extends android.widget.RadioButton implements androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundButton {
+ ctor public AppCompatRadioButton(android.content.Context!);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatRadioButton(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportButtonTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportButtonTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportButtonTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public class AppCompatRatingBar extends android.widget.RatingBar {
+ ctor public AppCompatRatingBar(android.content.Context!);
+ ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatRatingBar(android.content.Context!, android.util.AttributeSet!, int);
+ }
+
+ public class AppCompatSeekBar extends android.widget.SeekBar {
+ ctor public AppCompatSeekBar(android.content.Context!);
+ ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatSeekBar(android.content.Context!, android.util.AttributeSet!, int);
+ }
+
+ public class AppCompatSpinner extends android.widget.Spinner implements androidx.core.view.TintableBackgroundView {
+ ctor public AppCompatSpinner(android.content.Context!);
+ ctor public AppCompatSpinner(android.content.Context!, int);
+ ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public AppCompatSpinner(android.content.Context!, android.util.AttributeSet!, int, int, android.content.res.Resources.Theme!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public class AppCompatTextView extends android.widget.TextView implements androidx.core.widget.AutoSizeableTextView androidx.core.view.TintableBackgroundView androidx.core.widget.TintableCompoundDrawablesView {
+ ctor public AppCompatTextView(android.content.Context!);
+ ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatTextView(android.content.Context!, android.util.AttributeSet!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.PorterDuff.Mode? getSupportCompoundDrawablesTintMode();
+ method public androidx.core.text.PrecomputedTextCompat.Params getTextMetricsParamsCompat();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable!);
+ method public void setPrecomputedText(androidx.core.text.PrecomputedTextCompat);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportCompoundDrawablesTintList(android.content.res.ColorStateList?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSupportCompoundDrawablesTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTextAppearance(android.content.Context!, int);
+ method public void setTextFuture(java.util.concurrent.Future<androidx.core.text.PrecomputedTextCompat>?);
+ method public void setTextMetricsParamsCompat(androidx.core.text.PrecomputedTextCompat.Params);
+ }
+
+ public class AppCompatToggleButton extends android.widget.ToggleButton {
+ ctor public AppCompatToggleButton(android.content.Context!);
+ ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public AppCompatToggleButton(android.content.Context!, android.util.AttributeSet!, int);
+ }
+
+ public class LinearLayoutCompat extends android.view.ViewGroup {
+ ctor public LinearLayoutCompat(android.content.Context!);
+ ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!);
+ ctor public LinearLayoutCompat(android.content.Context!, android.util.AttributeSet!, int);
+ method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.LinearLayoutCompat.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public int getBaselineAlignedChildIndex();
+ method public android.graphics.drawable.Drawable! getDividerDrawable();
+ method public int getDividerPadding();
+ method public int getGravity();
+ method public int getOrientation();
+ method public int getShowDividers();
+ method public float getWeightSum();
+ method public boolean isBaselineAligned();
+ method public boolean isMeasureWithLargestChildEnabled();
+ method public void setBaselineAligned(boolean);
+ method public void setBaselineAlignedChildIndex(int);
+ method public void setDividerDrawable(android.graphics.drawable.Drawable!);
+ method public void setDividerPadding(int);
+ method public void setGravity(int);
+ method public void setHorizontalGravity(int);
+ method public void setMeasureWithLargestChildEnabled(boolean);
+ method public void setOrientation(int);
+ method public void setShowDividers(int);
+ method public void setVerticalGravity(int);
+ method public void setWeightSum(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int SHOW_DIVIDER_BEGINNING = 1; // 0x1
+ field public static final int SHOW_DIVIDER_END = 4; // 0x4
+ field public static final int SHOW_DIVIDER_MIDDLE = 2; // 0x2
+ field public static final int SHOW_DIVIDER_NONE = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class LinearLayoutCompat.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public LinearLayoutCompat.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public LinearLayoutCompat.LayoutParams(int, int);
+ ctor public LinearLayoutCompat.LayoutParams(int, int, float);
+ ctor public LinearLayoutCompat.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public LinearLayoutCompat.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public LinearLayoutCompat.LayoutParams(androidx.appcompat.widget.LinearLayoutCompat.LayoutParams!);
+ field public int gravity;
+ field public float weight;
+ }
+
+ public class ListPopupWindow {
+ ctor public ListPopupWindow(android.content.Context);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet?);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet?, @AttrRes int);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet?, @AttrRes int, @StyleRes int);
+ method public void clearListSelection();
+ method public android.view.View.OnTouchListener! createDragToOpenListener(android.view.View!);
+ method public void dismiss();
+ method public android.view.View? getAnchorView();
+ method @StyleRes public int getAnimationStyle();
+ method public android.graphics.drawable.Drawable? getBackground();
+ method public android.graphics.Rect? getEpicenterBounds();
+ method public int getHeight();
+ method public int getHorizontalOffset();
+ method public int getInputMethodMode();
+ method public android.widget.ListView? getListView();
+ method public int getPromptPosition();
+ method public Object? getSelectedItem();
+ method public long getSelectedItemId();
+ method public int getSelectedItemPosition();
+ method public android.view.View? getSelectedView();
+ method public int getSoftInputMode();
+ method public int getVerticalOffset();
+ method public int getWidth();
+ method public boolean isInputMethodNotNeeded();
+ method public boolean isModal();
+ method public boolean isShowing();
+ method public boolean onKeyDown(int, android.view.KeyEvent);
+ method public boolean onKeyPreIme(int, android.view.KeyEvent);
+ method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public boolean performItemClick(int);
+ method public void postShow();
+ method public void setAdapter(android.widget.ListAdapter?);
+ method public void setAnchorView(android.view.View?);
+ method public void setAnimationStyle(@StyleRes int);
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable?);
+ method public void setContentWidth(int);
+ method public void setDropDownGravity(int);
+ method public void setEpicenterBounds(android.graphics.Rect?);
+ method public void setHeight(int);
+ method public void setHorizontalOffset(int);
+ method public void setInputMethodMode(int);
+ method public void setListSelector(android.graphics.drawable.Drawable!);
+ method public void setModal(boolean);
+ method public void setOnDismissListener(android.widget.PopupWindow.OnDismissListener?);
+ method public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener?);
+ method public void setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener?);
+ method public void setPromptPosition(int);
+ method public void setPromptView(android.view.View?);
+ method public void setSelection(int);
+ method public void setSoftInputMode(int);
+ method public void setVerticalOffset(int);
+ method public void setWidth(int);
+ method public void setWindowLayoutType(int);
+ method public void show();
+ field public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; // 0x0
+ field public static final int INPUT_METHOD_NEEDED = 1; // 0x1
+ field public static final int INPUT_METHOD_NOT_NEEDED = 2; // 0x2
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int POSITION_PROMPT_ABOVE = 0; // 0x0
+ field public static final int POSITION_PROMPT_BELOW = 1; // 0x1
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public class PopupMenu {
+ ctor public PopupMenu(android.content.Context, android.view.View);
+ ctor public PopupMenu(android.content.Context, android.view.View, int);
+ ctor public PopupMenu(android.content.Context, android.view.View, int, @AttrRes int, @StyleRes int);
+ method public void dismiss();
+ method public android.view.View.OnTouchListener getDragToOpenListener();
+ method public int getGravity();
+ method public android.view.Menu getMenu();
+ method public android.view.MenuInflater getMenuInflater();
+ method public void inflate(@MenuRes int);
+ method public void setGravity(int);
+ method public void setOnDismissListener(androidx.appcompat.widget.PopupMenu.OnDismissListener?);
+ method public void setOnMenuItemClickListener(androidx.appcompat.widget.PopupMenu.OnMenuItemClickListener?);
+ method public void show();
+ }
+
+ public static interface PopupMenu.OnDismissListener {
+ method public void onDismiss(androidx.appcompat.widget.PopupMenu!);
+ }
+
+ public static interface PopupMenu.OnMenuItemClickListener {
+ method public boolean onMenuItemClick(android.view.MenuItem!);
+ }
+
+ public class SearchView extends androidx.appcompat.widget.LinearLayoutCompat implements androidx.appcompat.view.CollapsibleActionView {
+ ctor public SearchView(android.content.Context!);
+ ctor public SearchView(android.content.Context!, android.util.AttributeSet!);
+ ctor public SearchView(android.content.Context!, android.util.AttributeSet!, int);
+ method public int getImeOptions();
+ method public int getInputType();
+ method public int getMaxWidth();
+ method public CharSequence! getQuery();
+ method public CharSequence? getQueryHint();
+ method public androidx.cursoradapter.widget.CursorAdapter! getSuggestionsAdapter();
+ method public boolean isIconfiedByDefault();
+ method public boolean isIconified();
+ method public boolean isQueryRefinementEnabled();
+ method public boolean isSubmitButtonEnabled();
+ method public void onActionViewCollapsed();
+ method public void onActionViewExpanded();
+ method public void setIconified(boolean);
+ method public void setIconifiedByDefault(boolean);
+ method public void setImeOptions(int);
+ method public void setInputType(int);
+ method public void setMaxWidth(int);
+ method public void setOnCloseListener(androidx.appcompat.widget.SearchView.OnCloseListener!);
+ method public void setOnQueryTextFocusChangeListener(android.view.View.OnFocusChangeListener!);
+ method public void setOnQueryTextListener(androidx.appcompat.widget.SearchView.OnQueryTextListener!);
+ method public void setOnSearchClickListener(android.view.View.OnClickListener!);
+ method public void setOnSuggestionListener(androidx.appcompat.widget.SearchView.OnSuggestionListener!);
+ method public void setQuery(CharSequence!, boolean);
+ method public void setQueryHint(CharSequence?);
+ method public void setQueryRefinementEnabled(boolean);
+ method public void setSearchableInfo(android.app.SearchableInfo!);
+ method public void setSubmitButtonEnabled(boolean);
+ method public void setSuggestionsAdapter(androidx.cursoradapter.widget.CursorAdapter!);
+ }
+
+ public static interface SearchView.OnCloseListener {
+ method public boolean onClose();
+ }
+
+ public static interface SearchView.OnQueryTextListener {
+ method public boolean onQueryTextChange(String!);
+ method public boolean onQueryTextSubmit(String!);
+ }
+
+ public static interface SearchView.OnSuggestionListener {
+ method public boolean onSuggestionClick(int);
+ method public boolean onSuggestionSelect(int);
+ }
+
+ public class ShareActionProvider extends androidx.core.view.ActionProvider {
+ ctor public ShareActionProvider(android.content.Context!);
+ method public android.view.View! onCreateActionView();
+ method public void setOnShareTargetSelectedListener(androidx.appcompat.widget.ShareActionProvider.OnShareTargetSelectedListener!);
+ method public void setShareHistoryFileName(String!);
+ method public void setShareIntent(android.content.Intent!);
+ field public static final String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
+ }
+
+ public static interface ShareActionProvider.OnShareTargetSelectedListener {
+ method public boolean onShareTargetSelected(androidx.appcompat.widget.ShareActionProvider!, android.content.Intent!);
+ }
+
+ public class SwitchCompat extends android.widget.CompoundButton {
+ ctor public SwitchCompat(android.content.Context!);
+ ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!);
+ ctor public SwitchCompat(android.content.Context!, android.util.AttributeSet!, int);
+ method public boolean getShowText();
+ method public boolean getSplitTrack();
+ method public int getSwitchMinWidth();
+ method public int getSwitchPadding();
+ method public CharSequence! getTextOff();
+ method public CharSequence! getTextOn();
+ method public android.graphics.drawable.Drawable! getThumbDrawable();
+ method public int getThumbTextPadding();
+ method public android.content.res.ColorStateList? getThumbTintList();
+ method public android.graphics.PorterDuff.Mode? getThumbTintMode();
+ method public android.graphics.drawable.Drawable! getTrackDrawable();
+ method public android.content.res.ColorStateList? getTrackTintList();
+ method public android.graphics.PorterDuff.Mode? getTrackTintMode();
+ method public void onMeasure(int, int);
+ method public void setShowText(boolean);
+ method public void setSplitTrack(boolean);
+ method public void setSwitchMinWidth(int);
+ method public void setSwitchPadding(int);
+ method public void setSwitchTextAppearance(android.content.Context!, int);
+ method public void setSwitchTypeface(android.graphics.Typeface!, int);
+ method public void setSwitchTypeface(android.graphics.Typeface!);
+ method public void setTextOff(CharSequence!);
+ method public void setTextOn(CharSequence!);
+ method public void setThumbDrawable(android.graphics.drawable.Drawable!);
+ method public void setThumbResource(int);
+ method public void setThumbTextPadding(int);
+ method public void setThumbTintList(android.content.res.ColorStateList?);
+ method public void setThumbTintMode(android.graphics.PorterDuff.Mode?);
+ method public void setTrackDrawable(android.graphics.drawable.Drawable!);
+ method public void setTrackResource(int);
+ method public void setTrackTintList(android.content.res.ColorStateList?);
+ method public void setTrackTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public interface ThemedSpinnerAdapter extends android.widget.SpinnerAdapter {
+ method public android.content.res.Resources.Theme? getDropDownViewTheme();
+ method public void setDropDownViewTheme(android.content.res.Resources.Theme?);
+ }
+
+ public static final class ThemedSpinnerAdapter.Helper {
+ ctor public ThemedSpinnerAdapter.Helper(android.content.Context);
+ method public android.view.LayoutInflater getDropDownViewInflater();
+ method public android.content.res.Resources.Theme? getDropDownViewTheme();
+ method public void setDropDownViewTheme(android.content.res.Resources.Theme?);
+ }
+
+ public class Toolbar extends android.view.ViewGroup {
+ ctor public Toolbar(android.content.Context!);
+ ctor public Toolbar(android.content.Context!, android.util.AttributeSet?);
+ ctor public Toolbar(android.content.Context!, android.util.AttributeSet?, int);
+ method public void collapseActionView();
+ method public void dismissPopupMenus();
+ method protected androidx.appcompat.widget.Toolbar.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.appcompat.widget.Toolbar.LayoutParams! generateLayoutParams(android.util.AttributeSet!);
+ method protected androidx.appcompat.widget.Toolbar.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public CharSequence? getCollapseContentDescription();
+ method public android.graphics.drawable.Drawable? getCollapseIcon();
+ method public int getContentInsetEnd();
+ method public int getContentInsetEndWithActions();
+ method public int getContentInsetLeft();
+ method public int getContentInsetRight();
+ method public int getContentInsetStart();
+ method public int getContentInsetStartWithNavigation();
+ method public int getCurrentContentInsetEnd();
+ method public int getCurrentContentInsetLeft();
+ method public int getCurrentContentInsetRight();
+ method public int getCurrentContentInsetStart();
+ method public android.graphics.drawable.Drawable! getLogo();
+ method public CharSequence! getLogoDescription();
+ method public android.view.Menu! getMenu();
+ method public CharSequence? getNavigationContentDescription();
+ method public android.graphics.drawable.Drawable? getNavigationIcon();
+ method public android.graphics.drawable.Drawable? getOverflowIcon();
+ method public int getPopupTheme();
+ method public CharSequence! getSubtitle();
+ method public CharSequence! getTitle();
+ method public int getTitleMarginBottom();
+ method public int getTitleMarginEnd();
+ method public int getTitleMarginStart();
+ method public int getTitleMarginTop();
+ method public boolean hasExpandedActionView();
+ method public boolean hideOverflowMenu();
+ method public void inflateMenu(@MenuRes int);
+ method public boolean isOverflowMenuShowing();
+ method public void setCollapseContentDescription(@StringRes int);
+ method public void setCollapseContentDescription(CharSequence?);
+ method public void setCollapseIcon(@DrawableRes int);
+ method public void setCollapseIcon(android.graphics.drawable.Drawable?);
+ method public void setContentInsetEndWithActions(int);
+ method public void setContentInsetStartWithNavigation(int);
+ method public void setContentInsetsAbsolute(int, int);
+ method public void setContentInsetsRelative(int, int);
+ method public void setLogo(@DrawableRes int);
+ method public void setLogo(android.graphics.drawable.Drawable!);
+ method public void setLogoDescription(@StringRes int);
+ method public void setLogoDescription(CharSequence!);
+ method public void setNavigationContentDescription(@StringRes int);
+ method public void setNavigationContentDescription(CharSequence?);
+ method public void setNavigationIcon(@DrawableRes int);
+ method public void setNavigationIcon(android.graphics.drawable.Drawable?);
+ method public void setNavigationOnClickListener(android.view.View.OnClickListener!);
+ method public void setOnMenuItemClickListener(androidx.appcompat.widget.Toolbar.OnMenuItemClickListener!);
+ method public void setOverflowIcon(android.graphics.drawable.Drawable?);
+ method public void setPopupTheme(@StyleRes int);
+ method public void setSubtitle(@StringRes int);
+ method public void setSubtitle(CharSequence!);
+ method public void setSubtitleTextAppearance(android.content.Context!, @StyleRes int);
+ method public void setSubtitleTextColor(@ColorInt int);
+ method public void setSubtitleTextColor(android.content.res.ColorStateList);
+ method public void setTitle(@StringRes int);
+ method public void setTitle(CharSequence!);
+ method public void setTitleMargin(int, int, int, int);
+ method public void setTitleMarginBottom(int);
+ method public void setTitleMarginEnd(int);
+ method public void setTitleMarginStart(int);
+ method public void setTitleMarginTop(int);
+ method public void setTitleTextAppearance(android.content.Context!, @StyleRes int);
+ method public void setTitleTextColor(@ColorInt int);
+ method public void setTitleTextColor(android.content.res.ColorStateList);
+ method public boolean showOverflowMenu();
+ }
+
+ public static class Toolbar.LayoutParams extends androidx.appcompat.app.ActionBar.LayoutParams {
+ ctor public Toolbar.LayoutParams(android.content.Context, android.util.AttributeSet!);
+ ctor public Toolbar.LayoutParams(int, int);
+ ctor public Toolbar.LayoutParams(int, int, int);
+ ctor public Toolbar.LayoutParams(int);
+ ctor public Toolbar.LayoutParams(androidx.appcompat.widget.Toolbar.LayoutParams!);
+ ctor public Toolbar.LayoutParams(androidx.appcompat.app.ActionBar.LayoutParams!);
+ ctor public Toolbar.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public Toolbar.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ }
+
+ public static interface Toolbar.OnMenuItemClickListener {
+ method public boolean onMenuItemClick(android.view.MenuItem!);
+ }
+
+ public static class Toolbar.SavedState extends androidx.customview.view.AbsSavedState {
+ ctor public Toolbar.SavedState(android.os.Parcel!);
+ ctor public Toolbar.SavedState(android.os.Parcel!, ClassLoader!);
+ ctor public Toolbar.SavedState(android.os.Parcelable!);
+ field public static final android.os.Parcelable.Creator<androidx.appcompat.widget.Toolbar.SavedState>! CREATOR;
+ }
+
+ public class TooltipCompat {
+ method public static void setTooltipText(android.view.View, CharSequence?);
+ }
+
+}
+
diff --git a/appcompat/api/current.txt b/appcompat/api/current.txt
index 0b1a7fa..03862a1 100644
--- a/appcompat/api/current.txt
+++ b/appcompat/api/current.txt
@@ -218,6 +218,7 @@
public class AppCompatActivity extends androidx.fragment.app.FragmentActivity implements androidx.appcompat.app.ActionBarDrawerToggle.DelegateProvider androidx.appcompat.app.AppCompatCallback androidx.core.app.TaskStackBuilder.SupportParentable {
ctor public AppCompatActivity();
+ ctor @ContentView public AppCompatActivity(@LayoutRes int);
method public androidx.appcompat.app.AppCompatDelegate getDelegate();
method public androidx.appcompat.app.ActionBarDrawerToggle.Delegate? getDrawerToggleDelegate();
method public androidx.appcompat.app.ActionBar? getSupportActionBar();
@@ -274,7 +275,6 @@
method public abstract void onPostCreate(android.os.Bundle!);
method public abstract void onPostResume();
method public abstract void onSaveInstanceState(android.os.Bundle!);
- method public void onSetTheme(@StyleRes int);
method public abstract void onStart();
method public abstract void onStop();
method public abstract boolean requestWindowFeature(int);
@@ -286,6 +286,7 @@
method public abstract void setHandleNativeActionModesEnabled(boolean);
method public abstract void setLocalNightMode(int);
method public abstract void setSupportActionBar(androidx.appcompat.widget.Toolbar?);
+ method public void setTheme(@StyleRes int);
method public abstract void setTitle(CharSequence?);
method public abstract androidx.appcompat.view.ActionMode? startSupportActionMode(androidx.appcompat.view.ActionMode.Callback);
field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
diff --git a/appcompat/api/res-1.1.0-alpha04.txt b/appcompat/api/res-1.1.0-alpha04.txt
new file mode 100644
index 0000000..b171b18
--- /dev/null
+++ b/appcompat/api/res-1.1.0-alpha04.txt
@@ -0,0 +1,366 @@
+style TextAppearance_AppCompat
+style TextAppearance_AppCompat_Body1
+style TextAppearance_AppCompat_Body2
+style TextAppearance_AppCompat_Button
+style TextAppearance_AppCompat_Caption
+style TextAppearance_AppCompat_Display1
+style TextAppearance_AppCompat_Display2
+style TextAppearance_AppCompat_Display3
+style TextAppearance_AppCompat_Display4
+style TextAppearance_AppCompat_Headline
+style TextAppearance_AppCompat_Inverse
+style TextAppearance_AppCompat_Large
+style TextAppearance_AppCompat_Large_Inverse
+style TextAppearance_AppCompat_Light_SearchResult_Subtitle
+style TextAppearance_AppCompat_Light_SearchResult_Title
+style TextAppearance_AppCompat_Light_Widget_PopupMenu_Large
+style TextAppearance_AppCompat_Light_Widget_PopupMenu_Small
+style TextAppearance_AppCompat_Medium
+style TextAppearance_AppCompat_Medium_Inverse
+style TextAppearance_AppCompat_Menu
+style TextAppearance_AppCompat_SearchResult_Subtitle
+style TextAppearance_AppCompat_SearchResult_Title
+style TextAppearance_AppCompat_Small
+style TextAppearance_AppCompat_Small_Inverse
+style TextAppearance_AppCompat_Subhead
+style TextAppearance_AppCompat_Subhead_Inverse
+style TextAppearance_AppCompat_Title
+style TextAppearance_AppCompat_Title_Inverse
+style TextAppearance_AppCompat_Widget_ActionBar_Menu
+style TextAppearance_AppCompat_Widget_ActionBar_Subtitle
+style TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse
+style TextAppearance_AppCompat_Widget_ActionBar_Title
+style TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse
+style TextAppearance_AppCompat_Widget_ActionMode_Subtitle
+style TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse
+style TextAppearance_AppCompat_Widget_ActionMode_Title
+style TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse
+style TextAppearance_AppCompat_Widget_Button
+style TextAppearance_AppCompat_Widget_Button_Borderless_Colored
+style TextAppearance_AppCompat_Widget_Button_Colored
+style TextAppearance_AppCompat_Widget_Button_Inverse
+style TextAppearance_AppCompat_Widget_DropDownItem
+style TextAppearance_AppCompat_Widget_PopupMenu_Header
+style TextAppearance_AppCompat_Widget_PopupMenu_Large
+style TextAppearance_AppCompat_Widget_PopupMenu_Small
+style TextAppearance_AppCompat_Widget_Switch
+style TextAppearance_AppCompat_Widget_TextView_SpinnerItem
+style Theme_AppCompat
+style Theme_AppCompat_DayNight
+style Theme_AppCompat_DayNight_DarkActionBar
+style Theme_AppCompat_DayNight_Dialog
+style Theme_AppCompat_DayNight_Dialog_Alert
+style Theme_AppCompat_DayNight_Dialog_MinWidth
+style Theme_AppCompat_DayNight_DialogWhenLarge
+style Theme_AppCompat_DayNight_NoActionBar
+style Theme_AppCompat_Dialog
+style Theme_AppCompat_Dialog_Alert
+style Theme_AppCompat_Dialog_MinWidth
+style Theme_AppCompat_DialogWhenLarge
+style Theme_AppCompat_Light
+style Theme_AppCompat_Light_DarkActionBar
+style Theme_AppCompat_Light_Dialog
+style Theme_AppCompat_Light_Dialog_Alert
+style Theme_AppCompat_Light_Dialog_MinWidth
+style Theme_AppCompat_Light_DialogWhenLarge
+style Theme_AppCompat_Light_NoActionBar
+style Theme_AppCompat_NoActionBar
+style ThemeOverlay_AppCompat
+style ThemeOverlay_AppCompat_ActionBar
+style ThemeOverlay_AppCompat_Dark
+style ThemeOverlay_AppCompat_Dark_ActionBar
+style ThemeOverlay_AppCompat_DayNight
+style ThemeOverlay_AppCompat_DayNight_ActionBar
+style ThemeOverlay_AppCompat_Dialog
+style ThemeOverlay_AppCompat_Dialog_Alert
+style ThemeOverlay_AppCompat_Light
+style Widget_AppCompat_ActionBar
+style Widget_AppCompat_ActionBar_Solid
+style Widget_AppCompat_ActionBar_TabBar
+style Widget_AppCompat_ActionBar_TabText
+style Widget_AppCompat_ActionBar_TabView
+style Widget_AppCompat_ActionButton
+style Widget_AppCompat_ActionButton_CloseMode
+style Widget_AppCompat_ActionButton_Overflow
+style Widget_AppCompat_ActionMode
+style Widget_AppCompat_AutoCompleteTextView
+style Widget_AppCompat_Button
+style Widget_AppCompat_Button_Borderless
+style Widget_AppCompat_Button_Borderless_Colored
+style Widget_AppCompat_Button_ButtonBar_AlertDialog
+style Widget_AppCompat_Button_Colored
+style Widget_AppCompat_Button_Small
+style Widget_AppCompat_ButtonBar
+style Widget_AppCompat_ButtonBar_AlertDialog
+style Widget_AppCompat_CompoundButton_CheckBox
+style Widget_AppCompat_CompoundButton_RadioButton
+style Widget_AppCompat_CompoundButton_Switch
+style Widget_AppCompat_DrawerArrowToggle
+style Widget_AppCompat_DropDownItem_Spinner
+style Widget_AppCompat_EditText
+style Widget_AppCompat_ImageButton
+style Widget_AppCompat_Light_ActionBar
+style Widget_AppCompat_Light_ActionBar_Solid
+style Widget_AppCompat_Light_ActionBar_Solid_Inverse
+style Widget_AppCompat_Light_ActionBar_TabBar
+style Widget_AppCompat_Light_ActionBar_TabBar_Inverse
+style Widget_AppCompat_Light_ActionBar_TabText
+style Widget_AppCompat_Light_ActionBar_TabText_Inverse
+style Widget_AppCompat_Light_ActionBar_TabView
+style Widget_AppCompat_Light_ActionBar_TabView_Inverse
+style Widget_AppCompat_Light_ActionButton
+style Widget_AppCompat_Light_ActionButton_CloseMode
+style Widget_AppCompat_Light_ActionButton_Overflow
+style Widget_AppCompat_Light_ActionMode_Inverse
+style Widget_AppCompat_Light_AutoCompleteTextView
+style Widget_AppCompat_Light_DropDownItem_Spinner
+style Widget_AppCompat_Light_ListPopupWindow
+style Widget_AppCompat_Light_ListView_DropDown
+style Widget_AppCompat_Light_PopupMenu
+style Widget_AppCompat_Light_PopupMenu_Overflow
+style Widget_AppCompat_Light_SearchView
+style Widget_AppCompat_Light_Spinner_DropDown_ActionBar
+style Widget_AppCompat_ListPopupWindow
+style Widget_AppCompat_ListView
+style Widget_AppCompat_ListView_DropDown
+style Widget_AppCompat_ListView_Menu
+style Widget_AppCompat_PopupMenu
+style Widget_AppCompat_PopupMenu_Overflow
+style Widget_AppCompat_PopupWindow
+style Widget_AppCompat_ProgressBar
+style Widget_AppCompat_ProgressBar_Horizontal
+style Widget_AppCompat_RatingBar
+style Widget_AppCompat_RatingBar_Indicator
+style Widget_AppCompat_RatingBar_Small
+style Widget_AppCompat_SearchView
+style Widget_AppCompat_SearchView_ActionBar
+style Widget_AppCompat_SeekBar
+style Widget_AppCompat_SeekBar_Discrete
+style Widget_AppCompat_Spinner
+style Widget_AppCompat_Spinner_DropDown
+style Widget_AppCompat_Spinner_DropDown_ActionBar
+style Widget_AppCompat_Spinner_Underlined
+style Widget_AppCompat_TextView
+style Widget_AppCompat_TextView_SpinnerItem
+style Widget_AppCompat_Toolbar
+style Widget_AppCompat_Toolbar_Button_Navigation
+attr actionBarDivider
+attr actionBarItemBackground
+attr actionBarPopupTheme
+attr actionBarSize
+attr actionBarSplitStyle
+attr actionBarStyle
+attr actionBarTabBarStyle
+attr actionBarTabStyle
+attr actionBarTabTextStyle
+attr actionBarTheme
+attr actionBarWidgetTheme
+attr actionButtonStyle
+attr actionDropDownStyle
+attr actionLayout
+attr actionMenuTextAppearance
+attr actionMenuTextColor
+attr actionModeBackground
+attr actionModeCloseButtonStyle
+attr actionModeCloseDrawable
+attr actionModeCopyDrawable
+attr actionModeCutDrawable
+attr actionModeFindDrawable
+attr actionModePasteDrawable
+attr actionModeSelectAllDrawable
+attr actionModeShareDrawable
+attr actionModeSplitBackground
+attr actionModeStyle
+attr actionModeWebSearchDrawable
+attr actionOverflowButtonStyle
+attr actionOverflowMenuStyle
+attr actionProviderClass
+attr actionViewClass
+attr alertDialogStyle
+attr alertDialogTheme
+attr arrowHeadLength
+attr arrowShaftLength
+attr autoCompleteTextViewStyle
+attr autoSizeMaxTextSize
+attr autoSizeMinTextSize
+attr autoSizePresetSizes
+attr autoSizeStepGranularity
+attr autoSizeTextType
+attr background
+attr backgroundSplit
+attr backgroundStacked
+attr backgroundTint
+attr backgroundTintMode
+attr barLength
+attr borderlessButtonStyle
+attr buttonBarButtonStyle
+attr buttonBarNegativeButtonStyle
+attr buttonBarNeutralButtonStyle
+attr buttonBarPositiveButtonStyle
+attr buttonBarStyle
+attr buttonGravity
+attr buttonStyle
+attr buttonStyleSmall
+attr buttonTint
+attr buttonTintMode
+attr checkboxStyle
+attr checkedTextViewStyle
+attr closeIcon
+attr closeItemLayout
+attr collapseContentDescription
+attr collapseIcon
+attr color
+attr colorAccent
+attr colorBackgroundFloating
+attr colorButtonNormal
+attr colorControlActivated
+attr colorControlHighlight
+attr colorControlNormal
+attr colorError
+attr colorPrimary
+attr colorPrimaryDark
+attr commitIcon
+attr contentInsetEnd
+attr contentInsetEndWithActions
+attr contentInsetLeft
+attr contentInsetRight
+attr contentInsetStart
+attr contentInsetStartWithNavigation
+attr customNavigationLayout
+attr dialogCornerRadius
+attr dialogPreferredPadding
+attr dialogTheme
+attr displayOptions
+attr divider
+attr dividerHorizontal
+attr dividerPadding
+attr dividerVertical
+attr drawableSize
+attr drawerArrowStyle
+attr dropDownListViewStyle
+attr editTextBackground
+attr editTextColor
+attr editTextStyle
+attr elevation
+attr firstBaselineToTopHeight
+attr fontFamily
+attr fontVariationSettings
+attr gapBetweenBars
+attr goIcon
+attr height
+attr hideOnContentScroll
+attr homeAsUpIndicator
+attr homeLayout
+attr icon
+attr iconTint
+attr iconTintMode
+attr iconifiedByDefault
+attr imageButtonStyle
+attr indeterminateProgressStyle
+attr isLightTheme
+attr itemPadding
+attr lastBaselineToBottomHeight
+attr layout
+attr lineHeight
+attr listChoiceBackgroundIndicator
+attr listChoiceIndicatorMultipleAnimated
+attr listChoiceIndicatorSingleAnimated
+attr listDividerAlertDialog
+attr listPopupWindowStyle
+attr listPreferredItemHeight
+attr listPreferredItemHeightLarge
+attr listPreferredItemHeightSmall
+attr listPreferredItemPaddingEnd
+attr listPreferredItemPaddingLeft
+attr listPreferredItemPaddingRight
+attr listPreferredItemPaddingStart
+attr logo
+attr logoDescription
+attr maxButtonHeight
+attr measureWithLargestChild
+attr navigationContentDescription
+attr navigationIcon
+attr navigationMode
+attr overlapAnchor
+attr paddingEnd
+attr paddingStart
+attr panelBackground
+attr popupMenuStyle
+attr popupTheme
+attr popupWindowStyle
+attr preserveIconSpacing
+attr progressBarPadding
+attr progressBarStyle
+attr queryBackground
+attr queryHint
+attr radioButtonStyle
+attr ratingBarStyle
+attr ratingBarStyleIndicator
+attr ratingBarStyleSmall
+attr searchHintIcon
+attr searchIcon
+attr searchViewStyle
+attr seekBarStyle
+attr selectableItemBackground
+attr selectableItemBackgroundBorderless
+attr showAsAction
+attr showDividers
+attr showText
+attr spinBars
+attr spinnerDropDownItemStyle
+attr spinnerStyle
+attr splitTrack
+attr srcCompat
+attr state_above_anchor
+attr submitBackground
+attr subtitle
+attr subtitleTextAppearance
+attr subtitleTextColor
+attr subtitleTextStyle
+attr suggestionRowLayout
+layout support_simple_spinner_dropdown_item
+attr switchMinWidth
+attr switchPadding
+attr switchStyle
+attr switchTextAppearance
+attr textAllCaps
+attr textAppearanceLargePopupMenu
+attr textAppearanceListItem
+attr textAppearanceListItemSecondary
+attr textAppearanceListItemSmall
+attr textAppearancePopupMenuHeader
+attr textAppearanceSearchResultSubtitle
+attr textAppearanceSearchResultTitle
+attr textAppearanceSmallPopupMenu
+attr textColorAlertDialogListItem
+attr textLocale
+attr theme
+attr thickness
+attr thumbTextPadding
+attr thumbTint
+attr thumbTintMode
+attr tickMark
+attr tickMarkTint
+attr tickMarkTintMode
+attr tint
+attr tintMode
+attr title
+attr titleMargin
+attr titleMarginBottom
+attr titleMarginEnd
+attr titleMarginStart
+attr titleMarginTop
+attr titleMargins
+attr titleTextAppearance
+attr titleTextColor
+attr titleTextStyle
+attr toolbarNavigationButtonStyle
+attr toolbarStyle
+attr track
+attr trackTint
+attr trackTintMode
+attr voiceIcon
+attr windowActionBar
+attr windowActionBarOverlay
+attr windowActionModeOverlay
+attr windowNoTitle
diff --git a/appcompat/res-public/values/public_styles.xml b/appcompat/res-public/values/public_styles.xml
index aa1475e..a7cfee8 100644
--- a/appcompat/res-public/values/public_styles.xml
+++ b/appcompat/res-public/values/public_styles.xml
@@ -87,6 +87,8 @@
<public type="style" name="ThemeOverlay.AppCompat.ActionBar"/>
<public type="style" name="ThemeOverlay.AppCompat.Dark"/>
<public type="style" name="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+ <public type="style" name="ThemeOverlay.AppCompat.DayNight"/>
+ <public type="style" name="ThemeOverlay.AppCompat.DayNight.ActionBar"/>
<public type="style" name="ThemeOverlay.AppCompat.Dialog"/>
<public type="style" name="ThemeOverlay.AppCompat.Dialog.Alert"/>
<public type="style" name="ThemeOverlay.AppCompat.Light"/>
diff --git a/appcompat/res/values-night/themes_daynight.xml b/appcompat/res/values-night/themes_daynight.xml
index 6d4c57d..1acf5c9 100644
--- a/appcompat/res/values-night/themes_daynight.xml
+++ b/appcompat/res/values-night/themes_daynight.xml
@@ -47,4 +47,7 @@
{@candroidx.appcompat.app.app.AlertDialog} class. -->
<style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Dialog.Alert" />
+ <!-- Variant of ThemeOverlay.AppCompat which switches to light/dark based on the night mode -->
+ <style name="ThemeOverlay.AppCompat.DayNight" parent="ThemeOverlay.AppCompat.Dark" />
+
</resources>
\ No newline at end of file
diff --git a/appcompat/res/values/themes_daynight.xml b/appcompat/res/values/themes_daynight.xml
index 808ec8e..1ebf92f 100644
--- a/appcompat/res/values/themes_daynight.xml
+++ b/appcompat/res/values/themes_daynight.xml
@@ -47,4 +47,13 @@
{@candroidx.appcompat.app.app.AlertDialog} class. -->
<style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert" />
+ <!-- Variant of ThemeOverlay.AppCompat which switches to light/dark based on the night mode. -->
+ <style name="ThemeOverlay.AppCompat.DayNight" parent="ThemeOverlay.AppCompat.Light" />
+
+ <!-- Variant of ThemeOverlay.AppCompat.ActionBar which switches to light/dark based on the night mode. -->
+ <style name="ThemeOverlay.AppCompat.DayNight.ActionBar">
+ <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+ <item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
+ </style>
+
</resources>
\ No newline at end of file
diff --git a/appcompat/resources/api/1.1.0-alpha04.txt b/appcompat/resources/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..990b059
--- /dev/null
+++ b/appcompat/resources/api/1.1.0-alpha04.txt
@@ -0,0 +1,35 @@
+// Signature format: 3.0
+package androidx.appcompat.content.res {
+
+ public final class AppCompatResources {
+ method public static android.content.res.ColorStateList! getColorStateList(android.content.Context, @ColorRes int);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
+ }
+
+}
+
+package androidx.appcompat.graphics.drawable {
+
+ public class AnimatedStateListDrawableCompat extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback androidx.core.graphics.drawable.TintAwareDrawable {
+ ctor public AnimatedStateListDrawableCompat();
+ method public void addState(int[], android.graphics.drawable.Drawable, int);
+ method public void addState(int[]!, android.graphics.drawable.Drawable!);
+ method public <T extends android.graphics.drawable.Drawable & android.graphics.drawable.Animatable> void addTransition(int, int, T, boolean);
+ method public static androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat? create(android.content.Context, @DrawableRes int, android.content.res.Resources.Theme?);
+ method public static androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat! createFromXmlInner(android.content.Context, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method public final android.graphics.drawable.Drawable.ConstantState! getConstantState();
+ method public int getOpacity();
+ method public void inflate(android.content.Context, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void invalidateDrawable(android.graphics.drawable.Drawable);
+ method public void scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setDither(boolean);
+ method public void setEnterFadeDuration(int);
+ method public void setExitFadeDuration(int);
+ method public void unscheduleDrawable(android.graphics.drawable.Drawable, Runnable);
+ }
+
+}
+
diff --git a/appcompat/resources/api/res-1.1.0-alpha04.txt b/appcompat/resources/api/res-1.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appcompat/resources/api/res-1.1.0-alpha04.txt
diff --git a/appcompat/resources/build.gradle b/appcompat/resources/build.gradle
index ebdf4d3..b78317e 100644
--- a/appcompat/resources/build.gradle
+++ b/appcompat/resources/build.gradle
@@ -25,11 +25,10 @@
dependencies {
api(project(":annotation"))
-
api("androidx.core:core:1.0.1")
implementation("androidx.collection:collection:1.0.0")
- api("androidx.vectordrawable:vectordrawable:1.0.1")
- api("androidx.vectordrawable:vectordrawable-animated:1.0.0")
+ api(project(":vectordrawable"))
+ api(project(":vectordrawable-animated"))
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
diff --git a/appcompat/resources/src/main/java/androidx/appcompat/graphics/drawable/AnimatedStateListDrawableCompat.java b/appcompat/resources/src/main/java/androidx/appcompat/graphics/drawable/AnimatedStateListDrawableCompat.java
index 03e3203..f57aace 100644
--- a/appcompat/resources/src/main/java/androidx/appcompat/graphics/drawable/AnimatedStateListDrawableCompat.java
+++ b/appcompat/resources/src/main/java/androidx/appcompat/graphics/drawable/AnimatedStateListDrawableCompat.java
@@ -85,7 +85,6 @@
* {@link android.R.attr#state_middle}
* {@link android.R.attr#state_last}
* {@link android.R.attr#state_pressed}
- * @see ResourceManagerInternal#getDrawable(Context, int)
*/
@SuppressLint("RestrictedAPI") // Temporary until we have correct restriction scopes for 1.0
public class AnimatedStateListDrawableCompat extends StateListDrawable
@@ -129,7 +128,6 @@
* @param resId the resource ID for AnimatedStateListDrawable object.
* @param theme the theme to apply, may be null.
* @return a new AnimatedStateListDrawableCompat or null if parsing error is found.
- * @see ResourceManagerInternal#getDrawable(Context, int)
*/
@Nullable
public static AnimatedStateListDrawableCompat create(
diff --git a/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/src/androidTest/AndroidManifest.xml
index 67a3eb7..4bfb0be 100644
--- a/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/src/androidTest/AndroidManifest.xml
@@ -32,6 +32,10 @@
<activity
android:name="androidx.appcompat.app.WindowDecorAppCompatActivity"/>
+
+ <activity
+ android:name="androidx.appcompat.app.WindowDecorBeforeOnCreateAppCompatActivity"/>
+
<activity
android:name="androidx.appcompat.app.ToolbarAppCompatActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
index baec85d..02b45bf 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeTestCase.java
@@ -200,12 +200,6 @@
// Assert that the Activity received a new value
assertEquals(AppCompatDelegate.MODE_NIGHT_YES,
mActivityTestRule.getActivity().getLastNightModeAndReset());
-
- // Set local night mode to NO
- setLocalNightModeAndWait(mActivityTestRule, AppCompatDelegate.MODE_NIGHT_NO);
- // Assert that the Activity received a new value
- assertEquals(AppCompatDelegate.MODE_NIGHT_NO,
- mActivityTestRule.getActivity().getLastNightModeAndReset());
}
@Test
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateAppCompatActivity.java b/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateAppCompatActivity.java
new file mode 100644
index 0000000..ce65ed9
--- /dev/null
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateAppCompatActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.app;
+
+import android.os.Bundle;
+
+import androidx.appcompat.test.R;
+import androidx.appcompat.testutils.BaseTestActivity;
+
+public class WindowDecorBeforeOnCreateAppCompatActivity extends BaseTestActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getSupportActionBar();
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.window_decor_content;
+ }
+}
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateTestCase.java b/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateTestCase.java
new file mode 100644
index 0000000..4d0c1f3
--- /dev/null
+++ b/appcompat/src/androidTest/java/androidx/appcompat/app/WindowDecorBeforeOnCreateTestCase.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.app;
+
+import static org.junit.Assert.assertNotNull;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class WindowDecorBeforeOnCreateTestCase {
+ @Rule
+ public final ActivityTestRule<WindowDecorBeforeOnCreateAppCompatActivity> mActivityTestRule;
+
+ public WindowDecorBeforeOnCreateTestCase() {
+ mActivityTestRule = new ActivityTestRule<>(
+ WindowDecorBeforeOnCreateAppCompatActivity.class);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testSupportActionBar() {
+ assertNotNull(mActivityTestRule.getActivity().getSupportActionBar());
+ }
+}
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
index d4267b0..f9695e0 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
@@ -19,8 +19,10 @@
import static androidx.appcompat.testutils.TestUtilsMatchers.isCombinedBackground;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
+import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -32,6 +34,7 @@
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.os.SystemClock;
+import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
@@ -40,6 +43,11 @@
import androidx.appcompat.test.R;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.action.CoordinatesProvider;
+import androidx.test.espresso.action.GeneralSwipeAction;
+import androidx.test.espresso.action.Press;
+import androidx.test.espresso.action.Swipe;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
@@ -186,4 +194,74 @@
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
onView(withText(EARTH)).check(matches(isDisplayed()));
}
+
+ @LargeTest
+ @Test
+ public void testSlowScroll() {
+ onView(withId(R.id.spinner_dropdown_popup_with_scroll)).perform(click());
+
+ final AppCompatSpinner spinner = mContainer
+ .findViewById(R.id.spinner_dropdown_popup_with_scroll);
+ String secondItem = (String) spinner.getAdapter().getItem(1);
+
+ onView(isAssignableFrom(DropDownListView.class)).perform(slowScrollPopup());
+
+ // when we scroll slowly a second time the popup list might jump back to the first element
+ onView(isAssignableFrom(DropDownListView.class)).perform(slowScrollPopup());
+
+ // because we scroll twice with one element height each,
+ // the second item should not be visible
+ onView(withText(secondItem))
+ .check(doesNotExist());
+ }
+
+ private ViewAction slowScrollPopup() {
+ return new GeneralSwipeAction(Swipe.SLOW,
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ final float[] middleLocation = getViewMiddleLocation(view);
+ return new float[] {
+ middleLocation[0],
+ middleLocation[1]
+ };
+ }
+ },
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ final float[] middleLocation = getViewMiddleLocation(view);
+ return new float[] {
+ middleLocation[0],
+ middleLocation[1] - getElementSize(view)
+ };
+ }
+ },
+ Press.PINPOINT
+ );
+ }
+
+ private float[] getViewMiddleLocation(View view) {
+ final DropDownListView list = (DropDownListView) view;
+
+ final int[] location = new int[2];
+ list.getLocationOnScreen(location);
+
+ final float x = location[0] + list.getWidth() / 2f;
+ final float y = location[1] + list.getHeight() / 2f;
+
+ return new float[] {x, y};
+ }
+
+ private int getElementSize(View view) {
+ final DropDownListView list = (DropDownListView) view;
+
+ final View child = list.getChildAt(0);
+ final int[] location = new int[2];
+ child.getLocationOnScreen(location);
+
+ // espresso doesn't actually scroll for the full amount specified
+ // so we add a little bit more to be safe
+ return child.getHeight() * 2;
+ }
}
diff --git a/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java b/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
index 047a560..eb6ee0e 100644
--- a/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
+++ b/appcompat/src/androidTest/java/androidx/appcompat/widget/ToolbarTest.java
@@ -32,7 +32,6 @@
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.test.R;
import androidx.appcompat.testutils.TestUtils;
-import androidx.test.espresso.Espresso;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -155,18 +154,9 @@
}
@Test
- public void testToolbarOverflowIconWithThemedCSL() throws Throwable {
+ public void testToolbarOverflowIconWithThemedCSL() {
final Toolbar toolbar = mActivity.findViewById(R.id.toolbar_themedcsl_colorcontrolnormal);
- // Inflate a menu so that the overflow is displayed
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- toolbar.inflateMenu(R.menu.popup_menu);
- }
- });
- Espresso.onIdle();
-
// Assert that the overflow icon is tinted magenta, as per the theme
final Drawable icon = toolbar.getOverflowIcon();
assertNotNull(icon);
diff --git a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
index da55bdf..38228a9 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_spinner_activity.xml
@@ -93,6 +93,13 @@
android:layout_height="wrap_content"
android:entries="@array/planets_array"
android:spinnerMode="dropdown" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/spinner_dropdown_popup_with_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/numbers_array"
+ android:spinnerMode="dropdown" />
</LinearLayout>
</ScrollView>
diff --git a/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml b/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
index c1bab6a4..0d11cab 100644
--- a/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
+++ b/appcompat/src/androidTest/res/layout/appcompat_toolbar_activity.xml
@@ -56,6 +56,7 @@
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.ThemedCslMagenta"
+ app:menu="@menu/popup_menu"
app:subtitle="Subtitle"
app:title="Title" />
diff --git a/appcompat/src/androidTest/res/values/strings.xml b/appcompat/src/androidTest/res/values/strings.xml
index 964c32b..0188024 100644
--- a/appcompat/src/androidTest/res/values/strings.xml
+++ b/appcompat/src/androidTest/res/values/strings.xml
@@ -78,6 +78,48 @@
<item>Neptune</item>
<item>Pluto</item>
</string-array>
+ <string-array name="numbers_array">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ <item>4</item>
+ <item>5</item>
+ <item>6</item>
+ <item>7</item>
+ <item>8</item>
+ <item>9</item>
+ <item>10</item>
+ <item>11</item>
+ <item>12</item>
+ <item>13</item>
+ <item>14</item>
+ <item>15</item>
+ <item>16</item>
+ <item>17</item>
+ <item>18</item>
+ <item>19</item>
+ <item>20</item>
+ <item>21</item>
+ <item>22</item>
+ <item>23</item>
+ <item>24</item>
+ <item>25</item>
+ <item>26</item>
+ <item>27</item>
+ <item>28</item>
+ <item>29</item>
+ <item>30</item>
+ <item>31</item>
+ <item>32</item>
+ <item>33</item>
+ <item>34</item>
+ <item>35</item>
+ <item>36</item>
+ <item>37</item>
+ <item>38</item>
+ <item>39</item>
+ </string-array>
<string name="night_mode">DAY</string>
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
index c34cc57..8e7c7fd 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatActivity.java
@@ -31,6 +31,7 @@
import android.view.Window;
import androidx.annotation.CallSuper;
+import androidx.annotation.ContentView;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
@@ -67,6 +68,30 @@
private AppCompatDelegate mDelegate;
private Resources mResources;
+ /**
+ * Default constructor for AppCompatActivity. All Activities must have a default constructor
+ * for API 27 and lower devices or when using the default
+ * {@link android.app.AppComponentFactory}.
+ */
+ public AppCompatActivity() {
+ super();
+ }
+
+ /**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>.
+ *
+ * <p>This should generally be called from your constructor that takes no parameters,
+ * as is required for API 27 and lower or when using the default
+ * {@link android.app.AppComponentFactory}.
+ *
+ * @see #AppCompatActivity()
+ */
+ @ContentView
+ public AppCompatActivity(@LayoutRes int contentLayoutId) {
+ super(contentLayoutId);
+ }
+
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
@@ -84,7 +109,7 @@
@Override
public void setTheme(@StyleRes final int resId) {
super.setTheme(resId);
- getDelegate().onSetTheme(resId);
+ getDelegate().setTheme(resId);
}
@Override
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
index 6dca0e7..e0d27bb 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegate.java
@@ -316,7 +316,7 @@
* This should be called from {@link Activity#setTheme(int)} to notify AppCompat of what
* the current theme resource id is.
*/
- public void onSetTheme(@StyleRes int themeResId) {
+ public void setTheme(@StyleRes int themeResId) {
}
/**
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index bc93b5e..5d62ffa 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -325,13 +325,7 @@
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
- if (mWindow == null && mHost instanceof Activity) {
- attachToWindow(((Activity) mHost).getWindow());
- }
-
- if (mWindow == null) {
- throw new IllegalStateException("We have not been given a Window");
- }
+ ensureWindow();
if (mHost instanceof Activity) {
String parentActivityName = null;
@@ -591,10 +585,21 @@
}
@Override
- public void onSetTheme(@StyleRes int themeResId) {
+ public void setTheme(@StyleRes int themeResId) {
mThemeResId = themeResId;
}
+ private void ensureWindow() {
+ // We lazily fetch the Window for Activities, to allow DayNight to apply in
+ // attachBaseContext
+ if (mWindow == null && mHost instanceof Activity) {
+ attachToWindow(((Activity) mHost).getWindow());
+ }
+ if (mWindow == null) {
+ throw new IllegalStateException("We have not been given a Window");
+ }
+ }
+
private void attachToWindow(@NonNull Window window) {
if (mWindow != null) {
throw new IllegalStateException(
@@ -672,6 +677,7 @@
a.recycle();
// Now let's make sure that the Window has installed its decor by retrieving it
+ ensureWindow();
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
@@ -2191,6 +2197,9 @@
final boolean allowRecreation) {
boolean handled = false;
+ final int applicationNightMode = mContext.getApplicationContext()
+ .getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
int newNightMode;
switch (mode) {
case MODE_NIGHT_YES:
@@ -2203,16 +2212,14 @@
case MODE_NIGHT_FOLLOW_SYSTEM:
// If we're following the system, we just use the system default from the
// application context
- newNightMode = mContext.getApplicationContext()
- .getResources()
- .getConfiguration()
- .uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ newNightMode = applicationNightMode;
break;
}
final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode();
- if (!activityHandlingUiMode && Build.VERSION.SDK_INT >= 17 && !mBaseContextAttached
+ if (newNightMode != applicationNightMode && !activityHandlingUiMode
+ && Build.VERSION.SDK_INT >= 17 && !mBaseContextAttached
&& mHost instanceof android.view.ContextThemeWrapper) {
// If we're here then we can try and apply an override configuration on the Context.
final Configuration conf = new Configuration();
diff --git a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDialog.java b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDialog.java
index d0443af..865d6f5 100644
--- a/appcompat/src/main/java/androidx/appcompat/app/AppCompatDialog.java
+++ b/appcompat/src/main/java/androidx/appcompat/app/AppCompatDialog.java
@@ -58,7 +58,7 @@
final AppCompatDelegate delegate = getDelegate();
// Make sure we provide the delegate with the current theme res id
- delegate.onSetTheme(getThemeResId(context, theme));
+ delegate.setTheme(getThemeResId(context, theme));
// This is a bit weird, but Dialog's are typically created and setup before being shown,
// which means that we can't rely on onCreate() being called before a content view is set.
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
index 92b0c67..f0a8452 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatImageView.java
@@ -30,6 +30,7 @@
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.appcompat.R;
import androidx.core.view.TintableBackgroundView;
import androidx.core.widget.ImageViewCompat;
import androidx.core.widget.TintableImageSourceView;
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
index 86886bd..1033e46 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/AppCompatSpinner.java
@@ -1015,12 +1015,6 @@
}
@Override
- @SuppressLint("SyntheticAccessor")
- public void show() {
- showPopup();
- }
-
- @Override
public void show(int textDirection, int textAlignment) {
final boolean wasShowing = isShowing();
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java b/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
index 364f2cb..98d9795 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/SearchView.java
@@ -434,7 +434,7 @@
* @see TextView#setImeOptions(int)
* @param imeOptions the options to set on the query text field
*
- * {@link androidx.appcompat.R.attr#android_imeOptions}
+ * {@link android.R.attr#imeOptions}
*/
public void setImeOptions(int imeOptions) {
mSearchSrcTextView.setImeOptions(imeOptions);
@@ -445,7 +445,7 @@
* @return the ime options
* @see TextView#setImeOptions(int)
*
- * {@link androidx.appcompat.R.attr#android_imeOptions}
+ * {@link android.R.attr#imeOptions}
*/
public int getImeOptions() {
return mSearchSrcTextView.getImeOptions();
@@ -457,7 +457,7 @@
* @see TextView#setInputType(int)
* @param inputType the input type to set on the query text field
*
- * {@link androidx.appcompat.R.attr#android_inputType}
+ * {@link android.R.attr#inputType}
*/
public void setInputType(int inputType) {
mSearchSrcTextView.setInputType(inputType);
@@ -467,7 +467,7 @@
* Returns the input type set on the query text field.
* @return the input type
*
- * {@link androidx.appcompat.R.attr#android_inputType}
+ * {@link android.R.attr#inputType}
*/
public int getInputType() {
return mSearchSrcTextView.getInputType();
@@ -757,7 +757,7 @@
/**
* Makes the view at most this many pixels wide
*
- * {@link androidx.appcompat.R.attr#android_maxWidth}
+ * {@link android.R.attr#maxWidth}
*/
public void setMaxWidth(int maxpixels) {
mMaxWidth = maxpixels;
@@ -770,7 +770,7 @@
* no maximum width was specified.
* @return the maximum width of the view
*
- * {@link androidx.appcompat.R.attr#android_maxWidth}
+ * {@link android.R.attr#maxWidth}
*/
public int getMaxWidth() {
return mMaxWidth;
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java b/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
index 5449de0..f6964a2 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/SwitchCompat.java
@@ -73,12 +73,12 @@
* <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
* guide.</p>
*
- * {@link androidx.appcompat.R.attr#android_textOn}
- * {@link androidx.appcompat.R.attr#android_textOff}
+ * {@link android.R.attr#textOn}
+ * {@link android.R.attr#textOff}
* {@link androidx.appcompat.R.attr#switchMinWidth}
* {@link androidx.appcompat.R.attr#switchPadding}
* {@link androidx.appcompat.R.attr#switchTextAppearance}
- * {@link androidx.appcompat.R.attr#android_thumb}
+ * {@link android.R.attr#thumb}
* {@link androidx.appcompat.R.attr#thumbTextPadding}
* {@link androidx.appcompat.R.attr#track}
*/
@@ -597,7 +597,7 @@
*
* @param thumb Thumb drawable
*
- * {@link androidx.appcompat.R.attr#android_thumb}
+ * {@link android.R.attr#thumb}
*/
public void setThumbDrawable(Drawable thumb) {
if (mThumbDrawable != null) {
@@ -616,7 +616,7 @@
*
* @param resId Resource ID of a thumb drawable
*
- * {@link androidx.appcompat.R.attr#android_thumb}
+ * {@link android.R.attr#thumb}
*/
public void setThumbResource(int resId) {
setThumbDrawable(AppCompatResources.getDrawable(getContext(), resId));
@@ -628,7 +628,7 @@
*
* @return Thumb drawable
*
- * {@link androidx.appcompat.R.attr#android_thumb}
+ * {@link android.R.attr#thumb}
*/
public Drawable getThumbDrawable() {
return mThumbDrawable;
@@ -740,7 +740,7 @@
/**
* Returns the text displayed when the button is in the checked state.
*
- * {@link androidx.appcompat.R.attr#android_textOn}
+ * {@link android.R.attr#textOn}
*/
public CharSequence getTextOn() {
return mTextOn;
@@ -749,7 +749,7 @@
/**
* Sets the text displayed when the button is in the checked state.
*
- * {@link androidx.appcompat.R.attr#android_textOn}
+ * {@link android.R.attr#textOn}
*/
public void setTextOn(CharSequence textOn) {
mTextOn = textOn;
@@ -759,7 +759,7 @@
/**
* Returns the text displayed when the button is not in the checked state.
*
- * {@link androidx.appcompat.R.attr#android_textOff}
+ * {@link android.R.attr#textOff}
*/
public CharSequence getTextOff() {
return mTextOff;
@@ -768,7 +768,7 @@
/**
* Sets the text displayed when the button is not in the checked state.
*
- * {@link androidx.appcompat.R.attr#android_textOff}
+ * {@link android.R.attr#textOff}
*/
public void setTextOff(CharSequence textOff) {
mTextOff = textOff;
diff --git a/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java b/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
index 2754164..9b4a11d 100644
--- a/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
+++ b/appcompat/src/main/java/androidx/appcompat/widget/Toolbar.java
@@ -122,7 +122,7 @@
* {@link androidx.appcompat.R.attr#contentInsetStart}
* {@link androidx.appcompat.R.attr#contentInsetStartWithNavigation}
* {@link androidx.appcompat.R.attr#contentInsetEndWithActions}
- * {@link androidx.appcompat.R.attr#android_gravity}
+ * {@link android.R.attr#gravity}
* {@link androidx.appcompat.R.attr#logo}
* {@link androidx.appcompat.R.attr#logoDescription}
* {@link androidx.appcompat.R.attr#maxButtonHeight}
diff --git a/arch/core-common/api/2.1.0-alpha01.txt b/arch/core-common/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..4068f2c
--- /dev/null
+++ b/arch/core-common/api/2.1.0-alpha01.txt
@@ -0,0 +1,15 @@
+// Signature format: 3.0
+package androidx.arch.core.util {
+
+ public interface Cancellable {
+ method public void cancel();
+ method public boolean isCancelled();
+ field public static final androidx.arch.core.util.Cancellable CANCELLED;
+ }
+
+ public interface Function<I, O> {
+ method public O! apply(I!);
+ }
+
+}
+
diff --git a/arch/core-common/api/current.txt b/arch/core-common/api/current.txt
index dba1d6a7..4068f2c 100644
--- a/arch/core-common/api/current.txt
+++ b/arch/core-common/api/current.txt
@@ -1,6 +1,12 @@
// Signature format: 3.0
package androidx.arch.core.util {
+ public interface Cancellable {
+ method public void cancel();
+ method public boolean isCancelled();
+ field public static final androidx.arch.core.util.Cancellable CANCELLED;
+ }
+
public interface Function<I, O> {
method public O! apply(I!);
}
diff --git a/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java b/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java
new file mode 100644
index 0000000..232f208
--- /dev/null
+++ b/arch/core-common/src/main/java/androidx/arch/core/util/Cancellable.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.arch.core.util;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Token representing a cancellable operation.
+ */
+public interface Cancellable {
+ /**
+ * An instance of Cancellable that is always cancelled - i.e., {@link #isCancelled()} will
+ * always return true.
+ */
+ @NonNull
+ Cancellable CANCELLED = new Cancellable() {
+ @Override
+ public void cancel() {
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return true;
+ }
+ };
+
+ /**
+ * Cancel the subscription. This call should be idempotent, making it safe to
+ * call multiple times.
+ */
+ void cancel();
+
+ /**
+ * Returns true if the subscription has been cancelled. This is inherently a
+ * racy operation if you are calling {@link #cancel()} on another thread, so this
+ * should be treated as a 'best effort' signal.
+ *
+ * @return Whether the subscription has been cancelled.
+ */
+ boolean isCancelled();
+}
diff --git a/arch/core-runtime/api/2.1.0-alpha01.txt b/arch/core-runtime/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/arch/core-runtime/api/2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/arch/core-runtime/api/res-2.1.0-alpha01.txt b/arch/core-runtime/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/arch/core-runtime/api/res-2.1.0-alpha01.txt
diff --git a/arch/core-runtime/api/restricted_2.1.0-alpha01.txt b/arch/core-runtime/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..adaaab4
--- /dev/null
+++ b/arch/core-runtime/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.arch.core.executor {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ArchTaskExecutor extends androidx.arch.core.executor.TaskExecutor {
+ method public void executeOnDiskIO(Runnable!);
+ method public static java.util.concurrent.Executor getIOThreadExecutor();
+ method public static androidx.arch.core.executor.ArchTaskExecutor getInstance();
+ method public static java.util.concurrent.Executor getMainThreadExecutor();
+ method public boolean isMainThread();
+ method public void postToMainThread(Runnable!);
+ method public void setDelegate(androidx.arch.core.executor.TaskExecutor?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DefaultTaskExecutor extends androidx.arch.core.executor.TaskExecutor {
+ ctor public DefaultTaskExecutor();
+ method public void executeOnDiskIO(Runnable!);
+ method public boolean isMainThread();
+ method public void postToMainThread(Runnable!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TaskExecutor {
+ ctor public TaskExecutor();
+ method public abstract void executeOnDiskIO(Runnable);
+ method public void executeOnMainThread(Runnable);
+ method public abstract boolean isMainThread();
+ method public abstract void postToMainThread(Runnable);
+ }
+
+}
+
diff --git a/arch/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java b/arch/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
index 3b7aa69..8244ce1 100644
--- a/arch/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
+++ b/arch/core-runtime/src/main/java/androidx/arch/core/executor/DefaultTaskExecutor.java
@@ -38,7 +38,7 @@
private final Object mLock = new Object();
- private final ExecutorService mDiskIO = Executors.newFixedThreadPool(2, new ThreadFactory() {
+ private final ExecutorService mDiskIO = Executors.newFixedThreadPool(4, new ThreadFactory() {
private static final String THREAD_NAME_STEM = "arch_disk_io_%d";
private final AtomicInteger mThreadId = new AtomicInteger(0);
diff --git a/arch/core-testing/api/2.1.0-alpha01.txt b/arch/core-testing/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..7eb7fe1
--- /dev/null
+++ b/arch/core-testing/api/2.1.0-alpha01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.arch.core.executor.testing {
+
+ public class CountingTaskExecutorRule extends org.junit.rules.TestWatcher {
+ ctor public CountingTaskExecutorRule();
+ method public void drainTasks(int, java.util.concurrent.TimeUnit!) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
+ method public boolean isIdle();
+ method protected void onIdle();
+ }
+
+ public class InstantTaskExecutorRule extends org.junit.rules.TestWatcher {
+ ctor public InstantTaskExecutorRule();
+ }
+
+}
+
diff --git a/arch/core-testing/api/res-2.1.0-alpha01.txt b/arch/core-testing/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/arch/core-testing/api/res-2.1.0-alpha01.txt
diff --git a/arch/core-testing/api/restricted_2.1.0-alpha01.txt b/arch/core-testing/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..6b162df
--- /dev/null
+++ b/arch/core-testing/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1,20 @@
+// Signature format: 3.0
+package androidx.arch.core.executor {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class JunitTaskExecutorRule implements org.junit.rules.TestRule {
+ ctor public JunitTaskExecutorRule(int, boolean);
+ method public org.junit.runners.model.Statement! apply(org.junit.runners.model.Statement!, org.junit.runner.Description!);
+ method public void drainTasks(int) throws java.lang.InterruptedException;
+ method public androidx.arch.core.executor.TaskExecutor! getTaskExecutor();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TaskExecutorWithFakeMainThread extends androidx.arch.core.executor.TaskExecutor {
+ ctor public TaskExecutorWithFakeMainThread(int);
+ method public void drainTasks(int) throws java.lang.InterruptedException;
+ method public void executeOnDiskIO(Runnable!);
+ method public boolean isMainThread();
+ method public void postToMainThread(Runnable!);
+ }
+
+}
+
diff --git a/benchmark/api/1.0.0-alpha01.txt b/benchmark/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..12ccebb
--- /dev/null
+++ b/benchmark/api/1.0.0-alpha01.txt
@@ -0,0 +1,23 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ method public inline void keepRunning(kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Context,kotlin.Unit> block);
+ property public final androidx.benchmark.BenchmarkState state;
+ }
+
+ public final class BenchmarkRule.Context {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkState {
+ method public boolean keepRunning();
+ method public void pauseTiming();
+ method public void resumeTiming();
+ }
+
+}
+
diff --git a/benchmark/api/current.txt b/benchmark/api/current.txt
new file mode 100644
index 0000000..12ccebb
--- /dev/null
+++ b/benchmark/api/current.txt
@@ -0,0 +1,23 @@
+// Signature format: 3.0
+package androidx.benchmark {
+
+ public final class BenchmarkRule implements org.junit.rules.TestRule {
+ ctor public BenchmarkRule();
+ method public org.junit.runners.model.Statement apply(org.junit.runners.model.Statement base, org.junit.runner.Description description);
+ method public androidx.benchmark.BenchmarkState getState();
+ method public inline void keepRunning(kotlin.jvm.functions.Function1<? super androidx.benchmark.BenchmarkRule.Context,kotlin.Unit> block);
+ property public final androidx.benchmark.BenchmarkState state;
+ }
+
+ public final class BenchmarkRule.Context {
+ method public inline <T> T! runWithTimingDisabled(kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+ public final class BenchmarkState {
+ method public boolean keepRunning();
+ method public void pauseTiming();
+ method public void resumeTiming();
+ }
+
+}
+
diff --git a/benchmark/api/res-1.0.0-alpha01.txt b/benchmark/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/benchmark/api/res-1.0.0-alpha01.txt
diff --git a/benchmark/api/restricted_1.0.0-alpha01.txt b/benchmark/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/benchmark/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/benchmark/api/restricted_current.txt b/benchmark/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/benchmark/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/benchmark/build.gradle b/benchmark/build.gradle
index babffd4..6bdfc44 100644
--- a/benchmark/build.gradle
+++ b/benchmark/build.gradle
@@ -24,12 +24,11 @@
}
dependencies {
- api(project(":annotation"))
-
+ implementation(SUPPORT_ANNOTATIONS)
implementation(TEST_RUNNER)
androidTestImplementation(JUNIT)
- androidTestImplementation(KOTLIN_STDLIB)
+ implementation(KOTLIN_STDLIB)
}
supportLibrary {
diff --git a/benchmark/gradle-plugin/build.gradle b/benchmark/gradle-plugin/build.gradle
new file mode 100644
index 0000000..b3c8b788
--- /dev/null
+++ b/benchmark/gradle-plugin/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import androidx.build.CompilationTarget
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+apply plugin: androidx.build.SupportKotlinLibraryPlugin
+apply plugin: 'java-gradle-plugin'
+
+sourceSets {
+ test.java.srcDirs += 'src/main/kotlin'
+}
+
+dependencies {
+ implementation gradleApi()
+ implementation(build_libs.gradle)
+ implementation(KOTLIN_STDLIB)
+ implementation(TEST_RUNNER)
+
+ testImplementation gradleTestKit()
+ testImplementation(JUNIT)
+}
+
+gradlePlugin {
+ plugins {
+ benchmark {
+ id = "androidx.benchmark"
+ implementationClass = "androidx.benchmark.gradle.BenchmarkPlugin"
+ }
+ }
+}
+
+supportLibrary {
+ name = "Android Benchmark Gradle Plugin"
+ publish = true
+ toolingProject = true
+ mavenVersion = LibraryVersions.BENCHMARK
+ mavenGroup = LibraryGroups.BENCHMARK
+ inceptionYear = "2019"
+ description = "Android Benchmark Gradle Plugin"
+ compilationTarget = CompilationTarget.HOST
+}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
new file mode 100644
index 0000000..9893883
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/BenchmarkPlugin.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.gradle
+
+import com.android.build.gradle.AppExtension
+import com.android.build.gradle.AppPlugin
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.LibraryPlugin
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.tasks.StopExecutionException
+
+class BenchmarkPlugin : Plugin<Project> {
+ override fun apply(project: Project) {
+ var sdkPath: String? = null
+ project.plugins.all {
+ when (it) {
+ is LibraryPlugin -> {
+ val extension = project.extensions.getByType(LibraryExtension::class.java)
+ sdkPath = extension.sdkDirectory.path
+ }
+ is AppPlugin -> {
+ val extension = project.extensions.getByType(AppExtension::class.java)
+ sdkPath = extension.sdkDirectory.path
+ }
+ }
+ }
+
+ if (sdkPath.isNullOrEmpty()) {
+ throw StopExecutionException("Unable to find Android SDK")
+ }
+
+ project.tasks.create("lockClocks", LockClocksTask::class.java, sdkPath)
+ project.tasks.create("unlockClocks", UnlockClocksTask::class.java, sdkPath)
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/ClockTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/ClockTask.kt
new file mode 100644
index 0000000..770e2b9
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/ClockTask.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.logging.LogLevel
+import java.util.concurrent.TimeUnit
+
+open class ClockTask(private val sdkPath: String) : DefaultTask() {
+ init {
+ group = "Android"
+ }
+
+ fun execAdbSync(adbCmd: Array<String>, shouldThrow: Boolean = true): Process {
+ val cmd = arrayOf("$sdkPath/platform-tools/adb", *adbCmd)
+
+ logger.log(LogLevel.QUIET, cmd.joinToString(" "))
+ val process = Runtime.getRuntime().exec(cmd)
+
+ if (!process.waitFor(5, TimeUnit.SECONDS)) {
+ throw GradleException("Timeout waiting for ${cmd.joinToString(" ")}")
+ }
+
+ val stdout = process.inputStream.bufferedReader().use { it.readText() }
+ val stderr = process.errorStream.bufferedReader().use { it.readText() }
+
+ logger.log(LogLevel.QUIET, stdout)
+ logger.log(LogLevel.WARN, stderr)
+
+ if (shouldThrow && process.exitValue() != 0) {
+ throw GradleException(stderr)
+ }
+
+ return process
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
new file mode 100644
index 0000000..226ea65
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/LockClocksTask.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.gradle
+
+import org.gradle.api.tasks.TaskAction
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+import javax.inject.Inject
+
+open class LockClocksTask @Inject constructor(sdkPath: String) : ClockTask(sdkPath) {
+ init {
+ description = "locks clocks of connected, supported, rooted device"
+ }
+
+ @Suppress("unused")
+ @TaskAction
+ fun exec() {
+ // Skip "adb root" if already rooted as it will fail.
+ if (execAdbSync(arrayOf("shell", "su exit"), false).exitValue() != 0) {
+ execAdbSync(arrayOf("root"))
+ }
+
+ val dest = "/data/local/tmp/lockClocks.sh"
+ val source = javaClass.classLoader.getResource("scripts/lockClocks.sh")
+ val tmpSource = Files.createTempFile("lockClocks.sh", null).toString()
+ Files.copy(
+ source.openStream(),
+ Paths.get(tmpSource),
+ StandardCopyOption.REPLACE_EXISTING
+ )
+ execAdbSync(arrayOf("push", tmpSource, dest))
+
+ // Files pushed by adb push don't always preserve file permissions.
+ execAdbSync(arrayOf("shell", "chmod", "700", dest))
+ execAdbSync(arrayOf("shell", dest))
+ execAdbSync(arrayOf("shell", "rm", dest))
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt
new file mode 100644
index 0000000..4934664
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/kotlin/androidx/benchmark/gradle/UnlockClocksTask.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.gradle
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.TaskAction
+import javax.inject.Inject
+
+open class UnlockClocksTask @Inject constructor(sdkPath: String) : ClockTask(sdkPath) {
+ init {
+ description = "unlocks clocks of device by rebooting"
+ }
+
+ @Suppress("unused")
+ @TaskAction
+ fun exec() {
+ project.logger.log(LogLevel.LIFECYCLE, "Rebooting device to reset clocks")
+ execAdbSync(arrayOf("reboot"))
+ }
+}
diff --git a/benchmark/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.benchmark.gradle.properties b/benchmark/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.benchmark.gradle.properties
new file mode 100644
index 0000000..90bb461
--- /dev/null
+++ b/benchmark/gradle-plugin/src/main/resources/META-INF/gradle-plugins/androidx.benchmark.gradle.properties
@@ -0,0 +1,16 @@
+#
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+implementation-class=androidx.benchmark.gradle.BenchmarkPlugin
\ No newline at end of file
diff --git a/benchmark/lockClocks.sh b/benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
similarity index 100%
rename from benchmark/lockClocks.sh
rename to benchmark/gradle-plugin/src/main/resources/scripts/lockClocks.sh
diff --git a/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
new file mode 100644
index 0000000..23b10d9
--- /dev/null
+++ b/benchmark/gradle-plugin/src/test/kotlin/androidx/benchmark/gradle/BenchmarkPluginTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.internal.plugins.PluginApplicationException
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.io.File
+
+@RunWith(JUnit4::class)
+class BenchmarkPluginTest {
+
+ @get:Rule
+ val testProjectDir = TemporaryFolder()
+
+ private lateinit var localPropFile: File
+ private lateinit var project: Project
+
+ @Before
+ fun setUp() {
+ testProjectDir.root.mkdirs()
+
+ localPropFile = File(testProjectDir.root, "local.properties")
+ localPropFile.createNewFile()
+ localPropFile.writeText("sdk.dir=/usr/test/android/home")
+
+ project = ProjectBuilder.builder()
+ .withProjectDir(testProjectDir.root)
+ .build()
+ }
+
+ @Test
+ fun applyPluginAppProject() {
+ project.apply { it.plugin("com.android.application") }
+ project.apply { it.plugin("androidx.benchmark") }
+
+ Assert.assertNotNull(project.tasks.findByPath("lockClocks"))
+ Assert.assertNotNull(project.tasks.findByPath("unlockClocks"))
+ }
+
+ @Test
+ fun applyPluginAndroidLibProject() {
+ project.apply { it.plugin("com.android.library") }
+ project.apply { it.plugin("androidx.benchmark") }
+
+ Assert.assertNotNull(project.tasks.findByPath("lockClocks"))
+ Assert.assertNotNull(project.tasks.findByPath("unlockClocks"))
+ }
+
+ @Test(expected = PluginApplicationException::class)
+ fun applyPluginNonAndroidProject() {
+ project.apply { it.plugin("java-library") }
+ project.apply { it.plugin("androidx.benchmark") }
+
+ Assert.assertNotNull(project.tasks.findByPath("lockClocks"))
+ Assert.assertNotNull(project.tasks.findByPath("unlockClocks"))
+ }
+}
diff --git a/benchmark/src/androidTest/AndroidManifest.xml b/benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..5413ace
--- /dev/null
+++ b/benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="androidx.benchmark.test">
+ <!-- Important: disable debuggable for accurate performance results -->
+ <application
+ android:debuggable="false"
+ tools:replace="android:debuggable"/>
+</manifest>
diff --git a/benchmark/src/androidTest/assets/images/jetpack.png b/benchmark/src/androidTest/assets/images/jetpack.png
new file mode 100644
index 0000000..bfebbc8
--- /dev/null
+++ b/benchmark/src/androidTest/assets/images/jetpack.png
Binary files differ
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt
new file mode 100644
index 0000000..452605c
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleAnnotationTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import androidx.test.filters.SmallTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BenchmarkRuleAnnotationTest {
+ @Suppress("MemberVisibilityCanBePrivate") // intentionally public
+ // NOTE: not annotated, so will throw when state is accessed
+ val unannotatedRule = BenchmarkRule()
+
+ @Test(expected = IllegalStateException::class)
+ fun throwsIfNotAnnotated() {
+ unannotatedRule.state
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun throwsIfNotAnnotatedMeasure() {
+ unannotatedRule.keepRunning { }
+ }
+}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt
index a72ad3b..7b72753 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt
+++ b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkRuleTest.kt
@@ -16,20 +16,29 @@
package androidx.benchmark
-import androidx.test.filters.SmallTest
+import androidx.test.filters.LargeTest
+import org.junit.Assert.assertTrue
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import java.util.concurrent.TimeUnit
-@SmallTest
+@LargeTest
@RunWith(JUnit4::class)
class BenchmarkRuleTest {
- @Suppress("MemberVisibilityCanBePrivate") // intentionally public
- // NOTE: not annotated, so will throw when state is accessed
- val rule = BenchmarkRule()
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
- @Test(expected = IllegalStateException::class)
- fun throwsIfNotAnnotated() {
- rule.state
+ @Test
+ fun runWithTimingDisabled() {
+ benchmarkRule.keepRunning {
+ runWithTimingDisabled {
+ Thread.sleep(5)
+ }
+ }
+ val min = benchmarkRule.state.stats.min
+ assertTrue("minimum $min should be less than 1ms",
+ min < TimeUnit.MILLISECONDS.toNanos(1))
}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
index b79bba0..a8cec27 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
+++ b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
@@ -69,14 +69,14 @@
// nothing, we're ignoring numbers
}
}.getFullStatusReport("foo")
- val expectedLabel = WarningState.WARNING_PREFIX + "foo"
assertTrue(
- (bundle.get("android.studio.display.benchmark") as String).contains(expectedLabel))
+ (bundle.get("android.studio.display.benchmark") as String).contains("foo"))
// check attribute presence and naming
- assertNotNull(bundle.get(expectedLabel + "_min"))
- assertNotNull(bundle.get(expectedLabel + "_mean"))
- assertNotNull(bundle.get(expectedLabel + "_count"))
+ val prefix = WarningState.WARNING_PREFIX
+ assertNotNull(bundle.get("${prefix}min"))
+ assertNotNull(bundle.get("${prefix}mean"))
+ assertNotNull(bundle.get("${prefix}count"))
}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
new file mode 100644
index 0000000..1806c61
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ResultWriterTest {
+ private val report = BenchmarkState.Report(
+ nanos = 100,
+ data = listOf(100, 101, 102),
+ repeatIterations = 100000,
+ warmupIterations = 8000
+ )
+
+ @Test
+ fun validateXml() {
+ val manager = ResultWriter.fileManagers.find { it.extension == "xml" }!!
+ manager.currentContent = manager.initial
+ manager.append(report, "MethodA", "package.Class1")
+ manager.append(report, "MethodB", "package.Class2")
+ assertEquals("""
+ <benchmarksuite>
+ <testcase
+ name="MethodA"
+ classname="package.Class1"
+ nanos="100"
+ warmupIterations="8000"
+ repeatIterations="100000">
+ <run nanos="100"/>
+ <run nanos="101"/>
+ <run nanos="102"/>
+ </testcase>
+ <testcase
+ name="MethodB"
+ classname="package.Class2"
+ nanos="100"
+ warmupIterations="8000"
+ repeatIterations="100000">
+ <run nanos="100"/>
+ <run nanos="101"/>
+ <run nanos="102"/>
+ </testcase>
+ </benchmarksuite>
+ """.trimIndent(),
+ manager.fullFileContent
+ )
+ }
+
+ @Test
+ fun validateJson() {
+ val manager = ResultWriter.fileManagers.find { it.extension == "json" }!!
+ manager.currentContent = manager.initial
+ manager.append(report, "MethodA", "package.Class1")
+ manager.append(report, "MethodB", "package.Class2")
+ assertEquals("""
+ { "results": [
+ {
+ "name": "MethodA",
+ "classname": "package.Class1",
+ "nanos": 100,
+ "warmupIterations": 8000,
+ "repeatIterations": 100000,
+ "runs": [
+ 100,
+ 101,
+ 102
+ ]
+ },
+ {
+ "name": "MethodB",
+ "classname": "package.Class2",
+ "nanos": 100,
+ "warmupIterations": 8000,
+ "repeatIterations": 100000,
+ "runs": [
+ 100,
+ 101,
+ 102
+ ]
+ }
+ ]}
+ """.trimIndent(),
+ manager.fullFileContent
+ )
+ }
+}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/SynchronizedBenchmark.kt b/benchmark/src/androidTest/java/androidx/benchmark/SynchronizedBenchmark.kt
new file mode 100644
index 0000000..e02d358
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/SynchronizedBenchmark.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.atomic.AtomicInteger
+
+@LargeTest
+@RunWith(JUnit4::class)
+class SynchronizedBenchmark {
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @Volatile
+ private var volatileInt = 0
+
+ private val atomicInt = AtomicInteger(0)
+
+ @Test
+ fun atomicIncrementBenchmark() {
+ benchmarkRule.keepRunning {
+ atomicInt.getAndIncrement()
+ }
+ }
+
+ @Test
+ fun synchronizedIncrementBenchmark() {
+ benchmarkRule.keepRunning {
+ synchronized(volatileInt) {
+ volatileInt++
+ }
+ }
+ }
+}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmark.kt b/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmark.kt
index b267d6a..9404afa 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmark.kt
+++ b/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmark.kt
@@ -29,16 +29,12 @@
val benchmarkRule = BenchmarkRule()
@Test
- fun nothing() {
- val state = benchmarkRule.state
- while (state.keepRunning()) {}
- }
+ fun nothing() = benchmarkRule.keepRunning { }
@Test
fun increment() {
- val state = benchmarkRule.state
var i = 0
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
i++
}
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmarkJava.java b/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmarkJava.java
new file mode 100644
index 0000000..bfc5294
--- /dev/null
+++ b/benchmark/src/androidTest/java/androidx/benchmark/TrivialBenchmarkJava.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@LargeTest
+@RunWith(JUnit4.class)
+public class TrivialBenchmarkJava {
+ @Rule
+ public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Test
+ public void nothing() {
+ BenchmarkState state = mBenchmarkRule.getState();
+ while (state.keepRunning()) {
+ // nothing
+ }
+ }
+
+ @Test
+ public void increment() {
+ BenchmarkState state = mBenchmarkRule.getState();
+ int i = 0;
+ while (state.keepRunning()) {
+ i++;
+ }
+ }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java
deleted file mode 100644
index f1a8013..0000000
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Use this rule to make sure we report the status after the test success.
- *
- * <pre>
- * {@literal @}Rule public BenchmarkRule benchmarkRule = new BenchmarkRule();
- * {@literal @}Test public void functionName() {
- * ...
- * BenchmarkState state = benchmarkRule.getBenchmarkState();
- * while (state.keepRunning()) {
- * // DO YOUR TEST HERE!
- * }
- * ...
- * }
- * </pre>
- *
- * When test succeeded, the status report will use the key as
- * "functionName[optional subTestName]_*"
- *
- * Notice that optional subTestName can't be just numbers, that means each sub test needs to have a
- * name when using parameterization.
- */
-
-public class BenchmarkRule implements TestRule {
- private static final String TAG = "BenchmarkRule";
- @SuppressWarnings("WeakerAccess") // synthetic access
- final BenchmarkState mState = new BenchmarkState();
- @SuppressWarnings("WeakerAccess") // synthetic access
- boolean mApplied = false;
-
- @NonNull
- public BenchmarkState getState() {
- if (!mApplied) {
- throw new IllegalStateException("Cannot get state before BenchmarkRule is applied"
- + " to a test. Check that your BenchmarkRule is annotated correctly"
- + " (@Rule in Java, @get:Rule in Kotlin).");
- }
- return mState;
- }
-
- @NonNull
- @Override
- public Statement apply(@NonNull final Statement base, @NonNull final Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- mApplied = true;
- String invokeMethodName = description.getMethodName();
- Log.i(TAG, "Running " + description.getClassName() + "#" + invokeMethodName);
-
- // validate and simplify the function name.
- // First, remove the "test" prefix which normally comes from CTS test.
- // Then make sure the [subTestName] is valid, not just numbers like [0].
- if (invokeMethodName.startsWith("test")) {
- assertTrue("The test name " + invokeMethodName + " is too short",
- invokeMethodName.length() > 5);
- invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase()
- + invokeMethodName.substring(5);
- }
-
- int index = invokeMethodName.lastIndexOf('[');
- if (index > 0) {
- boolean allDigits = true;
- for (int i = index + 1; i < invokeMethodName.length() - 1; i++) {
- if (!Character.isDigit(invokeMethodName.charAt(i))) {
- allDigits = false;
- break;
- }
- }
- assertFalse("The name in [] can't contain only digits for " + invokeMethodName,
- allDigits);
- }
-
- base.evaluate();
-
- InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK,
- mState.getFullStatusReport(invokeMethodName));
- }
- };
- }
-}
-
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
new file mode 100644
index 0000000..f6472e6
--- /dev/null
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkRule.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import android.app.Activity
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * JUnit rule for benchmarking code on an Android device.
+ *
+ * In Kotlin, benchmark with [keepRunning]:
+ *
+ * ```
+ * @get:Rule
+ * val benchmarkRule = BenchmarkRule();
+ *
+ * @Test
+ * fun myBenchmark() {
+ * ...
+ * benchmarkRule.keepRunning {
+ * doSomeWork()
+ * }
+ * ...
+ * }
+ * ```
+ *
+ * In Java, use `getState()`:
+ *
+ * ```
+ * @Rule
+ * public BenchmarkRule benchmarkRule = new BenchmarkRule();
+ *
+ * @Test
+ * public void myBenchmark() {
+ * ...
+ * BenchmarkState state = benchmarkRule.getState();
+ * while (state.keepRunning()) {
+ * doSomeWork();
+ * }
+ * ...
+ * }
+ * ```
+ *
+ * Benchmark results will be output:
+ * - Summary in AndroidStudio in the test log,
+ * - In simple form in Logcat with the tag "Benchmark"
+ * - In csv form in Logcat with the tag "BenchmarkCsv"
+ * - To the instrumentation status result Bundle on the gradle command line
+ *
+ * Every test in the Class using this @Rule must contain a single benchmark.
+ */
+class BenchmarkRule : TestRule {
+ private val internalState = BenchmarkState()
+
+ /**
+ * Object used for benchmarking in Java.
+ *
+ * ```
+ * @Rule
+ * public BenchmarkRule benchmarkRule = new BenchmarkRule();
+ *
+ * @Test
+ * public void myBenchmark() {
+ * ...
+ * BenchmarkState state = benchmarkRule.getBenchmarkState();
+ * while (state.keepRunning()) {
+ * doSomeWork();
+ * }
+ * ...
+ * }
+ * ```
+ */
+ val state: BenchmarkState
+ get() {
+ if (!applied) {
+ throw IllegalStateException(
+ "Cannot get state before BenchmarkRule is applied to a test. Check that your " +
+ "BenchmarkRule is annotated correctly (@Rule in Java, @get:Rule in Kotlin)."
+ )
+ }
+ return internalState
+ }
+
+ internal // synthetic access
+ var applied = false
+
+ /** @hide */
+ val context = Context()
+
+ /**
+ * Handle used for controlling timing during [keepRunning].
+ */
+ inner class Context internal constructor() {
+ /**
+ * Disable timing for a block of code.
+ *
+ * Used for disabling timing for work that isn't part of the benchmark:
+ * - When constructing per-loop randomized inputs for operations with caching,
+ * - Controlling which parts of multi-stage work are measured (e.g. View measure/layout)
+ * - Disabling timing during per-loop verification
+ *
+ * ```
+ * @Test
+ * fun bitmapProcessing() = benchmarkRule.keepRunning {
+ * val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
+ * processBitmap(input)
+ * }
+ * ```
+ */
+ inline fun <T> runWithTimingDisabled(block: () -> T): T {
+ state.pauseTiming()
+ val ret = block()
+ state.resumeTiming()
+ return ret
+ }
+ }
+
+ /**
+ * Benchmark a block of code.
+ *
+ * ```
+ * @get:Rule
+ * val benchmarkRule = BenchmarkRule();
+ *
+ * @Test
+ * fun myBenchmark() {
+ * ...
+ * benchmarkRule.keepRunning {
+ * doSomeWork()
+ * }
+ * ...
+ * }
+ * ```
+ *
+ * @param block The block of code to benchmark.
+ */
+ inline fun keepRunning(crossinline block: Context.() -> Unit) {
+ // Extract members to locals, to ensure we check #applied, and we don't hit accessors
+ val localState = state
+ val localContext = context
+
+ while (localState.keepRunningInline()) {
+ block(localContext)
+ }
+ }
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ applied = true
+ var invokeMethodName = description.methodName
+ Log.i(TAG, "Running ${description.className}#$invokeMethodName")
+
+ // validate and simplify the function name.
+ // First, remove the "test" prefix which normally comes from CTS test.
+ // Then make sure the [subTestName] is valid, not just numbers like [0].
+ if (invokeMethodName.startsWith("test")) {
+ assertTrue(
+ "The test name $invokeMethodName is too short",
+ invokeMethodName.length > 5
+ )
+ invokeMethodName = invokeMethodName.substring(4, 5).toLowerCase() +
+ invokeMethodName.substring(5)
+ }
+
+ val index = invokeMethodName.lastIndexOf('[')
+ if (index > 0) {
+ val allDigits =
+ invokeMethodName.substring(index + 1, invokeMethodName.length - 1)
+ .all { Character.isDigit(it) }
+ assertFalse(
+ "The name in [] can't contain only digits for $invokeMethodName",
+ allDigits
+ )
+ }
+
+ base.evaluate()
+
+ val fullTestName = WarningState.WARNING_PREFIX +
+ description.testClass.simpleName + "." + invokeMethodName
+ InstrumentationRegistry.getInstrumentation().sendStatus(
+ Activity.RESULT_OK,
+ state.getFullStatusReport(fullTestName)
+ )
+
+ ResultWriter.appendStats(invokeMethodName, description.className, state.getReport())
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ companion object {
+ private const val TAG = "BenchmarkRule"
+ }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java
deleted file mode 100644
index 480bc92..0000000
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark;
-
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Debug;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import java.io.File;
-import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Provides a benchmark framework.
- *
- * Example usage:
- * // Executes the code while keepRunning returning true.
- *
- * public void sampleMethod() {
- * BenchmarkState state = new BenchmarkState();
- *
- * int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
- * while (state.keepRunning()) {
- * int[] dest = new int[src.length];
- * System.arraycopy(src, 0, dest, 0, src.length);
- * }
- * System.out.println(state.summaryLine());
- * }
- */
-public final class BenchmarkState {
- private static final String TAG = "Benchmark";
- private static final String CSV_TAG = "BenchmarkCsv";
- private static final String STUDIO_OUTPUT_KEY_PREFIX = "android.studio.display.";
- private static final String STUDIO_OUTPUT_KEY_ID = "benchmark";
-
- private static final boolean ENABLE_PROFILING = false;
-
- private static final int NOT_STARTED = 0; // The benchmark has not started yet.
- private static final int WARMUP = 1; // The benchmark is warming up.
- private static final int RUNNING = 2; // The benchmark is running.
- private static final int FINISHED = 3; // The benchmark has stopped.
-
- // values determined empirically
- private static final long TARGET_TEST_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(500);
- private static final int MAX_TEST_ITERATIONS = 1000000;
- private static final int MIN_TEST_ITERATIONS = 10;
- private static final int REPEAT_COUNT = 5;
-
- static {
- StringBuilder sb = new StringBuilder();
- sb.append("Benchmark");
- for (int i = 0; i < REPEAT_COUNT; i++) {
- sb.append(", Result ").append(i);
- }
-
- Log.i(CSV_TAG, sb.toString());
- }
-
- private int mState = NOT_STARTED; // Current benchmark state.
-
- private WarmupManager mWarmupManager = new WarmupManager();
-
- private long mStartTimeNs = 0; // System.nanoTime() at start of last warmup iter / test repeat.
-
- private boolean mPaused;
- private long mPausedTimeNs = 0; // The System.nanoTime() when the pauseTiming() is called.
- private long mPausedDurationNs = 0; // The duration of paused state in nano sec.
-
- private int mIteration = 0;
- private int mMaxIterations = 0;
-
- private int mRepeatCount = 0;
-
- // Statistics. These values will be filled when the benchmark has finished.
- // The computation needs double precision, but long int is fine for final reporting.
- private Stats mStats;
-
- // Individual duration in nano seconds.
- private ArrayList<Long> mResults = new ArrayList<>();
-
- /**
- * Stops the benchmark timer.
- * <p>
- * This method can be called only when the timer is running.
- */
- public void pauseTiming() {
- if (mPaused) {
- throw new IllegalStateException(
- "Unable to pause the benchmark. The benchmark has already paused.");
- }
- mPausedTimeNs = System.nanoTime();
- mPaused = true;
- }
-
- /**
- * Starts the benchmark timer.
- * <p>
- * This method can be called only when the timer is stopped.
- */
- public void resumeTiming() {
- if (!mPaused) {
- throw new IllegalStateException(
- "Unable to resume the benchmark. The benchmark is already running.");
- }
- mPausedDurationNs += System.nanoTime() - mPausedTimeNs;
- mPausedTimeNs = 0;
- mPaused = false;
- }
-
- private void beginWarmup() {
- mStartTimeNs = System.nanoTime();
- mIteration = 0;
- mState = WARMUP;
- }
-
- private void beginBenchmark() {
- if (ENABLE_PROFILING && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- // TODO: support data dir for old platforms
- File f = new File(
- InstrumentationRegistry.getInstrumentation().getContext().getDataDir(),
- "benchprof");
- Log.d(TAG, "Tracing to: " + f.getAbsolutePath());
- Debug.startMethodTracingSampling(f.getAbsolutePath(), 16 * 1024 * 1024, 100);
- }
- final int idealIterations =
- (int) (TARGET_TEST_DURATION_NS / mWarmupManager.getEstimatedIterationTime());
- mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
- Math.max(idealIterations, MIN_TEST_ITERATIONS));
- mPausedDurationNs = 0;
- mIteration = 0;
- mRepeatCount = 0;
- mState = RUNNING;
- mStartTimeNs = System.nanoTime();
- }
-
- private boolean startNextTestRun() {
- final long currentTime = System.nanoTime();
- mResults.add((currentTime - mStartTimeNs - mPausedDurationNs) / mMaxIterations);
- mRepeatCount++;
- if (mRepeatCount >= REPEAT_COUNT) {
- if (ENABLE_PROFILING) {
- Debug.stopMethodTracing();
- }
- mStats = new Stats(mResults);
- mState = FINISHED;
- return false;
- }
- mPausedDurationNs = 0;
- mIteration = 0;
- mStartTimeNs = System.nanoTime();
- return true;
- }
-
- /**
- * Judges whether the benchmark needs more samples.
- *
- * For the usage, see class comment.
- */
- public boolean keepRunning() {
- switch (mState) {
- case NOT_STARTED:
- beginWarmup();
- return true;
- case WARMUP:
- mIteration++;
- // Only check nanoTime on every iteration in WARMUP since we
- // don't yet have a target iteration count.
- final long time = System.nanoTime();
- final long lastDuration = time - mStartTimeNs;
- mStartTimeNs = time;
- if (mWarmupManager.onNextIteration(lastDuration)) {
- beginBenchmark();
- }
- return true;
- case RUNNING:
- mIteration++;
- if (mIteration >= mMaxIterations) {
- return startNextTestRun();
- }
- if (mPaused) {
- throw new IllegalStateException("Benchmark step finished with paused state. "
- + "Resume the benchmark before finishing each step.");
- }
- return true;
- case FINISHED:
- throw new IllegalStateException("The benchmark has finished.");
- default:
- throw new IllegalStateException("The benchmark is in unknown state.");
- }
- }
-
- /**
- * Get the end of run benchmark statistics.
- * <p>
- * This method may only be called keepRunning() returns {@code false}.
- *
- * @return Stats from run.
- */
- @NonNull
- public Stats getStats() {
- if (mState != FINISHED) {
- throw new IllegalStateException("The benchmark hasn't finished");
- }
- return mStats;
- }
-
- private long mean() {
- return (long) getStats().getMean();
- }
-
- private long median() {
- return getStats().getMedian();
- }
-
- private long min() {
- return getStats().getMin();
- }
-
- private long standardDeviation() {
- return (long) getStats().getStandardDeviation();
- }
-
- private long count() {
- return mMaxIterations;
- }
-
- private String summaryLine() {
- StringBuilder sb = new StringBuilder();
- sb.append("Summary: ");
- sb.append("median=").append(median()).append("ns, ");
- sb.append("mean=").append(mean()).append("ns, ");
- sb.append("min=").append(min()).append("ns, ");
- sb.append("stddev=").append(standardDeviation()).append(", ");
- sb.append("count=").append(count()).append(", ");
- // print out the first few iterations' number for double checking.
- int sampleNumber = Math.min(mResults.size(), 16);
- for (int i = 0; i < sampleNumber; i++) {
- sb.append("No ").append(i).append(" result is ").append(mResults.get(i)).append(", ");
- }
- return sb.toString();
- }
-
- @NonNull
- private String ideSummaryLineWrapped(@NonNull String key) {
- StringBuilder result = null;
-
- String warningString = WarningState.acquireWarningStringForLogging();
- if (warningString != null) {
- for (String s : warningString.split("\n")) {
- if (result == null) {
- if (s.isEmpty()) {
- continue;
- }
- result = new StringBuilder(s).append("\n");
- } else {
- result.append(STUDIO_OUTPUT_KEY_ID).append(": ").append(s).append("\n");
- }
- }
- if (result != null) {
- result.append(STUDIO_OUTPUT_KEY_ID).append(": ").append(ideSummaryLine(key));
- return result.toString();
- }
- }
-
- return ideSummaryLine(key);
- }
-
- @NonNull
- String ideSummaryLine(@NonNull String key) {
- // NOTE: this summary line will use default locale to determine separators. As
- // this line is only meant for human eyes, we don't worry about consistency here.
- return String.format(
- // 13 is used for alignment here, because it's enough that 9.99sec will still
- // align with any other output, without moving data too far to the right
- "%13s ns %s",
- NumberFormat.getNumberInstance().format(min()),
- key);
- }
-
- private String csvLine() {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mResults.size(); i++) {
- sb.append(", ").append(mResults.get(i));
- }
- return sb.toString();
- }
-
- /**
- * Acquires a status report bundle
- *
- * @param key Run identifier, prepended to bundle properties.
- */
- Bundle getFullStatusReport(@NonNull String key) {
- key = WarningState.WARNING_PREFIX + key;
- Log.i(TAG, key + summaryLine());
- Log.i(CSV_TAG, key + csvLine());
- Bundle status = new Bundle();
- status.putLong(key + "_median", median());
- status.putLong(key + "_mean", mean());
- status.putLong(key + "_min", min());
- status.putLong(key + "_standardDeviation", standardDeviation());
- status.putLong(key + "_count", count());
- status.putString(STUDIO_OUTPUT_KEY_PREFIX + STUDIO_OUTPUT_KEY_ID,
- ideSummaryLineWrapped(key));
- return status;
- }
-}
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
new file mode 100644
index 0000000..2c420f38
--- /dev/null
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import android.os.Build
+import android.os.Bundle
+import android.os.Debug
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+
+import java.io.File
+import java.text.NumberFormat
+import java.util.ArrayList
+import java.util.concurrent.TimeUnit
+
+/**
+ * Control object for benchmarking in the code in Java.
+ *
+ * Query a state object with [BenchmarkRule.state], and use it to measure a block of Java with
+ * [BenchmarkState.keepRunning]:
+ * ```
+ * @Rule
+ * public BenchmarkRule benchmarkRule = new BenchmarkRule();
+ *
+ * @Test
+ * public void sampleMethod() {
+ * BenchmarkState state = benchmarkRule.getState();
+ *
+ * int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ * while (state.keepRunning()) {
+ * int[] dest = new int[src.length];
+ * System.arraycopy(src, 0, dest, 0, src.length);
+ * }
+ * }
+ * ```
+ *
+ * @see BenchmarkRule#getState()
+ */
+class BenchmarkState internal constructor() {
+ private var warmupIteration = 0 // increasing iteration count during warmup
+
+ /**
+ * Decreasing iteration count used when [state] == [RUNNING], used to determine when main
+ * measurement loop finishes.
+ */
+ @JvmField /* Used by [BenchmarkState.keepRunningInline()] */
+ @PublishedApi
+ internal var iterationsRemaining = -1
+
+ private var maxIterations = 0
+
+ private var state = NOT_STARTED // Current benchmark state.
+
+ private val warmupManager = WarmupManager()
+
+ private var startTimeNs: Long = 0 // System.nanoTime() at start of last warmup/test iter.
+
+ private var paused: Boolean = false
+ private var pausedTimeNs: Long = 0 // The System.nanoTime() when the pauseTiming() is called.
+ private var pausedDurationNs: Long = 0 // The duration of paused state in nano sec.
+
+ private var repeatCount = 0
+
+ // Statistics. These values will be filled when the benchmark has finished.
+ // The computation needs double precision, but long int is fine for final reporting.
+ private var internalStats: Stats? = null
+
+ // Individual duration in nano seconds.
+ private val results = ArrayList<Long>()
+
+ /**
+ * Get the end of run benchmark statistics.
+ *
+ *
+ * This method may only be called keepRunning() returns `false`.
+ *
+ * @return Stats from run.
+ */
+ internal val stats: Stats
+ get() {
+ if (state != FINISHED) {
+ throw IllegalStateException("The benchmark hasn't finished")
+ }
+ return internalStats!!
+ }
+
+ /**
+ * Stops the benchmark timer.
+ *
+ * This method can be called only when the timer is running.
+ *
+ * ```
+ * @Test
+ * public void bitmapProcessing() {
+ * final BenchmarkState state = mBenchmarkRule.getState();
+ * while (state.keepRunning()) {
+ * state.pauseTiming();
+ * // disable timing while constructing test input
+ * Bitmap input = constructTestBitmap();
+ * state.resumeTiming();
+ *
+ * processBitmap(input);
+ * }
+ * }
+ * ```
+ *
+ * @see resumeTiming
+ */
+ fun pauseTiming() {
+ if (paused) {
+ throw IllegalStateException(
+ "Unable to pause the benchmark. The benchmark has already paused."
+ )
+ }
+ pausedTimeNs = System.nanoTime()
+ paused = true
+ }
+
+ /**
+ * Resumes the benchmark timer.
+ *
+ * This method can be called only when the timer is stopped.
+ *
+ * ```
+ * @Test
+ * public void bitmapProcessing() {
+ * final BenchmarkState state = mBenchmarkRule.getState();
+ * while (state.keepRunning()) {
+ * state.pauseTiming();
+ * // disable timing while constructing test input
+ * Bitmap input = constructTestBitmap();
+ * state.resumeTiming();
+ *
+ * processBitmap(input);
+ * }
+ * }
+ * ```
+ *
+ * @see pauseTiming
+ */
+ fun resumeTiming() {
+ if (!paused) {
+ throw IllegalStateException(
+ "Unable to resume the benchmark. The benchmark is already running."
+ )
+ }
+ pausedDurationNs += System.nanoTime() - pausedTimeNs
+ pausedTimeNs = 0
+ paused = false
+ }
+
+ private fun beginWarmup() {
+ startTimeNs = System.nanoTime()
+ warmupIteration = 0
+ state = WARMUP
+ }
+
+ private fun beginBenchmark() {
+ if (ENABLE_PROFILING && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ // TODO: support data dir for old platforms
+ val f = File(
+ InstrumentationRegistry.getInstrumentation().context.dataDir,
+ "benchprof"
+ )
+ Log.d(TAG, "Tracing to: " + f.absolutePath)
+ Debug.startMethodTracingSampling(f.absolutePath, 16 * 1024 * 1024, 100)
+ }
+ val idealIterations =
+ (TARGET_TEST_DURATION_NS / warmupManager.estimatedIterationTime).toInt()
+ maxIterations = Math.min(
+ MAX_TEST_ITERATIONS,
+ Math.max(idealIterations, MIN_TEST_ITERATIONS)
+ )
+ pausedDurationNs = 0
+ iterationsRemaining = maxIterations
+ repeatCount = 0
+ state = RUNNING
+ startTimeNs = System.nanoTime()
+ }
+
+ private fun startNextTestRun(): Boolean {
+ val currentTime = System.nanoTime()
+ results.add((currentTime - startTimeNs - pausedDurationNs) / maxIterations)
+ repeatCount++
+ if (repeatCount >= REPEAT_COUNT) {
+ if (ENABLE_PROFILING) {
+ Debug.stopMethodTracing()
+ }
+ internalStats = Stats(results)
+ state = FINISHED
+ return false
+ }
+ pausedDurationNs = 0
+ iterationsRemaining = maxIterations
+ startTimeNs = System.nanoTime()
+ return true
+ }
+
+ /**
+ * Inline fast-path function for inner benchmark loop.
+ *
+ * Kotlin users should use [BenchmarkRule.keepRunning]
+ *
+ * This codepath uses exclusively @JvmField/const members, so there are no method calls at all
+ * in the inlined loop. On recent Android Platform versions, ART inlines these accessors anyway,
+ * but we want to be sure it's as simple as possible.
+ *
+ * @hide
+ */
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun keepRunningInline(): Boolean {
+ if (iterationsRemaining > 0) {
+ iterationsRemaining--
+ return true
+ }
+ return keepRunningInternal()
+ }
+
+ /**
+ * Returns true if the benchmark needs more samples - use this as the condition of a while loop.
+ *
+ * ```
+ * while (state.keepRunning()) {
+ * int[] dest = new int[src.length];
+ * System.arraycopy(src, 0, dest, 0, src.length);
+ * }
+ * ```
+ */
+ fun keepRunning(): Boolean {
+ if (iterationsRemaining > 0) {
+ iterationsRemaining--
+ return true
+ }
+ return keepRunningInternal()
+ }
+
+ @PublishedApi
+ internal fun keepRunningInternal(): Boolean {
+ when (state) {
+ NOT_STARTED -> {
+ beginWarmup()
+ return true
+ }
+ WARMUP -> {
+ warmupIteration++
+ // Only check nanoTime on every iteration in WARMUP since we
+ // don't yet have a target iteration count.
+ val time = System.nanoTime()
+ val lastDuration = time - startTimeNs
+ startTimeNs = time
+ throwIfPaused() // check each loop during warmup
+ if (warmupManager.onNextIteration(lastDuration)) {
+ beginBenchmark()
+ }
+ return true
+ }
+ RUNNING -> {
+ iterationsRemaining--
+ if (iterationsRemaining <= 0) {
+ throwIfPaused() // only check at end of loop to save cycles
+ return startNextTestRun()
+ }
+ return true
+ }
+ FINISHED -> throw IllegalStateException("The benchmark has finished.")
+ else -> throw IllegalStateException("The benchmark is in unknown state.")
+ }
+ }
+
+ private fun throwIfPaused() {
+ if (paused) {
+ throw IllegalStateException(
+ "Benchmark step finished with paused state." +
+ " Resume the benchmark before finishing each step."
+ )
+ }
+ }
+
+ private fun mean(): Long = stats.mean.toLong()
+
+ private fun median(): Long = stats.median
+
+ private fun min(): Long = stats.min
+
+ private fun standardDeviation(): Long = stats.standardDeviation.toLong()
+
+ private fun count(): Long = maxIterations.toLong()
+
+ internal data class Report(
+ val nanos: Long,
+ val data: List<Long>,
+ val repeatIterations: Int,
+ val warmupIterations: Int
+ )
+
+ internal fun getReport(): Report {
+ return Report(
+ nanos = min(),
+ data = results,
+ repeatIterations = maxIterations,
+ warmupIterations = warmupIteration
+ )
+ }
+
+ private fun summaryLine() = "Summary: " +
+ "median=${median()}ns, " +
+ "mean=${mean()}ns, " +
+ "min=${min()}ns, " +
+ "stddev=${standardDeviation()}ns, " +
+ "count=${count()}, " +
+ results.mapIndexed { index, value ->
+ "No $index result is $value"
+ }.joinToString(", ")
+
+ private fun ideSummaryLineWrapped(key: String): String {
+ val warningLines = WarningState.acquireWarningStringForLogging()?.split("\n") ?: listOf()
+ return (warningLines + ideSummaryLine(key))
+ // remove first line if empty
+ .filterIndexed { index, it -> index != 0 || !it.isEmpty() }
+ // join, prepending key to everything but first string, to make each line look the same
+ .joinToString("\n$STUDIO_OUTPUT_KEY_ID: ")
+ }
+
+ // NOTE: this summary line will use default locale to determine separators. As
+ // this line is only meant for human eyes, we don't worry about consistency here.
+ internal fun ideSummaryLine(key: String) = String.format(
+ // 13 is used for alignment here, because it's enough that 9.99sec will still
+ // align with any other output, without moving data too far to the right
+ "%13s ns %s",
+ NumberFormat.getNumberInstance().format(min()),
+ key
+ )
+
+ /**
+ * Acquires a status report bundle
+ *
+ * @param key Run identifier, prepended to bundle properties.
+ */
+ internal fun getFullStatusReport(key: String): Bundle {
+ Log.i(TAG, key + summaryLine())
+ Log.i(CSV_TAG, results.joinToString(prefix = "$key, ", separator = ", "))
+ val status = Bundle()
+
+ val prefix = WarningState.WARNING_PREFIX
+ status.putLong("${prefix}median", median())
+ status.putLong("${prefix}mean", mean())
+ status.putLong("${prefix}min", min())
+ status.putLong("${prefix}standardDeviation", standardDeviation())
+ status.putLong("${prefix}count", count())
+ status.putString(
+ STUDIO_OUTPUT_KEY_PREFIX + STUDIO_OUTPUT_KEY_ID,
+ ideSummaryLineWrapped(key)
+ )
+ return status
+ }
+
+ /** @hide */
+ companion object {
+ private const val TAG = "Benchmark"
+ private const val CSV_TAG = "BenchmarkCsv"
+ private const val STUDIO_OUTPUT_KEY_PREFIX = "android.studio.display."
+ private const val STUDIO_OUTPUT_KEY_ID = "benchmark"
+
+ private const val ENABLE_PROFILING = false
+
+ private const val NOT_STARTED = 0 // The benchmark has not started yet.
+ private const val WARMUP = 1 // The benchmark is warming up.
+ private const val RUNNING = 2 // The benchmark is running.
+ private const val FINISHED = 3 // The benchmark has stopped.
+
+ // values determined empirically
+ private val TARGET_TEST_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(500)
+ private const val MAX_TEST_ITERATIONS = 1000000
+ private const val MIN_TEST_ITERATIONS = 10
+ private const val REPEAT_COUNT = 5
+
+ init {
+ Log.i(CSV_TAG, (0 until REPEAT_COUNT).joinToString(
+ prefix = "Benchmark, ",
+ separator = ", "
+ ) { "Result $it" })
+ }
+ }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
new file mode 100644
index 0000000..be93f15
--- /dev/null
+++ b/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.File
+
+internal object ResultWriter {
+ private fun List<Long>.toXmlWithMargin(): String {
+ return joinToString("\n") { "| <run nanos=\"$it\"/>" }
+ }
+
+ private fun BenchmarkState.Report.toXml(name: String, className: String): String {
+ return "\n" + """
+ | <testcase
+ | name="$name"
+ | classname="$className"
+ | nanos="$nanos"
+ | warmupIterations="$warmupIterations"
+ | repeatIterations="$repeatIterations">
+ ${data.toXmlWithMargin()}
+ | </testcase>
+ """.trimMargin()
+ }
+
+ private fun List<Long>.toJsonWithMargin(): String {
+ return joinToString(",\n") { "| $it" }
+ }
+
+ private fun BenchmarkState.Report.toJson(name: String, className: String): String {
+ return "\n" + """
+ | {
+ | "name": "$name",
+ | "classname": "$className",
+ | "nanos": $nanos,
+ | "warmupIterations": $warmupIterations,
+ | "repeatIterations": $repeatIterations,
+ | "runs": [
+ ${data.toJsonWithMargin()}
+ | ]
+ | }
+ """.trimMargin()
+ }
+
+ data class FileManager(
+ val extension: String,
+ val initial: String,
+ val tail: String,
+ val separator: String? = null,
+ val reportFormatter: (BenchmarkState.Report, String, String) -> String
+ ) {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext!!
+ val file = File("${context.filesDir}/benchmarkdata.$extension")
+ var currentContent = initial
+
+ val fullFileContent: String
+ get() = currentContent + tail
+
+ fun append(report: BenchmarkState.Report, name: String, className: String) {
+ if (currentContent != initial && separator != null) {
+ currentContent += separator
+ }
+ currentContent += reportFormatter(report, name, className)
+ }
+ }
+
+ val fileManagers = listOf(
+ FileManager(
+ extension = "xml",
+ initial = "<benchmarksuite>",
+ tail = "\n</benchmarksuite>",
+ reportFormatter = { report, name, className ->
+ report.toXml(name, className)
+ }
+ ),
+ FileManager(
+ extension = "json",
+ initial = "{ \"results\": [",
+ tail = "\n]}",
+ separator = ",",
+ reportFormatter = { report, name, className ->
+ report.toJson(name, className)
+ }
+ )
+ )
+
+ fun appendStats(name: String, className: String, report: BenchmarkState.Report) {
+ for (fileManager in fileManagers) {
+ fileManager.append(report, WarningState.WARNING_PREFIX + name, className)
+ fileManager.file.run {
+ if (!exists()) {
+ createNewFile()
+ }
+ // Currently, we just overwrite the whole file
+ // Ideally, truncate off the 'tail', and append for efficiency
+ writeText(fileManager.fullFileContent)
+ }
+ }
+ }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/Stats.java b/benchmark/src/main/java/androidx/benchmark/Stats.java
index 28c2bbc..c853df9 100644
--- a/benchmark/src/main/java/androidx/benchmark/Stats.java
+++ b/benchmark/src/main/java/androidx/benchmark/Stats.java
@@ -26,7 +26,7 @@
* Provides statistics such as mean, median, min, max, and percentiles, given a list of input
* values.
*/
-public class Stats {
+final class Stats {
private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
private double mMean, mStandardDeviation;
diff --git a/benchmark/src/main/java/androidx/benchmark/WarmupManager.java b/benchmark/src/main/java/androidx/benchmark/WarmupManager.java
deleted file mode 100644
index 98e2aca..0000000
--- a/benchmark/src/main/java/androidx/benchmark/WarmupManager.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark;
-
-import android.util.Log;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Used to detect when a benchmark has warmed up, given time taken for each iteration.
- *
- * Uses emperically determined constants, primarily looking for the convergence of two
- * exponential moving averages.
- *
- * Tuned to do minimal amount of intrusive work in onNextIteration to avoid polluting the benchmark.
- */
-class WarmupManager {
- static final long MIN_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(250);
- static final long MAX_DURATION_NS = TimeUnit.SECONDS.toNanos(8);
- static final int MIN_ITERATIONS = 30;
- private static final int MIN_SIMILAR_ITERATIONS = 40;
-
- private static final float FAST_RATIO = 0.1f;
- private static final float SLOW_RATIO = 0.005f;
- private static final float THRESHOLD = 0.04f;
-
- private float mFastMovingAvg;
- private float mSlowMovingAvg;
- private float mSimilarIterationCount;
-
- private int mIteration = 0;
-
- private long mTotalDuration;
-
- /**
- * Pass the just-run iteration timing, and return whether the warmup has completed.
- * <p>
- * NOTE: it is critical to do a minimum amount of work and memory access in this method, to
- * avoid polluting the benchmark's memory access patterns. This is why we chose exponential
- * moving averages, and why we only log once at the end.
- *
- * @param durationNs Duration of the next iteration.
- * @return True if the warmup has completed, false otherwise.
- */
- public boolean onNextIteration(long durationNs) {
- mIteration++;
- mTotalDuration += durationNs;
-
- if (mIteration == 1) {
- mFastMovingAvg = durationNs;
- mSlowMovingAvg = durationNs;
- return false;
- }
-
- mFastMovingAvg = FAST_RATIO * durationNs + (1 - FAST_RATIO) * mFastMovingAvg;
- mSlowMovingAvg = SLOW_RATIO * durationNs + (1 - SLOW_RATIO) * mSlowMovingAvg;
-
- // If fast moving avg is close to slow, the benchmark is stabilizing
- float ratio = mFastMovingAvg / mSlowMovingAvg;
- if (ratio < 1 + THRESHOLD && ratio > 1 - THRESHOLD) {
- mSimilarIterationCount++;
- } else {
- mSimilarIterationCount = 0;
- }
-
- if (mIteration >= MIN_ITERATIONS && mTotalDuration >= MIN_DURATION_NS) {
- if (mSimilarIterationCount > MIN_SIMILAR_ITERATIONS
- || mTotalDuration >= MAX_DURATION_NS) {
- // benchmark has stabilized, or we're out of time
- Log.d("WarmupManager", String.format(
- "Complete: t=%.3f, iter=%d, fastAvg=%3.0f, slowAvg=%3.0f",
- mTotalDuration / 1000000000.0,
- mIteration,
- mFastMovingAvg,
- mSlowMovingAvg));
- return true;
- }
- }
- return false;
- }
-
- float getEstimatedIterationTime() {
- return mFastMovingAvg;
- }
-
- public int getIteration() {
- return mIteration;
- }
-
- public long getTotalDuration() {
- return mTotalDuration;
- }
-}
diff --git a/benchmark/src/main/java/androidx/benchmark/WarmupManager.kt b/benchmark/src/main/java/androidx/benchmark/WarmupManager.kt
new file mode 100644
index 0000000..5558fe8
--- /dev/null
+++ b/benchmark/src/main/java/androidx/benchmark/WarmupManager.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import android.util.Log
+
+import java.util.concurrent.TimeUnit
+
+/**
+ * Used to detect when a benchmark has warmed up, given time taken for each iteration.
+ *
+ * Uses emperically determined constants, primarily looking for the convergence of two
+ * exponential moving averages.
+ *
+ * Tuned to do minimal amount of intrusive work in onNextIteration to avoid polluting the benchmark.
+ */
+internal class WarmupManager {
+ private var fastMovingAvg: Float = 0f
+ private var slowMovingAvg: Float = 0f
+ private var similarIterationCount: Int = 0
+
+ val estimatedIterationTime: Float get() = fastMovingAvg
+
+ var iteration = 0
+ private set
+
+ var totalDuration: Long = 0
+ private set
+
+ /**
+ * Pass the just-run iteration timing, and return whether the warmup has completed.
+ *
+ * NOTE: it is critical to do a minimum amount of work and memory access in this method, to
+ * avoid polluting the benchmark's memory access patterns. This is why we chose exponential
+ * moving averages, and why we only log once at the end.
+ *
+ * @param durationNs Duration of the next iteration.
+ * @return True if the warmup has completed, false otherwise.
+ */
+ fun onNextIteration(durationNs: Long): Boolean {
+ iteration++
+ totalDuration += durationNs
+
+ if (iteration == 1) {
+ fastMovingAvg = durationNs.toFloat()
+ slowMovingAvg = durationNs.toFloat()
+ return false
+ }
+
+ fastMovingAvg = FAST_RATIO * durationNs + (1 - FAST_RATIO) * fastMovingAvg
+ slowMovingAvg = SLOW_RATIO * durationNs + (1 - SLOW_RATIO) * slowMovingAvg
+
+ // If fast moving avg is close to slow, the benchmark is stabilizing
+ val ratio = fastMovingAvg / slowMovingAvg
+ if (ratio < 1 + THRESHOLD && ratio > 1 - THRESHOLD) {
+ similarIterationCount++
+ } else {
+ similarIterationCount = 0
+ }
+
+ if (iteration >= MIN_ITERATIONS && totalDuration >= MIN_DURATION_NS) {
+ if (similarIterationCount > MIN_SIMILAR_ITERATIONS ||
+ totalDuration >= MAX_DURATION_NS) {
+ // benchmark has stabilized, or we're out of time
+ Log.d(
+ "WarmupManager", String.format(
+ "Complete: t=%.3f, iter=%d, fastAvg=%3.0f, slowAvg=%3.0f",
+ totalDuration / 1e9,
+ iteration,
+ fastMovingAvg,
+ slowMovingAvg
+ )
+ )
+ return true
+ }
+ }
+ return false
+ }
+
+ companion object {
+ val MIN_DURATION_NS = TimeUnit.MILLISECONDS.toNanos(250)
+ val MAX_DURATION_NS = TimeUnit.SECONDS.toNanos(8)
+ const val MIN_ITERATIONS = 30
+ private const val MIN_SIMILAR_ITERATIONS = 40
+
+ private const val FAST_RATIO = 0.1f
+ private const val SLOW_RATIO = 0.005f
+ private const val THRESHOLD = 0.04f
+ }
+}
diff --git a/benchmark/src/main/java/androidx/benchmark/WarningState.java b/benchmark/src/main/java/androidx/benchmark/WarningState.java
deleted file mode 100644
index 742eb9c..0000000
--- a/benchmark/src/main/java/androidx/benchmark/WarningState.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark;
-
-import android.content.pm.ApplicationInfo;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-class WarningState {
- private static final String TAG = "Benchmark";
-
- static final String WARNING_PREFIX;
- private static String sWarningString;
-
- static {
- ApplicationInfo appInfo = InstrumentationRegistry.getInstrumentation().getTargetContext()
- .getApplicationInfo();
- String warningPrefix = "";
- String warningString = "";
- if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
- warningPrefix += "DEBUGGABLE_";
- warningString += "\nWARNING: Debuggable Benchmark\n"
- + " Benchmark is running with debuggable=true, which drastically reduces\n"
- + " runtime performance in order to support debugging features. Run\n"
- + " benchmarks with debuggable=false. Debuggable affects execution speed\n"
- + " in ways that mean benchmark improvements might not carry over to a\n"
- + " real user's experience (or even regress release performance).\n";
- }
- if (Build.FINGERPRINT.startsWith("generic")
- || Build.FINGERPRINT.startsWith("unknown")
- || Build.MODEL.contains("google_sdk")
- || Build.MODEL.contains("Emulator")
- || Build.MODEL.contains("Android SDK built for x86")
- || Build.MANUFACTURER.contains("Genymotion")
- || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
- || "google_sdk".equals(Build.PRODUCT)) {
- warningPrefix += "EMULATOR_";
- warningString += "\nWARNING: Running on Emulator\n"
- + " Benchmark is running on an emulator, which is not representative of\n"
- + " real user devices. Use a physical device to benchmark. Emulator\n"
- + " benchmark improvements might not carry over to a real user's\n"
- + " experience (or even regress real device performance).\n";
- }
- if (Build.FINGERPRINT.contains(":eng/")) {
- warningPrefix += "ENG-BUILD_";
- warningString += "\nWARNING: Running on Eng Build\n"
- + " Benchmark is running on device flashed with a '-eng' build. Eng builds\n"
- + " of the platform drastically reduce performance to enable testing\n"
- + " changes quickly. For this reason they should not be used for\n"
- + " benchmarking. Use a '-user' or '-userdebug' system image.\n";
- }
- WARNING_PREFIX = warningPrefix;
-
- if (!warningString.isEmpty()) {
- sWarningString = warningString;
- for (String line : sWarningString.split("\n")) {
- Log.w(TAG, line);
- }
- Log.w(TAG, "");
- }
- }
-
- @Nullable
- static String acquireWarningStringForLogging() {
- String ret = sWarningString;
- sWarningString = null;
- return ret;
- }
-}
diff --git a/benchmark/src/main/java/androidx/benchmark/WarningState.kt b/benchmark/src/main/java/androidx/benchmark/WarningState.kt
new file mode 100644
index 0000000..bb71ba5
--- /dev/null
+++ b/benchmark/src/main/java/androidx/benchmark/WarningState.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.benchmark
+
+import android.content.pm.ApplicationInfo
+import android.os.Build
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.File
+
+internal object WarningState {
+ private const val TAG = "Benchmark"
+
+ val WARNING_PREFIX: String
+ private var warningString: String? = null
+
+ fun acquireWarningStringForLogging(): String? {
+ val ret = warningString
+ warningString = null
+ return ret
+ }
+
+ private val isEmulator = Build.FINGERPRINT.startsWith("generic") ||
+ Build.FINGERPRINT.startsWith("unknown") ||
+ Build.MODEL.contains("google_sdk") ||
+ Build.MODEL.contains("Emulator") ||
+ Build.MODEL.contains("Android SDK built for x86") ||
+ Build.MANUFACTURER.contains("Genymotion") ||
+ Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") ||
+ "google_sdk" == Build.PRODUCT
+
+ private val isDeviceRooted =
+ arrayOf(
+ "/system/app/Superuser.apk",
+ "/sbin/su",
+ "/system/bin/su",
+ "/system/xbin/su",
+ "/data/local/xbin/su",
+ "/data/local/bin/su",
+ "/system/sd/xbin/su",
+ "/system/bin/failsafe/su",
+ "/data/local/su",
+ "/su/bin/su"
+ ).any { File(it).exists() }
+
+ private fun isCpuLocked(): Boolean {
+ // If any core is detected to have a variable clock speed, this flag is set to false.
+ var cpuClocksAreLocked = true
+ val cpuDir = File("/sys/devices/system/cpu")
+ val coreDirs = cpuDir.list { current, name ->
+ File(
+ current,
+ name
+ ).isDirectory && name.matches("^cpu[0-9]+".toRegex())
+ }.map { "${cpuDir.path}/$it" }
+
+ // Check cpu clock locking by testing against each core's minimum possible clock speed.
+ for (coreDir in coreDirs) {
+ try {
+ // skip disabled cores
+ if (readFileTextOrNull("$coreDir/online") == "0") {
+ break
+ }
+
+ val cpuMinFreqStr = readFileTextOrNull(
+ "$coreDir/cpufreq/scaling_min_freq"
+ ) ?: break
+ val cpuMinFreq = Integer.parseInt(cpuMinFreqStr)
+
+ val cpuAllAvailFreqStr = readFileTextOrNull(
+ "$coreDir/cpufreq/scaling_available_frequencies"
+ ) ?: break
+ val cpuAvailMinFreq =
+ cpuAllAvailFreqStr.split("\\s+".toRegex()).map { Integer.parseInt(it) }.min()
+
+ val coreMightBeLocked = cpuAvailMinFreq != cpuMinFreq
+ cpuClocksAreLocked = cpuClocksAreLocked && coreMightBeLocked
+ } catch (e: Exception) {
+ // Failed to read the cpu clock speed! This can happen in a number of cases where
+ // the required files are either missing due to running on an emulator or when the
+ // files have been tampered with / not generated by the OS for some cores.
+ if (!isEmulator) {
+ Log.d(TAG, "Error while reading cpu clock state", e)
+ }
+ }
+ }
+ return cpuClocksAreLocked
+ }
+
+ init {
+ val appInfo = InstrumentationRegistry.getInstrumentation().targetContext
+ .applicationInfo
+ var warningPrefix = ""
+ var warningString = ""
+ if (appInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
+ warningPrefix += "DEBUGGABLE_"
+ warningString += """
+ |WARNING: Debuggable Benchmark
+ | Benchmark is running with debuggable=true, which drastically reduces
+ | runtime performance in order to support debugging features. Run
+ | benchmarks with debuggable=false. Debuggable affects execution speed
+ | in ways that mean benchmark improvements might not carry over to a
+ | real user's experience (or even regress release performance).
+ """.trimMarginWrapNewlines()
+ }
+ if (isEmulator) {
+ warningPrefix += "EMULATOR_"
+ warningString += """
+ |WARNING: Running on Emulator
+ | Benchmark is running on an emulator, which is not representative of
+ | real user devices. Use a physical device to benchmark. Emulator
+ | benchmark improvements might not carry over to a real user's
+ | experience (or even regress real device performance).
+ """.trimMarginWrapNewlines()
+ }
+ if (Build.FINGERPRINT.contains(":eng/")) {
+ warningPrefix += "ENG-BUILD_"
+ warningString += """
+ |WARNING: Running on Eng Build
+ | Benchmark is running on device flashed with a '-eng' build. Eng builds
+ | of the platform drastically reduce performance to enable testing
+ | changes quickly. For this reason they should not be used for
+ | benchmarking. Use a '-user' or '-userdebug' system image.
+ """.trimMarginWrapNewlines()
+ }
+
+ if (isDeviceRooted && !isCpuLocked()) {
+ warningPrefix += "UNLOCKED_"
+ warningString += """
+ |WARNING: Unstable CPU clocks
+ | Benchmark appears to be running on a rooted device with unlocked CPU
+ | clocks. Unlocked CPU clocks can lead to inconsistent results due to
+ | dynamic frequency scaling, and thermal throttling. On a rooted
+ | device, lock your device clocks to a stable frequency with lockClocks.sh
+ """.trimMarginWrapNewlines()
+ }
+
+ WARNING_PREFIX = warningPrefix
+ if (!warningString.isEmpty()) {
+ this.warningString = warningString
+ warningString.split("\n").map { Log.w(TAG, it) }
+ }
+ }
+
+ /**
+ * Same as trimMargins, but add newlines on either side.
+ */
+ private fun String.trimMarginWrapNewlines(): String {
+ return "\n" + trimMargin() + " \n"
+ }
+
+ /**
+ * Read the text of a file as a String, null if file doesn't exist.
+ */
+ private fun readFileTextOrNull(path: String): String? {
+ File(path).run {
+ return if (exists()) readText().trim() else null
+ }
+ }
+}
diff --git a/biometric/res/values-ar/strings.xml b/biometric/res/values-ar/strings.xml
index a879778..09bb290 100644
--- a/biometric/res/values-ar/strings.xml
+++ b/biometric/res/values-ar/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"ليست هناك بصمات إصبع مسجَّلة."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"لا يحتوي هذا الجهاز على جهاز استشعار بصمات الأصابع."</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"تم إلغاء تشغيل بصمة الإصبع بواسطة المستخدم."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"خطأ غير معروف"</string>
</resources>
diff --git a/biometric/res/values-ca/strings.xml b/biometric/res/values-ca/strings.xml
index 01d08ef..7f5c4ac 100644
--- a/biometric/res/values-ca/strings.xml
+++ b/biometric/res/values-ca/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"No s\'ha registrat cap empremta digital."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Aquest dispositiu no té sensor d\'empremtes digitals"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"L\'usuari ha cancel·lat l\'operació d\'empremta digital."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Error desconegut"</string>
</resources>
diff --git a/biometric/res/values-de/strings.xml b/biometric/res/values-de/strings.xml
index 8c4a890..2c38bd3 100644
--- a/biometric/res/values-de/strings.xml
+++ b/biometric/res/values-de/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Keine Fingerabdrücke erfasst."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Dieses Gerät hat keinen Fingerabdrucksensor"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Vorgang der Fingerabdruckauthentifizierung vom Nutzer abgebrochen."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Unbekannter Fehler"</string>
</resources>
diff --git a/biometric/res/values-es/strings.xml b/biometric/res/values-es/strings.xml
index 9f5183a..f507aab 100644
--- a/biometric/res/values-es/strings.xml
+++ b/biometric/res/values-es/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"No se ha registrado ninguna huella digital."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"El dispositivo no tiene ningún sensor de huellas digitales"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"El usuario ha cancelado la operación de huella digital."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Error desconocido"</string>
</resources>
diff --git a/biometric/res/values-eu/strings.xml b/biometric/res/values-eu/strings.xml
index cc0ac0a..e81f79a 100644
--- a/biometric/res/values-eu/strings.xml
+++ b/biometric/res/values-eu/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Ez da erregistratu hatz-markarik."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Gailu honek ez du hatz-marken sentsorerik"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Erabiltzaileak bertan behera utzi du hatz-marka bidezko eragiketa."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Errore ezezaguna"</string>
</resources>
diff --git a/biometric/res/values-fr-rCA/strings.xml b/biometric/res/values-fr-rCA/strings.xml
index 5ecac9e..eaf5ca0 100644
--- a/biometric/res/values-fr-rCA/strings.xml
+++ b/biometric/res/values-fr-rCA/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Aucune empreinte digitale enregistrée."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Cet appareil ne possède pas de capteur d\'empreintes digitales"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"L\'opération d\'authentification par empreinte digitale a été annulée par l\'utilisateur."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Erreur inconnue"</string>
</resources>
diff --git a/biometric/res/values-fr/strings.xml b/biometric/res/values-fr/strings.xml
index 7511ebc..aea80df 100644
--- a/biometric/res/values-fr/strings.xml
+++ b/biometric/res/values-fr/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Aucune empreinte digitale enregistrée."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Aucun lecteur d\'empreinte digitale n\'est installé sur cet appareil"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Opération d\'authentification par empreinte digitale annulée par l\'utilisateur."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Erreur inconnue"</string>
</resources>
diff --git a/biometric/res/values-hy/strings.xml b/biometric/res/values-hy/strings.xml
index 3edc356..2475c3b 100644
--- a/biometric/res/values-hy/strings.xml
+++ b/biometric/res/values-hy/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Գրանցված մատնահետքեր չկան:"</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Սարքը չունի մատնահետքերի սկաներ"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Մատնահետքով նույնականացման գործողությունը չեղարկվել է օգտատիրոջ կողմից:"</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Անհայտ սխալ"</string>
</resources>
diff --git a/biometric/res/values-kk/strings.xml b/biometric/res/values-kk/strings.xml
index 4c6fb3e..64cb3a5 100644
--- a/biometric/res/values-kk/strings.xml
+++ b/biometric/res/values-kk/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Саусақ іздері тіркелмеген."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Бұл құрылғыда саусақ ізін оқу сканері жоқ"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Пайдаланушы саусақ ізі операциясынан бас тартты."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Белгісіз қате"</string>
</resources>
diff --git a/biometric/res/values-kn/strings.xml b/biometric/res/values-kn/strings.xml
index f773393..e9dd213 100644
--- a/biometric/res/values-kn/strings.xml
+++ b/biometric/res/values-kn/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"ಯಾವುದೇ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ನು ನೋಂದಣಿ ಮಾಡಿಲ್ಲ."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"ಈ ಸಾಧನವು ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಸೆನ್ಸಾರ್ ಅನ್ನು ಹೊಂದಿಲ್ಲ"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"ಬಳಕೆದಾರರಿಂದ ಫಿಂಗರ್ ಫ್ರಿಂಟ್ ಕಾರ್ಯಾಚರಣೆಯನ್ನು ರದ್ದುಪಡಿಸಲಾಗಿದೆ."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"ಅಪರಿಚಿತ ದೋಷ"</string>
</resources>
diff --git a/biometric/res/values-mr/strings.xml b/biometric/res/values-mr/strings.xml
index d3f28b15..06e9a0a 100644
--- a/biometric/res/values-mr/strings.xml
+++ b/biometric/res/values-mr/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"कोणत्याही फिंगरप्रिंटची नोंद झाली नाही."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"या डिव्हाइसवर फिंगरप्रिंट सेन्सर नाही"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"वापरकर्त्याने फिंगरप्रिंट ऑपरेशन रद्द केले."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"अज्ञात एरर"</string>
</resources>
diff --git a/biometric/res/values-pt-rPT/strings.xml b/biometric/res/values-pt-rPT/strings.xml
index 1215739..0790e10 100644
--- a/biometric/res/values-pt-rPT/strings.xml
+++ b/biometric/res/values-pt-rPT/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Nenhuma impressão digital registada."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Este dispositivo não tem sensor de impressões digitais."</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Operação de impressão digital cancelada pelo utilizador."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Erro desconhecido."</string>
</resources>
diff --git a/biometric/res/values-sk/strings.xml b/biometric/res/values-sk/strings.xml
index 8644574..8ea6570 100644
--- a/biometric/res/values-sk/strings.xml
+++ b/biometric/res/values-sk/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Neregistrovali ste žiadne odtlačky prstov."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Toto zariadenie nemá senzor odtlačkov prstov"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Overenie odtlačku prsta zrušil používateľ."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Neznáma chyba"</string>
</resources>
diff --git a/biometric/res/values-ta/strings.xml b/biometric/res/values-ta/strings.xml
index ad90b59..6f2dc1b 100644
--- a/biometric/res/values-ta/strings.xml
+++ b/biometric/res/values-ta/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"கைரேகைப் பதிவுகள் எதுவுமில்லை."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"இந்தச் சாதனத்தில் கைரேகை சென்சார் இல்லை"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"கைரேகைச் சரிபார்ப்பு பயனரால் ரத்துசெய்யப்பட்டது."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"அறியப்படாத பிழை"</string>
</resources>
diff --git a/biometric/res/values-vi/strings.xml b/biometric/res/values-vi/strings.xml
index 793d91c..6e04b1c 100644
--- a/biometric/res/values-vi/strings.xml
+++ b/biometric/res/values-vi/strings.xml
@@ -24,5 +24,7 @@
<string name="fingerprint_error_no_fingerprints" msgid="3350805046152877040">"Chưa đăng ký vân tay."</string>
<string name="fingerprint_error_hw_not_present" msgid="1176237289575184578">"Thiết bị này không có cảm biến vân tay"</string>
<string name="fingerprint_error_user_canceled" msgid="3421037373085129417">"Người dùng đã hủy thao tác dùng dấu vân tay."</string>
+ <!-- no translation found for fingerprint_error_lockout (1651062876313169162) -->
+ <skip />
<string name="default_error_msg" msgid="7497355367608150274">"Lỗi không xác định"</string>
</resources>
diff --git a/biometric/res/values/strings.xml b/biometric/res/values/strings.xml
index e961bcb..1ee1534 100644
--- a/biometric/res/values/strings.xml
+++ b/biometric/res/values/strings.xml
@@ -32,6 +32,8 @@
<string name="fingerprint_error_hw_not_present">This device does not have a fingerprint sensor</string>
<!-- Generic error message shown when the fingerprint authentication operation is canceled due to user input. Generally not shown to the user. [CHAR LIMIT=NONE] -->
<string name="fingerprint_error_user_canceled">Fingerprint operation canceled by user.</string>
+ <!-- Error message shown after too many failed authentication attempts. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_error_lockout">Too many attempts. Please try again later.</string>
<!-- Generic error message shown when an unknown error has occurred. [CHAR LIMIT=NONE] -->
<string name="default_error_msg">Unknown error</string>
</resources>
diff --git a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
index da6bf70..efdd360 100644
--- a/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
+++ b/biometric/src/main/java/androidx/biometric/BiometricPrompt.java
@@ -21,6 +21,7 @@
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
@@ -52,6 +53,9 @@
private static final String TAG = "BiometricPromptCompat";
private static final boolean DEBUG = false;
+ // In order to keep consistent behavior between versions, we need to send
+ // FingerprintDialogFragment a message indicating whether or not to dismiss the UI instantly.
+ private static final int DELAY_MILLIS = 500;
static final String DIALOG_FRAGMENT_TAG = "FingerprintDialogFragment";
static final String FINGERPRINT_HELPER_FRAGMENT_TAG = "FingerprintHelperFragment";
@@ -499,8 +503,12 @@
mFingerprintHelperFragment = FingerprintHelperFragment.newInstance();
}
mFingerprintHelperFragment.setCallback(mExecutor, mAuthenticationCallback);
- mFingerprintHelperFragment.setHandler(mFingerprintDialogFragment.getHandler());
+ final Handler fingerprintDialogHandler = mFingerprintDialogFragment.getHandler();
+ mFingerprintHelperFragment.setHandler(fingerprintDialogHandler);
mFingerprintHelperFragment.setCryptoObject(crypto);
+ fingerprintDialogHandler.sendMessageDelayed(
+ fingerprintDialogHandler.obtainMessage(
+ FingerprintDialogFragment.DISPLAYED_FOR_500_MS), DELAY_MILLIS);
if (fragmentManager.findFragmentByTag(FINGERPRINT_HELPER_FRAGMENT_TAG) == null) {
// If the fragment hasn't been added before, add it. It will also start the
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
index 763be0a..c8354ca 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintDialogFragment.java
@@ -65,9 +65,14 @@
// Show an error in the help area, and dismiss the dialog afterwards
protected static final int MSG_SHOW_ERROR = 2;
// Dismisses the authentication dialog
- protected static final int MSG_DISMISS_DIALOG = 3;
+ protected static final int MSG_DISMISS_DIALOG_ERROR = 3;
// Resets the help message
protected static final int MSG_RESET_MESSAGE = 4;
+ // Dismisses the authentication dialog after success.
+ protected static final int MSG_DISMISS_DIALOG_AUTHENTICATED = 5;
+ // The amount of time required that this fragment be displayed for in order that
+ // we show an error message on top of the UI.
+ protected static final int DISPLAYED_FOR_500_MS = 6;
// States for icon animation
private static final int STATE_NONE = 0;
@@ -93,12 +98,18 @@
case MSG_SHOW_ERROR:
handleShowError(msg.arg1, (CharSequence) msg.obj);
break;
- case MSG_DISMISS_DIALOG:
- handleDismissDialog();
+ case MSG_DISMISS_DIALOG_ERROR:
+ handleDismissDialogError();
+ break;
+ case MSG_DISMISS_DIALOG_AUTHENTICATED:
+ dismiss();
break;
case MSG_RESET_MESSAGE:
handleResetMessage();
break;
+ case DISPLAYED_FOR_500_MS:
+ mDismissInstantly = false;
+ break;
}
}
}
@@ -113,6 +124,13 @@
private Context mContext;
private Dialog mDialog;
+ /**
+ * This flag is used to control the instant dismissal of the dialog fragment. In the case where
+ * the user is already locked out this dialog will not appear. In the case where the user is
+ * being locked out for the first time an error message will be displayed on the UI before
+ * dismissing.
+ */
+ protected boolean mDismissInstantly = true;
// This should be re-set by the BiometricPromptCompat each time the lifecycle changes.
DialogInterface.OnClickListener mNegativeButtonListener;
@@ -328,11 +346,31 @@
mErrorText.setText(msg);
// Dismiss the dialog after a delay
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISMISS_DIALOG), HIDE_DIALOG_DELAY);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISMISS_DIALOG_ERROR),
+ HIDE_DIALOG_DELAY);
}
- void handleDismissDialog() {
- dismiss();
+ void dismissAfterDelay() {
+ mErrorText.setTextColor(mErrorColor);
+ mErrorText.setText(
+ R.string.fingerprint_error_lockout);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ dismiss();
+ }
+ }, HIDE_DIALOG_DELAY);
+ }
+
+ void handleDismissDialogError() {
+ if (mDismissInstantly) {
+ dismiss();
+ } else {
+ dismissAfterDelay();
+ }
+ // Always set this to true. In case the user tries to authenticate again the UI will not be
+ // shown.
+ mDismissInstantly = true;
}
void handleResetMessage() {
diff --git a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
index 12d5bce..468d819 100644
--- a/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
+++ b/biometric/src/main/java/androidx/biometric/FingerprintHelperFragment.java
@@ -70,7 +70,7 @@
private void dismissAndForwardResult(final int errMsgId,
final CharSequence errString) {
- mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG)
+ mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG_ERROR)
.sendToTarget();
mExecutor.execute(new Runnable() {
@Override
@@ -124,7 +124,8 @@
public void onAuthenticationSucceeded(
final FingerprintManagerCompat.AuthenticationResult result) {
mHandler.obtainMessage(
- FingerprintDialogFragment.MSG_DISMISS_DIALOG).sendToTarget();
+ FingerprintDialogFragment.MSG_DISMISS_DIALOG_AUTHENTICATED)
+ .sendToTarget();
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -182,7 +183,8 @@
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(
mContext);
if (handlePreAuthenticationErrors(fingerprintManagerCompat)) {
- mHandler.obtainMessage(FingerprintDialogFragment.MSG_DISMISS_DIALOG).sendToTarget();
+ mHandler.obtainMessage(
+ FingerprintDialogFragment.MSG_DISMISS_DIALOG_ERROR).sendToTarget();
cleanup();
} else {
fingerprintManagerCompat.authenticate(
diff --git a/browser/api/1.2.0-alpha01.txt b/browser/api/1.2.0-alpha01.txt
new file mode 100644
index 0000000..e9dfa22
--- /dev/null
+++ b/browser/api/1.2.0-alpha01.txt
@@ -0,0 +1,215 @@
+// Signature format: 3.0
+package androidx.browser.browseractions {
+
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public int getIconId();
+ method @Deprecated public String getTitle();
+ }
+
+ @Deprecated public class BrowserActionsIntent {
+ method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+ method @Deprecated public android.content.Intent getIntent();
+ method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+ method @Deprecated public static void launchIntent(android.content.Context!, android.content.Intent!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!, android.app.PendingIntent!);
+ method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem>! parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>!);
+ field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+ field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+ field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+ field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+ field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+ field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+ field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+ field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+ field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+ field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+ field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+ field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+ field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+ field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+ field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+ field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+ field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+ field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+ }
+
+ @Deprecated public static final class BrowserActionsIntent.Builder {
+ ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context!, android.net.Uri!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent! build();
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(androidx.browser.browseractions.BrowserActionItem...!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setOnItemSelectedAction(android.app.PendingIntent!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setUrlType(int);
+ }
+
+}
+
+package androidx.browser.customtabs {
+
+ public class CustomTabsCallback {
+ ctor public CustomTabsCallback();
+ method public void extraCallback(String!, android.os.Bundle!);
+ method public void onMessageChannelReady(android.os.Bundle!);
+ method public void onNavigationEvent(int, android.os.Bundle!);
+ method public void onPostMessage(String!, android.os.Bundle!);
+ method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, boolean, android.os.Bundle!);
+ field public static final int NAVIGATION_ABORTED = 4; // 0x4
+ field public static final int NAVIGATION_FAILED = 3; // 0x3
+ field public static final int NAVIGATION_FINISHED = 2; // 0x2
+ field public static final int NAVIGATION_STARTED = 1; // 0x1
+ field public static final int TAB_HIDDEN = 6; // 0x6
+ field public static final int TAB_SHOWN = 5; // 0x5
+ }
+
+ public class CustomTabsClient {
+ method public static boolean bindCustomTabsService(android.content.Context!, String!, androidx.browser.customtabs.CustomTabsServiceConnection!);
+ method public static boolean connectAndInitialize(android.content.Context!, String!);
+ method public android.os.Bundle! extraCommand(String!, android.os.Bundle!);
+ method public static String! getPackageName(android.content.Context!, java.util.List<java.lang.String>?);
+ method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String>?, boolean);
+ method public androidx.browser.customtabs.CustomTabsSession! newSession(androidx.browser.customtabs.CustomTabsCallback!);
+ method public boolean warmup(long);
+ }
+
+ public final class CustomTabsIntent {
+ method public static int getMaxToolbarItems();
+ method public void launchUrl(android.content.Context!, android.net.Uri!);
+ method public static android.content.Intent! setAlwaysUseBrowserUI(android.content.Intent!);
+ method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent!);
+ field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+ field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+ field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+ field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+ field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+ field public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+ field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+ field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+ field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+ field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+ field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+ field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+ field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+ field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+ field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+ field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+ field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+ field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+ field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+ field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+ field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+ field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+ field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+ field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+ field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+ field public static final int NO_TITLE = 0; // 0x0
+ field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+ field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+ field public final android.content.Intent intent;
+ field public final android.os.Bundle? startAnimationBundle;
+ }
+
+ public static final class CustomTabsIntent.Builder {
+ ctor public CustomTabsIntent.Builder();
+ ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+ method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent!) throws java.lang.IllegalStateException;
+ method public androidx.browser.customtabs.CustomTabsIntent build();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+ method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+ }
+
+ public abstract class CustomTabsService extends android.app.Service {
+ ctor public CustomTabsService();
+ method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method protected abstract android.os.Bundle! extraCommand(String!, android.os.Bundle!);
+ method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle>!);
+ method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken!, String!, android.os.Bundle!);
+ method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken!, android.net.Uri!);
+ method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken!, android.os.Bundle!);
+ method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken!, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri!, android.os.Bundle!);
+ method protected abstract boolean warmup(long);
+ field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+ field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+ field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+ field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+ field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+ field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+ field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) public static @interface CustomTabsService.Relation {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) public static @interface CustomTabsService.Result {
+ }
+
+ public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+ ctor public CustomTabsServiceConnection();
+ method public abstract void onCustomTabsServiceConnected(android.content.ComponentName!, androidx.browser.customtabs.CustomTabsClient!);
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ }
+
+ public final class CustomTabsSession {
+ method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+ method public boolean mayLaunchUrl(android.net.Uri!, android.os.Bundle!, java.util.List<android.os.Bundle>!);
+ method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String!, android.os.Bundle!);
+ method public boolean requestPostMessageChannel(android.net.Uri!);
+ method public boolean setActionButton(android.graphics.Bitmap, String);
+ method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+ method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+ method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+ }
+
+ public class CustomTabsSessionToken {
+ method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+ method public androidx.browser.customtabs.CustomTabsCallback! getCallback();
+ method public static androidx.browser.customtabs.CustomTabsSessionToken! getSessionTokenFromIntent(android.content.Intent!);
+ method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession!);
+ }
+
+ public class PostMessageService extends android.app.Service {
+ ctor public PostMessageService();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public abstract class PostMessageServiceConnection implements android.content.ServiceConnection {
+ ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken!);
+ method public boolean bindSessionToPostMessageService(android.content.Context!, String!);
+ method public final boolean notifyMessageChannelReady(android.os.Bundle!);
+ method public void onPostMessageServiceConnected();
+ method public void onPostMessageServiceDisconnected();
+ method public final void onServiceConnected(android.content.ComponentName!, android.os.IBinder!);
+ method public final void onServiceDisconnected(android.content.ComponentName!);
+ method public final boolean postMessage(String!, android.os.Bundle!);
+ method public void unbindFromContext(android.content.Context!);
+ }
+
+ public class TrustedWebUtils {
+ method public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+ field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+ }
+
+}
+
diff --git a/browser/api/current.txt b/browser/api/current.txt
index 56bcfe8..e9dfa22 100644
--- a/browser/api/current.txt
+++ b/browser/api/current.txt
@@ -1,52 +1,52 @@
// Signature format: 3.0
package androidx.browser.browseractions {
- public class BrowserActionItem {
- ctor public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
- ctor public BrowserActionItem(String, android.app.PendingIntent);
- method public android.app.PendingIntent getAction();
- method public int getIconId();
- method public String getTitle();
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+ ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public int getIconId();
+ method @Deprecated public String getTitle();
}
- public class BrowserActionsIntent {
+ @Deprecated public class BrowserActionsIntent {
method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
- method public android.content.Intent getIntent();
- method public static String? getUntrustedCreatorPackageName(android.content.Intent);
- method public static void launchIntent(android.content.Context!, android.content.Intent!);
- method public static void openBrowserAction(android.content.Context!, android.net.Uri!);
- method public static void openBrowserAction(android.content.Context!, android.net.Uri!, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!, android.app.PendingIntent!);
- method public static java.util.List<androidx.browser.browseractions.BrowserActionItem>! parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>!);
- field public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
- field public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
- field public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
- field public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
- field public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
- field public static final int ITEM_COPY = 3; // 0x3
- field public static final int ITEM_DOWNLOAD = 2; // 0x2
- field public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
- field public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
- field public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
- field public static final int ITEM_SHARE = 4; // 0x4
- field public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
- field public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
- field public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
- field public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
- field public static final int URL_TYPE_AUDIO = 3; // 0x3
- field public static final int URL_TYPE_FILE = 4; // 0x4
- field public static final int URL_TYPE_IMAGE = 1; // 0x1
- field public static final int URL_TYPE_NONE = 0; // 0x0
- field public static final int URL_TYPE_PLUGIN = 5; // 0x5
- field public static final int URL_TYPE_VIDEO = 2; // 0x2
+ method @Deprecated public android.content.Intent getIntent();
+ method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+ method @Deprecated public static void launchIntent(android.content.Context!, android.content.Intent!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!);
+ method @Deprecated public static void openBrowserAction(android.content.Context!, android.net.Uri!, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!, android.app.PendingIntent!);
+ method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem>! parseBrowserActionItems(java.util.ArrayList<android.os.Bundle>!);
+ field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+ field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+ field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+ field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+ field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+ field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+ field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+ field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+ field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+ field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+ field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+ field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+ field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+ field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+ field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+ field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+ field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+ field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+ field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
}
- public static final class BrowserActionsIntent.Builder {
- ctor public BrowserActionsIntent.Builder(android.content.Context!, android.net.Uri!);
- method public androidx.browser.browseractions.BrowserActionsIntent! build();
- method public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!);
- method public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(androidx.browser.browseractions.BrowserActionItem...!);
- method public androidx.browser.browseractions.BrowserActionsIntent.Builder! setOnItemSelectedAction(android.app.PendingIntent!);
- method public androidx.browser.browseractions.BrowserActionsIntent.Builder! setUrlType(int);
+ @Deprecated public static final class BrowserActionsIntent.Builder {
+ ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context!, android.net.Uri!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent! build();
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem>!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setCustomItems(androidx.browser.browseractions.BrowserActionItem...!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setOnItemSelectedAction(android.app.PendingIntent!);
+ method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder! setUrlType(int);
}
}
diff --git a/browser/api/res-1.2.0-alpha01.txt b/browser/api/res-1.2.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/browser/api/res-1.2.0-alpha01.txt
diff --git a/browser/api/restricted_1.2.0-alpha01.txt b/browser/api/restricted_1.2.0-alpha01.txt
new file mode 100644
index 0000000..9bc0fcb
--- /dev/null
+++ b/browser/api/restricted_1.2.0-alpha01.txt
@@ -0,0 +1,28 @@
+// Signature format: 3.0
+package androidx.browser.browseractions {
+
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public BrowserActionItem(String, android.app.PendingIntent, android.net.Uri);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BrowserActionsFallbackMenuView extends android.widget.LinearLayout {
+ ctor public BrowserActionsFallbackMenuView(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ @Deprecated public class BrowserActionsIntent {
+ }
+
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.ITEM_INVALID_ITEM, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_NEW_TAB, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_INCOGNITO, androidx.browser.browseractions.BrowserActionsIntent.ITEM_DOWNLOAD, androidx.browser.browseractions.BrowserActionsIntent.ITEM_COPY, androidx.browser.browseractions.BrowserActionsIntent.ITEM_SHARE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsItemId {
+ }
+
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_NONE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_IMAGE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_VIDEO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_AUDIO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_FILE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_PLUGIN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsUrlType {
+ }
+
+
+}
+
+package androidx.browser.customtabs {
+
+
+}
+
diff --git a/browser/api/restricted_current.txt b/browser/api/restricted_current.txt
index 13d91a5..9bc0fcb 100644
--- a/browser/api/restricted_current.txt
+++ b/browser/api/restricted_current.txt
@@ -1,21 +1,21 @@
// Signature format: 3.0
package androidx.browser.browseractions {
- public class BrowserActionItem {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public BrowserActionItem(String, android.app.PendingIntent, android.net.Uri);
+ @Deprecated public class BrowserActionItem {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public BrowserActionItem(String, android.app.PendingIntent, android.net.Uri);
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BrowserActionsFallbackMenuView extends android.widget.LinearLayout {
ctor public BrowserActionsFallbackMenuView(android.content.Context!, android.util.AttributeSet!);
}
- public class BrowserActionsIntent {
+ @Deprecated public class BrowserActionsIntent {
}
- @IntDef({androidx.browser.browseractions.BrowserActionsIntent.ITEM_INVALID_ITEM, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_NEW_TAB, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_INCOGNITO, androidx.browser.browseractions.BrowserActionsIntent.ITEM_DOWNLOAD, androidx.browser.browseractions.BrowserActionsIntent.ITEM_COPY, androidx.browser.browseractions.BrowserActionsIntent.ITEM_SHARE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsItemId {
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.ITEM_INVALID_ITEM, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_NEW_TAB, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_INCOGNITO, androidx.browser.browseractions.BrowserActionsIntent.ITEM_DOWNLOAD, androidx.browser.browseractions.BrowserActionsIntent.ITEM_COPY, androidx.browser.browseractions.BrowserActionsIntent.ITEM_SHARE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsItemId {
}
- @IntDef({androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_NONE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_IMAGE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_VIDEO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_AUDIO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_FILE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_PLUGIN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsUrlType {
+ @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_NONE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_IMAGE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_VIDEO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_AUDIO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_FILE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_PLUGIN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsUrlType {
}
diff --git a/browser/src/main/java/androidx/browser/browseractions/BrowserActionItem.java b/browser/src/main/java/androidx/browser/browseractions/BrowserActionItem.java
index c79ad83..db37c0a 100644
--- a/browser/src/main/java/androidx/browser/browseractions/BrowserActionItem.java
+++ b/browser/src/main/java/androidx/browser/browseractions/BrowserActionItem.java
@@ -30,7 +30,10 @@
/**
* A wrapper class holding custom item of Browser Actions menu.
* The Bitmap is optional for a BrowserActionItem.
+ *
+ * @deprecated Browser Actions are deprecated as of release 1.2.0.
*/
+@Deprecated
public class BrowserActionItem {
private final String mTitle;
private final PendingIntent mAction;
diff --git a/browser/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java b/browser/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java
index 42d0729..9f29b51 100644
--- a/browser/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java
+++ b/browser/src/main/java/androidx/browser/browseractions/BrowserActionsIntent.java
@@ -49,7 +49,10 @@
* <p>
* <strong>Note:</strong> The constants below are public for the browser implementation's benefit.
* You are strongly encouraged to use {@link BrowserActionsIntent.Builder}.
+ *
+ * @deprecated Browser Actions are deprecated as of release 1.2.0.
*/
+@Deprecated
public class BrowserActionsIntent {
private static final String TAG = "BrowserActions";
// Used to verify that an URL intent handler exists.
diff --git a/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java b/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
index 7cb492f..a7161ce 100644
--- a/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
+++ b/browser/src/main/java/androidx/browser/customtabs/CustomTabsClient.java
@@ -79,7 +79,7 @@
/**
* Returns the preferred package to use for Custom Tabs, preferring the default VIEW handler.
*
- * @see #getPackageName(Context, List<String>, boolean)
+ * see getPackageName(Context, List<String>, boolean)
*/
public static String getPackageName(Context context, @Nullable List<String> packages) {
return getPackageName(context, packages, false);
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index e7deb2c..d37ab75 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -18,7 +18,11 @@
build_versions.kotlin = '1.3.10'
build_versions.lint = '26.4.0-beta03'
-build_versions.dokka = "0.9.17-g20190228"
+build_versions.dokka = '0.9.17-g20190326'
+
+build_versions.studio = new Properties()
+new File(buildscript.sourceFile.parentFile, "studio_versions.properties").withInputStream { build_versions.studio.load(it) }
+
rootProject.ext['build_versions'] = build_versions
@@ -32,7 +36,7 @@
logger.warn("USING OVERRIDDEN ANDROID GRADLE PLUGIN DEPENDENCY OF " + build_libs.gradle)
} else {
// Keep gradle plugin version in sync with ub_supportlib-master manifest.
- build_libs.gradle = 'com.android.tools.build:gradle:3.4.0-beta03'
+ build_libs.gradle = "com.android.tools.build:gradle:${build_versions.studio.agp}"
}
build_libs.lint = [
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 9ddae11..8d8cefa 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -30,18 +30,6 @@
exclude group: 'androidx.annotation'
}
-libs.exclude_for_material = {
- transitive = true
- exclude group: 'androidx.annotation'
- exclude group: 'androidx.core'
- exclude group: 'androidx.legacy'
- exclude group: 'androidx.fragment'
- exclude group: 'androidx.transition'
- exclude group: 'androidx.appcompat'
- exclude group: 'androidx.recyclerview'
- exclude group: 'androidx.cardview'
-}
-
libs.exclude_for_espresso = {
exclude group: 'androidx.annotation'
exclude group: 'androidx.appcompat'
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index e52874a..cfde80e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -22,6 +22,7 @@
import androidx.build.SupportConfig.DEFAULT_MIN_SDK_VERSION
import androidx.build.SupportConfig.INSTRUMENTATION_RUNNER
import androidx.build.checkapi.ApiType
+import androidx.build.checkapi.getCurrentApiLocation
import androidx.build.checkapi.getLastReleasedApiFileFromDir
import androidx.build.checkapi.hasApiFolder
import androidx.build.dependencyTracker.AffectedModuleDetector
@@ -85,6 +86,7 @@
sourceCompatibility = VERSION_1_7
targetCompatibility = VERSION_1_7
}
+ project.hideJavadocTask()
val verifyDependencyVersionsTask = project.createVerifyDependencyVersionsTask()
verifyDependencyVersionsTask.configure {
it.dependsOn(project.tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME))
@@ -197,6 +199,10 @@
val jacocoUberJar = Jacoco.createUberJarTask(this)
buildOnServerTask.dependsOn(jacocoUberJar)
+ val checkSameVersionLibraryGroupsTask = project.tasks.register(
+ CHECK_SAME_VERSION_LIBRARY_GROUPS,
+ CheckSameVersionLibraryGroupsTask::class.java)
+ buildOnServerTask.dependsOn(checkSameVersionLibraryGroupsTask)
project.createClockLockTasks()
@@ -357,6 +363,7 @@
const val BUILD_TEST_APKS = "buildTestApks"
const val CHECK_RELEASE_READY_TASK = "checkReleaseReady"
const val CHECK_NO_WARNINGS_TASK = "checkNoWarnings"
+ const val CHECK_SAME_VERSION_LIBRARY_GROUPS = "checkSameVersionLibraryGroups"
}
}
@@ -365,6 +372,18 @@
return name.endsWith("-benchmark")
}
+fun Project.hideJavadocTask() {
+ // Most tasks named "javadoc" are unused
+ // So, few tasks named "javadoc" are interesting to developers
+ // So, we don't want "javadoc" to appear in the output of `./gradlew tasks`
+ // So, we set the group to null for any task named "javadoc"
+ project.tasks.all { task ->
+ if (task.name == "javadoc") {
+ task.group = null
+ }
+ }
+}
+
fun Project.addToProjectMap(group: String?) {
if (group != null) {
val module = "$group:${project.name}"
@@ -385,7 +404,7 @@
return project.tasks.createWithConfig("checkResourceApi",
CheckResourceApiTask::class.java) {
newApiFile = getGenerateResourceApiFile()
- oldApiFile = File(project.projectDir, "api/res-${project.version}.txt")
+ oldApiFile = project.getCurrentApiLocation().resourceFile
}
}
@@ -402,6 +421,7 @@
newApiFile = getGenerateResourceApiFile()
oldApiFile = getLastReleasedApiFileFromDir(File(project.projectDir, "api/"),
project.version(), true, false, ApiType.RESOURCEAPI)
+ destApiFile = project.getCurrentApiLocation().resourceFile
}
}
@@ -430,4 +450,4 @@
private fun Project.getGenerateResourceApiFile(): File {
return File(project.buildDir, "intermediates/public_res/release" +
"/packageReleaseResources/public.txt")
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/CheckResourceApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/CheckResourceApiTask.kt
index e97f7b3..bfaa10a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/CheckResourceApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/CheckResourceApiTask.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package androidx.build
import org.gradle.api.DefaultTask
diff --git a/buildSrc/src/main/kotlin/androidx/build/CheckSameVersionLibraryGroupsTask.kt b/buildSrc/src/main/kotlin/androidx/build/CheckSameVersionLibraryGroupsTask.kt
new file mode 100644
index 0000000..53005db
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/CheckSameVersionLibraryGroupsTask.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.build
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Task that checks that all libraries within groups with requireSameVersion set to true
+ * are actually of the same version.
+ */
+open class CheckSameVersionLibraryGroupsTask : DefaultTask() {
+
+ @TaskAction
+ fun checkSameVersionLibraryGroups() {
+ val map = HashMap<String, Pair<String, String>>()
+ project.subprojects { project ->
+ val library = project.extensions.findByType(SupportLibraryExtension::class.java)
+ val requireSameVersion = library?.mavenGroup?.requireSameVersion ?: false
+ if (requireSameVersion) {
+ if (project.version == SupportLibraryExtension.DEFAULT_UNSPECIFIED_VERSION) {
+ throw GradleException("Library $group:${project.name} does not specify " +
+ "a version, however it is within library group $group which requires" +
+ " all member libraries to be of the same version")
+ }
+ val group = library!!.mavenGroup!!.group
+ if (map.contains(group)) {
+ val existingVersion = map.get(group)!!.first
+ val libraryInSameGroup = map.get(group)!!.second
+ if (existingVersion != project.version) {
+ throw GradleException("Library $group:${project.name} with version " +
+ "${project.version} is part of the $group library group" +
+ " which requires all member libraries to have the same " +
+ "version, however library $group:$libraryInSameGroup within" +
+ " the same library group has different version $existingVersion")
+ }
+ } else {
+ map.put(group, Pair(project.version as String, project.name))
+ }
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 224130c..8c6efff 100644
--- a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -20,7 +20,6 @@
import androidx.build.Strategy.TipOfTree
import androidx.build.checkapi.ApiXmlConversionTask
import androidx.build.checkapi.CheckApiTasks
-import androidx.build.checkapi.hasApiTasks
import androidx.build.checkapi.initializeApiChecksForProject
import androidx.build.doclava.ChecksConfig
import androidx.build.doclava.DEFAULT_DOCLAVA_CONFIG
@@ -121,7 +120,8 @@
root.tasks.create("generateDocs") { task ->
task.group = JavaBasePlugin.DOCUMENTATION_GROUP
- task.description = "Generates distribution artifact for d.android.com-style docs."
+ task.description = "Generates documentation (both Java and Kotlin) from tip-of-tree " +
+ "sources, in the style of those used in d.android.com."
task.dependsOn(docsTasks[TIP_OF_TREE.name])
}
@@ -323,21 +323,6 @@
}
registerJavaProjectForDocsTask(generateDiffsTask, compileJava)
- if (!hasApiTasks(project, extension)) {
- return
- }
-
- val tasks = initializeApiChecksForProject(project,
- aggregateOldApiTxtsTask, aggregateNewApiTxtsTask)
- registerJavaProjectForDocsTask(tasks.generateApi, compileJava)
- setupApiVersioningInDocsTasks(extension, tasks)
- addCheckApiTasksToGraph(tasks)
- registerJavaProjectForDocsTask(tasks.generateLocalDiffs, compileJava)
- val generateApiDiffsArchiveTask = createGenerateLocalApiDiffsArchiveTask(project,
- tasks.generateLocalDiffs)
- generateApiDiffsArchiveTask.configure {
- it.dependsOn(tasks.generateLocalDiffs)
- }
}
/**
@@ -365,21 +350,6 @@
tipOfTreeTasks(extension) { task ->
registerAndroidProjectForDocsTask(task, variant)
}
-
- if (!hasApiTasks(project, extension)) {
- return@all
- }
- val tasks = initializeApiChecksForProject(project, aggregateOldApiTxtsTask,
- aggregateNewApiTxtsTask)
- registerAndroidProjectForDocsTask(tasks.generateApi, variant)
- setupApiVersioningInDocsTasks(extension, tasks)
- addCheckApiTasksToGraph(tasks)
- registerAndroidProjectForDocsTask(tasks.generateLocalDiffs, variant)
- val generateApiDiffsArchiveTask = createGenerateLocalApiDiffsArchiveTask(project,
- tasks.generateLocalDiffs)
- generateApiDiffsArchiveTask.configure {
- it.dependsOn(tasks.generateLocalDiffs)
- }
}
}
}
@@ -541,14 +511,15 @@
): TaskProvider<Zip> = project.tasks.register("dist${ruleName}Docs", Zip::class.java) {
it.apply {
dependsOn(generateDocs)
- group = JavaBasePlugin.DOCUMENTATION_GROUP
- description = "Generates distribution artifact for d.android.com-style documentation."
from(generateDocs.map {
it.destinationDir
})
baseName = "android-support-$ruleName-docs"
version = getBuildId()
destinationDir = project.getDistributionDirectory()
+ group = JavaBasePlugin.DOCUMENTATION_GROUP
+ description = "Zips ${ruleName} Java documentation (generated via Doclava in the " +
+ "style of d.android.com) into ${archivePath}"
doLast {
logger.lifecycle("'Wrote API reference to $archivePath")
}
@@ -596,9 +567,10 @@
): TaskProvider<GenerateDocsTask> =
project.tasks.register(taskName, GenerateDocsTask::class.java) {
it.apply {
+ exclude("**/R.java")
dependsOn(generateSdkApiTask, doclavaConfig)
group = JavaBasePlugin.DOCUMENTATION_GROUP
- description = "Generates d.android.com-style documentation. To generate offline " +
+ description = "Generates Java documentation in the style of d.android.com. To generate offline " +
"docs use \'-PofflineDocs=true\' parameter."
setDocletpath(doclavaConfig.resolve())
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 049ca9e..93493d5 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -20,68 +20,74 @@
* The list of maven group names of all the libraries in this project.
*/
object LibraryGroups {
- const val ACTIVITY = "androidx.activity"
- const val ANIMATION = "androidx.animation"
- const val ANNOTATION = "androidx.annotation"
- const val APPCOMPAT = "androidx.appcompat"
- const val ARCH_CORE = "androidx.arch.core"
- const val ASYNCLAYOUTINFLATER = "androidx.asynclayoutinflater"
- const val BIOMETRIC = "androidx.biometric"
- const val BROWSER = "androidx.browser"
- const val BENCHMARK = "androidx.benchmark"
- const val CAR = "androidx.car"
- const val CARDVIEW = "androidx.cardview"
- const val COLLECTION = "androidx.collection"
- const val CONCURRENT = "androidx.concurrent"
- const val CONTENTPAGER = "androidx.contentpager"
- const val COORDINATORLAYOUT = "androidx.coordinatorlayout"
- const val CORE = "androidx.core"
- const val CURSORADAPTER = "androidx.cursoradapter"
- const val CUSTOMVIEW = "androidx.customview"
- const val DOCUMENTFILE = "androidx.documentfile"
- const val DRAWERLAYOUT = "androidx.drawerlayout"
- const val DYNAMICANIMATION = "androidx.dynamicanimation"
- const val EMOJI = "androidx.emoji"
- const val ENTERPRISE = "androidx.enterprise"
- const val EXIFINTERFACE = "androidx.exifinterface"
- const val FRAGMENT = "androidx.fragment"
- const val GRIDLAYOUT = "androidx.gridlayout"
- const val HEIFWRITER = "androidx.heifwriter"
- const val INTERPOLATOR = "androidx.interpolator"
- const val JETIFIER = "com.android.tools.build.jetifier"
- const val LEANBACK = "androidx.leanback"
- const val LEGACY = "androidx.legacy"
- const val LIFECYCLE = "androidx.lifecycle"
- const val LOADER = "androidx.loader"
- const val LOCALBROADCASTMANAGER = "androidx.localbroadcastmanager"
- const val MEDIA = "androidx.media"
- const val MEDIA2 = "androidx.media2"
- const val MEDIAROUTER = "androidx.mediarouter"
- const val NAVIGATION = "androidx.navigation"
- const val PAGING = "androidx.paging"
- const val PALETTE = "androidx.palette"
- const val PERCENTLAYOUT = "androidx.percentlayout"
- const val PERSISTENCE = "androidx.sqlite"
- const val PREFERENCE = "androidx.preference"
- const val PRINT = "androidx.print"
- const val RECOMMENDATION = "androidx.recommendation"
- const val RECYCLERVIEW = "androidx.recyclerview"
- const val SAVEDSTATE = "androidx.savedstate"
- const val SECURITY = "androidx.security"
- const val SHARETARGET = "androidx.sharetarget"
- const val SLICE = "androidx.slice"
- const val REMOTECALLBACK = "androidx.remotecallback"
- const val ROOM = "androidx.room"
- const val SLIDINGPANELAYOUT = "androidx.slidingpanelayout"
- const val SWIPEREFRESHLAYOUT = "androidx.swiperefreshlayout"
- const val TEXTCLASSIFIER = "androidx.textclassifier"
- const val TRANSITION = "androidx.transition"
- const val TVPROVIDER = "androidx.tvprovider"
- const val VECTORDRAWABLE = "androidx.vectordrawable"
- const val VERSIONEDPARCELABLE = "androidx.versionedparcelable"
- const val VIEWPAGER = "androidx.viewpager"
- const val VIEWPAGER2 = "androidx.viewpager2"
- const val WEAR = "androidx.wear"
- const val WEBKIT = "androidx.webkit"
- const val WORKMANAGER = "android.arch.work"
+ val ACTIVITY = LibraryGroup("androidx.activity", false)
+ val ANIMATION = LibraryGroup("androidx.animation", false)
+ val ANNOTATION = LibraryGroup("androidx.annotation", false)
+ val APPCOMPAT = LibraryGroup("androidx.appcompat", false)
+ val ARCH_CORE = LibraryGroup("androidx.arch.core", false)
+ val ASYNCLAYOUTINFLATER = LibraryGroup("androidx.asynclayoutinflater", false)
+ val BIOMETRIC = LibraryGroup("androidx.biometric", false)
+ val BROWSER = LibraryGroup("androidx.browser", false)
+ val BENCHMARK = LibraryGroup("androidx.benchmark", false)
+ val CAR = LibraryGroup("androidx.car", false)
+ val CARDVIEW = LibraryGroup("androidx.cardview", false)
+ val COLLECTION = LibraryGroup("androidx.collection", false)
+ val CONCURRENT = LibraryGroup("androidx.concurrent", false)
+ val CONTENTPAGER = LibraryGroup("androidx.contentpager", false)
+ val COORDINATORLAYOUT = LibraryGroup("androidx.coordinatorlayout", false)
+ val CORE = LibraryGroup("androidx.core", false)
+ val CURSORADAPTER = LibraryGroup("androidx.cursoradapter", false)
+ val CUSTOMVIEW = LibraryGroup("androidx.customview", false)
+ val DOCUMENTFILE = LibraryGroup("androidx.documentfile", false)
+ val DRAWERLAYOUT = LibraryGroup("androidx.drawerlayout", false)
+ val DYNAMICANIMATION = LibraryGroup("androidx.dynamicanimation", false)
+ val EMOJI = LibraryGroup("androidx.emoji", false)
+ val ENTERPRISE = LibraryGroup("androidx.enterprise", false)
+ val EXIFINTERFACE = LibraryGroup("androidx.exifinterface", false)
+ val FRAGMENT = LibraryGroup("androidx.fragment", false)
+ val GRIDLAYOUT = LibraryGroup("androidx.gridlayout", false)
+ val HEIFWRITER = LibraryGroup("androidx.heifwriter", false)
+ val INTERPOLATOR = LibraryGroup("androidx.interpolator", false)
+ val JETIFIER = LibraryGroup("com.android.tools.build.jetifier", false)
+ val LEANBACK = LibraryGroup("androidx.leanback", false)
+ val LEGACY = LibraryGroup("androidx.legacy", false)
+ val LIFECYCLE = LibraryGroup("androidx.lifecycle", false)
+ val LOADER = LibraryGroup("androidx.loader", false)
+ val LOCALBROADCASTMANAGER = LibraryGroup("androidx.localbroadcastmanager", false)
+ val MEDIA = LibraryGroup("androidx.media", false)
+ val MEDIA2 = LibraryGroup("androidx.media2", false)
+ val MEDIAROUTER = LibraryGroup("androidx.mediarouter", false)
+ val NAVIGATION = LibraryGroup("androidx.navigation", false)
+ val PAGING = LibraryGroup("androidx.paging", false)
+ val PALETTE = LibraryGroup("androidx.palette", false)
+ val PERCENTLAYOUT = LibraryGroup("androidx.percentlayout", false)
+ val PERSISTENCE = LibraryGroup("androidx.sqlite", false)
+ val PREFERENCE = LibraryGroup("androidx.preference", false)
+ val PRINT = LibraryGroup("androidx.print", false)
+ val RECOMMENDATION = LibraryGroup("androidx.recommendation", false)
+ val RECYCLERVIEW = LibraryGroup("androidx.recyclerview", false)
+ val SAVEDSTATE = LibraryGroup("androidx.savedstate", false)
+ val SECURITY = LibraryGroup("androidx.security", false)
+ val SHARETARGET = LibraryGroup("androidx.sharetarget", false)
+ val SLICE = LibraryGroup("androidx.slice", false)
+ val REMOTECALLBACK = LibraryGroup("androidx.remotecallback", false)
+ val ROOM = LibraryGroup("androidx.room", false)
+ val SLIDINGPANELAYOUT = LibraryGroup("androidx.slidingpanelayout", false)
+ val SWIPEREFRESHLAYOUT = LibraryGroup("androidx.swiperefreshlayout", false)
+ val TEXTCLASSIFIER = LibraryGroup("androidx.textclassifier", false)
+ val TRANSITION = LibraryGroup("androidx.transition", false)
+ val TVPROVIDER = LibraryGroup("androidx.tvprovider", false)
+ val VECTORDRAWABLE = LibraryGroup("androidx.vectordrawable", false)
+ val VERSIONEDPARCELABLE = LibraryGroup("androidx.versionedparcelable", false)
+ val VIEWPAGER = LibraryGroup("androidx.viewpager", false)
+ val VIEWPAGER2 = LibraryGroup("androidx.viewpager2", false)
+ val WEAR = LibraryGroup("androidx.wear", false)
+ val WEBKIT = LibraryGroup("androidx.webkit", false)
+ val WORKMANAGER = LibraryGroup("androidx.work", false)
}
+
+/**
+ * This object contains the library group, as well as whether libraries
+ * in this group are all required to have the same development version.
+ */
+data class LibraryGroup(val group: String = "unspecified", val requireSameVersion: Boolean = false)
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index f99bef2..81a2ae0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -20,26 +20,26 @@
* The list of versions codes of all the libraries in this project.
*/
object LibraryVersions {
- val ACTIVITY = Version("1.0.0-alpha05")
+ val ACTIVITY = Version("1.0.0-alpha06")
val ANIMATION = Version("1.0.0-alpha01")
- val ANIMATION_TESTING = Version("1.0.0")
- val ANNOTATION = Version("1.1.0-alpha02")
- val APPCOMPAT = Version("1.1.0-alpha03")
- val ARCH_CORE = Version("2.0.0")
+ val ANIMATION_TESTING = Version("1.1.0-alpha01")
+ val ANNOTATION = Version("1.1.0-beta01")
+ val APPCOMPAT = Version("1.1.0-alpha04")
+ val ARCH_CORE = Version("2.1.0-alpha01")
val ARCH_CORE_TESTING = ARCH_CORE
- val ARCH_RUNTIME = Version("2.0.1-alpha01")
+ val ARCH_RUNTIME = Version("2.1.0-alpha01")
val ASYNCLAYOUTINFLATER = Version("1.1.0-alpha01")
val BENCHMARK = Version("1.0.0-alpha01")
val BIOMETRIC = Version("1.0.0-alpha04")
- val BROWSER = Version("1.1.0-alpha02")
+ val BROWSER = Version("1.2.0-alpha01")
val CAR = Version("1.0.0-alpha7")
val CAR_CLUSTER = Version("1.0.0-alpha5")
val CAR_MODERATOR = Version("1.0.0-alpha1")
val CARDVIEW = Version("1.1.0-alpha01")
- val COLLECTION = Version("1.1.0-alpha03")
+ val COLLECTION = Version("1.1.0-beta01")
val CONTENTPAGER = Version("1.1.0-alpha01")
val COORDINATORLAYOUT = Version("1.1.0-alpha02")
- val CORE = Version("1.1.0-alpha05")
+ val CORE = Version("1.1.0-alpha06")
val CURSORADAPTER = Version("1.1.0-alpha01")
val CUSTOMVIEW = Version("1.1.0-alpha01")
val DOCUMENTFILE = Version("1.1.0-alpha01")
@@ -48,8 +48,8 @@
val DYNAMICANIMATION_KTX = Version("1.0.0-alpha02")
val EMOJI = Version("1.1.0-alpha01")
val ENTERPRISE = Version("1.0.0-alpha01")
- val EXIFINTERFACE = Version("1.1.0-alpha01")
- val FRAGMENT = Version("1.1.0-alpha05")
+ val EXIFINTERFACE = Version("1.1.0-alpha02")
+ val FRAGMENT = Version("1.1.0-alpha06")
val FUTURES = Version("1.0.0-alpha03")
val GRIDLAYOUT = Version("1.1.0-alpha01")
val HEIFWRITER = Version("1.1.0-alpha01")
@@ -59,28 +59,28 @@
val LEANBACK_PREFERENCE = Version("1.1.0-alpha02")
val LEGACY = Version("1.1.0-alpha01")
val LOCALBROADCASTMANAGER = Version("1.1.0-alpha02")
- val LIFECYCLE = Version("2.1.0-alpha03")
+ val LIFECYCLE = Version("2.1.0-alpha04")
val LIFECYCLES_COROUTINES = Version("1.0.0-alpha01")
val LIFECYCLES_SAVEDSTATE = Version("1.0.0-alpha01")
- val LOADER = Version("1.1.0-beta01")
- val MEDIA = Version("1.1.0-alpha02")
+ val LOADER = Version("1.1.0-beta02")
+ val MEDIA = Version("1.1.0-alpha03")
val MEDIA2 = Version("1.0.0-alpha05")
val MEDIA2_EXOPLAYER = Version("1.0.0-alpha02")
val MEDIA2_WIDGET = Version("1.0.0-alpha07")
- val MEDIAROUTER = Version("1.1.0-alpha02")
- val NAVIGATION = Version("2.0.0-rc02")
+ val MEDIAROUTER = Version("1.1.0-alpha03")
+ val NAVIGATION = Version("2.1.0-alpha02")
val NAVIGATION_TESTING = Version("1.0.0-alpha08") // Unpublished
val PAGING = Version("2.2.0-alpha01")
val PALETTE = Version("1.1.0-alpha01")
val PRINT = Version("1.1.0-alpha01")
val PERCENTLAYOUT = Version("1.1.0-alpha01")
val PERSISTENCE = Version("2.0.1")
- val PREFERENCE = Version("1.1.0-alpha04")
+ val PREFERENCE = Version("1.1.0-alpha05")
val RECOMMENDATION = Version("1.1.0-alpha01")
- val RECYCLERVIEW = Version("1.1.0-alpha03")
+ val RECYCLERVIEW = Version("1.1.0-alpha04")
val REMOTECALLBACK = Version("1.0.0-alpha02")
- val ROOM = Version("2.1.0-alpha05")
- val SAVEDSTATE = Version("1.0.0-alpha02")
+ val ROOM = Version("2.1.0-alpha06")
+ val SAVEDSTATE = Version("1.0.0-alpha03")
val SECURITY = Version("1.0.0-alpha01")
val SHARETARGET = Version("1.0.0-alpha01")
val SLICE = Version("1.1.0-alpha01")
@@ -89,14 +89,14 @@
val SLIDINGPANELAYOUT = Version("1.1.0-alpha01")
val SWIPE_REFRESH_LAYOUT = Version("1.1.0-alpha01")
val TEXTCLASSIFIER = Version("1.0.0-alpha03")
- val TRANSITION = Version("1.1.0-alpha02")
+ val TRANSITION = Version("1.1.0-beta01")
val TVPROVIDER = Version("1.1.0-alpha01")
val VECTORDRAWABLE = Version("1.1.0-alpha02")
val VECTORDRAWABLE_ANIMATED = Version("1.1.0-alpha02")
val VERSIONED_PARCELABLE = Version("1.1.0-alpha02")
val VIEWPAGER = Version("1.1.0-alpha01")
- val VIEWPAGER2 = Version("1.0.0-alpha02")
+ val VIEWPAGER2 = Version("1.0.0-alpha03")
val WEAR = Version("1.1.0-alpha01")
val WEBKIT = Version("1.1.0-alpha01")
- val WORKMANAGER = Version("1.0.0")
+ val WORKMANAGER = Version("2.0.0")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/LockClocksTask.kt b/buildSrc/src/main/kotlin/androidx/build/LockClocksTask.kt
index 1123bf7..23b2254 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LockClocksTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LockClocksTask.kt
@@ -75,6 +75,7 @@
runAdb(arrayOf("shell", "rm", dest), "Failed to remove clock locking script")
}
}
+
open class UnlockClocksTask : ClockTask() {
init {
description = "unlocks clocks of device by rebooting"
diff --git a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 9451043..edb053b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -30,7 +30,7 @@
fun Project.configureMavenArtifactUpload(extension: SupportLibraryExtension) {
afterEvaluate {
if (extension.publish) {
- val mavenGroup = extension.mavenGroup
+ val mavenGroup = extension.mavenGroup?.group
if (mavenGroup == null) {
throw Exception("You must specify mavenGroup for $name project")
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 9a337e7..a268043 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -25,25 +25,24 @@
import androidx.build.Strategy.TipOfTree
val RELEASE_RULE = docsRules("public", false) {
- prebuilts(LibraryGroups.ACTIVITY, "1.0.0-alpha04")
- prebuilts(LibraryGroups.ANNOTATION, "1.1.0-alpha01")
- ignore(LibraryGroups.APPCOMPAT, "appcompat-resources")
- prebuilts(LibraryGroups.APPCOMPAT, "1.1.0-alpha02")
+ prebuilts(LibraryGroups.ACTIVITY, "1.0.0-alpha05")
+ prebuilts(LibraryGroups.ANNOTATION, "1.1.0-alpha02")
+ prebuilts(LibraryGroups.APPCOMPAT, "1.1.0-alpha03")
prebuilts(LibraryGroups.ARCH_CORE, "2.0.0")
prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, "1.0.0")
prebuilts(LibraryGroups.BIOMETRIC, "biometric", "1.0.0-alpha03")
prebuilts(LibraryGroups.BROWSER, "1.0.0")
- ignore(LibraryGroups.CAR, "car-moderator")
+ ignore(LibraryGroups.CAR.group, "car-moderator")
prebuilts(LibraryGroups.CAR, "car-cluster", "1.0.0-alpha5")
- prebuilts(LibraryGroups.CAR, "car", "1.0.0-alpha5")
+ prebuilts(LibraryGroups.CAR, "car", "1.0.0-alpha7")
.addStubs("car/stubs/android.car.jar")
prebuilts(LibraryGroups.CARDVIEW, "1.0.0")
- prebuilts(LibraryGroups.COLLECTION, "1.1.0-alpha02")
+ prebuilts(LibraryGroups.COLLECTION, "1.1.0-alpha03")
prebuilts(LibraryGroups.CONCURRENT, "1.0.0-alpha03")
prebuilts(LibraryGroups.CONTENTPAGER, "1.0.0")
prebuilts(LibraryGroups.COORDINATORLAYOUT, "1.1.0-alpha01")
- prebuilts(LibraryGroups.CORE, "core", "1.1.0-alpha04")
- prebuilts(LibraryGroups.CORE, "core-ktx", "1.1.0-alpha04")
+ prebuilts(LibraryGroups.CORE, "core", "1.1.0-alpha05")
+ prebuilts(LibraryGroups.CORE, "core-ktx", "1.1.0-alpha05")
prebuilts(LibraryGroups.CURSORADAPTER, "1.0.0")
prebuilts(LibraryGroups.CUSTOMVIEW, "1.0.0")
prebuilts(LibraryGroups.DOCUMENTFILE, "1.0.0")
@@ -52,71 +51,77 @@
prebuilts(LibraryGroups.DYNAMICANIMATION, "1.0.0")
prebuilts(LibraryGroups.EMOJI, "1.0.0")
prebuilts(LibraryGroups.ENTERPRISE, "1.0.0-alpha01")
- prebuilts(LibraryGroups.EXIFINTERFACE, "1.0.0")
- prebuilts(LibraryGroups.FRAGMENT, "1.1.0-alpha04")
+ prebuilts(LibraryGroups.EXIFINTERFACE, "1.1.0-alpha01")
+ prebuilts(LibraryGroups.FRAGMENT, "1.1.0-alpha05")
prebuilts(LibraryGroups.GRIDLAYOUT, "1.0.0")
prebuilts(LibraryGroups.HEIFWRITER, "1.0.0")
prebuilts(LibraryGroups.INTERPOLATOR, "1.0.0")
prebuilts(LibraryGroups.LEANBACK, "1.1.0-alpha01")
prebuilts(LibraryGroups.LEGACY, "1.0.0")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-savedstate-core")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-savedstate-fragment")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-fragment")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-livedata-ktx")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-livedata-core-ktx")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-compiler")
- ignore(LibraryGroups.LIFECYCLE, "lifecycle-common-eap")
- prebuilts(LibraryGroups.LIFECYCLE, "2.1.0-alpha02")
- prebuilts(LibraryGroups.LOADER, "1.1.0-alpha01")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-savedstate-core")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-savedstate-fragment")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-viewmodel-fragment")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-livedata-ktx")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-livedata-core-ktx")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-compiler")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-common-eap")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-runtime-eap")
+ ignore(LibraryGroups.LIFECYCLE.group, "lifecycle-livedata-eap")
+ prebuilts(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-savedstate", "1.0.0-alpha01")
+ prebuilts(LibraryGroups.LIFECYCLE, "2.1.0-alpha03")
+ prebuilts(LibraryGroups.LOADER, "1.1.0-beta01")
prebuilts(LibraryGroups.LOCALBROADCASTMANAGER, "1.1.0-alpha01")
- prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-alpha01")
+ prebuilts(LibraryGroups.MEDIA, "media", "1.1.0-alpha03")
// TODO: Rename media-widget to media2-widget after 1.0.0-alpha06
prebuilts(LibraryGroups.MEDIA, "media-widget", "1.0.0-alpha06")
- ignore(LibraryGroups.MEDIA2, "media2-widget")
- ignore(LibraryGroups.MEDIA2, "media2-exoplayer")
- prebuilts(LibraryGroups.MEDIA2, "1.0.0-alpha03")
- prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-alpha01")
- ignore(LibraryGroups.NAVIGATION, "navigation-testing")
- prebuilts(LibraryGroups.NAVIGATION, "2.0.0-rc02")
+ ignore(LibraryGroups.MEDIA2.group, "media2-widget")
+ ignore(LibraryGroups.MEDIA2.group, "media2-exoplayer")
+ prebuilts(LibraryGroups.MEDIA2, "1.0.0-alpha04")
+ prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-alpha02")
+ ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
+ prebuilts(LibraryGroups.NAVIGATION, "2.1.0-alpha01")
prebuilts(LibraryGroups.PAGING, "2.1.0")
prebuilts(LibraryGroups.PALETTE, "1.0.0")
prebuilts(LibraryGroups.PERCENTLAYOUT, "1.0.0")
prebuilts(LibraryGroups.PERSISTENCE, "2.0.0")
- prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.1.0-alpha03")
- prebuilts(LibraryGroups.PREFERENCE, "1.1.0-alpha03")
+ prebuilts(LibraryGroups.PREFERENCE, "preference-ktx", "1.1.0-alpha04")
+ prebuilts(LibraryGroups.PREFERENCE, "1.1.0-alpha04")
prebuilts(LibraryGroups.PRINT, "1.0.0")
prebuilts(LibraryGroups.RECOMMENDATION, "1.0.0")
- prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha02")
+ prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview", "1.1.0-alpha03")
prebuilts(LibraryGroups.RECYCLERVIEW, "recyclerview-selection", "1.1.0-alpha01")
prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha01")
- prebuilts(LibraryGroups.ROOM, "2.1.0-alpha05")
+ ignore(LibraryGroups.ROOM.group, "room-common-java8")
+ prebuilts(LibraryGroups.ROOM, "2.1.0-alpha06")
+ prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-alpha02")
+ prebuilts(LibraryGroups.SHARETARGET, "1.0.0-alpha01")
prebuilts(LibraryGroups.SLICE, "slice-builders", "1.0.0")
prebuilts(LibraryGroups.SLICE, "slice-builders-ktx", "1.0.0-alpha6")
prebuilts(LibraryGroups.SLICE, "slice-core", "1.0.0")
// TODO: land prebuilts
-// prebuilts(LibraryGroups.SLICE, "slice-test", "1.0.0")
- ignore(LibraryGroups.SLICE, "slice-test")
+// prebuilts(LibraryGroups.SLICE.group, "slice-test", "1.0.0")
+ ignore(LibraryGroups.SLICE.group, "slice-test")
prebuilts(LibraryGroups.SLICE, "slice-view", "1.0.0")
prebuilts(LibraryGroups.SLIDINGPANELAYOUT, "1.0.0")
prebuilts(LibraryGroups.SWIPEREFRESHLAYOUT, "1.1.0-alpha01")
prebuilts(LibraryGroups.TEXTCLASSIFIER, "1.0.0-alpha02")
- prebuilts(LibraryGroups.TRANSITION, "1.1.0-alpha01")
+ prebuilts(LibraryGroups.TRANSITION, "1.1.0-alpha02")
prebuilts(LibraryGroups.TVPROVIDER, "1.0.0")
prebuilts(LibraryGroups.VECTORDRAWABLE, "1.1.0-alpha01")
prebuilts(LibraryGroups.VECTORDRAWABLE, "vectordrawable-animated", "1.1.0-alpha01")
+ prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "1.1.0-alpha02")
prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
- prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-alpha01")
+ prebuilts(LibraryGroups.VIEWPAGER2, "1.0.0-alpha02")
prebuilts(LibraryGroups.WEAR, "1.0.0")
.addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
prebuilts(LibraryGroups.WEBKIT, "1.0.0")
- prebuilts(LibraryGroups.WORKMANAGER, "1.0.0-rc02")
+ prebuilts(LibraryGroups.WORKMANAGER, "2.0.0")
default(Ignore)
}
val TIP_OF_TREE = docsRules("tipOfTree", true) {
// TODO: remove once we'll figure out our strategy about it
- ignore(LibraryGroups.CONCURRENT)
+ ignore(LibraryGroups.CONCURRENT.group)
default(TipOfTree)
}
@@ -155,23 +160,24 @@
* docs for a project with the given [groupName] and [name] will be built from a prebuilt with
* the given [version].
*/
- fun prebuilts(groupName: String, moduleName: String, version: String): Prebuilts {
+ fun prebuilts(libraryGroup: LibraryGroup, moduleName: String, version: String): Prebuilts {
val strategy = Prebuilts(Version(version))
- rules.add(DocsRule(Exact(groupName, moduleName), strategy))
+ rules.add(DocsRule(Exact(libraryGroup.group, moduleName), strategy))
return strategy
}
/**
* docs for projects within [groupName] will be built from prebuilts with the given [version]
*/
- fun prebuilts(groupName: String, version: String) = prebuilts(groupName, Version(version))
+ fun prebuilts(libraryGroup: LibraryGroup, version: String) =
+ prebuilts(libraryGroup, Version(version))
/**
* docs for projects within [groupName] will be built from prebuilts with the given [version]
*/
- fun prebuilts(groupName: String, version: Version): Prebuilts {
+ fun prebuilts(libraryGroup: LibraryGroup, version: Version): Prebuilts {
val strategy = Prebuilts(version)
- rules.add(DocsRule(Group(groupName), strategy))
+ rules.add(DocsRule(Group(libraryGroup.group), strategy))
return strategy
}
@@ -245,7 +251,7 @@
override fun toString() = "Prebuilts(\"$version\")"
fun dependency(extension: SupportLibraryExtension): String {
- return "${extension.mavenGroup}:${extension.project.name}:$version"
+ return "${extension.mavenGroup?.group}:${extension.project.name}:$version"
}
}
}
@@ -253,7 +259,7 @@
class PublishDocsRules(val name: String, val offline: Boolean, private val rules: List<DocsRule>) {
fun resolve(extension: SupportLibraryExtension): DocsRule? {
val mavenGroup = extension.mavenGroup
- return if (mavenGroup == null) null else resolve(mavenGroup, extension.project.name)
+ return if (mavenGroup == null) null else resolve(mavenGroup.group, extension.project.name)
}
fun resolve(groupName: String, moduleName: String): DocsRule {
diff --git a/buildSrc/src/main/kotlin/androidx/build/Release.kt b/buildSrc/src/main/kotlin/androidx/build/Release.kt
index 8ef06ab..911c0ad 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Release.kt
@@ -197,7 +197,7 @@
" because publish is false!"
)
}
- val mavenGroup = extension.mavenGroup ?: throw IllegalArgumentException(
+ val mavenGroup = extension.mavenGroup?.group ?: throw IllegalArgumentException(
"Cannot register a project to release if it does not have a mavenGroup set up"
)
val version = extension.mavenVersion ?: throw IllegalArgumentException(
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
index 28f5356..24a70ca 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportAndroidLibraryPlugin.kt
@@ -57,7 +57,7 @@
}
if (supportLibraryExtension.publish) {
project.extra.set("publish", true)
- project.addToProjectMap(supportLibraryExtension.mavenGroup)
+ project.addToProjectMap(supportLibraryExtension.mavenGroup?.group)
}
val library = project.extensions.findByType(LibraryExtension::class.java)
?: return@afterEvaluate
@@ -128,7 +128,7 @@
}
}
- val group = supportLibraryExtension.mavenGroup
+ val group = supportLibraryExtension.mavenGroup?.group
// NOTE: we assume here that all benchmarks have package name $groupname.benchmark.test
val aotCompile = "cmd package compile -m speed -f $group.benchmark.test"
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
index f8299a0..65a4b95 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportJavaLibraryPlugin.kt
@@ -44,7 +44,7 @@
}
if (supportLibraryExtension.publish) {
project.extra.set("publish", true)
- project.addToProjectMap(supportLibraryExtension.mavenGroup)
+ project.addToProjectMap(supportLibraryExtension.mavenGroup?.group)
}
Dokka.registerJavaProject(project, supportLibraryExtension)
if (supportLibraryExtension.useMetalava) {
diff --git a/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt b/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
index dd00bb7..cf8b798 100644
--- a/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/SupportLibraryExtension.kt
@@ -29,7 +29,7 @@
var mavenVersion: Version? by Delegates.observable<Version?>(null) { _, _, new: Version? ->
project.version = new?.toString()
}
- var mavenGroup: String? = null
+ var mavenGroup: LibraryGroup? = null
var description: String? = null
var inceptionYear: String? = null
var url = SUPPORT_URL
@@ -66,6 +66,7 @@
"https://developer.android.com/topic/libraries/architecture/index.html"
@JvmField
val SUPPORT_URL = "http://developer.android.com/tools/extras/support-library.html"
+ val DEFAULT_UNSPECIFIED_VERSION = "unspecified"
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/UpdateResourceApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/UpdateResourceApiTask.kt
index 33af461..c616a64 100644
--- a/buildSrc/src/main/kotlin/androidx/build/UpdateResourceApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/UpdateResourceApiTask.kt
@@ -4,6 +4,7 @@
import org.gradle.api.GradleException
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.util.SortedSet
@@ -21,15 +22,18 @@
@Optional
var newApiFile: File? = null
+ @OutputFile
+ var destApiFile: File? = null
+
@TaskAction
fun UpdateResourceApi() {
+ val destApiFile = checkNotNull(destApiFile) { "destApiFile not set" }
if (oldApiFile == null || !!oldApiFile!!.exists()) {
if (newApiFile != null && newApiFile!!.exists()) {
- newApiFile?.copyTo(File(project.projectDir,
- "api/res-${project.version}.txt"), true, 8)
+ newApiFile?.copyTo(destApiFile, true, 8)
return
} else {
- File(project.projectDir, "api/res-${project.version}.txt").createNewFile()
+ destApiFile.createNewFile()
return
}
}
@@ -68,7 +72,7 @@
}
newResourceApi.addAll(newResourceApi)
val sortedNewResourceApi: SortedSet<String> = newResourceApi.toSortedSet()
- File(project.projectDir, "api/res-${project.version}.txt").bufferedWriter().use { out ->
+ destApiFile.bufferedWriter().use { out ->
sortedNewResourceApi.forEach {
out.write(it)
out.newLine()
diff --git a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 2743b7b..54dfb097 100644
--- a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -56,9 +56,11 @@
// If the version is unspecified then treat as an alpha version. If the depending project's
// version is unspecified then it won't matter, and if the dependency's version is
// unspecified then any non alpha project won't be able to depend on it to ensure safety.
- val projectVersionExtra = if (project.version == "unspecified") "-alpha01"
+ val projectVersionExtra = if (project.version ==
+ SupportLibraryExtension.DEFAULT_UNSPECIFIED_VERSION) "-alpha01"
else Version(project.version.toString()).extra ?: ""
- val dependencyVersionExtra = if (dependency.version!! == "unspecified") "-alpha01" else
+ val dependencyVersionExtra = if (dependency.version!! ==
+ SupportLibraryExtension.DEFAULT_UNSPECIFIED_VERSION) "-alpha01" else
Version(dependency.version!!).extra ?: ""
val projectReleasePhase = releasePhase(projectVersionExtra)
if (projectReleasePhase < 0) {
diff --git a/buildSrc/src/main/kotlin/androidx/build/Version.kt b/buildSrc/src/main/kotlin/androidx/build/Version.kt
index 3120499..41d17d7 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Version.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Version.kt
@@ -44,7 +44,10 @@
fun isAlpha(): Boolean = extra?.toLowerCase()?.startsWith("-alpha") ?: false
- fun isFinalApi(): Boolean = isPatch() || !(isSnapshot() || isAlpha())
+ fun isBeta(): Boolean = extra?.toLowerCase()?.startsWith("-beta") ?: false
+
+ // Returns whether the API surface is allowed to change within the current revision (see go/androidx/versioning for policy definition)
+ fun isFinalApi(): Boolean = !(isSnapshot() || isAlpha())
override fun compareTo(other: Version) = compareValuesBy(this, other,
{ it.major },
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiLocation.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiLocation.kt
index e90c6d1..5311c37 100644
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiLocation.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiLocation.kt
@@ -25,7 +25,9 @@
// file specifying the public API of the library
val publicApiFile: File,
// file specifying the restricted API (marked by the RestrictTo annotation) of the library
- val restrictedApiFile: File
+ val restrictedApiFile: File,
+ // file specifying the API of the resources
+ val resourceFile: File
) {
fun files() = listOf(publicApiFile, restrictedApiFile)
@@ -40,7 +42,7 @@
companion object {
fun fromPublicApiFile(f: File): ApiLocation {
- return ApiLocation(f, File(f.parentFile, "restricted_" + f.name))
+ return ApiLocation(f, File(f.parentFile, "restricted_" + f.name), File(f.parentFile, "res-" + f.name))
}
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
index b770e1a..99b486b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/CheckApi.kt
@@ -347,8 +347,17 @@
* @return the API file of this version
*/
private fun getApiFile(rootDir: File, version: Version): File {
+ if (version.patch != 0 && (version.isAlpha() || version.isBeta())) {
+ val suggestedVersion = Version("${version.major}.${version.minor}.${version.patch}-rc01")
+ throw GradleException("Illegal version ${version} . It is not allowed to have a nonzero patch number and be alpha or beta at the same time.\nDid you mean ${suggestedVersion}?")
+ }
+
+ var extra = ""
+ if (version.patch == 0 && version.extra != null) {
+ extra = version.extra
+ }
val apiDir = File(rootDir, "api")
- return File(apiDir, "${version.major}.${version.minor}.0${version.extra ?: ""}.txt")
+ return File(apiDir, "${version.major}.${version.minor}.0${extra}.txt")
}
/**
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index fd0ae44..6d4dac2 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -37,15 +37,16 @@
const val JAVAPOET = "com.squareup:javapoet:1.8.0"
const val JSR250 = "javax.annotation:javax.annotation-api:1.2"
const val JUNIT = "junit:junit:4.12"
-const val KOTLINPOET = "com.squareup:kotlinpoet:1.0.0"
+const val KOTLINPOET = "com.squareup:kotlinpoet:1.1.0"
const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:1.3.0"
const val KOTLIN_METADATA = "me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0"
const val KOTLIN_METADATA_JVM = "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.0.5"
-const val KOTLIN_COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
-const val KOTLIN_COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0"
-const val KOTLIN_COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.1.0"
+const val KOTLIN_COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
+const val KOTLIN_COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
+const val KOTLIN_COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.1.1"
const val LEAKCANARY_INSTRUMENTATION =
"com.squareup.leakcanary:leakcanary-android-instrumentation:1.6.2"
+const val MATERIAL = "com.google.android.material:material:1.0.0"
const val MOCKITO_CORE = "org.mockito:mockito-core:2.19.0"
const val MULTIDEX = "androidx.multidex:multidex:2.0.0"
const val NULLAWAY = "com.uber.nullaway:nullaway:0.3.7"
@@ -59,27 +60,14 @@
const val TEST_EXT_KTX = "androidx.test.ext:junit-ktx:1.1.0"
const val TEST_UIAUTOMATOR = "androidx.test.uiautomator:uiautomator:2.2.0"
const val TRUTH = "com.google.truth:truth:0.42"
-/**
- * this Xerial version is newer than we want but we need it to fix
- * https://github.com/xerial/sqlite-jdbc/issues/97
- * https://github.com/xerial/sqlite-jdbc/issues/267
- */
-const val XERIAL = "org.xerial:sqlite-jdbc:3.20.1"
+const val XERIAL = "org.xerial:sqlite-jdbc:3.25.2"
const val XPP3 = "xpp3:xpp3:1.1.4c"
const val XMLPULL = "xmlpull:xmlpull:1.1.3.1"
-private const val NAV_SUPPORT_VERSION = "1.0.0"
-const val NAV_SUPPORT_COMPAT = "androidx.core:core:$NAV_SUPPORT_VERSION"
-const val NAV_SUPPORT_CORE_UTILS = "androidx.legacy:legacy-support-core-utils:$NAV_SUPPORT_VERSION"
-const val NAV_SUPPORT_DESIGN = "com.google.android.material:material:$NAV_SUPPORT_VERSION"
-const val NAV_SUPPORT_FRAGMENTS = "androidx.fragment:fragment:$NAV_SUPPORT_VERSION"
-const val NAV_SUPPORT_COLLECTIONS = "androidx.collection:collection:$NAV_SUPPORT_VERSION"
-
private const val SUPPORT_VERSION = "1.0.0"
const val SUPPORT_ANNOTATIONS = "androidx.annotation:annotation:$SUPPORT_VERSION"
const val SUPPORT_APPCOMPAT = "androidx.appcompat:appcompat:$SUPPORT_VERSION"
const val SUPPORT_CORE_UTILS = "androidx.legacy:legacy-support-core-utils:$SUPPORT_VERSION"
-const val SUPPORT_DESIGN = "com.google.android.material:material:1.0.0@aar"
const val SUPPORT_FRAGMENTS = "androidx.fragment:fragment:$SUPPORT_VERSION"
const val SUPPORT_RECYCLERVIEW = "androidx.recyclerview:recyclerview:$SUPPORT_VERSION"
@@ -99,9 +87,9 @@
"androidx.lifecycle:lifecycle-livedata:2.0.0"
const val ARCH_LIFECYCLE_VIEWMODEL = "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
const val ARCH_LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:2.0.0"
-const val ARCH_CORE_COMMON = "androidx.arch.core:core-common:2.0.0@jar"
-const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.0"
-const val ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.0"
+const val ARCH_CORE_COMMON = "androidx.arch.core:core-common:2.0.1@jar"
+const val ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.1"
+const val ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.1"
const val SAFE_ARGS_ANDROID_GRADLE_PLUGIN = "com.android.tools.build:gradle:3.3.0"
const val SAFE_ARGS_KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20"
@@ -112,5 +100,9 @@
const val ARCH_ROOM_RUNTIME = "androidx.room:room-runtime:2.0.0"
const val ARCH_ROOM_COMPILER = "androidx.room:room-compiler:2.0.0"
const val ARCH_ROOM_RXJAVA = "androidx.room:room-rxjava2:2.0.0"
+const val ARCH_ROOM_TESTING = "androidx.room:room-testing:2.0.0"
+
+const val WORK_ARCH_CORE_RUNTIME = "androidx.arch.core:core-runtime:2.0.0"
+const val WORK_ARCH_CORE_TESTING = "androidx.arch.core:core-testing:2.0.0"
const val ROBOLECTRIC = "org.robolectric:robolectric:4.1"
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 7c3cf36..fdbc8a6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -218,16 +218,31 @@
* With param changedProjects, finds only directly changed modules
*
* If it cannot determine the containing module for a file (e.g. buildSrc or root), it
- * defaults to all projects unless [ignoreUnknownProjects] is set to true.
+ * defaults to all projects unless [ignoreUnknownProjects] is set to true. However,
+ * with param changedProjects, it only returns the dumb-test (see companion object below).
+ * This is because we run all tests including @large on the changed set. So when we must
+ * build all, we only want to run @small and @medium tests in the test runner for
+ * DEPENDENT_PROJECTS.
*/
private fun findLocallyAffectedProjects(): Set<Project> {
val lastMergeSha = git.findPreviousMergeCL() ?: return allProjects
val changedFiles = git.findChangedFilesSince(
sha = lastMergeSha,
includeUncommitted = true)
+
+ val alwaysBuild = rootProject.subprojects.filter { project ->
+ ALWAYS_BUILD.any {
+ project.name.contains(it)
+ }
+ }.toSet()
+
if (changedFiles.isEmpty()) {
logger?.info("Cannot find any changed files after last merge, will run all")
- return allProjects
+ return when (projectSubset) {
+ ProjectSubset.DEPENDENT_PROJECTS -> allProjects
+ ProjectSubset.CHANGED_PROJECTS -> alwaysBuild
+ ProjectSubset.ALL_AFFECTED_PROJECTS -> allProjects
+ }
}
val containingProjects = changedFiles
.map(::findContainingProject)
@@ -246,13 +261,12 @@
${expandToDependents(containingProjects.filterNotNull())}
""".trimIndent()
)
- return allProjects
- }
- val alwaysBuild = rootProject.subprojects.filter { project ->
- ALWAYS_BUILD.any {
- project.name.contains(it)
+ return when (projectSubset) {
+ ProjectSubset.DEPENDENT_PROJECTS -> allProjects
+ ProjectSubset.CHANGED_PROJECTS -> alwaysBuild
+ ProjectSubset.ALL_AFFECTED_PROJECTS -> allProjects
}
- }.toSet()
+ }
return alwaysBuild + when (projectSubset) {
ProjectSubset.DEPENDENT_PROJECTS
@@ -276,8 +290,8 @@
}
companion object {
- // dummy test to ensure no failure due to "no instrumentation. See b/112645580
- // and b/126377106
+ // dummy test to ensure no failure due to "no instrumentation. We can eventually remove
+ // if we resolve b/127819369
private val ALWAYS_BUILD = setOf("dumb-test")
}
}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
index 1ebba71..eda7384 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/Dokka.kt
@@ -25,6 +25,7 @@
import androidx.build.SupportLibraryExtension
import com.android.build.gradle.LibraryExtension
import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.apply
import org.jetbrains.dokka.gradle.DokkaAndroidPlugin
@@ -32,17 +33,28 @@
import org.jetbrains.dokka.gradle.PackageOptions
object Dokka {
+ fun generatorTaskNameForType(docsType: String): String {
+ return "dokka${docsType}Docs"
+ }
+ fun archiveTaskNameForType(docsType: String): String {
+ return "dist${docsType}DokkaDocs"
+ }
fun createDocsTask(
- taskName: String,
+ docsType: String, // "public" or "tipOfTree"
project: Project,
- hiddenPackages: List<String>,
- archiveTaskName: String
+ hiddenPackages: List<String>
) {
+ val taskName = generatorTaskNameForType(docsType)
+ val archiveTaskName = archiveTaskNameForType(docsType)
project.apply<DokkaAndroidPlugin>()
+ // We don't use the `dokka` task, but it normally appears in `./gradlew tasks`
+ // so replace it with a new task that doesn't show up and doesn't do anything
+ project.tasks.replace("dokka")
if (project.name != "support" && project.name != "docs-runner") {
throw Exception("Illegal project passed to createDocsTask: " + project.name)
}
val docsTask = project.tasks.create(taskName, DokkaAndroidTask::class.java) { docsTask ->
+ docsTask.description = "Generates ${docsType} Kotlin documentation in the style of d.android.com"
docsTask.moduleName = project.name
docsTask.outputDirectory = File(project.buildDir, taskName).absolutePath
docsTask.outputFormat = "dac"
@@ -59,14 +71,15 @@
project.tasks.create(archiveTaskName, Zip::class.java) { zipTask ->
zipTask.dependsOn(docsTask)
- zipTask.description = "Generates documentation artifact for pushing to " +
- "developer.android.com"
zipTask.from(docsTask.outputDirectory) { copySpec ->
copySpec.into("reference/kotlin")
}
zipTask.baseName = taskName
zipTask.version = getBuildId()
zipTask.destinationDir = project.getDistributionDirectory()
+ zipTask.description = "Zips ${docsType} Kotlin documentation (generated via "+
+ "Dokka in the style of d.android.com) into ${zipTask.archivePath}"
+ zipTask.group = JavaBasePlugin.DOCUMENTATION_GROUP
}
}
@@ -75,7 +88,10 @@
library: LibraryExtension,
extension: SupportLibraryExtension
) {
- DiffAndDocs.get(project).registerPrebuilts(extension)
+ if (project.name != "docs-runner") {
+ DiffAndDocs.get(project).registerAndroidProject(project, library, extension)
+ }
+
DokkaPublicDocs.registerProject(project, extension)
DokkaSourceDocs.registerAndroidProject(project, library, extension)
}
@@ -84,7 +100,9 @@
project: Project,
extension: SupportLibraryExtension
) {
- DiffAndDocs.get(project).registerPrebuilts(extension)
+ if (project.name != "docs-runner") {
+ DiffAndDocs.get(project).registerJavaProject(project, extension)
+ }
DokkaPublicDocs.registerProject(project, extension)
DokkaSourceDocs.registerJavaProject(project, extension)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
index 7ee82ed..769ba0f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaPublicDocs.kt
@@ -15,7 +15,7 @@
*/
// This file sets up building public docs from source jars
-// TODO: after DiffAndDocs and Doclava are fully obsoleted and removed, rename this from DokkaPublicDocs to just PublicDocs
+// TODO: after DiffAndDocs and Doclava are fully obsoleted and removed, rename this from DokkaPublicDocs to just publicDocs
package androidx.build.dokka
import java.io.File
@@ -35,8 +35,8 @@
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaPublicDocs {
- public val ARCHIVE_TASK_NAME: String = "distPublicDokkaDocs"
- private val RUNNER_TASK_NAME = "dokkaPublicDocs"
+ public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("Public")
+ private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("Public")
private val UNZIP_DEPS_TASK_NAME = "unzipDokkaPublicDocsDeps"
public val hiddenPackages = listOf(
@@ -77,10 +77,9 @@
@Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
val tasks = this
if (tasks.findByName(RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask(RUNNER_TASK_NAME,
+ Dokka.createDocsTask("Public",
runnerProject,
- hiddenPackages,
- ARCHIVE_TASK_NAME)
+ hiddenPackages)
val docsTask = runnerProject.tasks.getByName(RUNNER_TASK_NAME) as DokkaTask
tasks.create(UNZIP_DEPS_TASK_NAME, LocateJarsTask::class.java) { unzipTask ->
unzipTask.doLast {
@@ -149,7 +148,7 @@
configuration.resolvedConfiguration.resolvedArtifacts
} catch (e: ResolveException) {
runnerProject.logger.error("DokkaPublicDocs failed to find prebuilts for $mavenId. " +
- "specified in PublichDocsRules.kt ." +
+ "specified in publichDocsRules.kt ." +
"You should either add a prebuilt sources jar, " +
"or add an overriding \"ignore\" rule into PublishDocsRules.kt")
throw e
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
index 1e9a29b..d4be41a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/DokkaSourceDocs.kt
@@ -29,8 +29,8 @@
import org.jetbrains.dokka.gradle.DokkaTask
object DokkaSourceDocs {
- private val RUNNER_TASK_NAME = "dokkaTipOfTreeDocs"
- public val ARCHIVE_TASK_NAME: String = "distTipOfTreeDokkaDocs"
+ private val RUNNER_TASK_NAME = Dokka.generatorTaskNameForType("TipOfTree")
+ public val ARCHIVE_TASK_NAME: String = Dokka.archiveTaskNameForType("TipOfTree")
// TODO(b/72330103) make "generateDocs" be the only archive task once Doclava is fully removed
private val ALTERNATE_ARCHIVE_TASK_NAME: String = "generateDocs"
@@ -52,7 +52,7 @@
@Synchronized fun TaskContainer.getOrCreateDocsTask(runnerProject: Project): DokkaTask {
val tasks = this
if (tasks.findByName(DokkaSourceDocs.RUNNER_TASK_NAME) == null) {
- Dokka.createDocsTask(RUNNER_TASK_NAME, runnerProject, hiddenPackages, ARCHIVE_TASK_NAME)
+ Dokka.createDocsTask("TipOfTree", runnerProject, hiddenPackages)
if (tasks.findByName(DokkaSourceDocs.ALTERNATE_ARCHIVE_TASK_NAME) == null) {
tasks.create(ALTERNATE_ARCHIVE_TASK_NAME)
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
index 69a7259..bbddb85 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
@@ -73,19 +73,22 @@
}
fun copy(source: File, dest: File, permitOverwriting: Boolean, logger: Logger) {
- if (!permitOverwriting) {
- // determine whether file contents is changing
- if (dest.exists() && source.readText() != dest.readText()) {
- val message = "Modifying the API definition for a previously released artifact having a final API version (version not ending in '-alpha') is not allowed.\n\n" +
+ val overwriting = (dest.exists() && source.readText() != dest.readText())
+ val changing = overwriting || !dest.exists()
+ if (changing) {
+ if (overwriting && !permitOverwriting) {
+ val message = "Modifying the API definition for a previously released artifact " +
+ "having a final API version (version not ending in '-alpha') is not " +
+ "allowed.\n\n" +
"Previously declared definition is $dest\n" +
"Current generated definition is $source\n\n" +
"Did you mean to increment the library version first?\n\n" +
- "If you have reason to overwrite the API files for the previous release anyway, you can run `./gradlew updateApi -Pforce` to ignore this message"
+ "If you have reason to overwrite the API files for the previous release " +
+ "anyway, you can run `./gradlew updateApi -Pforce` to ignore this message"
throw GradleException(message)
}
+ Files.copy(source, dest)
+ logger.lifecycle("Copied $source to $dest")
}
-
- Files.copy(source, dest)
- logger.lifecycle("Copied $source to $dest")
}
}
diff --git a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt b/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
index 514f5c6..2b42700 100644
--- a/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
+++ b/buildSrc/src/test/kotlin/androidx/build/dependencyTracker/AffectedModuleDetectorImplTest.kt
@@ -167,7 +167,7 @@
changedFiles = emptyList())
)
MatcherAssert.assertThat(detector.affectedProjects, CoreMatchers.`is`(
- setOf(p1, p2, p3, p4, p5, p6, p7)
+ setOf()
))
}
@@ -317,7 +317,7 @@
changedFiles = listOf("foo.java"))
)
MatcherAssert.assertThat(detector.affectedProjects, CoreMatchers.`is`(
- setOf(p1, p2, p3, p4, p5, p6, p7)
+ setOf()
))
}
diff --git a/buildSrc/studio_versions.properties b/buildSrc/studio_versions.properties
new file mode 100644
index 0000000..f28884d
--- /dev/null
+++ b/buildSrc/studio_versions.properties
@@ -0,0 +1,9 @@
+# This file specifies the version of the Android Gradle Plugin and Android Studio to use
+# TODO: autogenerate this file based on the version of AGP
+
+# the version of the Android Gradle Plugin
+agp=3.4.0-beta03
+# version properties for studiow, which should correspond to the version of AGP
+studio_version=3.4.0.12
+idea_major_version=183
+studio_build_number=5256591
diff --git a/car/cluster/api/1.0.0-alpha5.txt b/car/cluster/api/1.0.0-alpha5.txt
index bcdbdbb..8469597 100644
--- a/car/cluster/api/1.0.0-alpha5.txt
+++ b/car/cluster/api/1.0.0-alpha5.txt
@@ -196,12 +196,13 @@
public class RichText implements androidx.versionedparcelable.VersionedParcelable {
method public java.util.List<androidx.car.cluster.navigation.RichTextElement> getElements();
+ method public String getText();
}
public static final class RichText.Builder {
ctor public RichText.Builder();
method public androidx.car.cluster.navigation.RichText.Builder addElement(androidx.car.cluster.navigation.RichTextElement);
- method public androidx.car.cluster.navigation.RichText build();
+ method public androidx.car.cluster.navigation.RichText build(String);
}
public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
@@ -209,10 +210,11 @@
method public String getText();
}
- public static class RichTextElement.Builder {
+ public static final class RichTextElement.Builder {
ctor public RichTextElement.Builder();
- method public androidx.car.cluster.navigation.RichTextElement! build(String);
- method public androidx.car.cluster.navigation.RichTextElement.Builder! setImage(androidx.car.cluster.navigation.ImageReference?);
+ method public androidx.car.cluster.navigation.RichTextElement build();
+ method public androidx.car.cluster.navigation.RichTextElement.Builder setImage(androidx.car.cluster.navigation.ImageReference?);
+ method public androidx.car.cluster.navigation.RichTextElement.Builder setText(String?);
}
public class Segment implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/car/cluster/api/current.txt b/car/cluster/api/current.txt
index bcdbdbb..8469597 100644
--- a/car/cluster/api/current.txt
+++ b/car/cluster/api/current.txt
@@ -196,12 +196,13 @@
public class RichText implements androidx.versionedparcelable.VersionedParcelable {
method public java.util.List<androidx.car.cluster.navigation.RichTextElement> getElements();
+ method public String getText();
}
public static final class RichText.Builder {
ctor public RichText.Builder();
method public androidx.car.cluster.navigation.RichText.Builder addElement(androidx.car.cluster.navigation.RichTextElement);
- method public androidx.car.cluster.navigation.RichText build();
+ method public androidx.car.cluster.navigation.RichText build(String);
}
public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
@@ -209,10 +210,11 @@
method public String getText();
}
- public static class RichTextElement.Builder {
+ public static final class RichTextElement.Builder {
ctor public RichTextElement.Builder();
- method public androidx.car.cluster.navigation.RichTextElement! build(String);
- method public androidx.car.cluster.navigation.RichTextElement.Builder! setImage(androidx.car.cluster.navigation.ImageReference?);
+ method public androidx.car.cluster.navigation.RichTextElement build();
+ method public androidx.car.cluster.navigation.RichTextElement.Builder setImage(androidx.car.cluster.navigation.ImageReference?);
+ method public androidx.car.cluster.navigation.RichTextElement.Builder setText(String?);
}
public class Segment implements androidx.versionedparcelable.VersionedParcelable {
diff --git a/car/cluster/api/restricted_1.0.0-alpha5.txt b/car/cluster/api/restricted_1.0.0-alpha5.txt
index 19769fb..63fd874 100644
--- a/car/cluster/api/restricted_1.0.0-alpha5.txt
+++ b/car/cluster/api/restricted_1.0.0-alpha5.txt
@@ -6,7 +6,7 @@
}
public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RichTextElement(String, androidx.car.cluster.navigation.ImageReference?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RichTextElement(String?, androidx.car.cluster.navigation.ImageReference?);
}
}
diff --git a/car/cluster/api/restricted_current.txt b/car/cluster/api/restricted_current.txt
index 19769fb..63fd874 100644
--- a/car/cluster/api/restricted_current.txt
+++ b/car/cluster/api/restricted_current.txt
@@ -6,7 +6,7 @@
}
public class RichTextElement implements androidx.versionedparcelable.VersionedParcelable {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RichTextElement(String, androidx.car.cluster.navigation.ImageReference?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RichTextElement(String?, androidx.car.cluster.navigation.ImageReference?);
}
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java
index f1a1cd7..79ae40d 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextElementTest.java
@@ -44,23 +44,17 @@
assertEquals(element, createSampleElement());
assertNotEquals(element, new RichTextElement.Builder()
.setImage(TEST_IMAGE)
- .build(""));
+ .setText("")
+ .build());
assertNotEquals(element, new RichTextElement.Builder()
.setImage(null)
- .build(TEST_TEXT));
+ .setText(TEST_TEXT)
+ .build());
assertEquals(element.hashCode(), createSampleElement().hashCode());
}
/**
- * Test that a text representation must be provided.
- */
- @Test(expected = NullPointerException.class)
- public void builder_textIsMandatory() {
- new RichTextElement.Builder().build(null);
- }
-
- /**
* Test that an empty string will be returned from {@link RichTextElement#getText()} to the
* consumer, even if no string was received.
*/
@@ -75,6 +69,7 @@
public RichTextElement createSampleElement() {
return new RichTextElement.Builder()
.setImage(TEST_IMAGE)
- .build(TEST_TEXT);
+ .setText(TEST_TEXT)
+ .build();
}
}
diff --git a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java
index cac4cb0..f7d7aac 100644
--- a/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java
+++ b/car/cluster/src/androidTest/java/androidx/car/cluster/navigation/RichTextTest.java
@@ -36,6 +36,7 @@
@SmallTest
public class RichTextTest {
private static final String TEST_TEXT = "foo";
+ private static final String TEST_TEXT_ELEMENT = "bar";
/**
* Test a few equality conditions
@@ -43,14 +44,14 @@
@Test
public void equality() {
RichText expected = createSampleRichText();
- RichTextElement element = new RichTextElement.Builder().build(TEST_TEXT);
+ RichTextElement element = new RichTextElement.Builder().setText(TEST_TEXT_ELEMENT).build();
assertEquals(expected, createSampleRichText());
- assertNotEquals(expected, new RichText.Builder().build());
+ assertNotEquals(expected, new RichText.Builder().build(TEST_TEXT));
assertNotEquals(expected, new RichText.Builder()
.addElement(element)
.addElement(element)
- .build());
+ .build(TEST_TEXT));
assertEquals(expected.hashCode(), createSampleRichText().hashCode());
}
@@ -60,7 +61,7 @@
*/
@Test
public void immutability() {
- assertImmutable(new RichText.Builder().build().getElements());
+ assertImmutable(new RichText.Builder().build(TEST_TEXT).getElements());
assertImmutable(new RichText().getElements());
}
@@ -74,6 +75,14 @@
}
/**
+ * Test that a text representation must not be null.
+ */
+ @Test(expected = NullPointerException.class)
+ public void builder_textIsMandatory() {
+ new RichText.Builder().build(null);
+ }
+
+ /**
* Builder doesn't accept null elements
*/
@Test(expected = NullPointerException.class)
@@ -86,7 +95,7 @@
*/
public static RichText createSampleRichText() {
return new RichText.Builder()
- .addElement(new RichTextElement.Builder().build(TEST_TEXT))
- .build();
+ .addElement(new RichTextElement.Builder().setText(TEST_TEXT_ELEMENT).build())
+ .build(TEST_TEXT);
}
}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java
index 60c31c7..6b61bc9 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichText.java
@@ -30,17 +30,27 @@
import java.util.Objects;
/**
- * Immutable sequence of graphic elements (e.g.: text, images) to be displayed one after another in
- * the same way as a {@link CharSequence} would. Elements in this sequence are represented by
- * {@link RichTextElement} instances.
+ * A {@link RichText} is an immutable sequence of graphic elements (e.g.: text, images)
+ * to be displayed one after another.
+ * <p>
+ * Elements in this sequence are represented by {@link RichTextElement} instances.
+ * <p>
+ * Each sequence will have a textual representation provided by {@link #getText()}
+ * and in the case of the absence of a rich representation, the sequence of elements
+ * {@link #getElements()} may be left empty. The textual representation may also be used as a
+ * fallback for when {@link RichTextElement}s fail to render.
*/
@VersionedParcelize
public class RichText implements VersionedParcelable {
@ParcelField(1)
List<RichTextElement> mElements;
+ @ParcelField(2)
+ String mText;
+
/**
* Used by {@link VersionedParcelable}
+ *
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@@ -51,7 +61,8 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
- RichText(@NonNull List<RichTextElement> elements) {
+ RichText(@NonNull String text, @NonNull List<RichTextElement> elements) {
+ mText = text;
mElements = new ArrayList<>(elements);
}
@@ -77,13 +88,24 @@
* Returns a {@link RichText} built with the provided information.
*/
@NonNull
- public RichText build() {
- return new RichText(mElements);
+ public RichText build(@NonNull String text) {
+ return new RichText(Preconditions.checkNotNull(text), mElements);
}
}
/**
- * Returns the sequence of graphic elements
+ * Returns the plaintext string of this {@link RichText}.
+ */
+ @NonNull
+ public String getText() {
+ return Common.nonNullOrEmpty(mText);
+ }
+
+ /**
+ * Returns the sequence of graphic elements.
+ * <p>
+ * If no rich representation is available, the list may be empty and {@link #getText()} should
+ * be used as a fallback.
*/
@NonNull
public List<RichTextElement> getElements() {
@@ -99,16 +121,17 @@
return false;
}
RichText richText = (RichText) o;
- return Objects.equals(getElements(), richText.getElements());
+ return Objects.equals(getText(), richText.getText())
+ && Objects.equals(getElements(), richText.getElements());
}
@Override
public int hashCode() {
- return Objects.hash(getElements());
+ return Objects.hash(getText(), getElements());
}
@Override
public String toString() {
- return String.format("{elements: %s}", mElements);
+ return String.format("{text: '%s', elements: %s}", mText, mElements);
}
}
diff --git a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java
index 96cb264..92a1723 100644
--- a/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java
+++ b/car/cluster/src/main/java/androidx/car/cluster/navigation/RichTextElement.java
@@ -21,7 +21,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
-import androidx.core.util.Preconditions;
import androidx.versionedparcelable.ParcelField;
import androidx.versionedparcelable.VersionedParcelable;
import androidx.versionedparcelable.VersionedParcelize;
@@ -32,10 +31,10 @@
* An item in a {@link RichText} sequence, acting as a union of different graphic elements that can
* be displayed one after another.
* <p>
- * All {@link RichTextElement} must contain a textual representation of its content, which will be
- * used by consumers incapable of rendering the desired graphic element. A {@link RichTextElement}
- * can only contain one other graphic element. Consumers must attempt to render such element and
- * only fallback to text if needed.
+ * A {@link RichTextElement} can contain text and a graphic element as its representation.
+ * Consumers must attempt to render the graphic element if present. In case of failure to render
+ * the element, the first line of fallback should be {@link #getText()}. If that is also empty,
+ * fallback to {@link RichText#getText()} will be used.
* <p>
* New graphic element types might be added in the future. If such elements are unknown to the
* consumer, they will be delivered to the consumer as just text.
@@ -49,6 +48,7 @@
/**
* Used by {@link VersionedParcelable}
+ *
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@@ -59,17 +59,17 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
- public RichTextElement(@NonNull String text, @Nullable ImageReference image) {
- mText = Preconditions.checkNotNull(text, "A textual representation of this "
- + "element must be provided.");
+ public RichTextElement(@Nullable String text, @Nullable ImageReference image) {
+ mText = text;
mImage = image;
}
/**
* Builder for creating a {@link RichTextElement}
*/
- public static class Builder {
+ public static final class Builder {
private ImageReference mImage;
+ private String mText;
/**
* Sets an image to be displayed as part of the {@link RichText} sequence. Images in the
@@ -80,6 +80,7 @@
* textual representation should be used.
* @return this object for chaining
*/
+ @NonNull
public Builder setImage(@Nullable ImageReference image) {
// Note: if new graphic element types are added in the future, this API should enforce
// that no more than one of them is set at each moment.
@@ -88,20 +89,34 @@
}
/**
- * Builds a {@link RichTextElement} with the given textual representation, and any other
- * optional representation provided to this builder. If no other graphic element is provided
- * or if such graphic element cannot be rendered by the consumer, this text will be used
- * instead.
+ * Sets the textual representation for this element to be displayed as part of the
+ * {@link RichText} sequence.
*
* @param text textual representation to use
+ * @return this object for chaining
*/
- public RichTextElement build(@NonNull String text) {
- return new RichTextElement(Preconditions.checkNotNull(text), mImage);
+ @NonNull
+ public Builder setText(@Nullable String text) {
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Builds a {@link RichTextElement} with an optional textual representation, and any other
+ * optional representation provided to this builder. If no other graphic element is provided
+ * or if such graphic element cannot be rendered by the consumer, text will be used instead.
+ */
+ @NonNull
+ public RichTextElement build() {
+ return new RichTextElement(Common.nonNullOrEmpty(mText), mImage);
}
}
/**
- * Returns the textual representation of this element
+ * Returns the textual representation of this element.
+ * <p>
+ * If {@link #getImage()} is provided, then this is used as a fallback in the case of render
+ * failures.
*/
@NonNull
public String getText() {
@@ -109,8 +124,12 @@
}
/**
- * Returns an image representing this element, or null if the textual representation should be
- * used instead.
+ * Returns an image representing this element. This representation should be used over
+ * the textual representation {@link #getText()} whenever possible.
+ * <p>
+ * In case of failure to render, initial fallback to {@link #getText()} should be used.
+ * Fallback to {@link RichText#getText()} should be used if textual fallback is not provided
+ * (empty string).
*/
@Nullable
public ImageReference getImage() {
diff --git a/car/core/api/1.0.0-alpha7.txt b/car/core/api/1.0.0-alpha7.txt
index a192354..7eea4f7 100644
--- a/car/core/api/1.0.0-alpha7.txt
+++ b/car/core/api/1.0.0-alpha7.txt
@@ -95,6 +95,7 @@
public class CarUxRestrictionsHelper {
ctor public CarUxRestrictionsHelper(android.content.Context!, androidx.car.uxrestrictions.OnUxRestrictionsChangedListener);
+ method public androidx.car.uxrestrictions.CarUxRestrictions getCurrentCarUxRestrictions();
method public void start();
method public void stop();
}
@@ -365,6 +366,7 @@
ctor public PagedListView(android.content.Context!, android.util.AttributeSet!, int, int);
method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getAdapter();
method public int getListContentBottomOffset();
method public int getListContentTopOffset();
@@ -383,8 +385,10 @@
method public void pageDown();
method public void pageUp();
method public int positionOf(android.view.View?);
+ method public void registerCallback(androidx.car.widget.PagedListView.Callback);
method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
method public void setDividerColor(@ColorRes int);
@@ -396,16 +400,23 @@
method public void setListContentBottomOffset(@Px int);
method public void setListContentTopOffset(@Px int);
method public void setMaxPages(int);
- method public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
+ method @Deprecated public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
method public void setScrollBarContainerWidth(int);
method public void setScrollBarTopMargin(int);
method public void setScrollbarThumbEnabled(boolean);
method public void setUpButtonIcon(android.graphics.drawable.Drawable!);
method public void showAlphaJump();
method public void snapToPosition(int);
+ method public void unregisterCallback(androidx.car.widget.PagedListView.Callback);
field public static final int UNLIMITED_PAGES = -1; // 0xffffffff
}
+ public static interface PagedListView.Callback {
+ method public default void onReachBottom();
+ method public default void onScrollDownButtonClicked();
+ method public default void onScrollUpButtonClicked();
+ }
+
public static interface PagedListView.DividerVisibilityManager {
method public boolean getShowDivider(int);
}
@@ -426,13 +437,13 @@
method public void setPositionOffset(int);
}
- public abstract static class PagedListView.OnScrollListener {
- ctor public PagedListView.OnScrollListener();
- method public void onReachBottom();
- method public void onScrollDownButtonClicked();
- method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
- method public void onScrollUpButtonClicked();
- method public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
+ @Deprecated public abstract static class PagedListView.OnScrollListener {
+ ctor @Deprecated public PagedListView.OnScrollListener();
+ method @Deprecated public void onReachBottom();
+ method @Deprecated public void onScrollDownButtonClicked();
+ method @Deprecated public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
+ method @Deprecated public void onScrollUpButtonClicked();
+ method @Deprecated public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
}
public class PagedScrollBarView extends android.view.ViewGroup {
@@ -475,7 +486,9 @@
method public void setChecked(boolean);
method public void setEnabled(boolean);
method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener);
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
+ method public void setPrimaryActionNoIcon();
method public void setShowRadioButtonDivider(boolean);
method public void setText(CharSequence?);
method public void setTextStartMargin(@DimenRes int);
@@ -505,16 +518,14 @@
method public void setMax(int);
method public void setOnSeekBarChangeListener(android.widget.SeekBar.OnSeekBarChangeListener!);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon);
- method @Deprecated public void setPrimaryActionIcon(@DrawableRes int);
- method @Deprecated public void setPrimaryActionIcon(android.graphics.drawable.Drawable!);
+ method public void setPrimaryActionIcon(@DrawableRes int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable);
method public void setPrimaryActionIconListener(android.view.View.OnClickListener!);
method public void setProgress(int);
method public void setSecondaryProgress(int);
method public void setSupplementalEmptyIcon(boolean);
- method public void setSupplementalIcon(android.graphics.drawable.Icon, boolean);
- method @Deprecated public void setSupplementalIcon(@DrawableRes int, boolean);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
+ method public void setSupplementalIcon(@DrawableRes int, boolean);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
method public void setSupplementalIconListener(android.view.View.OnClickListener!);
method public void setText(CharSequence?);
}
@@ -565,7 +576,8 @@
method public void setClickable(boolean);
method public void setEnabled(boolean);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
method public void setPrimaryActionNoIcon();
method public void setShowSwitchDivider(boolean);
method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
@@ -597,15 +609,13 @@
method public void setEnabled(boolean);
method public void setOnClickListener(android.view.View.OnClickListener!);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
- method @Deprecated public void setPrimaryActionIcon(@DrawableRes int, int);
- method @Deprecated public void setPrimaryActionIcon(android.graphics.drawable.Drawable?, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
method public void setPrimaryActionNoIcon();
- method public void setSupplementalIcon(android.graphics.drawable.Icon, boolean);
- method @Deprecated public void setSupplementalIcon(int, boolean);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean);
- method @Deprecated public void setSupplementalIcon(int, boolean, android.view.View.OnClickListener!);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean, android.view.View.OnClickListener!);
+ method public void setSupplementalIcon(@DrawableRes int, boolean);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
+ method public void setSupplementalIcon(@DrawableRes int, boolean, android.view.View.OnClickListener!);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean, android.view.View.OnClickListener!);
method public void setSupplementalIconOnClickListener(android.view.View.OnClickListener);
method @Deprecated public void setSwitch(boolean, boolean, android.widget.CompoundButton.OnCheckedChangeListener!);
method @Deprecated public void setSwitchState(boolean);
diff --git a/car/core/api/current.txt b/car/core/api/current.txt
index a192354..7eea4f7 100644
--- a/car/core/api/current.txt
+++ b/car/core/api/current.txt
@@ -95,6 +95,7 @@
public class CarUxRestrictionsHelper {
ctor public CarUxRestrictionsHelper(android.content.Context!, androidx.car.uxrestrictions.OnUxRestrictionsChangedListener);
+ method public androidx.car.uxrestrictions.CarUxRestrictions getCurrentCarUxRestrictions();
method public void start();
method public void stop();
}
@@ -365,6 +366,7 @@
ctor public PagedListView(android.content.Context!, android.util.AttributeSet!, int, int);
method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>? getAdapter();
method public int getListContentBottomOffset();
method public int getListContentTopOffset();
@@ -383,8 +385,10 @@
method public void pageDown();
method public void pageUp();
method public int positionOf(android.view.View?);
+ method public void registerCallback(androidx.car.widget.PagedListView.Callback);
method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
method public void scrollToPosition(int);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter<? extends androidx.recyclerview.widget.RecyclerView.ViewHolder>);
method public void setDividerColor(@ColorRes int);
@@ -396,16 +400,23 @@
method public void setListContentBottomOffset(@Px int);
method public void setListContentTopOffset(@Px int);
method public void setMaxPages(int);
- method public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
+ method @Deprecated public void setOnScrollListener(androidx.car.widget.PagedListView.OnScrollListener!);
method public void setScrollBarContainerWidth(int);
method public void setScrollBarTopMargin(int);
method public void setScrollbarThumbEnabled(boolean);
method public void setUpButtonIcon(android.graphics.drawable.Drawable!);
method public void showAlphaJump();
method public void snapToPosition(int);
+ method public void unregisterCallback(androidx.car.widget.PagedListView.Callback);
field public static final int UNLIMITED_PAGES = -1; // 0xffffffff
}
+ public static interface PagedListView.Callback {
+ method public default void onReachBottom();
+ method public default void onScrollDownButtonClicked();
+ method public default void onScrollUpButtonClicked();
+ }
+
public static interface PagedListView.DividerVisibilityManager {
method public boolean getShowDivider(int);
}
@@ -426,13 +437,13 @@
method public void setPositionOffset(int);
}
- public abstract static class PagedListView.OnScrollListener {
- ctor public PagedListView.OnScrollListener();
- method public void onReachBottom();
- method public void onScrollDownButtonClicked();
- method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
- method public void onScrollUpButtonClicked();
- method public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
+ @Deprecated public abstract static class PagedListView.OnScrollListener {
+ ctor @Deprecated public PagedListView.OnScrollListener();
+ method @Deprecated public void onReachBottom();
+ method @Deprecated public void onScrollDownButtonClicked();
+ method @Deprecated public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView!, int);
+ method @Deprecated public void onScrollUpButtonClicked();
+ method @Deprecated public void onScrolled(androidx.recyclerview.widget.RecyclerView!, int, int);
}
public class PagedScrollBarView extends android.view.ViewGroup {
@@ -475,7 +486,9 @@
method public void setChecked(boolean);
method public void setEnabled(boolean);
method public void setOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener);
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
+ method public void setPrimaryActionNoIcon();
method public void setShowRadioButtonDivider(boolean);
method public void setText(CharSequence?);
method public void setTextStartMargin(@DimenRes int);
@@ -505,16 +518,14 @@
method public void setMax(int);
method public void setOnSeekBarChangeListener(android.widget.SeekBar.OnSeekBarChangeListener!);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon);
- method @Deprecated public void setPrimaryActionIcon(@DrawableRes int);
- method @Deprecated public void setPrimaryActionIcon(android.graphics.drawable.Drawable!);
+ method public void setPrimaryActionIcon(@DrawableRes int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable);
method public void setPrimaryActionIconListener(android.view.View.OnClickListener!);
method public void setProgress(int);
method public void setSecondaryProgress(int);
method public void setSupplementalEmptyIcon(boolean);
- method public void setSupplementalIcon(android.graphics.drawable.Icon, boolean);
- method @Deprecated public void setSupplementalIcon(@DrawableRes int, boolean);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
+ method public void setSupplementalIcon(@DrawableRes int, boolean);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
method public void setSupplementalIconListener(android.view.View.OnClickListener!);
method public void setText(CharSequence?);
}
@@ -565,7 +576,8 @@
method public void setClickable(boolean);
method public void setEnabled(boolean);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
method public void setPrimaryActionNoIcon();
method public void setShowSwitchDivider(boolean);
method public void setSwitchOnCheckedChangeListener(android.widget.CompoundButton.OnCheckedChangeListener?);
@@ -597,15 +609,13 @@
method public void setEnabled(boolean);
method public void setOnClickListener(android.view.View.OnClickListener!);
method public void setPrimaryActionEmptyIcon();
- method public void setPrimaryActionIcon(android.graphics.drawable.Icon, int);
- method @Deprecated public void setPrimaryActionIcon(@DrawableRes int, int);
- method @Deprecated public void setPrimaryActionIcon(android.graphics.drawable.Drawable?, int);
+ method public void setPrimaryActionIcon(@DrawableRes int, int);
+ method public void setPrimaryActionIcon(android.graphics.drawable.Drawable, int);
method public void setPrimaryActionNoIcon();
- method public void setSupplementalIcon(android.graphics.drawable.Icon, boolean);
- method @Deprecated public void setSupplementalIcon(int, boolean);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean);
- method @Deprecated public void setSupplementalIcon(int, boolean, android.view.View.OnClickListener!);
- method @Deprecated public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean, android.view.View.OnClickListener!);
+ method public void setSupplementalIcon(@DrawableRes int, boolean);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable, boolean);
+ method public void setSupplementalIcon(@DrawableRes int, boolean, android.view.View.OnClickListener!);
+ method public void setSupplementalIcon(android.graphics.drawable.Drawable!, boolean, android.view.View.OnClickListener!);
method public void setSupplementalIconOnClickListener(android.view.View.OnClickListener);
method @Deprecated public void setSwitch(boolean, boolean, android.widget.CompoundButton.OnCheckedChangeListener!);
method @Deprecated public void setSwitchState(boolean);
diff --git a/car/core/api/restricted_1.0.0-alpha7.txt b/car/core/api/restricted_1.0.0-alpha7.txt
index 8a43c31..7dc7128 100644
--- a/car/core/api/restricted_1.0.0-alpha7.txt
+++ b/car/core/api/restricted_1.0.0-alpha7.txt
@@ -44,7 +44,7 @@
method public static void apply(android.content.Context!, androidx.car.uxrestrictions.CarUxRestrictions!, android.widget.TextView!);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.car.widget.PagedListView.OnScrollListener {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.recyclerview.widget.RecyclerView.OnScrollListener {
ctor public DropShadowScrollListener(android.view.View!);
}
diff --git a/car/core/api/restricted_current.txt b/car/core/api/restricted_current.txt
index 8a43c31..7dc7128 100644
--- a/car/core/api/restricted_current.txt
+++ b/car/core/api/restricted_current.txt
@@ -44,7 +44,7 @@
method public static void apply(android.content.Context!, androidx.car.uxrestrictions.CarUxRestrictions!, android.widget.TextView!);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.car.widget.PagedListView.OnScrollListener {
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DropShadowScrollListener extends androidx.recyclerview.widget.RecyclerView.OnScrollListener {
ctor public DropShadowScrollListener(android.view.View!);
}
diff --git a/car/core/build.gradle b/car/core/build.gradle
index 9fba2a1..8f4c678 100644
--- a/car/core/build.gradle
+++ b/car/core/build.gradle
@@ -15,7 +15,7 @@
api("androidx.gridlayout:gridlayout:1.0.0")
api("androidx.preference:preference:1.0.0")
api(CONSTRAINT_LAYOUT, { transitive = true })
- api(SUPPORT_DESIGN, libs.exclude_for_material)
+ api(MATERIAL)
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
diff --git a/car/core/res/values/styles.xml b/car/core/res/values/styles.xml
index c3eb2cd..517cc0c 100644
--- a/car/core/res/values/styles.xml
+++ b/car/core/res/values/styles.xml
@@ -72,7 +72,7 @@
</style>
<style name="TextAppearance.Car.Body1.Medium.Dark">
- <item name="android:textColor">@color/car_body1_light</item>
+ <item name="android:textColor">@color/car_body1_dark</item>
</style>
<!-- An alternate styling for body text that is both a different color and size than
diff --git a/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java b/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
index ef41766..32b9931 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/PagedListViewTest.java
@@ -36,6 +36,9 @@
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -179,6 +182,76 @@
}
@Test
+ public void testScrollButtonCallback() {
+ int itemCount = ITEMS_PER_PAGE * 3;
+ setUpPagedListView(itemCount);
+
+ PagedListView.Callback mockedCallbackOne = mock(PagedListView.Callback.class);
+ PagedListView.Callback mockedCallbackTwo = mock(PagedListView.Callback.class);
+ PagedListView.Callback mockedCallbackThree = mock(PagedListView.Callback.class);
+
+ mPagedListView.registerCallback(mockedCallbackOne);
+ mPagedListView.registerCallback(mockedCallbackTwo);
+ mPagedListView.registerCallback(mockedCallbackThree);
+
+ // Move one page down.
+ onView(withId(R.id.page_down)).perform(click());
+ verify(mockedCallbackOne, times(1)).onScrollDownButtonClicked();
+ verify(mockedCallbackTwo, times(1)).onScrollDownButtonClicked();
+ verify(mockedCallbackThree, times(1)).onScrollDownButtonClicked();
+
+ // Move one page up.
+ onView(withId(R.id.page_up)).perform(click());
+ verify(mockedCallbackOne, times(1)).onScrollUpButtonClicked();
+ verify(mockedCallbackTwo, times(1)).onScrollUpButtonClicked();
+ verify(mockedCallbackThree, times(1)).onScrollUpButtonClicked();
+
+ mPagedListView.unregisterCallback(mockedCallbackOne);
+ onView(withId(R.id.page_down)).perform(click());
+ verify(mockedCallbackOne, times(1)).onScrollDownButtonClicked();
+ verify(mockedCallbackTwo, times(2)).onScrollDownButtonClicked();
+ verify(mockedCallbackThree, times(2)).onScrollDownButtonClicked();
+ }
+
+ @Test
+ public void testMultipleScrollButtonCallback() {
+ int itemCount = ITEMS_PER_PAGE * 4;
+ setUpPagedListView(itemCount);
+
+ PagedListView.Callback mockedCallback = mock(PagedListView.Callback.class);
+ mPagedListView.registerCallback(mockedCallback);
+
+ // Move one page down.
+ onView(withId(R.id.page_down)).perform(click());
+ onView(withId(R.id.page_down)).perform(click());
+ onView(withId(R.id.page_down)).perform(click());
+ verify(mockedCallback, times(3)).onScrollDownButtonClicked();
+ }
+
+ @Test
+ public void testReachBottomCallback() {
+ int itemCount = ITEMS_PER_PAGE * 2;
+ setUpPagedListView(itemCount);
+
+ PagedListView.Callback mockedCallback = mock(PagedListView.Callback.class);
+ mPagedListView.registerCallback(mockedCallback);
+
+ // Moving down to bottom of list.
+ onView(withId(R.id.page_down)).perform(click());
+ onView(withId(R.id.page_down)).perform(click());
+
+ verify(mockedCallback, times(1)).onReachBottom();
+
+ // Moving up should not cause a onReachBottom event.
+ onView(withId(R.id.page_up)).perform(click());
+ verify(mockedCallback, times(1)).onReachBottom();
+
+ // Move to bottom of list again.
+ onView(withId(R.id.page_down)).perform(click());
+ verify(mockedCallback, times(2)).onReachBottom();
+ }
+
+ @Test
public void testPageUpButtonDisabledAtTop() {
int itemCount = ITEMS_PER_PAGE * 3;
setUpPagedListView(itemCount);
diff --git a/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
index 83d00c1..8879602 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/RadioButtonListItemTest.java
@@ -29,7 +29,6 @@
import static org.junit.Assert.assertTrue;
import android.content.pm.PackageManager;
-import android.graphics.drawable.Icon;
import android.view.View;
import androidx.car.test.R;
@@ -87,9 +86,9 @@
}
@Test
- public void testSetPrimaryActionIcon_NullIconTextNoOffset() {
+ public void testSetPrimaryActionIcon_NoIconTextNoOffset() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
- item.setPrimaryActionIcon(null, RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+ item.setPrimaryActionNoIcon();
item.setText("text");
setupPagedListView(Arrays.asList(item));
@@ -101,7 +100,7 @@
public void testSetPrimaryActionIcon_SmallIconTextOffset() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item.setText("text");
@@ -116,7 +115,7 @@
public void testSetPrimaryActionIcon_MediumIconTextOffset() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
item.setText("text");
@@ -131,7 +130,7 @@
public void testSetPrimaryActionIcon_LargeIconTextOffset() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
item.setText("text");
@@ -155,7 +154,7 @@
@Test
public void testSetTextStartMargin_NoIcon() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
- item.setPrimaryActionIcon(null, RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+ item.setPrimaryActionNoIcon();
item.setText("text");
item.setTextStartMargin(R.dimen.car_keyline_1);
@@ -170,7 +169,7 @@
public void testSetTextStartMargin_MarginPlusOffsetByIcon() {
RadioButtonListItem item = new RadioButtonListItem(mActivity);
item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item.setText("text");
item.setTextStartMargin(R.dimen.car_keyline_1);
diff --git a/car/core/src/androidTest/java/androidx/car/widget/SeekbarListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/SeekbarListItemTest.java
index 8e4e7de..788c27d 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/SeekbarListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/SeekbarListItemTest.java
@@ -31,7 +31,6 @@
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
@@ -96,8 +95,7 @@
@Test
public void testPrimaryActionVisible() {
SeekbarListItem item0 = initSeekbarListItem();
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon));
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
SeekbarListItem item1 = initSeekbarListItem();
item1.setPrimaryActionIcon(new ColorDrawable(Color.BLACK));
@@ -139,8 +137,7 @@
@Test
public void testSupplementalIconVisible() {
SeekbarListItem item = initSeekbarListItem();
- item.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
setupPagedListView(Arrays.asList(item));
@@ -153,8 +150,7 @@
@Test
public void testSupplementalIconDividerVisible() {
SeekbarListItem item = initSeekbarListItem();
- item.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
setupPagedListView(Arrays.asList(item));
@@ -244,10 +240,8 @@
public void testDisabledItemDisablesViewHolder() {
SeekbarListItem item = new SeekbarListItem(mActivity);
item.setText("text");
- item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon));
- item.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), false);
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
item.setEnabled(false);
setupPagedListView(Arrays.asList(item));
@@ -262,8 +256,7 @@
@Test
public void testPrimaryIconIsNotClickableWithoutListener() {
SeekbarListItem item0 = initSeekbarListItem();
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon));
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
SeekbarListItem item1 = initSeekbarListItem();
item1.setPrimaryActionIcon(new ColorDrawable(Color.BLACK));
@@ -278,8 +271,7 @@
public void testClickingPrimaryActionIcon() {
boolean[] clicked = {false};
SeekbarListItem item = initSeekbarListItem();
- item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
item.setPrimaryActionIconListener(v -> clicked[0] = true);
setupPagedListView(Arrays.asList(item));
@@ -294,8 +286,7 @@
@Test
public void testSupplementalIconNotClickableWithoutListener() {
SeekbarListItem item = initSeekbarListItem();
- item.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
setupPagedListView(Arrays.asList(item));
@@ -306,8 +297,7 @@
public void testClickingSupplementalIcon() {
boolean[] clicked = {false};
SeekbarListItem item = initSeekbarListItem();
- item.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon, false);
item.setSupplementalIconListener(v -> clicked[0] = true);
setupPagedListView(Arrays.asList(item));
@@ -321,8 +311,7 @@
@Test
public void testPrimaryActionEmptyIconSpacing() {
SeekbarListItem item0 = initSeekbarListItem();
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon));
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
SeekbarListItem item1 = initSeekbarListItem();
item1.setPrimaryActionIcon(new ColorDrawable(Color.BLACK));
@@ -343,9 +332,7 @@
public void testSupplementalIconSpacingWithoutDivider() {
final boolean showDivider = false;
SeekbarListItem item0 = initSeekbarListItem();
- item0.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
- showDivider);
+ item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, showDivider);
SeekbarListItem item1 = initSeekbarListItem();
item1.setSupplementalEmptyIcon(showDivider);
@@ -363,9 +350,7 @@
public void testSupplementalIconSpacingWithDivider() {
final boolean showDivider = true;
SeekbarListItem item0 = initSeekbarListItem();
- item0.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
- showDivider);
+ item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, showDivider);
SeekbarListItem item1 = initSeekbarListItem();
item1.setSupplementalEmptyIcon(showDivider);
diff --git a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
index cd842bd..27afc3f 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/SwitchListItemTest.java
@@ -41,7 +41,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
-import android.graphics.drawable.Icon;
import android.text.InputFilter;
import android.text.TextUtils;
import android.view.View;
@@ -323,18 +322,15 @@
@Test
public void testPrimaryActionVisible() {
SwitchListItem largeIcon = new SwitchListItem(mActivity);
- largeIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
SwitchListItem mediumIcon = new SwitchListItem(mActivity);
- mediumIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ mediumIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
SwitchListItem smallIcon = new SwitchListItem(mActivity);
- smallIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
List<SwitchListItem> items = Arrays.asList(largeIcon, mediumIcon, smallIcon);
@@ -368,18 +364,15 @@
@Test
public void testTextStartMarginMatchesPrimaryActionType() {
SwitchListItem largeIcon = new SwitchListItem(mActivity);
- largeIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ largeIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
SwitchListItem mediumIcon = new SwitchListItem(mActivity);
- mediumIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ mediumIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
SwitchListItem smallIcon = new SwitchListItem(mActivity);
- smallIcon.setPrimaryActionIcon(
- Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ smallIcon.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
SwitchListItem emptyIcon = new SwitchListItem(mActivity);
@@ -474,10 +467,22 @@
}
@Test
- public void testSetPrimaryActionIcon() {
+ public void testSetPrimaryActionIcon_withIcon() {
+ SwitchListItem item = new SwitchListItem(mActivity);
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+ SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
+
+ List<SwitchListItem> items = Arrays.asList(item);
+ setupPagedListView(items);
+
+ assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable(), is(notNullValue()));
+ }
+
+ @Test
+ public void testSetPrimaryActionIcon_withDrawable() {
SwitchListItem item = new SwitchListItem(mActivity);
item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ mActivity.getDrawable(android.R.drawable.sym_def_app_icon),
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
List<SwitchListItem> items = Arrays.asList(item);
@@ -489,18 +494,15 @@
@Test
public void testPrimaryIconSizesInIncreasingOrder() {
SwitchListItem small = new SwitchListItem(mActivity);
- small.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ small.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
SwitchListItem medium = new SwitchListItem(mActivity);
- medium.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ medium.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
SwitchListItem large = new SwitchListItem(mActivity);
- large.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ large.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
List<SwitchListItem> items = Arrays.asList(small, medium, large);
@@ -519,8 +521,7 @@
@Test
public void testLargePrimaryIconHasNoStartMargin() {
SwitchListItem item0 = new SwitchListItem(mActivity);
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
List<SwitchListItem> items = Arrays.asList(item0);
@@ -535,12 +536,12 @@
public void testSmallAndMediumPrimaryIconStartMargin() {
SwitchListItem item0 = new SwitchListItem(mActivity);
item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
SwitchListItem item1 = new SwitchListItem(mActivity);
item1.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
List<SwitchListItem> items = Arrays.asList(item0, item1);
@@ -568,14 +569,14 @@
// Single line item.
SwitchListItem item0 = new SwitchListItem(mActivity);
item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item0.setTitle("one line text");
// Double line item with one line text.
SwitchListItem item1 = new SwitchListItem(mActivity);
item1.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item1.setTitle("one line text");
item1.setBody("one line text");
@@ -583,7 +584,7 @@
// Double line item with long text.
SwitchListItem item2 = new SwitchListItem(mActivity);
item2.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item2.setTitle("one line text");
item2.setBody(longText);
@@ -591,14 +592,14 @@
// Body text only - long text.
SwitchListItem item3 = new SwitchListItem(mActivity);
item3.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item3.setBody(longText);
// Body text only - one line text.
SwitchListItem item4 = new SwitchListItem(mActivity);
item4.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ android.R.drawable.sym_def_app_icon,
SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item4.setBody("one line text");
diff --git a/car/core/src/androidTest/java/androidx/car/widget/TextListItemTest.java b/car/core/src/androidTest/java/androidx/car/widget/TextListItemTest.java
index daf37a3..f098fe9 100644
--- a/car/core/src/androidTest/java/androidx/car/widget/TextListItemTest.java
+++ b/car/core/src/androidTest/java/androidx/car/widget/TextListItemTest.java
@@ -23,7 +23,6 @@
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
@@ -42,7 +41,6 @@
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.text.InputFilter;
import android.text.TextUtils;
import android.view.View;
@@ -503,33 +501,17 @@
}
@Test
- public void testSetPrimaryActionIcon() {
- TextListItem item = new TextListItem(mActivity);
- item.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
- TextListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
-
- List<TextListItem> items = Arrays.asList(item);
- setupPagedListView(items);
-
- assertThat(getViewHolderAtPosition(0).getPrimaryIcon().getDrawable(), is(notNullValue()));
- }
-
- @Test
public void testPrimaryIconSizesInIncreasingOrder() {
TextListItem small = new TextListItem(mActivity);
- small.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ small.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
TextListItem medium = new TextListItem(mActivity);
- medium.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ medium.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
TextListItem large = new TextListItem(mActivity);
- large.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ large.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
List<TextListItem> items = Arrays.asList(small, medium, large);
@@ -548,8 +530,7 @@
@Test
public void testLargePrimaryIconHasNoStartMargin() {
TextListItem item0 = new TextListItem(mActivity);
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
List<TextListItem> items = Arrays.asList(item0);
@@ -563,13 +544,11 @@
@Test
public void testSmallAndMediumPrimaryIconStartMargin() {
TextListItem item0 = new TextListItem(mActivity);
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
TextListItem item1 = new TextListItem(mActivity);
- item1.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
List<TextListItem> items = Arrays.asList(item0, item1);
@@ -596,38 +575,33 @@
// Single line item.
TextListItem item0 = new TextListItem(mActivity);
- item0.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item0.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item0.setTitle("one line text");
// Double line item with one line text.
TextListItem item1 = new TextListItem(mActivity);
- item1.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item1.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item1.setTitle("one line text");
item1.setBody("one line text");
// Double line item with long text.
TextListItem item2 = new TextListItem(mActivity);
- item2.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item2.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item2.setTitle("one line text");
item2.setBody(longText);
// Body text only - long text.
TextListItem item3 = new TextListItem(mActivity);
- item3.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item3.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item3.setBody(longText);
// Body text only - one line text.
TextListItem item4 = new TextListItem(mActivity);
- item4.setPrimaryActionIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon),
+ item4.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
TextListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item4.setBody("one line text");
@@ -660,8 +634,7 @@
TextListItem item0 = new TextListItem(mActivity);
item0.setOnClickListener(v -> clicked[0] = true);
- item0.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), true);
+ item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
item0.setSupplementalIconOnClickListener(v -> clicked[1] = true);
List<TextListItem> items = Arrays.asList(item0);
@@ -681,8 +654,7 @@
final boolean[] clicked = {false};
TextListItem item0 = new TextListItem(mActivity);
- item0.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), true);
+ item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
item0.setSupplementalIconOnClickListener(v -> clicked[0] = true);
List<TextListItem> items = Arrays.asList(item0);
@@ -696,8 +668,7 @@
@Test
public void testSupplementalIconWithoutClickListenerIsNotClickable() {
TextListItem item0 = new TextListItem(mActivity);
- item0.setSupplementalIcon(
- Icon.createWithResource(mActivity, android.R.drawable.sym_def_app_icon), true);
+ item0.setSupplementalIcon(android.R.drawable.sym_def_app_icon, true);
List<TextListItem> items = Arrays.asList(item0);
setupPagedListView(items);
diff --git a/car/core/src/main/java/androidx/car/app/CarListDialog.java b/car/core/src/main/java/androidx/car/app/CarListDialog.java
index 47bf005..b49ef1b 100644
--- a/car/core/src/main/java/androidx/car/app/CarListDialog.java
+++ b/car/core/src/main/java/androidx/car/app/CarListDialog.java
@@ -164,7 +164,7 @@
return;
}
- mList.setOnScrollListener(new DropShadowScrollListener(mTitleView));
+ mList.addOnScrollListener(new DropShadowScrollListener(mTitleView));
}
@Override
diff --git a/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java b/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
index afcec38..74743c2 100644
--- a/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
+++ b/car/core/src/main/java/androidx/car/drawer/CarDrawerController.java
@@ -117,7 +117,7 @@
theme.resolveAttribute(R.attr.drawerToolbarId, outValue, true)
? outValue.resourceId
: R.id.drawer_toolbar);
- mDrawerList.setOnScrollListener(new DropShadowScrollListener(toolbar));
+ mDrawerList.addOnScrollListener(new DropShadowScrollListener(toolbar));
@IdRes int backButtonId = theme.resolveAttribute(R.attr.drawerBackButtonId, outValue, true)
? outValue.resourceId
diff --git a/car/core/src/main/java/androidx/car/util/CarUxRestrictionsHelper.java b/car/core/src/main/java/androidx/car/util/CarUxRestrictionsHelper.java
index 75375df..65ad2f8 100644
--- a/car/core/src/main/java/androidx/car/util/CarUxRestrictionsHelper.java
+++ b/car/core/src/main/java/androidx/car/util/CarUxRestrictionsHelper.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
+import android.os.SystemClock;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -54,7 +55,7 @@
}
mListener = listener;
mCar = Car.createCar(context, mServiceConnection);
- };
+ }
/**
* Starts monitoring any changes in {@link CarUxRestrictions}.
@@ -103,6 +104,29 @@
}
}
+ /**
+ * Gets the current UX restrictions {@link CarUxRestrictions} in place.
+ *
+ * @return current UX restrictions that is in effect. If the current UX restrictions cannot
+ * be obtained, the default of no active restrictions is returned.
+ */
+ @NonNull
+ public CarUxRestrictions getCurrentCarUxRestrictions() {
+ try {
+ if (mCarUxRestrictionsManager != null) {
+ return new CarUxRestrictions(
+ mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
+ }
+ } catch (CarNotConnectedException e) {
+ // Do nothing.
+ Log.w(TAG, "getCurrentCarUxRestrictions(); cannot get current UX restrictions.");
+ }
+
+ return new CarUxRestrictions.Builder(false,
+ CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos())
+ .build();
+ }
+
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
diff --git a/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java b/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
index 4d3a4fd..1c3567e 100644
--- a/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
+++ b/car/core/src/main/java/androidx/car/util/DropShadowScrollListener.java
@@ -37,7 +37,7 @@
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public class DropShadowScrollListener extends PagedListView.OnScrollListener {
+public class DropShadowScrollListener extends RecyclerView.OnScrollListener {
private static final String TAG = "DropShadowScrollListener";
private static final int ANIMATION_DURATION_MS = 100;
diff --git a/car/core/src/main/java/androidx/car/widget/AlphaJumpAdapter.java b/car/core/src/main/java/androidx/car/widget/AlphaJumpAdapter.java
index 7987323..e023cb7 100644
--- a/car/core/src/main/java/androidx/car/widget/AlphaJumpAdapter.java
+++ b/car/core/src/main/java/androidx/car/widget/AlphaJumpAdapter.java
@@ -20,6 +20,9 @@
/**
* An interface that you can implement on your Adapter to enable support for Alpha-Jump.
+ *
+ * <p>Alpha-Jump buckets only support characters from the {@code en} language. Characters from other
+ * languages are not supported and bucketing behavior is undefined.
*/
public interface AlphaJumpAdapter {
diff --git a/car/core/src/main/java/androidx/car/widget/AlphaJumpBucket.java b/car/core/src/main/java/androidx/car/widget/AlphaJumpBucket.java
index d96d315..2285969 100644
--- a/car/core/src/main/java/androidx/car/widget/AlphaJumpBucket.java
+++ b/car/core/src/main/java/androidx/car/widget/AlphaJumpBucket.java
@@ -18,6 +18,9 @@
/**
* A bucket represents a "button" in the alpha-jump menu.
+ *
+ * <p>Alpha-Jump buckets only support characters from the {@code en} language. Characters from other
+ * languages are not supported and bucketing behavior is undefined.
*/
public interface AlphaJumpBucket {
diff --git a/car/core/src/main/java/androidx/car/widget/AlphaJumpBucketer.java b/car/core/src/main/java/androidx/car/widget/AlphaJumpBucketer.java
index fd21708e..df506af 100644
--- a/car/core/src/main/java/androidx/car/widget/AlphaJumpBucketer.java
+++ b/car/core/src/main/java/androidx/car/widget/AlphaJumpBucketer.java
@@ -24,6 +24,9 @@
/**
* A helper class for building the list of buckets for alpha jump.
+ *
+ * <p>Alpha-Jump buckets only support characters from the {@code en} language. Characters from other
+ * languages are not supported and bucketing behavior is undefined.
*/
public class AlphaJumpBucketer {
private static final Character[] DEFAULT_INITIAL_CHARS = {
@@ -40,7 +43,7 @@
if (ch == '0') {
mBuckets.add(new Bucket("123", (String s) -> s.matches("^[0-9]")));
} else {
- String prefix = new String(new char[] {ch});
+ String prefix = new String(new char[]{ch});
mBuckets.add(new Bucket(prefix, (String s) -> s.startsWith(prefix.toLowerCase())));
}
}
@@ -60,7 +63,7 @@
/**
* Creates a list of {@link AlphaJumpBucket}s from the given iterable collection
* of strings.
- */
+ */
public List<AlphaJumpBucket> createBuckets(Iterable<String> values) {
return createBuckets(values.iterator());
}
@@ -91,7 +94,7 @@
*
* @param s The string to pre-process.
* @return The input string with whitespace trimmed, and also words like "the", "a" and so on
- * removed.
+ * removed.
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
static String preprocess(String s) {
diff --git a/car/core/src/main/java/androidx/car/widget/PagedListView.java b/car/core/src/main/java/androidx/car/widget/PagedListView.java
index f5c1a70..8ad0ec7 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedListView.java
@@ -51,6 +51,8 @@
import androidx.recyclerview.widget.RecyclerView;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
/**
* View that wraps a {@link RecyclerView} and a scroll bar that has
@@ -106,7 +108,8 @@
* which point we'll construct it and add it to the view hierarchy as a child of this frame
* layout.
*/
- @Nullable private AlphaJumpOverlayView mAlphaJumpView;
+ @Nullable
+ private AlphaJumpOverlayView mAlphaJumpView;
private int mRowsPerPage = -1;
private RecyclerView.Adapter<? extends RecyclerView.ViewHolder> mAdapter;
@@ -114,6 +117,8 @@
/** Maximum number of pages to show. */
private int mMaxPages = UNLIMITED_PAGES;
+ /** Package private to allow access to nested classes. */
+ final List<Callback> mCallbacks = new ArrayList<>();
OnScrollListener mOnScrollListener;
/** Used to check if there are more items added to the list. */
@@ -244,7 +249,34 @@
mSnapHelper = new PagedSnapHelper(context);
mSnapHelper.attachToRecyclerView(mRecyclerView);
- mRecyclerView.addOnScrollListener(mRecyclerViewOnScrollListener);
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrollStateChanged(recyclerView, newState);
+ }
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
+ }
+ }
+
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ if (mOnScrollListener != null) {
+ mOnScrollListener.onScrolled(recyclerView, dx, dy);
+
+ if (!isAtStart() && isAtEnd()) {
+ mOnScrollListener.onReachBottom();
+ }
+ }
+ if (!isAtStart() && isAtEnd()) {
+ for (Callback callback : mCallbacks) {
+ callback.onReachBottom();
+ }
+ }
+ updatePaginationButtons(false);
+ }
+ });
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 12);
if (a.getBoolean(R.styleable.PagedListView_verticallyCenterListContent, false)) {
@@ -298,12 +330,18 @@
switch (direction) {
case PagedScrollBarView.PaginationListener.PAGE_UP:
pageUp();
+ for (Callback callback : mCallbacks) {
+ callback.onScrollUpButtonClicked();
+ }
if (mOnScrollListener != null) {
mOnScrollListener.onScrollUpButtonClicked();
}
break;
case PagedScrollBarView.PaginationListener.PAGE_DOWN:
pageDown();
+ for (Callback callback : mCallbacks) {
+ callback.onScrollDownButtonClicked();
+ }
if (mOnScrollListener != null) {
mOnScrollListener.onScrollDownButtonClicked();
}
@@ -831,11 +869,53 @@
* PagedListView.
*
* @param listener The scroll listener to set.
+ * @deprecated Use {@link #addOnScrollListener(RecyclerView.OnScrollListener)} to be notified
+ * of scroll events within the PagedListView. To be notified of other PagedListView events, use
+ * {@link #registerCallback(Callback)}.
*/
+ @Deprecated
public void setOnScrollListener(OnScrollListener listener) {
mOnScrollListener = listener;
}
+ /**
+ * Adds a {@link RecyclerView.OnScrollListener} that will be notified of scroll events
+ * within the PagedListView.
+ *
+ * @param listener The scroll listener to add.
+ */
+ public void addOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
+ mRecyclerView.addOnScrollListener(listener);
+ }
+
+ /**
+ * Remove a {@link RecyclerView.OnScrollListener} that was notified of scroll events
+ * within the PagedListView.
+ *
+ * @param listener The scroll listener to remove.
+ */
+ public void removeOnScrollListener(@NonNull RecyclerView.OnScrollListener listener) {
+ mRecyclerView.removeOnScrollListener(listener);
+ }
+
+ /**
+ * Add a {@link Callback} that will be notified of PagedListView events.
+ *
+ * @param callback The callback to add.
+ */
+ public void registerCallback(@NonNull Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * Remove a {@link Callback} that was notified of PagedListView events.
+ *
+ * @param callback The callback to remove.
+ */
+ public void unregisterCallback(@NonNull Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
/** Returns the page the given position is on, starting with page 0. */
public int getPage(int position) {
if (mRowsPerPage == -1) {
@@ -1246,31 +1326,6 @@
}
}
- private final RecyclerView.OnScrollListener mRecyclerViewOnScrollListener =
- new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- if (mOnScrollListener != null) {
- mOnScrollListener.onScrolled(recyclerView, dx, dy);
-
- if (!isAtStart() && isAtEnd()) {
- mOnScrollListener.onReachBottom();
- }
- }
- updatePaginationButtons(false);
- }
-
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- if (mOnScrollListener != null) {
- mOnScrollListener.onScrollStateChanged(recyclerView, newState);
- }
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- mHandler.postDelayed(mPaginationRunnable, PAGINATION_HOLD_DELAY_MS);
- }
- }
- };
-
final Runnable mPaginationRunnable =
new Runnable() {
@Override
@@ -1291,25 +1346,56 @@
private final Runnable mUpdatePaginationRunnable =
() -> updatePaginationButtons(true /*animate*/);
- /** Used to listen for {@code PagedListView} scroll events. */
+ /** Used to listen for {@code PagedListView} events. */
+ public interface Callback {
+ /**
+ * Called when the {@code PagedListView} has been scrolled so that the last item is
+ * completely visible.
+ */
+ default void onReachBottom() {
+ }
+
+ /** Called when scroll up button is clicked */
+ default void onScrollUpButtonClicked() {
+ }
+
+ /** Called when scroll down button is clicked */
+ default void onScrollDownButtonClicked() {
+ }
+ }
+
+ /**
+ * Used to listen for {@code PagedListView} scroll events.
+ *
+ * @deprecated Use {@link RecyclerView.OnScrollListener} to be notified of scroll events within
+ * the PagedListView. To be notified of other PagedListView events, use {@link Callback}.
+ */
+ @Deprecated
public abstract static class OnScrollListener {
/**
* Called when the {@code PagedListView} has been scrolled so that the last item is
* completely visible.
*/
- public void onReachBottom() {}
+ public void onReachBottom() {
+ }
+
/** Called when scroll up button is clicked */
- public void onScrollUpButtonClicked() {}
+ public void onScrollUpButtonClicked() {
+ }
+
/** Called when scroll down button is clicked */
- public void onScrollDownButtonClicked() {}
+ public void onScrollDownButtonClicked() {
+ }
/**
* Called when RecyclerView.OnScrollListener#onScrolled is called. See
* RecyclerView.OnScrollListener
*/
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {}
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ }
/** See RecyclerView.OnScrollListener */
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {}
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ }
}
}
diff --git a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
index 9041a42..3279cf3 100644
--- a/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
+++ b/car/core/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -55,6 +55,10 @@
* Called when the 'alpha jump' button is clicked and the linked view should switch into
* alpha jump mode, where we display a list of buttons to allow the user to quickly scroll
* to a certain point in the list, bypassing a lot of manual scrolling.
+ *
+ * <p>AlphaJump buckets only support characters from the {@code en} language. Characters
+ * from other languages not supported and bucketing behavior is undefined. AlphaJump overlay
+ * is still displayed if all buckets are empty.
*/
void onAlphaJump();
}
diff --git a/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java b/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
index e108863..368ae65 100644
--- a/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/RadioButtonListItem.java
@@ -19,9 +19,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +29,7 @@
import android.widget.TextView;
import androidx.annotation.DimenRes;
+import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -79,7 +78,7 @@
private final Context mContext;
private boolean mIsEnabled = true;
- @Nullable private Icon mPrimaryActionIcon;
+ private Drawable mPrimaryActionIconDrawable;
@PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
private int mTextStartMargin;
@@ -148,21 +147,44 @@
* Sets {@code Primary Action} to be represented by an icon. The size of icon automatically
* adjusts the start of {@code Text}.
*
- * @param icon the icon to set as primary action. Setting {@code null} clears the icon and
- * aligns text to the start of list item; {@code size} will be ignored.
+ * <p>Call {@link #setPrimaryActionNoIcon()} to clear the content and aligns text to the start
+ * of list item
+ *
+ * @param drawable the Drawable to set as primary action.
* @param size constant that represents the size of icon. See
* {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
* {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, and
* {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
- * If {@code null} is passed in for icon, size will be ignored.
*/
- public void setPrimaryActionIcon(@NonNull Icon icon, @PrimaryActionIconSize int size) {
- mPrimaryActionIcon = icon;
+ public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
+ mPrimaryActionIconDrawable = drawable;
mPrimaryActionIconSize = size;
markDirty();
}
/**
+ * Sets {@code Primary Action} to be represented by an icon. The size of icon automatically
+ * adjusts the start of {@code Text}.
+ *
+ * @param iconResId the resource identifier of the drawable.
+ * @param size constant that represents the size of icon. See
+ * {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
+ * {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM}, and
+ * {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
+ */
+ public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
+ setPrimaryActionIcon(getContext().getDrawable(iconResId), size);
+ }
+
+ /**
+ * Sets {@code Primary Action} to have no icon. Text would align to the start of list item.
+ */
+ public void setPrimaryActionNoIcon() {
+ mPrimaryActionIconDrawable = null;
+ markDirty();
+ }
+
+ /**
* Sets text to be displayed next to icon.
*
* @param text Text to be displayed, or {@code null} to clear the content.
@@ -242,13 +264,11 @@
private void setPrimaryIconContent() {
mBinders.add(vh -> {
- if (mPrimaryActionIcon == null) {
+ if (mPrimaryActionIconDrawable == null) {
vh.getPrimaryIcon().setVisibility(View.GONE);
} else {
vh.getPrimaryIcon().setVisibility(View.VISIBLE);
- mPrimaryActionIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
+ vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
}
});
}
@@ -262,7 +282,7 @@
* at the same position in list item regardless of item height.
*/
private void setPrimaryIconLayout() {
- if (mPrimaryActionIcon == null) {
+ if (mPrimaryActionIconDrawable == null) {
return;
}
@@ -322,7 +342,7 @@
*/
private void setTextStartMargin() {
int offset = 0;
- if (mPrimaryActionIcon != null) {
+ if (mPrimaryActionIconDrawable != null) {
// If there is an icon, offset text to accommodate it.
@DimenRes int startMarginResId =
mPrimaryActionIconSize == PRIMARY_ACTION_ICON_SIZE_LARGE
diff --git a/car/core/src/main/java/androidx/car/widget/SeekbarListItem.java b/car/core/src/main/java/androidx/car/widget/SeekbarListItem.java
index 3c9a82d..8787ba7 100644
--- a/car/core/src/main/java/androidx/car/widget/SeekbarListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/SeekbarListItem.java
@@ -21,9 +21,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -105,7 +102,6 @@
private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();
@PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
- private Icon mPrimaryActionIcon;
private Drawable mPrimaryActionIconDrawable;
private View.OnClickListener mPrimaryActionIconOnClickListener;
@@ -120,7 +116,6 @@
private final int mSupplementalGuidelineBegin;
@SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
- private Icon mSupplementalIcon;
private Drawable mSupplementalIconDrawable;
private View.OnClickListener mSupplementalIconOnClickListener;
private boolean mShowSupplementalIconDivider;
@@ -296,13 +291,7 @@
mBinders.add(vh -> {
vh.getPrimaryIcon().setVisibility(View.VISIBLE);
- if (mPrimaryActionIcon != null) {
- mPrimaryActionIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
- } else {
- vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
- }
+ vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
vh.getPrimaryIcon().setOnClickListener(
mPrimaryActionIconOnClickListener);
vh.getPrimaryIcon().setClickable(
@@ -435,14 +424,7 @@
if (mShowSupplementalIconDivider) {
vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
}
-
- if (mSupplementalIcon != null) {
- mSupplementalIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getSupplementalIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
- } else {
- vh.getSupplementalIcon().setImageDrawable(mSupplementalIconDrawable);
- }
+ vh.getSupplementalIcon().setImageDrawable(mSupplementalIconDrawable);
vh.getSupplementalIcon().setOnClickListener(
mSupplementalIconOnClickListener);
@@ -458,35 +440,18 @@
/**
* Sets {@code Primary Action} to be represented by an icon.
*
- * @param icon An icon to set as primary action.
- */
- public void setPrimaryActionIcon(@NonNull Icon icon) {
- mPrimaryActionType = PRIMARY_ACTION_TYPE_SMALL_ICON;
- mPrimaryActionIcon = icon;
- markDirty();
- }
-
- /**
- * Sets {@code Primary Action} to be represented by an icon.
- *
* @param iconResId the resource identifier of the drawable.
- *
- * @deprecated Use {@link #setPrimaryActionIcon(Icon)}.
*/
- @Deprecated
public void setPrimaryActionIcon(@DrawableRes int iconResId) {
- setPrimaryActionIcon(Icon.createWithResource(getContext(), iconResId));
+ setPrimaryActionIcon(getContext().getDrawable(iconResId));
}
/**
* Sets {@code Primary Action} to be represented by an icon.
*
- * @param drawable the Drawable to set, or null to clear the content.
- *
- * @deprecated Use {@link #setPrimaryActionIcon(Icon)}.
+ * @param drawable the Drawable to set.
*/
- @Deprecated
- public void setPrimaryActionIcon(Drawable drawable) {
+ public void setPrimaryActionIcon(@NonNull Drawable drawable) {
mPrimaryActionType = PRIMARY_ACTION_TYPE_SMALL_ICON;
mPrimaryActionIconDrawable = drawable;
markDirty();
@@ -516,31 +481,15 @@
/**
* Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
*/
- public void setSupplementalIcon(@NonNull Icon icon, boolean showSupplementalIconDivider) {
- mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
- mSupplementalIcon = icon;
- mShowSupplementalIconDivider = showSupplementalIconDivider;
- markDirty();
- }
-
- /**
- * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
- */
- @Deprecated
public void setSupplementalIcon(@DrawableRes int iconResId,
boolean showSupplementalIconDivider) {
- setSupplementalIcon(Icon.createWithResource(getContext(), iconResId),
+ setSupplementalIcon(getContext().getDrawable(iconResId),
showSupplementalIconDivider);
}
/**
* Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
*/
- @Deprecated
public void setSupplementalIcon(@NonNull Drawable drawable,
boolean showSupplementalIconDivider) {
mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
diff --git a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
index d0ae858..71d5a5f 100644
--- a/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/SwitchListItem.java
@@ -20,9 +20,7 @@
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
@@ -34,6 +32,7 @@
import androidx.annotation.CallSuper;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
+import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -113,7 +112,7 @@
private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>();
@PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
- private Icon mPrimaryActionIcon;
+ private Drawable mPrimaryActionIconDrawable;
@PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
private CharSequence mTitle;
@@ -202,19 +201,31 @@
/**
* Sets {@code Primary Action} to be represented by an icon.
*
- * @param icon An icon to set as primary action.
+ * @param drawable the Drawable to set.
* @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
* {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
* {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
*/
- public void setPrimaryActionIcon(@NonNull Icon icon, @PrimaryActionIconSize int size) {
+ public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
- mPrimaryActionIcon = icon;
+ mPrimaryActionIconDrawable = drawable;
mPrimaryActionIconSize = size;
markDirty();
}
/**
+ * Sets {@code Primary Action} to be represented by an icon.
+ *
+ * @param iconResId the resource identifier of the drawable.
+ * @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
+ * {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
+ * {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
+ */
+ public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
+ setPrimaryActionIcon(getContext().getDrawable(iconResId), size);
+ }
+
+ /**
* Sets {@code Primary Action} to be empty icon.
*
* <p>{@code Text} would have a start margin as if {@code Primary Action} were set to primary
@@ -324,9 +335,7 @@
case PRIMARY_ACTION_TYPE_ICON:
mBinders.add(vh -> {
vh.getPrimaryIcon().setVisibility(View.VISIBLE);
- mPrimaryActionIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
+ vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
});
break;
case PRIMARY_ACTION_TYPE_EMPTY_ICON:
diff --git a/car/core/src/main/java/androidx/car/widget/TextListItem.java b/car/core/src/main/java/androidx/car/widget/TextListItem.java
index 1b35a1e..8090bf8 100644
--- a/car/core/src/main/java/androidx/car/widget/TextListItem.java
+++ b/car/core/src/main/java/androidx/car/widget/TextListItem.java
@@ -21,9 +21,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Handler;
-import android.os.Looper;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -38,7 +35,6 @@
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.car.R;
import androidx.car.util.CarUxRestrictionsUtils;
import androidx.car.uxrestrictions.CarUxRestrictions;
@@ -131,7 +127,6 @@
private View.OnClickListener mOnClickListener;
@PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
- private Icon mPrimaryActionIcon;
private Drawable mPrimaryActionIconDrawable;
@PrimaryActionIconSize private int mPrimaryActionIconSize = PRIMARY_ACTION_ICON_SIZE_SMALL;
@@ -142,7 +137,6 @@
private final int mSupplementalGuidelineBegin;
@SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
- private Icon mSupplementalIcon;
private Drawable mSupplementalIconDrawable;
private View.OnClickListener mSupplementalIconOnClickListener;
private boolean mShowSupplementalIconDivider;
@@ -250,13 +244,7 @@
case PRIMARY_ACTION_TYPE_ICON:
mBinders.add(vh -> {
vh.getPrimaryIcon().setVisibility(View.VISIBLE);
- if (mPrimaryActionIcon != null) {
- mPrimaryActionIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getPrimaryIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
- } else {
- vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
- }
+ vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
});
break;
case PRIMARY_ACTION_TYPE_EMPTY_ICON:
@@ -483,13 +471,7 @@
vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
}
- if (mSupplementalIcon != null) {
- mSupplementalIcon.loadDrawableAsync(getContext(),
- drawable -> vh.getSupplementalIcon().setImageDrawable(drawable),
- new Handler(Looper.getMainLooper()));
- } else {
- vh.getSupplementalIcon().setImageDrawable(mSupplementalIconDrawable);
- }
+ vh.getSupplementalIcon().setImageDrawable(mSupplementalIconDrawable);
vh.getSupplementalIcon().setOnClickListener(
mSupplementalIconOnClickListener);
@@ -547,45 +529,27 @@
/**
* Sets {@code Primary Action} to be represented by an icon.
*
- * @param icon An icon to set as primary action.
- * @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
- * {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
- * {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
- */
- public void setPrimaryActionIcon(@NonNull Icon icon, @PrimaryActionIconSize int size) {
- mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
- mPrimaryActionIcon = icon;
- mPrimaryActionIconSize = size;
- markDirty();
- }
-
- /**
- * Sets {@code Primary Action} to be represented by an icon.
- *
* @param iconResId the resource identifier of the drawable.
* @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
* {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
* {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
- *
- * @deprecated Use {@link #setPrimaryActionIcon(Icon, int)}.
*/
- @Deprecated
public void setPrimaryActionIcon(@DrawableRes int iconResId, @PrimaryActionIconSize int size) {
- setPrimaryActionIcon(Icon.createWithResource(getContext(), iconResId), size);
+ setPrimaryActionIcon(getContext().getDrawable(iconResId), size);
}
/**
* Sets {@code Primary Action} to be represented by an icon.
*
- * @param drawable the Drawable to set, or null to clear the content.
+ * <p>Call {@link #setPrimaryActionEmptyIcon()} or {@link #setPrimaryActionNoIcon()} to clear
+ * content.
+ *
+ * @param drawable the Drawable to set.
* @param size small/medium/large. Available as {@link #PRIMARY_ACTION_ICON_SIZE_SMALL},
* {@link #PRIMARY_ACTION_ICON_SIZE_MEDIUM},
* {@link #PRIMARY_ACTION_ICON_SIZE_LARGE}.
- *
- * @deprecated Use {@link #setPrimaryActionIcon(Icon, int)}.
*/
- @Deprecated
- public void setPrimaryActionIcon(@Nullable Drawable drawable, @PrimaryActionIconSize int size) {
+ public void setPrimaryActionIcon(@NonNull Drawable drawable, @PrimaryActionIconSize int size) {
mPrimaryActionType = PRIMARY_ACTION_TYPE_ICON;
mPrimaryActionIconDrawable = drawable;
mPrimaryActionIconSize = size;
@@ -639,21 +603,6 @@
}
/**
- * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
- *
- * @param icon An icon to set as supplemental action.
- * @param showDivider whether to display a vertical bar that separates {@code text} and
- * {@code Supplemental Icon}.
- */
- public void setSupplementalIcon(@NonNull Icon icon, boolean showDivider) {
- mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
-
- mSupplementalIcon = icon;
- mShowSupplementalIconDivider = showDivider;
- markDirty();
- }
-
- /**
* Sets an {@code OnClickListener} for the icon representing the {@code Supplemental Action}.
*
* @param listener the callback that will run when icon is clicked.
@@ -669,25 +618,19 @@
* @param iconResId drawable resource id.
* @param showDivider whether to display a vertical bar that separates {@code text} and
* {@code Supplemental Icon}.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
*/
- @Deprecated
- public void setSupplementalIcon(int iconResId, boolean showDivider) {
- setSupplementalIcon(Icon.createWithResource(getContext(), iconResId), showDivider);
+ public void setSupplementalIcon(@DrawableRes int iconResId, boolean showDivider) {
+ setSupplementalIcon(getContext().getDrawable(iconResId), showDivider);
}
/**
* Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
*
- * @param drawable the Drawable to set, or null to clear the content.
+ * @param drawable the Drawable to set.
* @param showDivider whether to display a vertical bar that separates {@code text} and
* {@code Supplemental Icon}.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)}.
*/
- @Deprecated
- public void setSupplementalIcon(Drawable drawable, boolean showDivider) {
+ public void setSupplementalIcon(@NonNull Drawable drawable, boolean showDivider) {
setSupplementalIcon(drawable, showDivider, null);
}
@@ -698,14 +641,10 @@
* @param showDivider whether to display a vertical bar that separates {@code text} and
* {@code Supplemental Icon}.
* @param listener the callback that will run when icon is clicked.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)} and
- * {@link #setSupplementalIconOnClickListener(View.OnClickListener)}.
*/
- @Deprecated
- public void setSupplementalIcon(int iconResId, boolean showDivider,
+ public void setSupplementalIcon(@DrawableRes int iconResId, boolean showDivider,
View.OnClickListener listener) {
- setSupplementalIcon(Icon.createWithResource(getContext(), iconResId), showDivider);
+ setSupplementalIcon(getContext().getDrawable(iconResId), showDivider);
setSupplementalIconOnClickListener(listener);
}
@@ -716,11 +655,7 @@
* @param showDivider whether to display a vertical bar that separates {@code text} and
* {@code Supplemental Icon}.
* @param listener the callback that will run when icon is clicked.
- *
- * @deprecated Use {@link #setSupplementalIcon(Icon, boolean)} and
- * {@link #setSupplementalIconOnClickListener(View.OnClickListener)}.
*/
- @Deprecated
public void setSupplementalIcon(Drawable drawable, boolean showDivider,
View.OnClickListener listener) {
mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
diff --git a/collection/api/1.1.0-alpha04.txt b/collection/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..179d17f
--- /dev/null
+++ b/collection/api/1.1.0-alpha04.txt
@@ -0,0 +1,183 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+ ctor public ArrayMap();
+ ctor public ArrayMap(int);
+ ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public java.util.Set<java.util.Map.Entry<K,V>>! entrySet();
+ method public java.util.Set<K>! keySet();
+ method public void putAll(java.util.Map<? extends K,? extends V>!);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public java.util.Collection<V>! values();
+ }
+
+ public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+ ctor public ArraySet();
+ ctor public ArraySet(int);
+ ctor public ArraySet(androidx.collection.ArraySet<E>?);
+ ctor public ArraySet(java.util.Collection<E>?);
+ method public boolean add(E?);
+ method public void addAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean addAll(java.util.Collection<? extends E>);
+ method public void clear();
+ method public boolean contains(Object?);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public void ensureCapacity(int);
+ method public int indexOf(Object?);
+ method public boolean isEmpty();
+ method public java.util.Iterator<E>! iterator();
+ method public boolean remove(Object?);
+ method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public E! removeAt(int);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public int size();
+ method public Object[] toArray();
+ method public <T> T[] toArray(T[]);
+ method public E? valueAt(int);
+ }
+
+ public final class CircularArray<E> {
+ ctor public CircularArray();
+ ctor public CircularArray(int);
+ method public void addFirst(E!);
+ method public void addLast(E!);
+ method public void clear();
+ method public E! get(int);
+ method public E! getFirst();
+ method public E! getLast();
+ method public boolean isEmpty();
+ method public E! popFirst();
+ method public E! popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public final class CircularIntArray {
+ ctor public CircularIntArray();
+ ctor public CircularIntArray(int);
+ method public void addFirst(int);
+ method public void addLast(int);
+ method public void clear();
+ method public int get(int);
+ method public int getFirst();
+ method public int getLast();
+ method public boolean isEmpty();
+ method public int popFirst();
+ method public int popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public class LongSparseArray<E> implements java.lang.Cloneable {
+ ctor public LongSparseArray();
+ ctor public LongSparseArray(int);
+ method public void append(long, E!);
+ method public void clear();
+ method public androidx.collection.LongSparseArray<E>! clone();
+ method public boolean containsKey(long);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(long);
+ method public E? get(long);
+ method public E! get(long, E!);
+ method public int indexOfKey(long);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public long keyAt(int);
+ method public void put(long, E!);
+ method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+ method public E? putIfAbsent(long, E!);
+ method public void remove(long);
+ method public boolean remove(long, Object!);
+ method public void removeAt(int);
+ method public E? replace(long, E!);
+ method public boolean replace(long, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+ public class LruCache<K, V> {
+ ctor public LruCache(int);
+ method protected V? create(K);
+ method public final int createCount();
+ method protected void entryRemoved(boolean, K, V, V?);
+ method public final void evictAll();
+ method public final int evictionCount();
+ method public final V? get(K);
+ method public final int hitCount();
+ method public final int maxSize();
+ method public final int missCount();
+ method public final V? put(K, V);
+ method public final int putCount();
+ method public final V? remove(K);
+ method public void resize(int);
+ method public final int size();
+ method protected int sizeOf(K, V);
+ method public final java.util.Map<K,V>! snapshot();
+ method public final String toString();
+ method public void trimToSize(int);
+ }
+
+ public class SimpleArrayMap<K, V> {
+ ctor public SimpleArrayMap();
+ ctor public SimpleArrayMap(int);
+ ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K,V>!);
+ method public void clear();
+ method public boolean containsKey(Object?);
+ method public boolean containsValue(Object!);
+ method public void ensureCapacity(int);
+ method public V? get(Object!);
+ method public V! getOrDefault(Object!, V!);
+ method public int indexOfKey(Object?);
+ method public boolean isEmpty();
+ method public K! keyAt(int);
+ method public V? put(K!, V!);
+ method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+ method public V? putIfAbsent(K!, V!);
+ method public V? remove(Object!);
+ method public boolean remove(Object!, Object!);
+ method public V! removeAt(int);
+ method public V? replace(K!, V!);
+ method public boolean replace(K!, V!, V!);
+ method public V! setValueAt(int, V!);
+ method public int size();
+ method public V! valueAt(int);
+ }
+
+ public class SparseArrayCompat<E> implements java.lang.Cloneable {
+ ctor public SparseArrayCompat();
+ ctor public SparseArrayCompat(int);
+ method public void append(int, E!);
+ method public void clear();
+ method public androidx.collection.SparseArrayCompat<E>! clone();
+ method public boolean containsKey(int);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(int);
+ method public E? get(int);
+ method public E! get(int, E!);
+ method public int indexOfKey(int);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public int keyAt(int);
+ method public void put(int, E!);
+ method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+ method public E? putIfAbsent(int, E!);
+ method public void remove(int);
+ method public boolean remove(int, Object!);
+ method public void removeAt(int);
+ method public void removeAtRange(int, int);
+ method public E? replace(int, E!);
+ method public boolean replace(int, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+}
+
diff --git a/collection/api/1.1.0-beta01.txt b/collection/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..179d17f
--- /dev/null
+++ b/collection/api/1.1.0-beta01.txt
@@ -0,0 +1,183 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public class ArrayMap<K, V> extends androidx.collection.SimpleArrayMap<K,V> implements java.util.Map<K,V> {
+ ctor public ArrayMap();
+ ctor public ArrayMap(int);
+ ctor public ArrayMap(androidx.collection.SimpleArrayMap!);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public java.util.Set<java.util.Map.Entry<K,V>>! entrySet();
+ method public java.util.Set<K>! keySet();
+ method public void putAll(java.util.Map<? extends K,? extends V>!);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public java.util.Collection<V>! values();
+ }
+
+ public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
+ ctor public ArraySet();
+ ctor public ArraySet(int);
+ ctor public ArraySet(androidx.collection.ArraySet<E>?);
+ ctor public ArraySet(java.util.Collection<E>?);
+ method public boolean add(E?);
+ method public void addAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean addAll(java.util.Collection<? extends E>);
+ method public void clear();
+ method public boolean contains(Object?);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public void ensureCapacity(int);
+ method public int indexOf(Object?);
+ method public boolean isEmpty();
+ method public java.util.Iterator<E>! iterator();
+ method public boolean remove(Object?);
+ method public boolean removeAll(androidx.collection.ArraySet<? extends E>);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public E! removeAt(int);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public int size();
+ method public Object[] toArray();
+ method public <T> T[] toArray(T[]);
+ method public E? valueAt(int);
+ }
+
+ public final class CircularArray<E> {
+ ctor public CircularArray();
+ ctor public CircularArray(int);
+ method public void addFirst(E!);
+ method public void addLast(E!);
+ method public void clear();
+ method public E! get(int);
+ method public E! getFirst();
+ method public E! getLast();
+ method public boolean isEmpty();
+ method public E! popFirst();
+ method public E! popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public final class CircularIntArray {
+ ctor public CircularIntArray();
+ ctor public CircularIntArray(int);
+ method public void addFirst(int);
+ method public void addLast(int);
+ method public void clear();
+ method public int get(int);
+ method public int getFirst();
+ method public int getLast();
+ method public boolean isEmpty();
+ method public int popFirst();
+ method public int popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public class LongSparseArray<E> implements java.lang.Cloneable {
+ ctor public LongSparseArray();
+ ctor public LongSparseArray(int);
+ method public void append(long, E!);
+ method public void clear();
+ method public androidx.collection.LongSparseArray<E>! clone();
+ method public boolean containsKey(long);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(long);
+ method public E? get(long);
+ method public E! get(long, E!);
+ method public int indexOfKey(long);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public long keyAt(int);
+ method public void put(long, E!);
+ method public void putAll(androidx.collection.LongSparseArray<? extends E>);
+ method public E? putIfAbsent(long, E!);
+ method public void remove(long);
+ method public boolean remove(long, Object!);
+ method public void removeAt(int);
+ method public E? replace(long, E!);
+ method public boolean replace(long, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+ public class LruCache<K, V> {
+ ctor public LruCache(int);
+ method protected V? create(K);
+ method public final int createCount();
+ method protected void entryRemoved(boolean, K, V, V?);
+ method public final void evictAll();
+ method public final int evictionCount();
+ method public final V? get(K);
+ method public final int hitCount();
+ method public final int maxSize();
+ method public final int missCount();
+ method public final V? put(K, V);
+ method public final int putCount();
+ method public final V? remove(K);
+ method public void resize(int);
+ method public final int size();
+ method protected int sizeOf(K, V);
+ method public final java.util.Map<K,V>! snapshot();
+ method public final String toString();
+ method public void trimToSize(int);
+ }
+
+ public class SimpleArrayMap<K, V> {
+ ctor public SimpleArrayMap();
+ ctor public SimpleArrayMap(int);
+ ctor public SimpleArrayMap(androidx.collection.SimpleArrayMap<K,V>!);
+ method public void clear();
+ method public boolean containsKey(Object?);
+ method public boolean containsValue(Object!);
+ method public void ensureCapacity(int);
+ method public V? get(Object!);
+ method public V! getOrDefault(Object!, V!);
+ method public int indexOfKey(Object?);
+ method public boolean isEmpty();
+ method public K! keyAt(int);
+ method public V? put(K!, V!);
+ method public void putAll(androidx.collection.SimpleArrayMap<? extends K,? extends V>);
+ method public V? putIfAbsent(K!, V!);
+ method public V? remove(Object!);
+ method public boolean remove(Object!, Object!);
+ method public V! removeAt(int);
+ method public V? replace(K!, V!);
+ method public boolean replace(K!, V!, V!);
+ method public V! setValueAt(int, V!);
+ method public int size();
+ method public V! valueAt(int);
+ }
+
+ public class SparseArrayCompat<E> implements java.lang.Cloneable {
+ ctor public SparseArrayCompat();
+ ctor public SparseArrayCompat(int);
+ method public void append(int, E!);
+ method public void clear();
+ method public androidx.collection.SparseArrayCompat<E>! clone();
+ method public boolean containsKey(int);
+ method public boolean containsValue(E!);
+ method @Deprecated public void delete(int);
+ method public E? get(int);
+ method public E! get(int, E!);
+ method public int indexOfKey(int);
+ method public int indexOfValue(E!);
+ method public boolean isEmpty();
+ method public int keyAt(int);
+ method public void put(int, E!);
+ method public void putAll(androidx.collection.SparseArrayCompat<? extends E>);
+ method public E? putIfAbsent(int, E!);
+ method public void remove(int);
+ method public boolean remove(int, Object!);
+ method public void removeAt(int);
+ method public void removeAtRange(int, int);
+ method public E? replace(int, E!);
+ method public boolean replace(int, E!, E!);
+ method public void setValueAt(int, E!);
+ method public int size();
+ method public E! valueAt(int);
+ }
+
+}
+
diff --git a/collection/api/restricted_1.1.0-alpha04.txt b/collection/api/restricted_1.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/api/restricted_1.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/api/restricted_1.1.0-beta01.txt b/collection/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/api/restricted_current.txt b/collection/api/restricted_current.txt
index 7345795..da4f6cc 100644
--- a/collection/api/restricted_current.txt
+++ b/collection/api/restricted_current.txt
@@ -1,9 +1 @@
// Signature format: 3.0
-package androidx.collection {
-
- public final class ArraySet<E> implements java.util.Collection<E> java.util.Set<E> {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void append(E!);
- }
-
-}
-
diff --git a/collection/ktx/api/1.1.0-alpha04.txt b/collection/ktx/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..3fe6a36
--- /dev/null
+++ b/collection/ktx/api/1.1.0-alpha04.txt
@@ -0,0 +1,52 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public final class ArrayMapKt {
+ ctor public ArrayMapKt();
+ method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+ method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+ }
+
+ public final class ArraySetKt {
+ ctor public ArraySetKt();
+ method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+ method public static <T> androidx.collection.ArraySet<T> arraySetOf(T... values);
+ }
+
+ public final class LongSparseArrayKt {
+ ctor public LongSparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+ method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+ method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+ method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ ctor public LruCacheKt();
+ method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+ }
+
+ public final class SparseArrayKt {
+ ctor public SparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+ method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+ method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+ }
+
+}
+
diff --git a/collection/ktx/api/1.1.0-beta01.txt b/collection/ktx/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..3fe6a36
--- /dev/null
+++ b/collection/ktx/api/1.1.0-beta01.txt
@@ -0,0 +1,52 @@
+// Signature format: 3.0
+package androidx.collection {
+
+ public final class ArrayMapKt {
+ ctor public ArrayMapKt();
+ method public static inline <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf();
+ method public static <K, V> androidx.collection.ArrayMap<K,V> arrayMapOf(kotlin.Pair<? extends K,? extends V>... pairs);
+ }
+
+ public final class ArraySetKt {
+ ctor public ArraySetKt();
+ method public static inline <T> androidx.collection.ArraySet<T> arraySetOf();
+ method public static <T> androidx.collection.ArraySet<T> arraySetOf(T... values);
+ }
+
+ public final class LongSparseArrayKt {
+ ctor public LongSparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.LongSparseArray<T>, long key);
+ method public static inline <T> void forEach(androidx.collection.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.LongSparseArray<T>, long key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.LongSparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.LongSparseArray<T>);
+ method public static <T> kotlin.collections.LongIterator keyIterator(androidx.collection.LongSparseArray<T>);
+ method public static operator <T> androidx.collection.LongSparseArray<T> plus(androidx.collection.LongSparseArray<T>, androidx.collection.LongSparseArray<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static inline operator <T> void set(androidx.collection.LongSparseArray<T>, long key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ ctor public LruCacheKt();
+ method public static inline <K, V> androidx.collection.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+ }
+
+ public final class SparseArrayKt {
+ ctor public SparseArrayKt();
+ method public static inline operator <T> boolean contains(androidx.collection.SparseArrayCompat<T>, int key);
+ method public static inline <T> void forEach(androidx.collection.SparseArrayCompat<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(androidx.collection.SparseArrayCompat<T>, int key, T! defaultValue);
+ method public static inline <T> T! getOrElse(androidx.collection.SparseArrayCompat<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(androidx.collection.SparseArrayCompat<T>);
+ method public static inline <T> boolean isNotEmpty(androidx.collection.SparseArrayCompat<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(androidx.collection.SparseArrayCompat<T>);
+ method public static operator <T> androidx.collection.SparseArrayCompat<T> plus(androidx.collection.SparseArrayCompat<T>, androidx.collection.SparseArrayCompat<T> other);
+ method @Deprecated public static <T> boolean remove(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static inline operator <T> void set(androidx.collection.SparseArrayCompat<T>, int key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(androidx.collection.SparseArrayCompat<T>);
+ }
+
+}
+
diff --git a/collection/ktx/api/restricted_1.1.0-alpha04.txt b/collection/ktx/api/restricted_1.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/ktx/api/restricted_1.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/ktx/api/restricted_1.1.0-beta01.txt b/collection/ktx/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/collection/ktx/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/collection/src/main/java/androidx/collection/ArraySet.java b/collection/src/main/java/androidx/collection/ArraySet.java
index 7963bbc..0ae9d0e 100644
--- a/collection/src/main/java/androidx/collection/ArraySet.java
+++ b/collection/src/main/java/androidx/collection/ArraySet.java
@@ -16,11 +16,8 @@
package androidx.collection;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
import java.lang.reflect.Array;
import java.util.Collection;
@@ -406,35 +403,6 @@
}
/**
- * Special fast path for appending items to the end of the array without validation.
- * The array must already be large enough to contain the item.
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- public void append(E value) {
- final int index = mSize;
- final int hash = value == null ? 0 : value.hashCode();
- if (index >= mHashes.length) {
- throw new IllegalStateException("Array is full");
- }
- if (index > 0 && mHashes[index - 1] > hash) {
- // Cannot optimize since it would break the sorted order - fallback to add()
- if (DEBUG) {
- RuntimeException e = new RuntimeException("here");
- System.err.println(TAG + " New hash " + hash
- + " is before end of array hash " + mHashes[index - 1]
- + " at index " + index);
- e.printStackTrace();
- }
- add(value);
- return;
- }
- mSize = index + 1;
- mHashes[index] = hash;
- mArray[index] = value;
- }
-
- /**
* Perform a {@link #add(Object)} of all values in <var>array</var>
* @param array The array whose contents are to be retrieved.
*/
diff --git a/concurrent/futures/src/test/java/androidx/concurrent/futures/CallbackToFutureAdapterTest.java b/concurrent/futures/src/test/java/androidx/concurrent/futures/CallbackToFutureAdapterTest.java
index 8e8148c..5e15221 100644
--- a/concurrent/futures/src/test/java/androidx/concurrent/futures/CallbackToFutureAdapterTest.java
+++ b/concurrent/futures/src/test/java/androidx/concurrent/futures/CallbackToFutureAdapterTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
@@ -81,9 +82,9 @@
assertThat(t).isEqualTo(t);
}
- // Temporarily disabled due to b/120236209.
+ @Test
public void testGcedCallback() throws Exception {
- AtomicBoolean wasCalled = new AtomicBoolean();
+ CountDownLatch wasCalled = new CountDownLatch(1);
ListenableFuture<String> future = exampleLeakyCallbackAdapter(wasCalled);
final long deadline = System.nanoTime() + TIMEOUT_NANOS;
while (!future.isDone() && (deadline - System.nanoTime() > 0)) {
@@ -93,7 +94,7 @@
System.runFinalization();
latch.await(GC_AWAIT_TIME_MS, TimeUnit.MILLISECONDS);
}
- assertThat(wasCalled.get()).isTrue();
+ assertThat(wasCalled.await(TIMEOUT_NANOS, NANOSECONDS)).isTrue();
try {
future.get();
Assert.fail("Future was expected to fail");
@@ -117,7 +118,7 @@
};
}
- private ListenableFuture<String> exampleLeakyCallbackAdapter(final AtomicBoolean wasCalled) {
+ private ListenableFuture<String> exampleLeakyCallbackAdapter(final CountDownLatch wasCalled) {
return CallbackToFutureAdapter.getFuture(
new CallbackToFutureAdapter.Resolver<String>() {
@Override
@@ -132,7 +133,7 @@
completer.addCancellationListener(new Runnable() {
@Override
public void run() {
- wasCalled.set(true);
+ wasCalled.countDown();
}
}, directExecutor());
// Whoops! Forgot to actually call the callback!
diff --git a/core/api/1.1.0-alpha06.ignore b/core/api/1.1.0-alpha06.ignore
new file mode 100644
index 0000000..efa470d
--- /dev/null
+++ b/core/api/1.1.0-alpha06.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedDeprecatedMethod: androidx.core.graphics.drawable.IconCompat#createFromIcon(android.graphics.drawable.Icon):
+ Removed deprecated method androidx.core.graphics.drawable.IconCompat.createFromIcon(android.graphics.drawable.Icon)
+
+
diff --git a/core/api/1.1.0-alpha06.txt b/core/api/1.1.0-alpha06.txt
new file mode 100644
index 0000000..84939d0
--- /dev/null
+++ b/core/api/1.1.0-alpha06.txt
@@ -0,0 +1,2941 @@
+// Signature format: 3.0
+package androidx.core.accessibilityservice {
+
+ public final class AccessibilityServiceInfoCompat {
+ method public static String capabilityToString(int);
+ method public static String feedbackTypeToString(int);
+ method public static String? flagToString(int);
+ method public static int getCapabilities(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static String? loadDescription(android.accessibilityservice.AccessibilityServiceInfo, android.content.pm.PackageManager);
+ field public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 8; // 0x8
+ field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
+ field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
+ field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
+ field public static final int FEEDBACK_BRAILLE = 32; // 0x20
+ field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
+ field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
+ field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
+ }
+
+}
+
+package androidx.core.app {
+
+ public class ActivityCompat extends androidx.core.content.ContextCompat {
+ ctor protected ActivityCompat();
+ method public static void finishAffinity(android.app.Activity);
+ method public static void finishAfterTransition(android.app.Activity);
+ method public static android.net.Uri? getReferrer(android.app.Activity);
+ method @Deprecated public static boolean invalidateOptionsMenu(android.app.Activity!);
+ method public static void postponeEnterTransition(android.app.Activity);
+ method public static void recreate(android.app.Activity);
+ method public static androidx.core.view.DragAndDropPermissionsCompat? requestDragAndDropPermissions(android.app.Activity!, android.view.DragEvent!);
+ method public static void requestPermissions(android.app.Activity, String[], @IntRange(from=0) int);
+ method public static <T extends android.view.View> T requireViewById(android.app.Activity, @IdRes int);
+ method public static void setEnterSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setExitSharedElementCallback(android.app.Activity, androidx.core.app.SharedElementCallback?);
+ method public static void setPermissionCompatDelegate(androidx.core.app.ActivityCompat.PermissionCompatDelegate?);
+ method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, String);
+ method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle?);
+ method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public static void startPostponedEnterTransition(android.app.Activity);
+ }
+
+ public static interface ActivityCompat.OnRequestPermissionsResultCallback {
+ method public void onRequestPermissionsResult(int, String[], int[]);
+ }
+
+ public static interface ActivityCompat.PermissionCompatDelegate {
+ method public boolean onActivityResult(android.app.Activity, @IntRange(from=0) int, int, android.content.Intent?);
+ method public boolean requestPermissions(android.app.Activity, String[], @IntRange(from=0) int);
+ }
+
+ public final class ActivityManagerCompat {
+ method public static boolean isLowRamDevice(android.app.ActivityManager);
+ }
+
+ public class ActivityOptionsCompat {
+ ctor protected ActivityOptionsCompat();
+ method public android.graphics.Rect? getLaunchBounds();
+ method public static androidx.core.app.ActivityOptionsCompat makeBasic();
+ method public static androidx.core.app.ActivityOptionsCompat makeClipRevealAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeCustomAnimation(android.content.Context, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeScaleUpAnimation(android.view.View, int, int, int, int);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, android.view.View, String);
+ method public static androidx.core.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, androidx.core.util.Pair<android.view.View,java.lang.String>...!);
+ method public static androidx.core.app.ActivityOptionsCompat makeTaskLaunchBehind();
+ method public static androidx.core.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
+ method public void requestUsageTimeReport(android.app.PendingIntent);
+ method public androidx.core.app.ActivityOptionsCompat setLaunchBounds(android.graphics.Rect?);
+ method public android.os.Bundle? toBundle();
+ method public void update(androidx.core.app.ActivityOptionsCompat);
+ field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
+ field public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+ }
+
+ public final class AlarmManagerCompat {
+ method public static void setAlarmClock(android.app.AlarmManager, long, android.app.PendingIntent, android.app.PendingIntent);
+ method public static void setAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExact(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ method public static void setExactAndAllowWhileIdle(android.app.AlarmManager, int, long, android.app.PendingIntent);
+ }
+
+ @RequiresApi(28) public class AppComponentFactory extends android.app.AppComponentFactory {
+ ctor public AppComponentFactory();
+ method public final android.app.Activity! instantiateActivity(ClassLoader!, String!, android.content.Intent!) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Activity instantiateActivityCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Application! instantiateApplication(ClassLoader!, String!) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Application instantiateApplicationCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.ContentProvider! instantiateProvider(ClassLoader!, String!) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.ContentProvider instantiateProviderCompat(ClassLoader, String) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.content.BroadcastReceiver! instantiateReceiver(ClassLoader!, String!, android.content.Intent!) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.content.BroadcastReceiver instantiateReceiverCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public final android.app.Service! instantiateService(ClassLoader!, String!, android.content.Intent!) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ method public android.app.Service instantiateServiceCompat(ClassLoader, String, android.content.Intent?) throws java.lang.ClassNotFoundException, java.lang.IllegalAccessException, java.lang.InstantiationException;
+ }
+
+ public class AppLaunchChecker {
+ ctor @Deprecated public AppLaunchChecker();
+ method public static boolean hasStartedFromLauncher(android.content.Context);
+ method public static void onActivityCreate(android.app.Activity);
+ }
+
+ public final class AppOpsManagerCompat {
+ method public static int noteOp(android.content.Context, String, int, String);
+ method public static int noteOpNoThrow(android.content.Context, String, int, String);
+ method public static int noteProxyOp(android.content.Context, String, String);
+ method public static int noteProxyOpNoThrow(android.content.Context, String, String);
+ method public static String? permissionToOp(String);
+ field public static final int MODE_ALLOWED = 0; // 0x0
+ field public static final int MODE_DEFAULT = 3; // 0x3
+ field public static final int MODE_ERRORED = 2; // 0x2
+ field public static final int MODE_IGNORED = 1; // 0x1
+ }
+
+ public final class BundleCompat {
+ method public static android.os.IBinder? getBinder(android.os.Bundle, String?);
+ method public static void putBinder(android.os.Bundle, String?, android.os.IBinder?);
+ }
+
+ public class DialogCompat {
+ method public static android.view.View requireViewById(android.app.Dialog, int);
+ }
+
+ public class FrameMetricsAggregator {
+ ctor public FrameMetricsAggregator();
+ ctor public FrameMetricsAggregator(int);
+ method public void add(android.app.Activity);
+ method public android.util.SparseIntArray[]? getMetrics();
+ method public android.util.SparseIntArray[]? remove(android.app.Activity);
+ method public android.util.SparseIntArray[]? reset();
+ method public android.util.SparseIntArray[]? stop();
+ field public static final int ANIMATION_DURATION = 256; // 0x100
+ field public static final int ANIMATION_INDEX = 8; // 0x8
+ field public static final int COMMAND_DURATION = 32; // 0x20
+ field public static final int COMMAND_INDEX = 5; // 0x5
+ field public static final int DELAY_DURATION = 128; // 0x80
+ field public static final int DELAY_INDEX = 7; // 0x7
+ field public static final int DRAW_DURATION = 8; // 0x8
+ field public static final int DRAW_INDEX = 3; // 0x3
+ field public static final int EVERY_DURATION = 511; // 0x1ff
+ field public static final int INPUT_DURATION = 2; // 0x2
+ field public static final int INPUT_INDEX = 1; // 0x1
+ field public static final int LAYOUT_MEASURE_DURATION = 4; // 0x4
+ field public static final int LAYOUT_MEASURE_INDEX = 2; // 0x2
+ field public static final int SWAP_DURATION = 64; // 0x40
+ field public static final int SWAP_INDEX = 6; // 0x6
+ field public static final int SYNC_DURATION = 16; // 0x10
+ field public static final int SYNC_INDEX = 4; // 0x4
+ field public static final int TOTAL_DURATION = 1; // 0x1
+ field public static final int TOTAL_INDEX = 0; // 0x0
+ }
+
+ public abstract class JobIntentService extends android.app.Service {
+ ctor public JobIntentService();
+ method public static void enqueueWork(android.content.Context, Class, int, android.content.Intent);
+ method public static void enqueueWork(android.content.Context, android.content.ComponentName, int, android.content.Intent);
+ method public boolean isStopped();
+ method public android.os.IBinder! onBind(android.content.Intent);
+ method protected abstract void onHandleWork(android.content.Intent);
+ method public boolean onStopCurrentWork();
+ method public void setInterruptIfStopped(boolean);
+ }
+
+ public final class NavUtils {
+ method public static android.content.Intent? getParentActivityIntent(android.app.Activity);
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, Class<?>) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static android.content.Intent? getParentActivityIntent(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static String? getParentActivityName(android.app.Activity);
+ method public static String? getParentActivityName(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void navigateUpFromSameTask(android.app.Activity);
+ method public static void navigateUpTo(android.app.Activity, android.content.Intent);
+ method public static boolean shouldUpRecreateTask(android.app.Activity, android.content.Intent);
+ field public static final String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY";
+ }
+
+ public class NotificationCompat {
+ ctor @Deprecated public NotificationCompat();
+ method public static androidx.core.app.NotificationCompat.Action! getAction(android.app.Notification!, int);
+ method public static int getActionCount(android.app.Notification!);
+ method public static int getBadgeIconType(android.app.Notification!);
+ method public static String! getCategory(android.app.Notification!);
+ method public static String! getChannelId(android.app.Notification!);
+ method @RequiresApi(19) public static CharSequence! getContentTitle(android.app.Notification!);
+ method public static android.os.Bundle? getExtras(android.app.Notification!);
+ method public static String! getGroup(android.app.Notification!);
+ method public static int getGroupAlertBehavior(android.app.Notification!);
+ method @RequiresApi(21) public static java.util.List<androidx.core.app.NotificationCompat.Action>! getInvisibleActions(android.app.Notification!);
+ method public static boolean getLocalOnly(android.app.Notification!);
+ method public static String! getShortcutId(android.app.Notification!);
+ method public static String! getSortKey(android.app.Notification!);
+ method public static long getTimeoutAfter(android.app.Notification!);
+ method public static boolean isGroupSummary(android.app.Notification!);
+ field public static final int BADGE_ICON_LARGE = 2; // 0x2
+ field public static final int BADGE_ICON_NONE = 0; // 0x0
+ field public static final int BADGE_ICON_SMALL = 1; // 0x1
+ field public static final String CATEGORY_ALARM = "alarm";
+ field public static final String CATEGORY_CALL = "call";
+ field public static final String CATEGORY_EMAIL = "email";
+ field public static final String CATEGORY_ERROR = "err";
+ field public static final String CATEGORY_EVENT = "event";
+ field public static final String CATEGORY_MESSAGE = "msg";
+ field public static final String CATEGORY_NAVIGATION = "navigation";
+ field public static final String CATEGORY_PROGRESS = "progress";
+ field public static final String CATEGORY_PROMO = "promo";
+ field public static final String CATEGORY_RECOMMENDATION = "recommendation";
+ field public static final String CATEGORY_REMINDER = "reminder";
+ field public static final String CATEGORY_SERVICE = "service";
+ field public static final String CATEGORY_SOCIAL = "social";
+ field public static final String CATEGORY_STATUS = "status";
+ field public static final String CATEGORY_SYSTEM = "sys";
+ field public static final String CATEGORY_TRANSPORT = "transport";
+ field @ColorInt public static final int COLOR_DEFAULT = 0; // 0x0
+ field public static final int DEFAULT_ALL = -1; // 0xffffffff
+ field public static final int DEFAULT_LIGHTS = 4; // 0x4
+ field public static final int DEFAULT_SOUND = 1; // 0x1
+ field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
+ field public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
+ field public static final String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+ field public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+ field public static final String EXTRA_HIDDEN_CONVERSATION_TITLE = "android.hiddenConversationTitle";
+ field public static final String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+ field public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+ field public static final String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
+ field public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";
+ field public static final String EXTRA_MESSAGES = "android.messages";
+ field public static final String EXTRA_MESSAGING_STYLE_USER = "android.messagingStyleUser";
+ field public static final String EXTRA_PEOPLE = "android.people";
+ field public static final String EXTRA_PICTURE = "android.picture";
+ field public static final String EXTRA_PROGRESS = "android.progress";
+ field public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+ field public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
+ field public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+ field public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+ field public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+ field public static final String EXTRA_SMALL_ICON = "android.icon";
+ field public static final String EXTRA_SUB_TEXT = "android.subText";
+ field public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+ field public static final String EXTRA_TEMPLATE = "android.template";
+ field public static final String EXTRA_TEXT = "android.text";
+ field public static final String EXTRA_TEXT_LINES = "android.textLines";
+ field public static final String EXTRA_TITLE = "android.title";
+ field public static final String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
+ field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
+ field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
+ field @Deprecated public static final int FLAG_HIGH_PRIORITY = 128; // 0x80
+ field public static final int FLAG_INSISTENT = 4; // 0x4
+ field public static final int FLAG_LOCAL_ONLY = 256; // 0x100
+ field public static final int FLAG_NO_CLEAR = 32; // 0x20
+ field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
+ field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
+ field public static final int GROUP_ALERT_ALL = 0; // 0x0
+ field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
+ field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+ field public static final int PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int PRIORITY_HIGH = 1; // 0x1
+ field public static final int PRIORITY_LOW = -1; // 0xffffffff
+ field public static final int PRIORITY_MAX = 2; // 0x2
+ field public static final int PRIORITY_MIN = -2; // 0xfffffffe
+ field public static final int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final int VISIBILITY_PRIVATE = 0; // 0x0
+ field public static final int VISIBILITY_PUBLIC = 1; // 0x1
+ field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
+ }
+
+ public static class NotificationCompat.Action {
+ ctor public NotificationCompat.Action(int, CharSequence!, android.app.PendingIntent!);
+ method public android.app.PendingIntent! getActionIntent();
+ method public boolean getAllowGeneratedReplies();
+ method public androidx.core.app.RemoteInput[]! getDataOnlyRemoteInputs();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public androidx.core.app.RemoteInput[]! getRemoteInputs();
+ method @androidx.core.app.NotificationCompat.Action.SemanticAction public int getSemanticAction();
+ method public boolean getShowsUserInterface();
+ method public CharSequence! getTitle();
+ field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+ field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+ field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+ field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+ field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+ field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+ field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+ field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+ field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+ field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+ field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
+ field public android.app.PendingIntent! actionIntent;
+ field public int icon;
+ field public CharSequence! title;
+ }
+
+ public static final class NotificationCompat.Action.Builder {
+ ctor public NotificationCompat.Action.Builder(int, CharSequence!, android.app.PendingIntent!);
+ ctor public NotificationCompat.Action.Builder(androidx.core.app.NotificationCompat.Action!);
+ method public androidx.core.app.NotificationCompat.Action.Builder! addExtras(android.os.Bundle!);
+ method public androidx.core.app.NotificationCompat.Action.Builder! addRemoteInput(androidx.core.app.RemoteInput!);
+ method public androidx.core.app.NotificationCompat.Action! build();
+ method public androidx.core.app.NotificationCompat.Action.Builder! extend(androidx.core.app.NotificationCompat.Action.Extender!);
+ method public android.os.Bundle! getExtras();
+ method public androidx.core.app.NotificationCompat.Action.Builder! setAllowGeneratedReplies(boolean);
+ method public androidx.core.app.NotificationCompat.Action.Builder! setSemanticAction(@androidx.core.app.NotificationCompat.Action.SemanticAction int);
+ method public androidx.core.app.NotificationCompat.Action.Builder! setShowsUserInterface(boolean);
+ }
+
+ public static interface NotificationCompat.Action.Extender {
+ method public androidx.core.app.NotificationCompat.Action.Builder! extend(androidx.core.app.NotificationCompat.Action.Builder!);
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_NONE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_REPLY, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_UNREAD, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_DELETE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_ARCHIVE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_MUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_UNMUTE, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_UP, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_THUMBS_DOWN, androidx.core.app.NotificationCompat.Action.SEMANTIC_ACTION_CALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.Action.SemanticAction {
+ }
+
+ public static final class NotificationCompat.Action.WearableExtender implements androidx.core.app.NotificationCompat.Action.Extender {
+ ctor public NotificationCompat.Action.WearableExtender();
+ ctor public NotificationCompat.Action.WearableExtender(androidx.core.app.NotificationCompat.Action!);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender! clone();
+ method public androidx.core.app.NotificationCompat.Action.Builder! extend(androidx.core.app.NotificationCompat.Action.Builder!);
+ method @Deprecated public CharSequence! getCancelLabel();
+ method @Deprecated public CharSequence! getConfirmLabel();
+ method public boolean getHintDisplayActionInline();
+ method public boolean getHintLaunchesActivity();
+ method @Deprecated public CharSequence! getInProgressLabel();
+ method public boolean isAvailableOffline();
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender! setAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender! setCancelLabel(CharSequence!);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender! setConfirmLabel(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender! setHintDisplayActionInline(boolean);
+ method public androidx.core.app.NotificationCompat.Action.WearableExtender! setHintLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.Action.WearableExtender! setInProgressLabel(CharSequence!);
+ }
+
+ public static class NotificationCompat.BigPictureStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigPictureStyle();
+ ctor public NotificationCompat.BigPictureStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle! bigLargeIcon(android.graphics.Bitmap!);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle! bigPicture(android.graphics.Bitmap!);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle! setBigContentTitle(CharSequence!);
+ method public androidx.core.app.NotificationCompat.BigPictureStyle! setSummaryText(CharSequence!);
+ }
+
+ public static class NotificationCompat.BigTextStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigTextStyle();
+ ctor public NotificationCompat.BigTextStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public androidx.core.app.NotificationCompat.BigTextStyle! bigText(CharSequence!);
+ method public androidx.core.app.NotificationCompat.BigTextStyle! setBigContentTitle(CharSequence!);
+ method public androidx.core.app.NotificationCompat.BigTextStyle! setSummaryText(CharSequence!);
+ }
+
+ public static class NotificationCompat.Builder {
+ ctor public NotificationCompat.Builder(android.content.Context, String);
+ ctor @Deprecated public NotificationCompat.Builder(android.content.Context!);
+ method public androidx.core.app.NotificationCompat.Builder! addAction(int, CharSequence!, android.app.PendingIntent!);
+ method public androidx.core.app.NotificationCompat.Builder! addAction(androidx.core.app.NotificationCompat.Action!);
+ method public androidx.core.app.NotificationCompat.Builder! addExtras(android.os.Bundle!);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder! addInvisibleAction(int, CharSequence!, android.app.PendingIntent!);
+ method @RequiresApi(21) public androidx.core.app.NotificationCompat.Builder! addInvisibleAction(androidx.core.app.NotificationCompat.Action!);
+ method public androidx.core.app.NotificationCompat.Builder! addPerson(String!);
+ method public android.app.Notification! build();
+ method public androidx.core.app.NotificationCompat.Builder! extend(androidx.core.app.NotificationCompat.Extender!);
+ method public android.os.Bundle! getExtras();
+ method @Deprecated public android.app.Notification! getNotification();
+ method protected static CharSequence! limitCharSequenceLength(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setAutoCancel(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setBadgeIconType(int);
+ method public androidx.core.app.NotificationCompat.Builder! setCategory(String!);
+ method public androidx.core.app.NotificationCompat.Builder! setChannelId(String);
+ method public androidx.core.app.NotificationCompat.Builder! setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.Builder! setColorized(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setContent(android.widget.RemoteViews!);
+ method public androidx.core.app.NotificationCompat.Builder! setContentInfo(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setContentIntent(android.app.PendingIntent!);
+ method public androidx.core.app.NotificationCompat.Builder! setContentText(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setContentTitle(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setCustomBigContentView(android.widget.RemoteViews!);
+ method public androidx.core.app.NotificationCompat.Builder! setCustomContentView(android.widget.RemoteViews!);
+ method public androidx.core.app.NotificationCompat.Builder! setCustomHeadsUpContentView(android.widget.RemoteViews!);
+ method public androidx.core.app.NotificationCompat.Builder! setDefaults(int);
+ method public androidx.core.app.NotificationCompat.Builder! setDeleteIntent(android.app.PendingIntent!);
+ method public androidx.core.app.NotificationCompat.Builder! setExtras(android.os.Bundle!);
+ method public androidx.core.app.NotificationCompat.Builder! setFullScreenIntent(android.app.PendingIntent!, boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setGroup(String!);
+ method public androidx.core.app.NotificationCompat.Builder! setGroupAlertBehavior(int);
+ method public androidx.core.app.NotificationCompat.Builder! setGroupSummary(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
+ method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
+ method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
+ method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setPriority(int);
+ method public androidx.core.app.NotificationCompat.Builder! setProgress(int, int, boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setPublicVersion(android.app.Notification!);
+ method public androidx.core.app.NotificationCompat.Builder! setRemoteInputHistory(CharSequence[]!);
+ method public androidx.core.app.NotificationCompat.Builder! setShortcutId(String!);
+ method public androidx.core.app.NotificationCompat.Builder! setShowWhen(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setSmallIcon(int);
+ method public androidx.core.app.NotificationCompat.Builder! setSmallIcon(int, int);
+ method public androidx.core.app.NotificationCompat.Builder! setSortKey(String!);
+ method public androidx.core.app.NotificationCompat.Builder! setSound(android.net.Uri!);
+ method public androidx.core.app.NotificationCompat.Builder! setSound(android.net.Uri!, int);
+ method public androidx.core.app.NotificationCompat.Builder! setStyle(androidx.core.app.NotificationCompat.Style!);
+ method public androidx.core.app.NotificationCompat.Builder! setSubText(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setTicker(CharSequence!);
+ method public androidx.core.app.NotificationCompat.Builder! setTicker(CharSequence!, android.widget.RemoteViews!);
+ method public androidx.core.app.NotificationCompat.Builder! setTimeoutAfter(long);
+ method public androidx.core.app.NotificationCompat.Builder! setUsesChronometer(boolean);
+ method public androidx.core.app.NotificationCompat.Builder! setVibrate(long[]!);
+ method public androidx.core.app.NotificationCompat.Builder! setVisibility(int);
+ method public androidx.core.app.NotificationCompat.Builder! setWhen(long);
+ field @Deprecated public java.util.ArrayList<java.lang.String>! mPeople;
+ }
+
+ public static final class NotificationCompat.CarExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.CarExtender();
+ ctor public NotificationCompat.CarExtender(android.app.Notification!);
+ method public androidx.core.app.NotificationCompat.Builder! extend(androidx.core.app.NotificationCompat.Builder!);
+ method @ColorInt public int getColor();
+ method public android.graphics.Bitmap! getLargeIcon();
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation! getUnreadConversation();
+ method public androidx.core.app.NotificationCompat.CarExtender! setColor(@ColorInt int);
+ method public androidx.core.app.NotificationCompat.CarExtender! setLargeIcon(android.graphics.Bitmap!);
+ method public androidx.core.app.NotificationCompat.CarExtender! setUnreadConversation(androidx.core.app.NotificationCompat.CarExtender.UnreadConversation!);
+ }
+
+ public static class NotificationCompat.CarExtender.UnreadConversation {
+ method public long getLatestTimestamp();
+ method public String[]! getMessages();
+ method public String! getParticipant();
+ method public String[]! getParticipants();
+ method public android.app.PendingIntent! getReadPendingIntent();
+ method public androidx.core.app.RemoteInput! getRemoteInput();
+ method public android.app.PendingIntent! getReplyPendingIntent();
+ }
+
+ public static class NotificationCompat.CarExtender.UnreadConversation.Builder {
+ ctor public NotificationCompat.CarExtender.UnreadConversation.Builder(String!);
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder! addMessage(String!);
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation! build();
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder! setLatestTimestamp(long);
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder! setReadPendingIntent(android.app.PendingIntent!);
+ method public androidx.core.app.NotificationCompat.CarExtender.UnreadConversation.Builder! setReplyAction(android.app.PendingIntent!, androidx.core.app.RemoteInput!);
+ }
+
+ public static class NotificationCompat.DecoratedCustomViewStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.DecoratedCustomViewStyle();
+ }
+
+ public static interface NotificationCompat.Extender {
+ method public androidx.core.app.NotificationCompat.Builder! extend(androidx.core.app.NotificationCompat.Builder!);
+ }
+
+ public static class NotificationCompat.InboxStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.InboxStyle();
+ ctor public NotificationCompat.InboxStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public androidx.core.app.NotificationCompat.InboxStyle! addLine(CharSequence!);
+ method public androidx.core.app.NotificationCompat.InboxStyle! setBigContentTitle(CharSequence!);
+ method public androidx.core.app.NotificationCompat.InboxStyle! setSummaryText(CharSequence!);
+ }
+
+ public static class NotificationCompat.MessagingStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor @Deprecated public NotificationCompat.MessagingStyle(CharSequence);
+ ctor public NotificationCompat.MessagingStyle(androidx.core.app.Person);
+ method public void addCompatExtras(android.os.Bundle!);
+ method @Deprecated public androidx.core.app.NotificationCompat.MessagingStyle! addMessage(CharSequence!, long, CharSequence!);
+ method public androidx.core.app.NotificationCompat.MessagingStyle! addMessage(CharSequence!, long, androidx.core.app.Person!);
+ method public androidx.core.app.NotificationCompat.MessagingStyle! addMessage(androidx.core.app.NotificationCompat.MessagingStyle.Message!);
+ method public static androidx.core.app.NotificationCompat.MessagingStyle? extractMessagingStyleFromNotification(android.app.Notification!);
+ method public CharSequence? getConversationTitle();
+ method public java.util.List<androidx.core.app.NotificationCompat.MessagingStyle.Message>! getMessages();
+ method public androidx.core.app.Person! getUser();
+ method @Deprecated public CharSequence! getUserDisplayName();
+ method public boolean isGroupConversation();
+ method public androidx.core.app.NotificationCompat.MessagingStyle! setConversationTitle(CharSequence?);
+ method public androidx.core.app.NotificationCompat.MessagingStyle! setGroupConversation(boolean);
+ field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
+ }
+
+ public static final class NotificationCompat.MessagingStyle.Message {
+ ctor public NotificationCompat.MessagingStyle.Message(CharSequence!, long, androidx.core.app.Person?);
+ ctor @Deprecated public NotificationCompat.MessagingStyle.Message(CharSequence!, long, CharSequence!);
+ method public String? getDataMimeType();
+ method public android.net.Uri? getDataUri();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.Person? getPerson();
+ method @Deprecated public CharSequence? getSender();
+ method public CharSequence getText();
+ method public long getTimestamp();
+ method public androidx.core.app.NotificationCompat.MessagingStyle.Message! setData(String!, android.net.Uri!);
+ }
+
+ public abstract static class NotificationCompat.Style {
+ ctor public NotificationCompat.Style();
+ method public android.app.Notification! build();
+ method public void setBuilder(androidx.core.app.NotificationCompat.Builder!);
+ }
+
+ public static final class NotificationCompat.WearableExtender implements androidx.core.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.WearableExtender();
+ ctor public NotificationCompat.WearableExtender(android.app.Notification!);
+ method public androidx.core.app.NotificationCompat.WearableExtender! addAction(androidx.core.app.NotificationCompat.Action!);
+ method public androidx.core.app.NotificationCompat.WearableExtender! addActions(java.util.List<androidx.core.app.NotificationCompat.Action>!);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! addPage(android.app.Notification!);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! addPages(java.util.List<android.app.Notification>!);
+ method public androidx.core.app.NotificationCompat.WearableExtender! clearActions();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! clearPages();
+ method public androidx.core.app.NotificationCompat.WearableExtender! clone();
+ method public androidx.core.app.NotificationCompat.Builder! extend(androidx.core.app.NotificationCompat.Builder!);
+ method public java.util.List<androidx.core.app.NotificationCompat.Action>! getActions();
+ method @Deprecated public android.graphics.Bitmap! getBackground();
+ method public String! getBridgeTag();
+ method public int getContentAction();
+ method @Deprecated public int getContentIcon();
+ method @Deprecated public int getContentIconGravity();
+ method public boolean getContentIntentAvailableOffline();
+ method @Deprecated public int getCustomContentHeight();
+ method @Deprecated public int getCustomSizePreset();
+ method public String! getDismissalId();
+ method @Deprecated public android.app.PendingIntent! getDisplayIntent();
+ method @Deprecated public int getGravity();
+ method @Deprecated public boolean getHintAmbientBigPicture();
+ method @Deprecated public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
+ method @Deprecated public boolean getHintHideIcon();
+ method @Deprecated public int getHintScreenTimeout();
+ method @Deprecated public boolean getHintShowBackgroundOnly();
+ method @Deprecated public java.util.List<android.app.Notification>! getPages();
+ method public boolean getStartScrollBottom();
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setBackground(android.graphics.Bitmap!);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setBridgeTag(String!);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setContentAction(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setContentIcon(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setContentIconGravity(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setContentIntentAvailableOffline(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setCustomContentHeight(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setCustomSizePreset(int);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setDismissalId(String!);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setDisplayIntent(android.app.PendingIntent!);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setGravity(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setHintAmbientBigPicture(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setHintAvoidBackgroundClipping(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setHintContentIntentLaunchesActivity(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setHintHideIcon(boolean);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setHintScreenTimeout(int);
+ method @Deprecated public androidx.core.app.NotificationCompat.WearableExtender! setHintShowBackgroundOnly(boolean);
+ method public androidx.core.app.NotificationCompat.WearableExtender! setStartScrollBottom(boolean);
+ field @Deprecated public static final int SCREEN_TIMEOUT_LONG = -1; // 0xffffffff
+ field @Deprecated public static final int SCREEN_TIMEOUT_SHORT = 0; // 0x0
+ field @Deprecated public static final int SIZE_DEFAULT = 0; // 0x0
+ field @Deprecated public static final int SIZE_FULL_SCREEN = 5; // 0x5
+ field @Deprecated public static final int SIZE_LARGE = 4; // 0x4
+ field @Deprecated public static final int SIZE_MEDIUM = 3; // 0x3
+ field @Deprecated public static final int SIZE_SMALL = 2; // 0x2
+ field @Deprecated public static final int SIZE_XSMALL = 1; // 0x1
+ field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
+ }
+
+ public final class NotificationCompatExtras {
+ field public static final String EXTRA_ACTION_EXTRAS = "android.support.actionExtras";
+ field public static final String EXTRA_GROUP_KEY = "android.support.groupKey";
+ field public static final String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
+ field public static final String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+ field public static final String EXTRA_REMOTE_INPUTS = "android.support.remoteInputs";
+ field public static final String EXTRA_SORT_KEY = "android.support.sortKey";
+ }
+
+ public abstract class NotificationCompatSideChannelService extends android.app.Service {
+ ctor public NotificationCompatSideChannelService();
+ method public abstract void cancel(String!, int, String!);
+ method public abstract void cancelAll(String!);
+ method public abstract void notify(String!, int, String!, android.app.Notification!);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ }
+
+ public final class NotificationManagerCompat {
+ method public boolean areNotificationsEnabled();
+ method public void cancel(int);
+ method public void cancel(String?, int);
+ method public void cancelAll();
+ method public void createNotificationChannel(android.app.NotificationChannel);
+ method public void createNotificationChannelGroup(android.app.NotificationChannelGroup);
+ method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>);
+ method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>);
+ method public void deleteNotificationChannel(String);
+ method public void deleteNotificationChannelGroup(String);
+ method public static androidx.core.app.NotificationManagerCompat from(android.content.Context);
+ method public static java.util.Set<java.lang.String> getEnabledListenerPackages(android.content.Context);
+ method public int getImportance();
+ method public android.app.NotificationChannel? getNotificationChannel(String);
+ method public android.app.NotificationChannelGroup? getNotificationChannelGroup(String);
+ method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
+ method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
+ method public void notify(int, android.app.Notification);
+ method public void notify(String?, int, android.app.Notification);
+ field public static final String ACTION_BIND_SIDE_CHANNEL = "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
+ field public static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
+ }
+
+ public class Person {
+ method public static androidx.core.app.Person fromBundle(android.os.Bundle);
+ method public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method public String? getKey();
+ method public CharSequence? getName();
+ method public String? getUri();
+ method public boolean isBot();
+ method public boolean isImportant();
+ method public androidx.core.app.Person.Builder toBuilder();
+ method public android.os.Bundle toBundle();
+ }
+
+ public static class Person.Builder {
+ ctor public Person.Builder();
+ method public androidx.core.app.Person build();
+ method public androidx.core.app.Person.Builder setBot(boolean);
+ method public androidx.core.app.Person.Builder setIcon(androidx.core.graphics.drawable.IconCompat?);
+ method public androidx.core.app.Person.Builder setImportant(boolean);
+ method public androidx.core.app.Person.Builder setKey(String?);
+ method public androidx.core.app.Person.Builder setName(CharSequence?);
+ method public androidx.core.app.Person.Builder setUri(String?);
+ }
+
+ public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
+ ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
+ ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
+ method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
+ method public android.app.PendingIntent getActionIntent();
+ method public CharSequence getContentDescription();
+ method public androidx.core.graphics.drawable.IconCompat getIcon();
+ method public CharSequence getTitle();
+ method public boolean isEnabled();
+ method public void setEnabled(boolean);
+ method public void setShouldShowIcon(boolean);
+ method public boolean shouldShowIcon();
+ method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
+ }
+
+ public final class RemoteInput {
+ method public static void addDataResultToIntent(androidx.core.app.RemoteInput!, android.content.Intent!, java.util.Map<java.lang.String,android.net.Uri>!);
+ method public static void addResultsToIntent(androidx.core.app.RemoteInput[]!, android.content.Intent!, android.os.Bundle!);
+ method public boolean getAllowFreeFormInput();
+ method public java.util.Set<java.lang.String>! getAllowedDataTypes();
+ method public CharSequence[]! getChoices();
+ method public static java.util.Map<java.lang.String,android.net.Uri>! getDataResultsFromIntent(android.content.Intent!, String!);
+ method public android.os.Bundle! getExtras();
+ method public CharSequence! getLabel();
+ method public String! getResultKey();
+ method public static android.os.Bundle! getResultsFromIntent(android.content.Intent!);
+ method public static int getResultsSource(android.content.Intent);
+ method public boolean isDataOnly();
+ method public static void setResultsSource(android.content.Intent, int);
+ field public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+ field public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+ field public static final int SOURCE_CHOICE = 1; // 0x1
+ field public static final int SOURCE_FREE_FORM_INPUT = 0; // 0x0
+ }
+
+ public static final class RemoteInput.Builder {
+ ctor public RemoteInput.Builder(String);
+ method public androidx.core.app.RemoteInput.Builder addExtras(android.os.Bundle);
+ method public androidx.core.app.RemoteInput build();
+ method public android.os.Bundle getExtras();
+ method public androidx.core.app.RemoteInput.Builder setAllowDataType(String, boolean);
+ method public androidx.core.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
+ method public androidx.core.app.RemoteInput.Builder setChoices(CharSequence[]?);
+ method public androidx.core.app.RemoteInput.Builder setLabel(CharSequence?);
+ }
+
+ public final class ServiceCompat {
+ method public static void stopForeground(android.app.Service, int);
+ field public static final int START_STICKY = 1; // 0x1
+ field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+ field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
+ }
+
+ public final class ShareCompat {
+ method public static void configureMenuItem(android.view.MenuItem!, androidx.core.app.ShareCompat.IntentBuilder!);
+ method public static void configureMenuItem(android.view.Menu!, int, androidx.core.app.ShareCompat.IntentBuilder!);
+ method public static android.content.ComponentName! getCallingActivity(android.app.Activity!);
+ method public static String! getCallingPackage(android.app.Activity!);
+ field public static final String EXTRA_CALLING_ACTIVITY = "androidx.core.app.EXTRA_CALLING_ACTIVITY";
+ field public static final String EXTRA_CALLING_PACKAGE = "androidx.core.app.EXTRA_CALLING_PACKAGE";
+ }
+
+ public static class ShareCompat.IntentBuilder {
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailBcc(String!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailBcc(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailCc(String!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailCc(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailTo(String!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addEmailTo(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! addStream(android.net.Uri!);
+ method public android.content.Intent! createChooserIntent();
+ method public static androidx.core.app.ShareCompat.IntentBuilder! from(android.app.Activity!);
+ method public android.content.Intent! getIntent();
+ method public androidx.core.app.ShareCompat.IntentBuilder! setChooserTitle(CharSequence!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setChooserTitle(@StringRes int);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setEmailBcc(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setEmailCc(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setEmailTo(String[]!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setHtmlText(String!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setStream(android.net.Uri!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setSubject(String!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setText(CharSequence!);
+ method public androidx.core.app.ShareCompat.IntentBuilder! setType(String!);
+ method public void startChooser();
+ }
+
+ public static class ShareCompat.IntentReader {
+ method public static androidx.core.app.ShareCompat.IntentReader! from(android.app.Activity!);
+ method public android.content.ComponentName! getCallingActivity();
+ method public android.graphics.drawable.Drawable! getCallingActivityIcon();
+ method public android.graphics.drawable.Drawable! getCallingApplicationIcon();
+ method public CharSequence! getCallingApplicationLabel();
+ method public String! getCallingPackage();
+ method public String[]! getEmailBcc();
+ method public String[]! getEmailCc();
+ method public String[]! getEmailTo();
+ method public String! getHtmlText();
+ method public android.net.Uri! getStream();
+ method public android.net.Uri! getStream(int);
+ method public int getStreamCount();
+ method public String! getSubject();
+ method public CharSequence! getText();
+ method public String! getType();
+ method public boolean isMultipleShare();
+ method public boolean isShareIntent();
+ method public boolean isSingleShare();
+ }
+
+ public abstract class SharedElementCallback {
+ ctor public SharedElementCallback();
+ method public android.os.Parcelable! onCaptureSharedElementSnapshot(android.view.View!, android.graphics.Matrix!, android.graphics.RectF!);
+ method public android.view.View! onCreateSnapshotView(android.content.Context!, android.os.Parcelable!);
+ method public void onMapSharedElements(java.util.List<java.lang.String>!, java.util.Map<java.lang.String,android.view.View>!);
+ method public void onRejectSharedElements(java.util.List<android.view.View>!);
+ method public void onSharedElementEnd(java.util.List<java.lang.String>!, java.util.List<android.view.View>!, java.util.List<android.view.View>!);
+ method public void onSharedElementStart(java.util.List<java.lang.String>!, java.util.List<android.view.View>!, java.util.List<android.view.View>!);
+ method public void onSharedElementsArrived(java.util.List<java.lang.String>!, java.util.List<android.view.View>!, androidx.core.app.SharedElementCallback.OnSharedElementsReadyListener!);
+ }
+
+ public static interface SharedElementCallback.OnSharedElementsReadyListener {
+ method public void onSharedElementsReady();
+ }
+
+ public final class TaskStackBuilder implements java.lang.Iterable<android.content.Intent> {
+ method public androidx.core.app.TaskStackBuilder addNextIntent(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
+ method public androidx.core.app.TaskStackBuilder addParentStack(android.app.Activity);
+ method public androidx.core.app.TaskStackBuilder addParentStack(Class<?>);
+ method public androidx.core.app.TaskStackBuilder! addParentStack(android.content.ComponentName!);
+ method public static androidx.core.app.TaskStackBuilder create(android.content.Context);
+ method public android.content.Intent? editIntentAt(int);
+ method @Deprecated public static androidx.core.app.TaskStackBuilder! from(android.content.Context!);
+ method @Deprecated public android.content.Intent! getIntent(int);
+ method public int getIntentCount();
+ method public android.content.Intent[] getIntents();
+ method public android.app.PendingIntent? getPendingIntent(int, int);
+ method public android.app.PendingIntent? getPendingIntent(int, int, android.os.Bundle?);
+ method @Deprecated public java.util.Iterator<android.content.Intent>! iterator();
+ method public void startActivities();
+ method public void startActivities(android.os.Bundle?);
+ }
+
+ public static interface TaskStackBuilder.SupportParentable {
+ method public android.content.Intent? getSupportParentActivityIntent();
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentResolverCompat {
+ method public static android.database.Cursor! query(android.content.ContentResolver!, android.net.Uri!, String[]!, String!, String[]!, String!, androidx.core.os.CancellationSignal!);
+ }
+
+ public class ContextCompat {
+ ctor protected ContextCompat();
+ method public static int checkSelfPermission(android.content.Context, String);
+ method public static android.content.Context? createDeviceProtectedStorageContext(android.content.Context);
+ method public static java.io.File! getCodeCacheDir(android.content.Context);
+ method @ColorInt public static int getColor(android.content.Context, @ColorRes int);
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.Context, @ColorRes int);
+ method public static java.io.File? getDataDir(android.content.Context);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.Context, @DrawableRes int);
+ method public static java.io.File[] getExternalCacheDirs(android.content.Context);
+ method public static java.io.File[] getExternalFilesDirs(android.content.Context, String?);
+ method public static java.util.concurrent.Executor! getMainExecutor(android.content.Context!);
+ method public static java.io.File? getNoBackupFilesDir(android.content.Context);
+ method public static java.io.File[] getObbDirs(android.content.Context);
+ method public static <T> T? getSystemService(android.content.Context, Class<T>);
+ method public static String? getSystemServiceName(android.content.Context, Class<?>);
+ method public static boolean isDeviceProtectedStorage(android.content.Context);
+ method public static boolean startActivities(android.content.Context, android.content.Intent[]);
+ method public static boolean startActivities(android.content.Context, android.content.Intent[], android.os.Bundle?);
+ method public static void startActivity(android.content.Context, android.content.Intent, android.os.Bundle?);
+ method public static void startForegroundService(android.content.Context, android.content.Intent);
+ }
+
+ public class FileProvider extends android.content.ContentProvider {
+ ctor public FileProvider();
+ method public int delete(android.net.Uri, String?, String[]?);
+ method public String! getType(android.net.Uri);
+ method public static android.net.Uri! getUriForFile(android.content.Context, String, java.io.File);
+ method public android.net.Uri! insert(android.net.Uri, android.content.ContentValues!);
+ method public boolean onCreate();
+ method public android.database.Cursor! query(android.net.Uri, String[]?, String?, String[]?, String?);
+ method public int update(android.net.Uri, android.content.ContentValues!, String?, String[]?);
+ }
+
+ public final class IntentCompat {
+ method public static android.content.Intent makeMainSelectorActivity(String, String);
+ field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+ field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
+ }
+
+ public final class MimeTypeFilter {
+ method public static boolean matches(String?, String);
+ method public static String? matches(String?, String[]);
+ method public static String? matches(String[]?, String);
+ method public static String[] matchesMany(String[]?, String);
+ }
+
+ public final class PermissionChecker {
+ method public static int checkCallingOrSelfPermission(android.content.Context, String);
+ method public static int checkCallingPermission(android.content.Context, String, String?);
+ method public static int checkPermission(android.content.Context, String, int, int, String?);
+ method public static int checkSelfPermission(android.content.Context, String);
+ field public static final int PERMISSION_DENIED = -1; // 0xffffffff
+ field public static final int PERMISSION_DENIED_APP_OP = -2; // 0xfffffffe
+ field public static final int PERMISSION_GRANTED = 0; // 0x0
+ }
+
+ @Deprecated public final class SharedPreferencesCompat {
+ }
+
+ @Deprecated public static final class SharedPreferencesCompat.EditorCompat {
+ method @Deprecated public void apply(android.content.SharedPreferences.Editor);
+ method @Deprecated public static androidx.core.content.SharedPreferencesCompat.EditorCompat! getInstance();
+ }
+
+}
+
+package androidx.core.content.pm {
+
+ @Deprecated public final class ActivityInfoCompat {
+ field @Deprecated public static final int CONFIG_UI_MODE = 512; // 0x200
+ }
+
+ public final class PackageInfoCompat {
+ method public static long getLongVersionCode(android.content.pm.PackageInfo);
+ }
+
+ public final class PermissionInfoCompat {
+ method public static int getProtection(android.content.pm.PermissionInfo);
+ method public static int getProtectionFlags(android.content.pm.PermissionInfo);
+ }
+
+ public class ShortcutInfoCompat {
+ method public android.content.ComponentName? getActivity();
+ method public java.util.Set<java.lang.String>? getCategories();
+ method public CharSequence? getDisabledMessage();
+ method public String getId();
+ method public android.content.Intent getIntent();
+ method public android.content.Intent[] getIntents();
+ method public CharSequence? getLongLabel();
+ method public CharSequence getShortLabel();
+ method @RequiresApi(25) public android.content.pm.ShortcutInfo! toShortcutInfo();
+ }
+
+ public static class ShortcutInfoCompat.Builder {
+ ctor public ShortcutInfoCompat.Builder(android.content.Context, String);
+ method public androidx.core.content.pm.ShortcutInfoCompat build();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setActivity(android.content.ComponentName);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setAlwaysBadged();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setCategories(java.util.Set<java.lang.String>);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setDisabledMessage(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIcon(androidx.core.graphics.drawable.IconCompat!);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntent(android.content.Intent);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setIntents(android.content.Intent[]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLabel(CharSequence);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setLongLived();
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPerson(androidx.core.app.Person);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setPersons(androidx.core.app.Person[]);
+ method public androidx.core.content.pm.ShortcutInfoCompat.Builder setShortLabel(CharSequence);
+ }
+
+ public class ShortcutManagerCompat {
+ method public static boolean addDynamicShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat>);
+ method public static android.content.Intent createShortcutResultIntent(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat);
+ method public static java.util.List<androidx.core.content.pm.ShortcutInfoCompat> getDynamicShortcuts(android.content.Context);
+ method public static int getMaxShortcutCountPerActivity(android.content.Context);
+ method public static boolean isRequestPinShortcutSupported(android.content.Context);
+ method public static void removeAllDynamicShortcuts(android.content.Context);
+ method public void removeDynamicShortcuts(android.content.Context, java.util.List<java.lang.String>);
+ method public static boolean requestPinShortcut(android.content.Context, androidx.core.content.pm.ShortcutInfoCompat, android.content.IntentSender?);
+ method public static boolean updateShortcuts(android.content.Context, java.util.List<androidx.core.content.pm.ShortcutInfoCompat>);
+ field public static final String EXTRA_SHORTCUT_ID = "android.intent.extra.shortcut.ID";
+ }
+
+}
+
+package androidx.core.content.res {
+
+ public final class ConfigurationHelper {
+ method public static int getDensityDpi(android.content.res.Resources);
+ }
+
+ public final class ResourcesCompat {
+ method @ColorInt public static int getColor(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.content.res.ColorStateList? getColorStateList(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.res.Resources, @DrawableRes int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable? getDrawableForDensity(android.content.res.Resources, @DrawableRes int, int, android.content.res.Resources.Theme?) throws android.content.res.Resources.NotFoundException;
+ method public static float getFloat(android.content.res.Resources, @DimenRes int);
+ method public static android.graphics.Typeface? getFont(android.content.Context, @FontRes int) throws android.content.res.Resources.NotFoundException;
+ method public static void getFont(android.content.Context, @FontRes int, androidx.core.content.res.ResourcesCompat.FontCallback, android.os.Handler?) throws android.content.res.Resources.NotFoundException;
+ }
+
+ public abstract static class ResourcesCompat.FontCallback {
+ ctor public ResourcesCompat.FontCallback();
+ method public abstract void onFontRetrievalFailed(int);
+ method public abstract void onFontRetrieved(android.graphics.Typeface);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorWindowCompat {
+ method public static android.database.CursorWindow create(String?, long);
+ }
+
+ @Deprecated public final class DatabaseUtilsCompat {
+ method @Deprecated public static String[]! appendSelectionArgs(String[]!, String[]!);
+ method @Deprecated public static String! concatenateWhere(String!, String!);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteCursorCompat {
+ method public static void setFillWindowForwardOnly(android.database.sqlite.SQLiteCursor, boolean);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapCompat {
+ method public static int getAllocationByteCount(android.graphics.Bitmap);
+ method public static boolean hasMipMap(android.graphics.Bitmap);
+ method public static void setHasMipMap(android.graphics.Bitmap, boolean);
+ }
+
+ public final class ColorUtils {
+ method @ColorInt public static int HSLToColor(float[]);
+ method @ColorInt public static int LABToColor(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double);
+ method public static void LABToXYZ(@FloatRange(from=0.0f, to=100) double, @FloatRange(from=0xffffff80, to=127) double, @FloatRange(from=0xffffff80, to=127) double, double[]);
+ method public static void RGBToHSL(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, float[]);
+ method public static void RGBToLAB(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method public static void RGBToXYZ(@IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, @IntRange(from=0, to=255) int, double[]);
+ method @ColorInt public static int XYZToColor(@FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_X) double, @FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_Y) double, @FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_Z) double);
+ method public static void XYZToLAB(@FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_X) double, @FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_Y) double, @FloatRange(from=0.0f, to=androidx.core.graphics.ColorUtils.XYZ_WHITE_REFERENCE_Z) double, double[]);
+ method @ColorInt public static int blendARGB(@ColorInt int, @ColorInt int, @FloatRange(from=0.0, to=1.0) float);
+ method public static void blendHSL(float[], float[], @FloatRange(from=0.0, to=1.0) float, float[]);
+ method public static void blendLAB(double[], double[], @FloatRange(from=0.0, to=1.0) double, double[]);
+ method public static double calculateContrast(@ColorInt int, @ColorInt int);
+ method @FloatRange(from=0.0, to=1.0) public static double calculateLuminance(@ColorInt int);
+ method public static int calculateMinimumAlpha(@ColorInt int, @ColorInt int, float);
+ method public static void colorToHSL(@ColorInt int, float[]);
+ method public static void colorToLAB(@ColorInt int, double[]);
+ method public static void colorToXYZ(@ColorInt int, double[]);
+ method public static int compositeColors(@ColorInt int, @ColorInt int);
+ method @RequiresApi(26) public static android.graphics.Color compositeColors(android.graphics.Color, android.graphics.Color);
+ method public static double distanceEuclidean(double[], double[]);
+ method @ColorInt public static int setAlphaComponent(@ColorInt int, @IntRange(from=0, to=255) int);
+ }
+
+ public final class PaintCompat {
+ method public static boolean hasGlyph(android.graphics.Paint, String);
+ }
+
+ public final class PathSegment {
+ ctor public PathSegment(android.graphics.PointF, float, android.graphics.PointF, float);
+ method public android.graphics.PointF getEnd();
+ method public float getEndFraction();
+ method public android.graphics.PointF getStart();
+ method public float getStartFraction();
+ }
+
+ public final class PathUtils {
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment> flatten(android.graphics.Path);
+ method @RequiresApi(26) public static java.util.Collection<androidx.core.graphics.PathSegment> flatten(android.graphics.Path, @FloatRange(from=0) float);
+ }
+
+ public class TypefaceCompat {
+ method public static android.graphics.Typeface create(android.content.Context, android.graphics.Typeface?, int);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class DrawableCompat {
+ method public static void applyTheme(android.graphics.drawable.Drawable, android.content.res.Resources.Theme);
+ method public static boolean canApplyTheme(android.graphics.drawable.Drawable);
+ method public static void clearColorFilter(android.graphics.drawable.Drawable);
+ method public static int getAlpha(android.graphics.drawable.Drawable);
+ method public static android.graphics.ColorFilter! getColorFilter(android.graphics.drawable.Drawable);
+ method public static int getLayoutDirection(android.graphics.drawable.Drawable);
+ method public static void inflate(android.graphics.drawable.Drawable, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static boolean isAutoMirrored(android.graphics.drawable.Drawable);
+ method @Deprecated public static void jumpToCurrentState(android.graphics.drawable.Drawable);
+ method public static void setAutoMirrored(android.graphics.drawable.Drawable, boolean);
+ method public static void setHotspot(android.graphics.drawable.Drawable, float, float);
+ method public static void setHotspotBounds(android.graphics.drawable.Drawable, int, int, int, int);
+ method public static boolean setLayoutDirection(android.graphics.drawable.Drawable, int);
+ method public static void setTint(android.graphics.drawable.Drawable, @ColorInt int);
+ method public static void setTintList(android.graphics.drawable.Drawable, android.content.res.ColorStateList?);
+ method public static void setTintMode(android.graphics.drawable.Drawable, android.graphics.PorterDuff.Mode);
+ method public static <T extends android.graphics.drawable.Drawable> T! unwrap(android.graphics.drawable.Drawable);
+ method public static android.graphics.drawable.Drawable! wrap(android.graphics.drawable.Drawable);
+ }
+
+ public class IconCompat extends androidx.versionedparcelable.CustomVersionedParcelable {
+ method public static androidx.core.graphics.drawable.IconCompat? createFromBundle(android.os.Bundle);
+ method @RequiresApi(23) public static androidx.core.graphics.drawable.IconCompat? createFromIcon(android.content.Context, android.graphics.drawable.Icon);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithAdaptiveBitmap(android.graphics.Bitmap!);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithBitmap(android.graphics.Bitmap!);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithContentUri(String!);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithContentUri(android.net.Uri!);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithData(byte[]!, int, int);
+ method public static androidx.core.graphics.drawable.IconCompat! createWithResource(android.content.Context!, @DrawableRes int);
+ method @IdRes public int getResId();
+ method public String getResPackage();
+ method public int getType();
+ method public android.net.Uri getUri();
+ method public android.graphics.drawable.Drawable! loadDrawable(android.content.Context!);
+ method public androidx.core.graphics.drawable.IconCompat! setTint(@ColorInt int);
+ method public androidx.core.graphics.drawable.IconCompat! setTintList(android.content.res.ColorStateList!);
+ method public androidx.core.graphics.drawable.IconCompat! setTintMode(android.graphics.PorterDuff.Mode!);
+ method public android.os.Bundle! toBundle();
+ method @RequiresApi(23) public android.graphics.drawable.Icon! toIcon();
+ field public static final int TYPE_UNKNOWN = -1; // 0xffffffff
+ }
+
+ public abstract class RoundedBitmapDrawable extends android.graphics.drawable.Drawable {
+ method public void draw(android.graphics.Canvas);
+ method public final android.graphics.Bitmap? getBitmap();
+ method public float getCornerRadius();
+ method public int getGravity();
+ method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
+ method public boolean hasAntiAlias();
+ method public boolean hasMipMap();
+ method public boolean isCircular();
+ method public void setAlpha(int);
+ method public void setAntiAlias(boolean);
+ method public void setCircular(boolean);
+ method public void setColorFilter(android.graphics.ColorFilter!);
+ method public void setCornerRadius(float);
+ method public void setDither(boolean);
+ method public void setGravity(int);
+ method public void setMipMap(boolean);
+ method public void setTargetDensity(android.graphics.Canvas);
+ method public void setTargetDensity(android.util.DisplayMetrics);
+ method public void setTargetDensity(int);
+ }
+
+ public final class RoundedBitmapDrawableFactory {
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, android.graphics.Bitmap?);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, String);
+ method public static androidx.core.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.io.InputStream);
+ }
+
+}
+
+package androidx.core.hardware.display {
+
+ public final class DisplayManagerCompat {
+ method public android.view.Display? getDisplay(int);
+ method public android.view.Display[] getDisplays();
+ method public android.view.Display[] getDisplays(String?);
+ method public static androidx.core.hardware.display.DisplayManagerCompat getInstance(android.content.Context);
+ field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+ }
+
+}
+
+package androidx.core.hardware.fingerprint {
+
+ @Deprecated public final class FingerprintManagerCompat {
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public void authenticate(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject?, int, androidx.core.os.CancellationSignal?, androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler?);
+ method @Deprecated public static androidx.core.hardware.fingerprint.FingerprintManagerCompat from(android.content.Context);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ }
+
+ @Deprecated public abstract static class FingerprintManagerCompat.AuthenticationCallback {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationCallback();
+ method @Deprecated public void onAuthenticationError(int, CharSequence!);
+ method @Deprecated public void onAuthenticationFailed();
+ method @Deprecated public void onAuthenticationHelp(int, CharSequence!);
+ method @Deprecated public void onAuthenticationSucceeded(androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationResult!);
+ }
+
+ @Deprecated public static final class FingerprintManagerCompat.AuthenticationResult {
+ ctor @Deprecated public FingerprintManagerCompat.AuthenticationResult(androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject!);
+ method @Deprecated public androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject! getCryptoObject();
+ }
+
+ @Deprecated public static class FingerprintManagerCompat.CryptoObject {
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(java.security.Signature);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Cipher);
+ ctor @Deprecated public FingerprintManagerCompat.CryptoObject(javax.crypto.Mac);
+ method @Deprecated public javax.crypto.Cipher? getCipher();
+ method @Deprecated public javax.crypto.Mac? getMac();
+ method @Deprecated public java.security.Signature? getSignature();
+ }
+
+}
+
+package androidx.core.location {
+
+ public final class LocationManagerCompat {
+ method public static boolean isLocationEnabled(android.location.LocationManager);
+ }
+
+}
+
+package androidx.core.math {
+
+ public class MathUtils {
+ method public static float clamp(float, float, float);
+ method public static double clamp(double, double, double);
+ method public static int clamp(int, int, int);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class ConnectivityManagerCompat {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static android.net.NetworkInfo? getNetworkInfoFromBroadcast(android.net.ConnectivityManager, android.content.Intent);
+ method public static int getRestrictBackgroundStatus(android.net.ConnectivityManager);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public static boolean isActiveNetworkMetered(android.net.ConnectivityManager);
+ field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1
+ field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3
+ field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2
+ }
+
+ public final class TrafficStatsCompat {
+ method @Deprecated public static void clearThreadStatsTag();
+ method @Deprecated public static int getThreadStatsTag();
+ method @Deprecated public static void incrementOperationCount(int);
+ method @Deprecated public static void incrementOperationCount(int, int);
+ method @Deprecated public static void setThreadStatsTag(int);
+ method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void tagSocket(java.net.Socket!) throws java.net.SocketException;
+ method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
+ method @Deprecated public static void untagSocket(java.net.Socket!) throws java.net.SocketException;
+ }
+
+}
+
+package androidx.core.os {
+
+ public class BuildCompat {
+ method @Deprecated public static boolean isAtLeastN();
+ method @Deprecated public static boolean isAtLeastNMR1();
+ method @Deprecated public static boolean isAtLeastO();
+ method @Deprecated public static boolean isAtLeastOMR1();
+ method @Deprecated public static boolean isAtLeastP();
+ method public static boolean isAtLeastQ();
+ }
+
+ public final class CancellationSignal {
+ ctor public CancellationSignal();
+ method public void cancel();
+ method public Object? getCancellationSignalObject();
+ method public boolean isCanceled();
+ method public void setOnCancelListener(androidx.core.os.CancellationSignal.OnCancelListener?);
+ method public void throwIfCanceled();
+ }
+
+ public static interface CancellationSignal.OnCancelListener {
+ method public void onCancel();
+ }
+
+ public final class ConfigurationCompat {
+ method public static androidx.core.os.LocaleListCompat getLocales(android.content.res.Configuration);
+ }
+
+ public final class EnvironmentCompat {
+ method public static String! getStorageState(java.io.File);
+ field public static final String MEDIA_UNKNOWN = "unknown";
+ }
+
+ public final class HandlerCompat {
+ method public static android.os.Handler createAsync(android.os.Looper);
+ method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+ method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
+ }
+
+ public final class LocaleListCompat {
+ method public static androidx.core.os.LocaleListCompat create(java.util.Locale...);
+ method public static androidx.core.os.LocaleListCompat forLanguageTags(String?);
+ method public java.util.Locale! get(int);
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getAdjustedDefault();
+ method @Size(min=1) public static androidx.core.os.LocaleListCompat getDefault();
+ method public static androidx.core.os.LocaleListCompat getEmptyLocaleList();
+ method public java.util.Locale? getFirstMatch(String[]);
+ method @IntRange(from=0xffffffff) public int indexOf(java.util.Locale!);
+ method public boolean isEmpty();
+ method @IntRange(from=0) public int size();
+ method public String toLanguageTags();
+ method public Object? unwrap();
+ method @Deprecated @RequiresApi(24) public static androidx.core.os.LocaleListCompat! wrap(Object!);
+ method @RequiresApi(24) public static androidx.core.os.LocaleListCompat wrap(android.os.LocaleList);
+ }
+
+ public final class MessageCompat {
+ method public static boolean isAsynchronous(android.os.Message);
+ method public static void setAsynchronous(android.os.Message, boolean);
+ }
+
+ public class OperationCanceledException extends java.lang.RuntimeException {
+ ctor public OperationCanceledException();
+ ctor public OperationCanceledException(String?);
+ }
+
+ public final class ParcelCompat {
+ method public static boolean readBoolean(android.os.Parcel);
+ method public static void writeBoolean(android.os.Parcel, boolean);
+ }
+
+ @Deprecated public final class ParcelableCompat {
+ method @Deprecated public static <T> android.os.Parcelable.Creator<T>! newCreator(androidx.core.os.ParcelableCompatCreatorCallbacks<T>!);
+ }
+
+ @Deprecated public interface ParcelableCompatCreatorCallbacks<T> {
+ method @Deprecated public T! createFromParcel(android.os.Parcel!, ClassLoader!);
+ method @Deprecated public T[]! newArray(int);
+ }
+
+ public final class TraceCompat {
+ method public static void beginSection(String);
+ method public static void endSection();
+ }
+
+ public class UserManagerCompat {
+ method public static boolean isUserUnlocked(android.content.Context);
+ }
+
+}
+
+package androidx.core.provider {
+
+ public final class FontRequest {
+ ctor public FontRequest(String, String, String, java.util.List<java.util.List<byte[]>>);
+ ctor public FontRequest(String, String, String, @ArrayRes int);
+ method public java.util.List<java.util.List<byte[]>>? getCertificates();
+ method @ArrayRes public int getCertificatesArrayResId();
+ method public String getProviderAuthority();
+ method public String getProviderPackage();
+ method public String getQuery();
+ }
+
+ public class FontsContractCompat {
+ method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo[]);
+ method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
+ }
+
+ public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
+ ctor public FontsContractCompat.Columns();
+ field public static final String FILE_ID = "file_id";
+ field public static final String ITALIC = "font_italic";
+ field public static final String RESULT_CODE = "result_code";
+ field public static final int RESULT_CODE_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int RESULT_CODE_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int RESULT_CODE_MALFORMED_QUERY = 3; // 0x3
+ field public static final int RESULT_CODE_OK = 0; // 0x0
+ field public static final String TTC_INDEX = "font_ttc_index";
+ field public static final String VARIATION_SETTINGS = "font_variation_settings";
+ field public static final String WEIGHT = "font_weight";
+ }
+
+ public static class FontsContractCompat.FontFamilyResult {
+ method public androidx.core.provider.FontsContractCompat.FontInfo[]! getFonts();
+ method public int getStatusCode();
+ field public static final int STATUS_OK = 0; // 0x0
+ field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+ field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+ }
+
+ public static class FontsContractCompat.FontInfo {
+ method public int getResultCode();
+ method @IntRange(from=0) public int getTtcIndex();
+ method public android.net.Uri getUri();
+ method @IntRange(from=1, to=1000) public int getWeight();
+ method public boolean isItalic();
+ }
+
+ public static class FontsContractCompat.FontRequestCallback {
+ ctor public FontsContractCompat.FontRequestCallback();
+ method public void onTypefaceRequestFailed(int);
+ method public void onTypefaceRetrieved(android.graphics.Typeface!);
+ field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+ field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+ field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+ field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+ field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+ field public static final int FAIL_REASON_SECURITY_VIOLATION = -4; // 0xfffffffc
+ field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+ }
+
+}
+
+package androidx.core.telephony.mbms {
+
+ public final class MbmsHelper {
+ method public static CharSequence? getBestNameForService(android.content.Context, android.telephony.mbms.ServiceInfo);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class BidiFormatter {
+ method public static androidx.core.text.BidiFormatter! getInstance();
+ method public static androidx.core.text.BidiFormatter! getInstance(boolean);
+ method public static androidx.core.text.BidiFormatter! getInstance(java.util.Locale!);
+ method public boolean getStereoReset();
+ method public boolean isRtl(String!);
+ method public boolean isRtl(CharSequence!);
+ method public boolean isRtlContext();
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!, boolean);
+ method public String! unicodeWrap(String!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public CharSequence! unicodeWrap(CharSequence!, androidx.core.text.TextDirectionHeuristicCompat!);
+ method public String! unicodeWrap(String!, boolean);
+ method public CharSequence! unicodeWrap(CharSequence!, boolean);
+ method public String! unicodeWrap(String!);
+ method public CharSequence! unicodeWrap(CharSequence!);
+ }
+
+ public static final class BidiFormatter.Builder {
+ ctor public BidiFormatter.Builder();
+ ctor public BidiFormatter.Builder(boolean);
+ ctor public BidiFormatter.Builder(java.util.Locale!);
+ method public androidx.core.text.BidiFormatter! build();
+ method public androidx.core.text.BidiFormatter.Builder! setTextDirectionHeuristic(androidx.core.text.TextDirectionHeuristicCompat!);
+ method public androidx.core.text.BidiFormatter.Builder! stereoReset(boolean);
+ }
+
+ public final class HtmlCompat {
+ method public static android.text.Spanned fromHtml(String, int);
+ method public static android.text.Spanned fromHtml(String, int, android.text.Html.ImageGetter?, android.text.Html.TagHandler?);
+ method public static String toHtml(android.text.Spanned, int);
+ field public static final int FROM_HTML_MODE_COMPACT = 63; // 0x3f
+ field public static final int FROM_HTML_MODE_LEGACY = 0; // 0x0
+ field public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 256; // 0x100
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 32; // 0x20
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 16; // 0x10
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 2; // 0x2
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 8; // 0x8
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 4; // 0x4
+ field public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 1; // 0x1
+ field public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0; // 0x0
+ field public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 1; // 0x1
+ }
+
+ public final class ICUCompat {
+ method public static String? maximizeAndGetScript(java.util.Locale!);
+ }
+
+ public class PrecomputedTextCompat implements android.text.Spannable {
+ method public char charAt(int);
+ method public static androidx.core.text.PrecomputedTextCompat! create(CharSequence, androidx.core.text.PrecomputedTextCompat.Params);
+ method @IntRange(from=0) public int getParagraphCount();
+ method @IntRange(from=0) public int getParagraphEnd(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getParagraphStart(@IntRange(from=0) int);
+ method public androidx.core.text.PrecomputedTextCompat.Params getParams();
+ method public int getSpanEnd(Object!);
+ method public int getSpanFlags(Object!);
+ method public int getSpanStart(Object!);
+ method public <T> T[]! getSpans(int, int, Class<T>!);
+ method @UiThread public static java.util.concurrent.Future<androidx.core.text.PrecomputedTextCompat>! getTextFuture(CharSequence, androidx.core.text.PrecomputedTextCompat.Params, java.util.concurrent.Executor?);
+ method public int length();
+ method public int nextSpanTransition(int, int, Class!);
+ method public void removeSpan(Object!);
+ method public void setSpan(Object!, int, int, int);
+ method public CharSequence! subSequence(int, int);
+ }
+
+ public static final class PrecomputedTextCompat.Params {
+ ctor @RequiresApi(28) public PrecomputedTextCompat.Params(android.text.PrecomputedText.Params);
+ method @RequiresApi(23) public int getBreakStrategy();
+ method @RequiresApi(23) public int getHyphenationFrequency();
+ method @RequiresApi(18) public android.text.TextDirectionHeuristic? getTextDirection();
+ method public android.text.TextPaint getTextPaint();
+ }
+
+ public static class PrecomputedTextCompat.Params.Builder {
+ ctor public PrecomputedTextCompat.Params.Builder(android.text.TextPaint);
+ method public androidx.core.text.PrecomputedTextCompat.Params build();
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setBreakStrategy(int);
+ method @RequiresApi(23) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setHyphenationFrequency(int);
+ method @RequiresApi(18) public androidx.core.text.PrecomputedTextCompat.Params.Builder! setTextDirection(android.text.TextDirectionHeuristic);
+ }
+
+ public interface TextDirectionHeuristicCompat {
+ method public boolean isRtl(char[]!, int, int);
+ method public boolean isRtl(CharSequence!, int, int);
+ }
+
+ public final class TextDirectionHeuristicsCompat {
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! ANYRTL_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! FIRSTSTRONG_RTL;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LOCALE;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! LTR;
+ field public static final androidx.core.text.TextDirectionHeuristicCompat! RTL;
+ }
+
+ public final class TextUtilsCompat {
+ method public static int getLayoutDirectionFromLocale(java.util.Locale?);
+ method public static String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.text.util {
+
+ public final class LinkifyCompat {
+ method public static boolean addLinks(android.text.Spannable, int);
+ method public static boolean addLinks(android.widget.TextView, int);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static void addLinks(android.widget.TextView, java.util.regex.Pattern, String?, String[]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ method public static boolean addLinks(android.text.Spannable, java.util.regex.Pattern, String?, String[]?, android.text.util.Linkify.MatchFilter?, android.text.util.Linkify.TransformFilter?);
+ }
+
+}
+
+package androidx.core.util {
+
+ public class AtomicFile {
+ ctor public AtomicFile(java.io.File);
+ method public void delete();
+ method public void failWrite(java.io.FileOutputStream?);
+ method public void finishWrite(java.io.FileOutputStream?);
+ method public java.io.File getBaseFile();
+ method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
+ method public byte[] readFully() throws java.io.IOException;
+ method public java.io.FileOutputStream startWrite() throws java.io.IOException;
+ }
+
+ public interface Consumer<T> {
+ method public void accept(T!);
+ }
+
+ public class ObjectsCompat {
+ method public static boolean equals(Object?, Object?);
+ method public static int hash(java.lang.Object...?);
+ method public static int hashCode(Object?);
+ }
+
+ public class Pair<F, S> {
+ ctor public Pair(F?, S?);
+ method public static <A, B> androidx.core.util.Pair<A,B> create(A?, B?);
+ field public final F? first;
+ field public final S? second;
+ }
+
+ public final class PatternsCompat {
+ field public static final java.util.regex.Pattern! DOMAIN_NAME;
+ field public static final java.util.regex.Pattern! EMAIL_ADDRESS;
+ field public static final java.util.regex.Pattern! IP_ADDRESS;
+ field public static final java.util.regex.Pattern! WEB_URL;
+ }
+
+ public final class Pools {
+ }
+
+ public static interface Pools.Pool<T> {
+ method public T? acquire();
+ method public boolean release(T);
+ }
+
+ public static class Pools.SimplePool<T> implements androidx.core.util.Pools.Pool<T> {
+ ctor public Pools.SimplePool(int);
+ method public T! acquire();
+ method public boolean release(T);
+ }
+
+ public static class Pools.SynchronizedPool<T> extends androidx.core.util.Pools.SimplePool<T> {
+ ctor public Pools.SynchronizedPool(int);
+ }
+
+ public interface Supplier<T> {
+ method public T! get();
+ }
+
+}
+
+package androidx.core.view {
+
+ public class AccessibilityDelegateCompat {
+ ctor public AccessibilityDelegateCompat();
+ method public boolean dispatchPopulateAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public androidx.core.view.accessibility.AccessibilityNodeProviderCompat! getAccessibilityNodeProvider(android.view.View!);
+ method public void onInitializeAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public void onInitializeAccessibilityNodeInfo(android.view.View!, androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method public void onPopulateAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public boolean onRequestSendAccessibilityEvent(android.view.ViewGroup!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public boolean performAccessibilityAction(android.view.View!, int, android.os.Bundle!);
+ method public void sendAccessibilityEvent(android.view.View!, int);
+ method public void sendAccessibilityEventUnchecked(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ }
+
+ public abstract class ActionProvider {
+ ctor public ActionProvider(android.content.Context!);
+ method public android.content.Context! getContext();
+ method public boolean hasSubMenu();
+ method public boolean isVisible();
+ method public abstract android.view.View! onCreateActionView();
+ method public android.view.View! onCreateActionView(android.view.MenuItem!);
+ method public boolean onPerformDefaultAction();
+ method public void onPrepareSubMenu(android.view.SubMenu!);
+ method public boolean overridesItemVisibility();
+ method public void refreshVisibility();
+ method public void setVisibilityListener(androidx.core.view.ActionProvider.VisibilityListener!);
+ }
+
+ public static interface ActionProvider.VisibilityListener {
+ method public void onActionProviderVisibilityChanged(boolean);
+ }
+
+ public final class DisplayCutoutCompat {
+ ctor public DisplayCutoutCompat(android.graphics.Rect!, java.util.List<android.graphics.Rect>!);
+ method public java.util.List<android.graphics.Rect>! getBoundingRects();
+ method public int getSafeInsetBottom();
+ method public int getSafeInsetLeft();
+ method public int getSafeInsetRight();
+ method public int getSafeInsetTop();
+ }
+
+ public final class DragAndDropPermissionsCompat {
+ method public void release();
+ }
+
+ public class DragStartHelper {
+ ctor public DragStartHelper(android.view.View!, androidx.core.view.DragStartHelper.OnDragStartListener!);
+ method public void attach();
+ method public void detach();
+ method public void getTouchPosition(android.graphics.Point!);
+ method public boolean onLongClick(android.view.View!);
+ method public boolean onTouch(android.view.View!, android.view.MotionEvent!);
+ }
+
+ public static interface DragStartHelper.OnDragStartListener {
+ method public boolean onDragStart(android.view.View!, androidx.core.view.DragStartHelper!);
+ }
+
+ public final class GestureDetectorCompat {
+ ctor public GestureDetectorCompat(android.content.Context!, android.view.GestureDetector.OnGestureListener!);
+ ctor public GestureDetectorCompat(android.content.Context!, android.view.GestureDetector.OnGestureListener!, android.os.Handler!);
+ method public boolean isLongpressEnabled();
+ method public boolean onTouchEvent(android.view.MotionEvent!);
+ method public void setIsLongpressEnabled(boolean);
+ method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener!);
+ }
+
+ public final class GravityCompat {
+ method public static void apply(int, int, int, android.graphics.Rect!, android.graphics.Rect!, int);
+ method public static void apply(int, int, int, android.graphics.Rect!, int, int, android.graphics.Rect!, int);
+ method public static void applyDisplay(int, android.graphics.Rect!, android.graphics.Rect!, int);
+ method public static int getAbsoluteGravity(int, int);
+ field public static final int END = 8388613; // 0x800005
+ field public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = 8388615; // 0x800007
+ field public static final int RELATIVE_LAYOUT_DIRECTION = 8388608; // 0x800000
+ field public static final int START = 8388611; // 0x800003
+ }
+
+ public final class InputDeviceCompat {
+ field public static final int SOURCE_ANY = -256; // 0xffffff00
+ field public static final int SOURCE_CLASS_BUTTON = 1; // 0x1
+ field public static final int SOURCE_CLASS_JOYSTICK = 16; // 0x10
+ field public static final int SOURCE_CLASS_MASK = 255; // 0xff
+ field public static final int SOURCE_CLASS_NONE = 0; // 0x0
+ field public static final int SOURCE_CLASS_POINTER = 2; // 0x2
+ field public static final int SOURCE_CLASS_POSITION = 8; // 0x8
+ field public static final int SOURCE_CLASS_TRACKBALL = 4; // 0x4
+ field public static final int SOURCE_DPAD = 513; // 0x201
+ field public static final int SOURCE_GAMEPAD = 1025; // 0x401
+ field public static final int SOURCE_HDMI = 33554433; // 0x2000001
+ field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
+ field public static final int SOURCE_KEYBOARD = 257; // 0x101
+ field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_ROTARY_ENCODER = 4194304; // 0x400000
+ field public static final int SOURCE_STYLUS = 16386; // 0x4002
+ field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
+ field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
+ field public static final int SOURCE_TOUCH_NAVIGATION = 2097152; // 0x200000
+ field public static final int SOURCE_TRACKBALL = 65540; // 0x10004
+ field public static final int SOURCE_UNKNOWN = 0; // 0x0
+ }
+
+ public final class LayoutInflaterCompat {
+ method @Deprecated public static androidx.core.view.LayoutInflaterFactory! getFactory(android.view.LayoutInflater!);
+ method @Deprecated public static void setFactory(android.view.LayoutInflater, androidx.core.view.LayoutInflaterFactory);
+ method public static void setFactory2(android.view.LayoutInflater, android.view.LayoutInflater.Factory2);
+ }
+
+ @Deprecated public interface LayoutInflaterFactory {
+ method @Deprecated public android.view.View! onCreateView(android.view.View!, String!, android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public final class MarginLayoutParamsCompat {
+ method public static int getLayoutDirection(android.view.ViewGroup.MarginLayoutParams!);
+ method public static int getMarginEnd(android.view.ViewGroup.MarginLayoutParams!);
+ method public static int getMarginStart(android.view.ViewGroup.MarginLayoutParams!);
+ method public static boolean isMarginRelative(android.view.ViewGroup.MarginLayoutParams!);
+ method public static void resolveLayoutDirection(android.view.ViewGroup.MarginLayoutParams!, int);
+ method public static void setLayoutDirection(android.view.ViewGroup.MarginLayoutParams!, int);
+ method public static void setMarginEnd(android.view.ViewGroup.MarginLayoutParams!, int);
+ method public static void setMarginStart(android.view.ViewGroup.MarginLayoutParams!, int);
+ }
+
+ public final class MenuCompat {
+ method public static void setGroupDividerEnabled(android.view.Menu!, boolean);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ }
+
+ public final class MenuItemCompat {
+ method @Deprecated public static boolean collapseActionView(android.view.MenuItem!);
+ method @Deprecated public static boolean expandActionView(android.view.MenuItem!);
+ method public static androidx.core.view.ActionProvider! getActionProvider(android.view.MenuItem!);
+ method @Deprecated public static android.view.View! getActionView(android.view.MenuItem!);
+ method public static int getAlphabeticModifiers(android.view.MenuItem!);
+ method public static CharSequence! getContentDescription(android.view.MenuItem!);
+ method public static android.content.res.ColorStateList! getIconTintList(android.view.MenuItem!);
+ method public static android.graphics.PorterDuff.Mode! getIconTintMode(android.view.MenuItem!);
+ method public static int getNumericModifiers(android.view.MenuItem!);
+ method public static CharSequence! getTooltipText(android.view.MenuItem!);
+ method @Deprecated public static boolean isActionViewExpanded(android.view.MenuItem!);
+ method public static android.view.MenuItem! setActionProvider(android.view.MenuItem!, androidx.core.view.ActionProvider!);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, android.view.View!);
+ method @Deprecated public static android.view.MenuItem! setActionView(android.view.MenuItem!, int);
+ method public static void setAlphabeticShortcut(android.view.MenuItem!, char, int);
+ method public static void setContentDescription(android.view.MenuItem!, CharSequence!);
+ method public static void setIconTintList(android.view.MenuItem!, android.content.res.ColorStateList!);
+ method public static void setIconTintMode(android.view.MenuItem!, android.graphics.PorterDuff.Mode!);
+ method public static void setNumericShortcut(android.view.MenuItem!, char, int);
+ method @Deprecated public static android.view.MenuItem! setOnActionExpandListener(android.view.MenuItem!, androidx.core.view.MenuItemCompat.OnActionExpandListener!);
+ method public static void setShortcut(android.view.MenuItem!, char, char, int, int);
+ method @Deprecated public static void setShowAsAction(android.view.MenuItem!, int);
+ method public static void setTooltipText(android.view.MenuItem!, CharSequence!);
+ field @Deprecated public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field @Deprecated public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field @Deprecated public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field @Deprecated public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field @Deprecated public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ @Deprecated public static interface MenuItemCompat.OnActionExpandListener {
+ method @Deprecated public boolean onMenuItemActionCollapse(android.view.MenuItem!);
+ method @Deprecated public boolean onMenuItemActionExpand(android.view.MenuItem!);
+ }
+
+ public final class MotionEventCompat {
+ method @Deprecated public static int findPointerIndex(android.view.MotionEvent!, int);
+ method @Deprecated public static int getActionIndex(android.view.MotionEvent!);
+ method @Deprecated public static int getActionMasked(android.view.MotionEvent!);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int);
+ method @Deprecated public static float getAxisValue(android.view.MotionEvent!, int, int);
+ method @Deprecated public static int getButtonState(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerCount(android.view.MotionEvent!);
+ method @Deprecated public static int getPointerId(android.view.MotionEvent!, int);
+ method @Deprecated public static int getSource(android.view.MotionEvent!);
+ method @Deprecated public static float getX(android.view.MotionEvent!, int);
+ method @Deprecated public static float getY(android.view.MotionEvent!, int);
+ method public static boolean isFromSource(android.view.MotionEvent!, int);
+ field @Deprecated public static final int ACTION_HOVER_ENTER = 9; // 0x9
+ field @Deprecated public static final int ACTION_HOVER_EXIT = 10; // 0xa
+ field @Deprecated public static final int ACTION_HOVER_MOVE = 7; // 0x7
+ field @Deprecated public static final int ACTION_MASK = 255; // 0xff
+ field @Deprecated public static final int ACTION_POINTER_DOWN = 5; // 0x5
+ field @Deprecated public static final int ACTION_POINTER_INDEX_MASK = 65280; // 0xff00
+ field @Deprecated public static final int ACTION_POINTER_INDEX_SHIFT = 8; // 0x8
+ field @Deprecated public static final int ACTION_POINTER_UP = 6; // 0x6
+ field @Deprecated public static final int ACTION_SCROLL = 8; // 0x8
+ field @Deprecated public static final int AXIS_BRAKE = 23; // 0x17
+ field @Deprecated public static final int AXIS_DISTANCE = 24; // 0x18
+ field @Deprecated public static final int AXIS_GAS = 22; // 0x16
+ field @Deprecated public static final int AXIS_GENERIC_1 = 32; // 0x20
+ field @Deprecated public static final int AXIS_GENERIC_10 = 41; // 0x29
+ field @Deprecated public static final int AXIS_GENERIC_11 = 42; // 0x2a
+ field @Deprecated public static final int AXIS_GENERIC_12 = 43; // 0x2b
+ field @Deprecated public static final int AXIS_GENERIC_13 = 44; // 0x2c
+ field @Deprecated public static final int AXIS_GENERIC_14 = 45; // 0x2d
+ field @Deprecated public static final int AXIS_GENERIC_15 = 46; // 0x2e
+ field @Deprecated public static final int AXIS_GENERIC_16 = 47; // 0x2f
+ field @Deprecated public static final int AXIS_GENERIC_2 = 33; // 0x21
+ field @Deprecated public static final int AXIS_GENERIC_3 = 34; // 0x22
+ field @Deprecated public static final int AXIS_GENERIC_4 = 35; // 0x23
+ field @Deprecated public static final int AXIS_GENERIC_5 = 36; // 0x24
+ field @Deprecated public static final int AXIS_GENERIC_6 = 37; // 0x25
+ field @Deprecated public static final int AXIS_GENERIC_7 = 38; // 0x26
+ field @Deprecated public static final int AXIS_GENERIC_8 = 39; // 0x27
+ field @Deprecated public static final int AXIS_GENERIC_9 = 40; // 0x28
+ field @Deprecated public static final int AXIS_HAT_X = 15; // 0xf
+ field @Deprecated public static final int AXIS_HAT_Y = 16; // 0x10
+ field @Deprecated public static final int AXIS_HSCROLL = 10; // 0xa
+ field @Deprecated public static final int AXIS_LTRIGGER = 17; // 0x11
+ field @Deprecated public static final int AXIS_ORIENTATION = 8; // 0x8
+ field @Deprecated public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+ field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
+ field @Deprecated public static final int AXIS_RTRIGGER = 18; // 0x12
+ field @Deprecated public static final int AXIS_RUDDER = 20; // 0x14
+ field @Deprecated public static final int AXIS_RX = 12; // 0xc
+ field @Deprecated public static final int AXIS_RY = 13; // 0xd
+ field @Deprecated public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
+ field @Deprecated public static final int AXIS_SIZE = 3; // 0x3
+ field @Deprecated public static final int AXIS_THROTTLE = 19; // 0x13
+ field @Deprecated public static final int AXIS_TILT = 25; // 0x19
+ field @Deprecated public static final int AXIS_TOOL_MAJOR = 6; // 0x6
+ field @Deprecated public static final int AXIS_TOOL_MINOR = 7; // 0x7
+ field @Deprecated public static final int AXIS_TOUCH_MAJOR = 4; // 0x4
+ field @Deprecated public static final int AXIS_TOUCH_MINOR = 5; // 0x5
+ field @Deprecated public static final int AXIS_VSCROLL = 9; // 0x9
+ field @Deprecated public static final int AXIS_WHEEL = 21; // 0x15
+ field @Deprecated public static final int AXIS_X = 0; // 0x0
+ field @Deprecated public static final int AXIS_Y = 1; // 0x1
+ field @Deprecated public static final int AXIS_Z = 11; // 0xb
+ field @Deprecated public static final int BUTTON_PRIMARY = 1; // 0x1
+ }
+
+ public interface NestedScrollingChild {
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean isNestedScrollingEnabled();
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(int);
+ method public void stopNestedScroll();
+ }
+
+ public interface NestedScrollingChild2 extends androidx.core.view.NestedScrollingChild {
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ }
+
+ public interface NestedScrollingChild3 extends androidx.core.view.NestedScrollingChild2 {
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]);
+ }
+
+ public class NestedScrollingChildHelper {
+ ctor public NestedScrollingChildHelper(android.view.View);
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?);
+ method public boolean dispatchNestedPreScroll(int, int, int[]?, int[]?, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]?, int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]?);
+ method public boolean hasNestedScrollingParent();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean isNestedScrollingEnabled();
+ method public void onDetachedFromWindow();
+ method public void onStopNestedScroll(android.view.View);
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll();
+ method public void stopNestedScroll(int);
+ }
+
+ public interface NestedScrollingParent {
+ method public int getNestedScrollAxes();
+ method public boolean onNestedFling(android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(android.view.View, float, float);
+ method public void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.view.View, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.view.View);
+ }
+
+ public interface NestedScrollingParent2 extends androidx.core.view.NestedScrollingParent {
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ }
+
+ public interface NestedScrollingParent3 extends androidx.core.view.NestedScrollingParent2 {
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ }
+
+ public class NestedScrollingParentHelper {
+ ctor public NestedScrollingParentHelper(android.view.ViewGroup);
+ method public int getNestedScrollAxes();
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View);
+ method public void onStopNestedScroll(android.view.View, int);
+ }
+
+ public interface OnApplyWindowInsetsListener {
+ method public androidx.core.view.WindowInsetsCompat! onApplyWindowInsets(android.view.View!, androidx.core.view.WindowInsetsCompat!);
+ }
+
+ public final class OneShotPreDrawListener implements android.view.View.OnAttachStateChangeListener android.view.ViewTreeObserver.OnPreDrawListener {
+ method public static androidx.core.view.OneShotPreDrawListener add(android.view.View, Runnable);
+ method public boolean onPreDraw();
+ method public void onViewAttachedToWindow(android.view.View!);
+ method public void onViewDetachedFromWindow(android.view.View!);
+ method public void removeListener();
+ }
+
+ public final class PointerIconCompat {
+ method public static androidx.core.view.PointerIconCompat! create(android.graphics.Bitmap!, float, float);
+ method public static androidx.core.view.PointerIconCompat! getSystemIcon(android.content.Context!, int);
+ method public static androidx.core.view.PointerIconCompat! load(android.content.res.Resources!, int);
+ field public static final int TYPE_ALIAS = 1010; // 0x3f2
+ field public static final int TYPE_ALL_SCROLL = 1013; // 0x3f5
+ field public static final int TYPE_ARROW = 1000; // 0x3e8
+ field public static final int TYPE_CELL = 1006; // 0x3ee
+ field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
+ field public static final int TYPE_COPY = 1011; // 0x3f3
+ field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
+ field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+ field public static final int TYPE_GRAB = 1020; // 0x3fc
+ field public static final int TYPE_GRABBING = 1021; // 0x3fd
+ field public static final int TYPE_HAND = 1002; // 0x3ea
+ field public static final int TYPE_HELP = 1003; // 0x3eb
+ field public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+ field public static final int TYPE_NO_DROP = 1012; // 0x3f4
+ field public static final int TYPE_NULL = 0; // 0x0
+ field public static final int TYPE_TEXT = 1008; // 0x3f0
+ field public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+ field public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+ field public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+ field public static final int TYPE_VERTICAL_TEXT = 1009; // 0x3f1
+ field public static final int TYPE_WAIT = 1004; // 0x3ec
+ field public static final int TYPE_ZOOM_IN = 1018; // 0x3fa
+ field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
+ }
+
+ public final class ScaleGestureDetectorCompat {
+ method @Deprecated public static boolean isQuickScaleEnabled(Object!);
+ method public static boolean isQuickScaleEnabled(android.view.ScaleGestureDetector!);
+ method @Deprecated public static void setQuickScaleEnabled(Object!, boolean);
+ method public static void setQuickScaleEnabled(android.view.ScaleGestureDetector!, boolean);
+ }
+
+ public interface ScrollingView {
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ }
+
+ public interface TintableBackgroundView {
+ method public android.content.res.ColorStateList? getSupportBackgroundTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportBackgroundTintMode();
+ method public void setSupportBackgroundTintList(android.content.res.ColorStateList?);
+ method public void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ @Deprecated public final class VelocityTrackerCompat {
+ method @Deprecated public static float getXVelocity(android.view.VelocityTracker!, int);
+ method @Deprecated public static float getYVelocity(android.view.VelocityTracker!, int);
+ }
+
+ public class ViewCompat {
+ ctor protected ViewCompat();
+ method public static int addAccessibilityAction(android.view.View, CharSequence, androidx.core.view.accessibility.AccessibilityViewCommand);
+ method public static void addKeyboardNavigationClusters(android.view.View, java.util.Collection<android.view.View>, int);
+ method public static void addOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method public static androidx.core.view.ViewPropertyAnimatorCompat animate(android.view.View);
+ method @Deprecated public static boolean canScrollHorizontally(android.view.View!, int);
+ method @Deprecated public static boolean canScrollVertically(android.view.View!, int);
+ method public static void cancelDragAndDrop(android.view.View);
+ method @Deprecated public static int combineMeasuredStates(int, int);
+ method public static androidx.core.view.WindowInsetsCompat! dispatchApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat!);
+ method public static void dispatchFinishTemporaryDetach(android.view.View);
+ method public static boolean dispatchNestedFling(android.view.View, float, float, boolean);
+ method public static boolean dispatchNestedPreFling(android.view.View, float, float);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[]?, int[]?, int);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?);
+ method public static void dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, int, int[]);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]?, int);
+ method public static void dispatchStartTemporaryDetach(android.view.View);
+ method public static void enableAccessibleClickableSpanSupport(android.view.View!);
+ method public static int generateViewId();
+ method public static androidx.core.view.AccessibilityDelegateCompat? getAccessibilityDelegate(android.view.View);
+ method public static int getAccessibilityLiveRegion(android.view.View);
+ method public static androidx.core.view.accessibility.AccessibilityNodeProviderCompat! getAccessibilityNodeProvider(android.view.View);
+ method @UiThread public static CharSequence! getAccessibilityPaneTitle(android.view.View!);
+ method @Deprecated public static float getAlpha(android.view.View!);
+ method public static android.content.res.ColorStateList! getBackgroundTintList(android.view.View);
+ method public static android.graphics.PorterDuff.Mode! getBackgroundTintMode(android.view.View);
+ method public static android.graphics.Rect? getClipBounds(android.view.View);
+ method public static android.view.Display? getDisplay(android.view.View);
+ method public static float getElevation(android.view.View);
+ method public static boolean getFitsSystemWindows(android.view.View);
+ method public static int getImportantForAccessibility(android.view.View);
+ method public static int getImportantForAutofill(android.view.View);
+ method public static int getLabelFor(android.view.View);
+ method @Deprecated public static int getLayerType(android.view.View!);
+ method public static int getLayoutDirection(android.view.View);
+ method @Deprecated public static android.graphics.Matrix? getMatrix(android.view.View!);
+ method @Deprecated public static int getMeasuredHeightAndState(android.view.View!);
+ method @Deprecated public static int getMeasuredState(android.view.View!);
+ method @Deprecated public static int getMeasuredWidthAndState(android.view.View!);
+ method public static int getMinimumHeight(android.view.View);
+ method public static int getMinimumWidth(android.view.View);
+ method public static int getNextClusterForwardId(android.view.View);
+ method @Deprecated public static int getOverScrollMode(android.view.View!);
+ method @Px public static int getPaddingEnd(android.view.View);
+ method @Px public static int getPaddingStart(android.view.View);
+ method public static android.view.ViewParent! getParentForAccessibility(android.view.View);
+ method @Deprecated public static float getPivotX(android.view.View!);
+ method @Deprecated public static float getPivotY(android.view.View!);
+ method @Deprecated public static float getRotation(android.view.View!);
+ method @Deprecated public static float getRotationX(android.view.View!);
+ method @Deprecated public static float getRotationY(android.view.View!);
+ method @Deprecated public static float getScaleX(android.view.View!);
+ method @Deprecated public static float getScaleY(android.view.View!);
+ method public static int getScrollIndicators(android.view.View);
+ method public static String? getTransitionName(android.view.View);
+ method @Deprecated public static float getTranslationX(android.view.View!);
+ method @Deprecated public static float getTranslationY(android.view.View!);
+ method public static float getTranslationZ(android.view.View);
+ method public static int getWindowSystemUiVisibility(android.view.View);
+ method @Deprecated public static float getX(android.view.View!);
+ method @Deprecated public static float getY(android.view.View!);
+ method public static float getZ(android.view.View);
+ method public static boolean hasAccessibilityDelegate(android.view.View);
+ method public static boolean hasExplicitFocusable(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View, int);
+ method public static boolean hasOnClickListeners(android.view.View);
+ method public static boolean hasOverlappingRendering(android.view.View);
+ method public static boolean hasTransientState(android.view.View);
+ method @UiThread public static boolean isAccessibilityHeading(android.view.View!);
+ method public static boolean isAttachedToWindow(android.view.View);
+ method public static boolean isFocusedByDefault(android.view.View);
+ method public static boolean isImportantForAccessibility(android.view.View);
+ method public static boolean isImportantForAutofill(android.view.View);
+ method public static boolean isInLayout(android.view.View);
+ method public static boolean isKeyboardNavigationCluster(android.view.View);
+ method public static boolean isLaidOut(android.view.View);
+ method public static boolean isLayoutDirectionResolved(android.view.View);
+ method public static boolean isNestedScrollingEnabled(android.view.View);
+ method @Deprecated public static boolean isOpaque(android.view.View!);
+ method public static boolean isPaddingRelative(android.view.View);
+ method @UiThread public static boolean isScreenReaderFocusable(android.view.View!);
+ method @Deprecated public static void jumpDrawablesToCurrentState(android.view.View!);
+ method public static android.view.View! keyboardNavigationClusterSearch(android.view.View, android.view.View!, int);
+ method public static void offsetLeftAndRight(android.view.View, int);
+ method public static void offsetTopAndBottom(android.view.View, int);
+ method public static androidx.core.view.WindowInsetsCompat! onApplyWindowInsets(android.view.View, androidx.core.view.WindowInsetsCompat!);
+ method @Deprecated public static void onInitializeAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public static void onInitializeAccessibilityNodeInfo(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method @Deprecated public static void onPopulateAccessibilityEvent(android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public static boolean performAccessibilityAction(android.view.View, int, android.os.Bundle!);
+ method public static void postInvalidateOnAnimation(android.view.View);
+ method public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
+ method public static void postOnAnimation(android.view.View, Runnable!);
+ method public static void postOnAnimationDelayed(android.view.View, Runnable!, long);
+ method public static void removeAccessibilityAction(android.view.View, int);
+ method public static void removeOnUnhandledKeyEventListener(android.view.View, androidx.core.view.ViewCompat.OnUnhandledKeyEventListenerCompat);
+ method public static void replaceAccessibilityAction(android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat, CharSequence?, androidx.core.view.accessibility.AccessibilityViewCommand?);
+ method public static void requestApplyInsets(android.view.View);
+ method public static <T extends android.view.View> T requireViewById(android.view.View, @IdRes int);
+ method @Deprecated public static int resolveSizeAndState(int, int, int);
+ method public static boolean restoreDefaultFocus(android.view.View);
+ method public static void setAccessibilityDelegate(android.view.View, androidx.core.view.AccessibilityDelegateCompat!);
+ method @UiThread public static void setAccessibilityHeading(android.view.View!, boolean);
+ method public static void setAccessibilityLiveRegion(android.view.View, int);
+ method @UiThread public static void setAccessibilityPaneTitle(android.view.View!, CharSequence!);
+ method @Deprecated public static void setActivated(android.view.View!, boolean);
+ method @Deprecated public static void setAlpha(android.view.View!, @FloatRange(from=0.0, to=1.0) float);
+ method public static void setAutofillHints(android.view.View, java.lang.String...?);
+ method public static void setBackground(android.view.View, android.graphics.drawable.Drawable?);
+ method public static void setBackgroundTintList(android.view.View, android.content.res.ColorStateList!);
+ method public static void setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode!);
+ method @Deprecated public static void setChildrenDrawingOrderEnabled(android.view.ViewGroup!, boolean);
+ method public static void setClipBounds(android.view.View, android.graphics.Rect!);
+ method public static void setElevation(android.view.View, float);
+ method @Deprecated public static void setFitsSystemWindows(android.view.View!, boolean);
+ method public static void setFocusedByDefault(android.view.View, boolean);
+ method public static void setHasTransientState(android.view.View, boolean);
+ method public static void setImportantForAccessibility(android.view.View, int);
+ method public static void setImportantForAutofill(android.view.View, int);
+ method public static void setKeyboardNavigationCluster(android.view.View, boolean);
+ method public static void setLabelFor(android.view.View, @IdRes int);
+ method public static void setLayerPaint(android.view.View, android.graphics.Paint!);
+ method @Deprecated public static void setLayerType(android.view.View!, int, android.graphics.Paint!);
+ method public static void setLayoutDirection(android.view.View, int);
+ method public static void setNestedScrollingEnabled(android.view.View, boolean);
+ method public static void setNextClusterForwardId(android.view.View, int);
+ method public static void setOnApplyWindowInsetsListener(android.view.View, androidx.core.view.OnApplyWindowInsetsListener!);
+ method @Deprecated public static void setOverScrollMode(android.view.View!, int);
+ method public static void setPaddingRelative(android.view.View, @Px int, @Px int, @Px int, @Px int);
+ method @Deprecated public static void setPivotX(android.view.View!, float);
+ method @Deprecated public static void setPivotY(android.view.View!, float);
+ method public static void setPointerIcon(android.view.View, androidx.core.view.PointerIconCompat!);
+ method @Deprecated public static void setRotation(android.view.View!, float);
+ method @Deprecated public static void setRotationX(android.view.View!, float);
+ method @Deprecated public static void setRotationY(android.view.View!, float);
+ method @Deprecated public static void setSaveFromParentEnabled(android.view.View!, boolean);
+ method @Deprecated public static void setScaleX(android.view.View!, float);
+ method @Deprecated public static void setScaleY(android.view.View!, float);
+ method @UiThread public static void setScreenReaderFocusable(android.view.View!, boolean);
+ method public static void setScrollIndicators(android.view.View, int);
+ method public static void setScrollIndicators(android.view.View, int, int);
+ method public static void setTooltipText(android.view.View, CharSequence?);
+ method public static void setTransitionName(android.view.View, String!);
+ method @Deprecated public static void setTranslationX(android.view.View!, float);
+ method @Deprecated public static void setTranslationY(android.view.View!, float);
+ method public static void setTranslationZ(android.view.View, float);
+ method @Deprecated public static void setX(android.view.View!, float);
+ method @Deprecated public static void setY(android.view.View!, float);
+ method public static void setZ(android.view.View, float);
+ method public static boolean startDragAndDrop(android.view.View, android.content.ClipData!, android.view.View.DragShadowBuilder!, Object!, int);
+ method public static boolean startNestedScroll(android.view.View, int);
+ method public static boolean startNestedScroll(android.view.View, int, int);
+ method public static void stopNestedScroll(android.view.View);
+ method public static void stopNestedScroll(android.view.View, int);
+ method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder!);
+ field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
+ field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
+ field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 4; // 0x4
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
+ field @Deprecated public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
+ field @Deprecated public static final int LAYER_TYPE_NONE = 0; // 0x0
+ field @Deprecated public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
+ field public static final int LAYOUT_DIRECTION_INHERIT = 2; // 0x2
+ field public static final int LAYOUT_DIRECTION_LOCALE = 3; // 0x3
+ field public static final int LAYOUT_DIRECTION_LTR = 0; // 0x0
+ field public static final int LAYOUT_DIRECTION_RTL = 1; // 0x1
+ field @Deprecated public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; // 0x10
+ field @Deprecated public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
+ field @Deprecated public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
+ field @Deprecated public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field @Deprecated public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
+ field @Deprecated public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
+ field @Deprecated public static final int OVER_SCROLL_NEVER = 2; // 0x2
+ field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int SCROLL_AXIS_NONE = 0; // 0x0
+ field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_END = 32; // 0x20
+ field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
+ field public static final int SCROLL_INDICATOR_RIGHT = 8; // 0x8
+ field public static final int SCROLL_INDICATOR_START = 16; // 0x10
+ field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
+ field public static final int TYPE_NON_TOUCH = 1; // 0x1
+ field public static final int TYPE_TOUCH = 0; // 0x0
+ }
+
+ public static interface ViewCompat.OnUnhandledKeyEventListenerCompat {
+ method public boolean onUnhandledKeyEvent(android.view.View!, android.view.KeyEvent!);
+ }
+
+ public final class ViewConfigurationCompat {
+ method public static float getScaledHorizontalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method public static int getScaledHoverSlop(android.view.ViewConfiguration!);
+ method @Deprecated public static int getScaledPagingTouchSlop(android.view.ViewConfiguration!);
+ method public static float getScaledVerticalScrollFactor(android.view.ViewConfiguration, android.content.Context);
+ method @Deprecated public static boolean hasPermanentMenuKey(android.view.ViewConfiguration!);
+ method public static boolean shouldShowMenuShortcutsWhenKeyboardPresent(android.view.ViewConfiguration!, android.content.Context);
+ }
+
+ public final class ViewGroupCompat {
+ method public static int getLayoutMode(android.view.ViewGroup);
+ method public static int getNestedScrollAxes(android.view.ViewGroup);
+ method public static boolean isTransitionGroup(android.view.ViewGroup);
+ method @Deprecated public static boolean onRequestSendAccessibilityEvent(android.view.ViewGroup!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ method public static void setLayoutMode(android.view.ViewGroup, int);
+ method @Deprecated public static void setMotionEventSplittingEnabled(android.view.ViewGroup!, boolean);
+ method public static void setTransitionGroup(android.view.ViewGroup, boolean);
+ field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
+ field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
+ }
+
+ public final class ViewParentCompat {
+ method public static void notifySubtreeAccessibilityStateChanged(android.view.ViewParent!, android.view.View!, android.view.View!, int);
+ method public static boolean onNestedFling(android.view.ViewParent!, android.view.View!, float, float, boolean);
+ method public static boolean onNestedPreFling(android.view.ViewParent!, android.view.View!, float, float);
+ method public static void onNestedPreScroll(android.view.ViewParent!, android.view.View!, int, int, int[]!);
+ method public static void onNestedPreScroll(android.view.ViewParent!, android.view.View!, int, int, int[]!, int);
+ method public static void onNestedScroll(android.view.ViewParent!, android.view.View!, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent!, android.view.View!, int, int, int, int, int);
+ method public static void onNestedScroll(android.view.ViewParent!, android.view.View!, int, int, int, int, int, int[]);
+ method public static void onNestedScrollAccepted(android.view.ViewParent!, android.view.View!, android.view.View!, int);
+ method public static void onNestedScrollAccepted(android.view.ViewParent!, android.view.View!, android.view.View!, int, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent!, android.view.View!, android.view.View!, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent!, android.view.View!, android.view.View!, int, int);
+ method public static void onStopNestedScroll(android.view.ViewParent!, android.view.View!);
+ method public static void onStopNestedScroll(android.view.ViewParent!, android.view.View!, int);
+ method @Deprecated public static boolean requestSendAccessibilityEvent(android.view.ViewParent!, android.view.View!, android.view.accessibility.AccessibilityEvent!);
+ }
+
+ public final class ViewPropertyAnimatorCompat {
+ method public androidx.core.view.ViewPropertyAnimatorCompat! alpha(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! alphaBy(float);
+ method public void cancel();
+ method public long getDuration();
+ method public android.view.animation.Interpolator! getInterpolator();
+ method public long getStartDelay();
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotation(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotationBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! rotationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! scaleX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! scaleXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! scaleY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! scaleYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! setDuration(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! setInterpolator(android.view.animation.Interpolator!);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! setListener(androidx.core.view.ViewPropertyAnimatorListener!);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! setStartDelay(long);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! setUpdateListener(androidx.core.view.ViewPropertyAnimatorUpdateListener!);
+ method public void start();
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationX(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationXBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationY(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationYBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationZ(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! translationZBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! withEndAction(Runnable!);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! withLayer();
+ method public androidx.core.view.ViewPropertyAnimatorCompat! withStartAction(Runnable!);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! x(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! xBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! y(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! yBy(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! z(float);
+ method public androidx.core.view.ViewPropertyAnimatorCompat! zBy(float);
+ }
+
+ public interface ViewPropertyAnimatorListener {
+ method public void onAnimationCancel(android.view.View!);
+ method public void onAnimationEnd(android.view.View!);
+ method public void onAnimationStart(android.view.View!);
+ }
+
+ public class ViewPropertyAnimatorListenerAdapter implements androidx.core.view.ViewPropertyAnimatorListener {
+ ctor public ViewPropertyAnimatorListenerAdapter();
+ method public void onAnimationCancel(android.view.View!);
+ method public void onAnimationEnd(android.view.View!);
+ method public void onAnimationStart(android.view.View!);
+ }
+
+ public interface ViewPropertyAnimatorUpdateListener {
+ method public void onAnimationUpdate(android.view.View!);
+ }
+
+ public final class WindowCompat {
+ method public static <T extends android.view.View> T requireViewById(android.view.Window, @IdRes int);
+ field public static final int FEATURE_ACTION_BAR = 8; // 0x8
+ field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ }
+
+ public class WindowInsetsCompat {
+ ctor public WindowInsetsCompat(androidx.core.view.WindowInsetsCompat!);
+ method public androidx.core.view.WindowInsetsCompat! consumeDisplayCutout();
+ method public androidx.core.view.WindowInsetsCompat! consumeStableInsets();
+ method public androidx.core.view.WindowInsetsCompat! consumeSystemWindowInsets();
+ method public androidx.core.view.DisplayCutoutCompat? getDisplayCutout();
+ method public int getStableInsetBottom();
+ method public int getStableInsetLeft();
+ method public int getStableInsetRight();
+ method public int getStableInsetTop();
+ method public int getSystemWindowInsetBottom();
+ method public int getSystemWindowInsetLeft();
+ method public int getSystemWindowInsetRight();
+ method public int getSystemWindowInsetTop();
+ method public boolean hasInsets();
+ method public boolean hasStableInsets();
+ method public boolean hasSystemWindowInsets();
+ method public boolean isConsumed();
+ method public boolean isRound();
+ method public androidx.core.view.WindowInsetsCompat! replaceSystemWindowInsets(int, int, int, int);
+ method public androidx.core.view.WindowInsetsCompat! replaceSystemWindowInsets(android.graphics.Rect!);
+ }
+
+}
+
+package androidx.core.view.accessibility {
+
+ public final class AccessibilityClickableSpanCompat extends android.text.style.ClickableSpan {
+ method public void onClick(android.view.View!);
+ }
+
+ public final class AccessibilityEventCompat {
+ method @Deprecated public static void appendRecord(android.view.accessibility.AccessibilityEvent!, androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! asRecord(android.view.accessibility.AccessibilityEvent!);
+ method public static int getAction(android.view.accessibility.AccessibilityEvent!);
+ method public static int getContentChangeTypes(android.view.accessibility.AccessibilityEvent!);
+ method public static int getMovementGranularity(android.view.accessibility.AccessibilityEvent!);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! getRecord(android.view.accessibility.AccessibilityEvent!, int);
+ method @Deprecated public static int getRecordCount(android.view.accessibility.AccessibilityEvent!);
+ method public static void setAction(android.view.accessibility.AccessibilityEvent!, int);
+ method public static void setContentChangeTypes(android.view.accessibility.AccessibilityEvent!, int);
+ method public static void setMovementGranularity(android.view.accessibility.AccessibilityEvent!, int);
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
+ field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
+ field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
+ field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+ field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
+ field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
+ field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
+ field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
+ field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
+ field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
+ field @Deprecated public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
+ field public static final int TYPE_TOUCH_INTERACTION_END = 2097152; // 0x200000
+ field public static final int TYPE_TOUCH_INTERACTION_START = 1048576; // 0x100000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 32768; // 0x8000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 65536; // 0x10000
+ field public static final int TYPE_VIEW_CONTEXT_CLICKED = 8388608; // 0x800000
+ field @Deprecated public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
+ field @Deprecated public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
+ field @Deprecated public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000
+ field @Deprecated public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
+ field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072; // 0x20000
+ field public static final int TYPE_WINDOWS_CHANGED = 4194304; // 0x400000
+ field @Deprecated public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
+ }
+
+ public final class AccessibilityManagerCompat {
+ method @Deprecated public static boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener!);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo>! getEnabledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!, int);
+ method @Deprecated public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo>! getInstalledAccessibilityServiceList(android.view.accessibility.AccessibilityManager!);
+ method @Deprecated public static boolean isTouchExplorationEnabled(android.view.accessibility.AccessibilityManager!);
+ method @Deprecated public static boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener!);
+ method public static boolean removeTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager!, androidx.core.view.accessibility.AccessibilityManagerCompat.TouchExplorationStateChangeListener!);
+ }
+
+ @Deprecated public static interface AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ method @Deprecated public void onAccessibilityStateChanged(boolean);
+ }
+
+ @Deprecated public abstract static class AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat implements androidx.core.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListener {
+ ctor @Deprecated public AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat();
+ }
+
+ public static interface AccessibilityManagerCompat.TouchExplorationStateChangeListener {
+ method public void onTouchExplorationStateChanged(boolean);
+ }
+
+ public class AccessibilityNodeInfoCompat {
+ ctor @Deprecated public AccessibilityNodeInfoCompat(Object!);
+ method public void addAction(int);
+ method public void addAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public void addChild(android.view.View!);
+ method public void addChild(android.view.View!, int);
+ method public boolean canOpenPopup();
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat>! findAccessibilityNodeInfosByText(String!);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat>! findAccessibilityNodeInfosByViewId(String!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! findFocus(int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! focusSearch(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat>! getActionList();
+ method public int getActions();
+ method public void getBoundsInParent(android.graphics.Rect!);
+ method public void getBoundsInScreen(android.graphics.Rect!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getChild(int);
+ method public int getChildCount();
+ method public CharSequence! getClassName();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! getCollectionInfo();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! getCollectionItemInfo();
+ method public CharSequence! getContentDescription();
+ method public int getDrawingOrder();
+ method public CharSequence! getError();
+ method public android.os.Bundle! getExtras();
+ method public CharSequence? getHintText();
+ method @Deprecated public Object! getInfo();
+ method public int getInputType();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabelFor();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getLabeledBy();
+ method public int getLiveRegion();
+ method public int getMaxTextLength();
+ method public int getMovementGranularities();
+ method public CharSequence! getPackageName();
+ method public CharSequence? getPaneTitle();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getParent();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! getRangeInfo();
+ method public CharSequence? getRoleDescription();
+ method public CharSequence! getText();
+ method public int getTextSelectionEnd();
+ method public int getTextSelectionStart();
+ method public CharSequence? getTooltipText();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalAfter();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getTraversalBefore();
+ method public String! getViewIdResourceName();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat! getWindow();
+ method public int getWindowId();
+ method public boolean isAccessibilityFocused();
+ method public boolean isCheckable();
+ method public boolean isChecked();
+ method public boolean isClickable();
+ method public boolean isContentInvalid();
+ method public boolean isContextClickable();
+ method public boolean isDismissable();
+ method public boolean isEditable();
+ method public boolean isEnabled();
+ method public boolean isFocusable();
+ method public boolean isFocused();
+ method public boolean isHeading();
+ method public boolean isImportantForAccessibility();
+ method public boolean isLongClickable();
+ method public boolean isMultiLine();
+ method public boolean isPassword();
+ method public boolean isScreenReaderFocusable();
+ method public boolean isScrollable();
+ method public boolean isSelected();
+ method public boolean isShowingHintText();
+ method public boolean isVisibleToUser();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(android.view.View!, int);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! obtain(androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method public boolean performAction(int);
+ method public boolean performAction(int, android.os.Bundle!);
+ method public void recycle();
+ method public boolean refresh();
+ method public boolean removeAction(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat!);
+ method public boolean removeChild(android.view.View!);
+ method public boolean removeChild(android.view.View!, int);
+ method public void setAccessibilityFocused(boolean);
+ method public void setBoundsInParent(android.graphics.Rect!);
+ method public void setBoundsInScreen(android.graphics.Rect!);
+ method public void setCanOpenPopup(boolean);
+ method public void setCheckable(boolean);
+ method public void setChecked(boolean);
+ method public void setClassName(CharSequence!);
+ method @Deprecated public void setClickable(boolean);
+ method public void setCollectionInfo(Object!);
+ method public void setCollectionItemInfo(Object!);
+ method public void setContentDescription(CharSequence!);
+ method public void setContentInvalid(boolean);
+ method @Deprecated public void setContextClickable(boolean);
+ method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
+ method public void setEditable(boolean);
+ method public void setEnabled(boolean);
+ method public void setError(CharSequence!);
+ method @Deprecated public void setFocusable(boolean);
+ method public void setFocused(boolean);
+ method public void setHeading(boolean);
+ method public void setHintText(CharSequence?);
+ method public void setImportantForAccessibility(boolean);
+ method public void setInputType(int);
+ method public void setLabelFor(android.view.View!);
+ method public void setLabelFor(android.view.View!, int);
+ method public void setLabeledBy(android.view.View!);
+ method public void setLabeledBy(android.view.View!, int);
+ method public void setLiveRegion(int);
+ method @Deprecated public void setLongClickable(boolean);
+ method public void setMaxTextLength(int);
+ method public void setMovementGranularities(int);
+ method public void setMultiLine(boolean);
+ method public void setPackageName(CharSequence!);
+ method public void setPaneTitle(CharSequence?);
+ method public void setParent(android.view.View!);
+ method public void setParent(android.view.View!, int);
+ method public void setPassword(boolean);
+ method public void setRangeInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat!);
+ method public void setRoleDescription(CharSequence?);
+ method public void setScreenReaderFocusable(boolean);
+ method @Deprecated public void setScrollable(boolean);
+ method public void setSelected(boolean);
+ method public void setShowingHintText(boolean);
+ method public void setSource(android.view.View!);
+ method public void setSource(android.view.View!, int);
+ method public void setText(CharSequence!);
+ method public void setTextSelection(int, int);
+ method public void setTooltipText(CharSequence?);
+ method public void setTraversalAfter(android.view.View!);
+ method public void setTraversalAfter(android.view.View!, int);
+ method public void setTraversalBefore(android.view.View!);
+ method public void setTraversalBefore(android.view.View!, int);
+ method public void setViewIdResourceName(String!);
+ method public void setVisibleToUser(boolean);
+ method public android.view.accessibility.AccessibilityNodeInfo! unwrap();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat! wrap(android.view.accessibility.AccessibilityNodeInfo);
+ field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
+ field public static final String ACTION_ARGUMENT_COLUMN_INT = "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+ field public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+ field public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ field public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_X = "ACTION_ARGUMENT_MOVE_WINDOW_X";
+ field public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y = "ACTION_ARGUMENT_MOVE_WINDOW_Y";
+ field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
+ field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
+ field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
+ field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+ field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
+ field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
+ field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+ field public static final int ACTION_CLICK = 16; // 0x10
+ field public static final int ACTION_COLLAPSE = 524288; // 0x80000
+ field public static final int ACTION_COPY = 16384; // 0x4000
+ field public static final int ACTION_CUT = 65536; // 0x10000
+ field public static final int ACTION_DISMISS = 1048576; // 0x100000
+ field public static final int ACTION_EXPAND = 262144; // 0x40000
+ field public static final int ACTION_FOCUS = 1; // 0x1
+ field public static final int ACTION_LONG_CLICK = 32; // 0x20
+ field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100
+ field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
+ field public static final int ACTION_PASTE = 32768; // 0x8000
+ field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200
+ field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
+ field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000
+ field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000
+ field public static final int ACTION_SELECT = 4; // 0x4
+ field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
+ field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+ field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
+ field public static final int FOCUS_INPUT = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
+ field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
+ field public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 8; // 0x8
+ field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2
+ }
+
+ public static class AccessibilityNodeInfoCompat.AccessibilityActionCompat {
+ ctor public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, CharSequence!);
+ method public int getId();
+ method public CharSequence! getLabel();
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLEAR_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COLLAPSE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CONTEXT_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_COPY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_CUT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_DISMISS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_EXPAND;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_FOCUS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_HIDE_TOOLTIP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_LONG_CLICK;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_MOVE_WINDOW;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_NEXT_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PASTE;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_PREVIOUS_HTML_ELEMENT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_BACKWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_DOWN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_FORWARD;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_LEFT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_RIGHT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_TO_POSITION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SCROLL_UP;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SELECT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_PROGRESS;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_SELECTION;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SET_TEXT;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_ON_SCREEN;
+ field public static final androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! ACTION_SHOW_TOOLTIP;
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionInfoCompat {
+ method public int getColumnCount();
+ method public int getRowCount();
+ method public int getSelectionMode();
+ method public boolean isHierarchical();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean, int);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat! obtain(int, int, boolean);
+ field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
+ field public static final int SELECTION_MODE_NONE = 0; // 0x0
+ field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionItemInfoCompat {
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ method @Deprecated public boolean isHeading();
+ method public boolean isSelected();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean, boolean);
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat! obtain(int, int, int, int, boolean);
+ }
+
+ public static class AccessibilityNodeInfoCompat.RangeInfoCompat {
+ method public float getCurrent();
+ method public float getMax();
+ method public float getMin();
+ method public int getType();
+ method public static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat! obtain(int, float, float, float);
+ field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+ field public static final int RANGE_TYPE_INT = 0; // 0x0
+ field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
+ }
+
+ public class AccessibilityNodeProviderCompat {
+ ctor public AccessibilityNodeProviderCompat();
+ ctor public AccessibilityNodeProviderCompat(Object!);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? createAccessibilityNodeInfo(int);
+ method public java.util.List<androidx.core.view.accessibility.AccessibilityNodeInfoCompat>? findAccessibilityNodeInfosByText(String!, int);
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat? findFocus(int);
+ method public Object! getProvider();
+ method public boolean performAction(int, int, android.os.Bundle!);
+ field public static final int HOST_VIEW_ID = -1; // 0xffffffff
+ }
+
+ public class AccessibilityRecordCompat {
+ ctor @Deprecated public AccessibilityRecordCompat(Object!);
+ method @Deprecated public boolean equals(Object?);
+ method @Deprecated public int getAddedCount();
+ method @Deprecated public CharSequence! getBeforeText();
+ method @Deprecated public CharSequence! getClassName();
+ method @Deprecated public CharSequence! getContentDescription();
+ method @Deprecated public int getCurrentItemIndex();
+ method @Deprecated public int getFromIndex();
+ method @Deprecated public Object! getImpl();
+ method @Deprecated public int getItemCount();
+ method @Deprecated public int getMaxScrollX();
+ method public static int getMaxScrollX(android.view.accessibility.AccessibilityRecord!);
+ method @Deprecated public int getMaxScrollY();
+ method public static int getMaxScrollY(android.view.accessibility.AccessibilityRecord!);
+ method @Deprecated public android.os.Parcelable! getParcelableData();
+ method @Deprecated public int getRemovedCount();
+ method @Deprecated public int getScrollX();
+ method @Deprecated public int getScrollY();
+ method @Deprecated public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getSource();
+ method @Deprecated public java.util.List<java.lang.CharSequence>! getText();
+ method @Deprecated public int getToIndex();
+ method @Deprecated public int getWindowId();
+ method @Deprecated public int hashCode();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isEnabled();
+ method @Deprecated public boolean isFullScreen();
+ method @Deprecated public boolean isPassword();
+ method @Deprecated public boolean isScrollable();
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain(androidx.core.view.accessibility.AccessibilityRecordCompat!);
+ method @Deprecated public static androidx.core.view.accessibility.AccessibilityRecordCompat! obtain();
+ method @Deprecated public void recycle();
+ method @Deprecated public void setAddedCount(int);
+ method @Deprecated public void setBeforeText(CharSequence!);
+ method @Deprecated public void setChecked(boolean);
+ method @Deprecated public void setClassName(CharSequence!);
+ method @Deprecated public void setContentDescription(CharSequence!);
+ method @Deprecated public void setCurrentItemIndex(int);
+ method @Deprecated public void setEnabled(boolean);
+ method @Deprecated public void setFromIndex(int);
+ method @Deprecated public void setFullScreen(boolean);
+ method @Deprecated public void setItemCount(int);
+ method @Deprecated public void setMaxScrollX(int);
+ method public static void setMaxScrollX(android.view.accessibility.AccessibilityRecord!, int);
+ method @Deprecated public void setMaxScrollY(int);
+ method public static void setMaxScrollY(android.view.accessibility.AccessibilityRecord!, int);
+ method @Deprecated public void setParcelableData(android.os.Parcelable!);
+ method @Deprecated public void setPassword(boolean);
+ method @Deprecated public void setRemovedCount(int);
+ method @Deprecated public void setScrollX(int);
+ method @Deprecated public void setScrollY(int);
+ method @Deprecated public void setScrollable(boolean);
+ method @Deprecated public void setSource(android.view.View!);
+ method @Deprecated public void setSource(android.view.View!, int);
+ method public static void setSource(android.view.accessibility.AccessibilityRecord, android.view.View!, int);
+ method @Deprecated public void setToIndex(int);
+ }
+
+ public interface AccessibilityViewCommand {
+ method public boolean perform(android.view.View, androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments?);
+ }
+
+ public abstract static class AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.CommandArguments();
+ }
+
+ public static final class AccessibilityViewCommand.MoveAtGranularityArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveAtGranularityArguments();
+ method public boolean getExtendSelection();
+ method public int getGranularity();
+ }
+
+ public static final class AccessibilityViewCommand.MoveHtmlArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveHtmlArguments();
+ method public String! getHTMLElement();
+ }
+
+ public static final class AccessibilityViewCommand.MoveWindowArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.MoveWindowArguments();
+ method public int getX();
+ method public int getY();
+ }
+
+ public static final class AccessibilityViewCommand.ScrollToPositionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.ScrollToPositionArguments();
+ method public int getColumn();
+ method public int getRow();
+ }
+
+ public static final class AccessibilityViewCommand.SetProgressArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetProgressArguments();
+ method public float getProgress();
+ }
+
+ public static final class AccessibilityViewCommand.SetSelectionArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetSelectionArguments();
+ method public int getEnd();
+ method public int getStart();
+ }
+
+ public static final class AccessibilityViewCommand.SetTextArguments extends androidx.core.view.accessibility.AccessibilityViewCommand.CommandArguments {
+ ctor public AccessibilityViewCommand.SetTextArguments();
+ method public CharSequence! getText();
+ }
+
+ public class AccessibilityWindowInfoCompat {
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getAnchor();
+ method public void getBoundsInScreen(android.graphics.Rect!);
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat! getChild(int);
+ method public int getChildCount();
+ method public int getId();
+ method public int getLayer();
+ method public androidx.core.view.accessibility.AccessibilityWindowInfoCompat! getParent();
+ method public androidx.core.view.accessibility.AccessibilityNodeInfoCompat! getRoot();
+ method public CharSequence! getTitle();
+ method public int getType();
+ method public boolean isAccessibilityFocused();
+ method public boolean isActive();
+ method public boolean isFocused();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat! obtain();
+ method public static androidx.core.view.accessibility.AccessibilityWindowInfoCompat! obtain(androidx.core.view.accessibility.AccessibilityWindowInfoCompat!);
+ method public void recycle();
+ field public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; // 0x4
+ field public static final int TYPE_APPLICATION = 1; // 0x1
+ field public static final int TYPE_INPUT_METHOD = 2; // 0x2
+ field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5
+ field public static final int TYPE_SYSTEM = 3; // 0x3
+ }
+
+}
+
+package androidx.core.view.animation {
+
+ public final class PathInterpolatorCompat {
+ method public static android.view.animation.Interpolator! create(android.graphics.Path!);
+ method public static android.view.animation.Interpolator! create(float, float);
+ method public static android.view.animation.Interpolator! create(float, float, float, float);
+ }
+
+}
+
+package androidx.core.view.inputmethod {
+
+ public final class EditorInfoCompat {
+ ctor @Deprecated public EditorInfoCompat();
+ method public static String[] getContentMimeTypes(android.view.inputmethod.EditorInfo!);
+ method public static void setContentMimeTypes(android.view.inputmethod.EditorInfo, String[]?);
+ field public static final int IME_FLAG_FORCE_ASCII = -2147483648; // 0x80000000
+ field public static final int IME_FLAG_NO_PERSONALIZED_LEARNING = 16777216; // 0x1000000
+ }
+
+ public final class InputConnectionCompat {
+ ctor @Deprecated public InputConnectionCompat();
+ method public static boolean commitContent(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputContentInfoCompat, int, android.os.Bundle?);
+ method public static android.view.inputmethod.InputConnection createWrapper(android.view.inputmethod.InputConnection, android.view.inputmethod.EditorInfo, androidx.core.view.inputmethod.InputConnectionCompat.OnCommitContentListener);
+ field public static final int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = 1; // 0x1
+ }
+
+ public static interface InputConnectionCompat.OnCommitContentListener {
+ method public boolean onCommitContent(androidx.core.view.inputmethod.InputContentInfoCompat!, int, android.os.Bundle!);
+ }
+
+ public final class InputContentInfoCompat {
+ ctor public InputContentInfoCompat(android.net.Uri, android.content.ClipDescription, android.net.Uri?);
+ method public android.net.Uri getContentUri();
+ method public android.content.ClipDescription getDescription();
+ method public android.net.Uri? getLinkUri();
+ method public void releasePermission();
+ method public void requestPermission();
+ method public Object? unwrap();
+ method public static androidx.core.view.inputmethod.InputContentInfoCompat? wrap(Object?);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public abstract class AutoScrollHelper implements android.view.View.OnTouchListener {
+ ctor public AutoScrollHelper(android.view.View);
+ method public abstract boolean canTargetScrollHorizontally(int);
+ method public abstract boolean canTargetScrollVertically(int);
+ method public boolean isEnabled();
+ method public boolean isExclusive();
+ method public boolean onTouch(android.view.View!, android.view.MotionEvent!);
+ method public abstract void scrollTargetBy(int, int);
+ method public androidx.core.widget.AutoScrollHelper setActivationDelay(int);
+ method public androidx.core.widget.AutoScrollHelper setEdgeType(int);
+ method public androidx.core.widget.AutoScrollHelper! setEnabled(boolean);
+ method public androidx.core.widget.AutoScrollHelper! setExclusive(boolean);
+ method public androidx.core.widget.AutoScrollHelper setMaximumEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMaximumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setMinimumVelocity(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRampDownDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRampUpDuration(int);
+ method public androidx.core.widget.AutoScrollHelper setRelativeEdges(float, float);
+ method public androidx.core.widget.AutoScrollHelper setRelativeVelocity(float, float);
+ field public static final int EDGE_TYPE_INSIDE = 0; // 0x0
+ field public static final int EDGE_TYPE_INSIDE_EXTEND = 1; // 0x1
+ field public static final int EDGE_TYPE_OUTSIDE = 2; // 0x2
+ field public static final float NO_MAX = 3.4028235E38f;
+ field public static final float NO_MIN = 0.0f;
+ field public static final float RELATIVE_UNSPECIFIED = 0.0f;
+ }
+
+ public final class CompoundButtonCompat {
+ method public static android.graphics.drawable.Drawable? getButtonDrawable(android.widget.CompoundButton);
+ method public static android.content.res.ColorStateList? getButtonTintList(android.widget.CompoundButton);
+ method public static android.graphics.PorterDuff.Mode? getButtonTintMode(android.widget.CompoundButton);
+ method public static void setButtonTintList(android.widget.CompoundButton, android.content.res.ColorStateList?);
+ method public static void setButtonTintMode(android.widget.CompoundButton, android.graphics.PorterDuff.Mode?);
+ }
+
+ public class ContentLoadingProgressBar extends android.widget.ProgressBar {
+ ctor public ContentLoadingProgressBar(android.content.Context);
+ ctor public ContentLoadingProgressBar(android.content.Context, android.util.AttributeSet?);
+ method public void hide();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void show();
+ }
+
+ public final class EdgeEffectCompat {
+ ctor @Deprecated public EdgeEffectCompat(android.content.Context!);
+ method @Deprecated public boolean draw(android.graphics.Canvas!);
+ method @Deprecated public void finish();
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean onAbsorb(int);
+ method @Deprecated public boolean onPull(float);
+ method @Deprecated public boolean onPull(float, float);
+ method public static void onPull(android.widget.EdgeEffect, float, float);
+ method @Deprecated public boolean onRelease();
+ method @Deprecated public void setSize(int, int);
+ }
+
+ public class ImageViewCompat {
+ method public static android.content.res.ColorStateList? getImageTintList(android.widget.ImageView);
+ method public static android.graphics.PorterDuff.Mode? getImageTintMode(android.widget.ImageView);
+ method public static void setImageTintList(android.widget.ImageView, android.content.res.ColorStateList?);
+ method public static void setImageTintMode(android.widget.ImageView, android.graphics.PorterDuff.Mode?);
+ }
+
+ public final class ListPopupWindowCompat {
+ method @Deprecated public static android.view.View.OnTouchListener! createDragToOpenListener(Object!, android.view.View!);
+ method public static android.view.View.OnTouchListener? createDragToOpenListener(android.widget.ListPopupWindow, android.view.View);
+ }
+
+ public class ListViewAutoScrollHelper extends androidx.core.widget.AutoScrollHelper {
+ ctor public ListViewAutoScrollHelper(android.widget.ListView);
+ method public boolean canTargetScrollHorizontally(int);
+ method public boolean canTargetScrollVertically(int);
+ method public void scrollTargetBy(int, int);
+ }
+
+ public final class ListViewCompat {
+ method public static boolean canScrollList(android.widget.ListView, int);
+ method public static void scrollListBy(android.widget.ListView, int);
+ }
+
+ public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild3 androidx.core.view.NestedScrollingParent3 androidx.core.view.ScrollingView {
+ ctor public NestedScrollView(android.content.Context);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet?, int);
+ method public boolean arrowScroll(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollRange();
+ method protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]!, int[]!, int);
+ method public void dispatchNestedScroll(int, int, int, int, int[]?, int, int[]);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]!, int);
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fling(int);
+ method public boolean fullScroll(int);
+ method public int getMaxScrollAmount();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean isFillViewport();
+ method public boolean isSmoothScrollingEnabled();
+ method public void onAttachedToWindow();
+ method public void onNestedPreScroll(android.view.View, int, int, int[], int);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int, int[]);
+ method public void onNestedScroll(android.view.View, int, int, int, int, int);
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int, int);
+ method public boolean onStartNestedScroll(android.view.View, android.view.View, int, int);
+ method public void onStopNestedScroll(android.view.View, int);
+ method public boolean pageScroll(int);
+ method public void setFillViewport(boolean);
+ method public void setOnScrollChangeListener(androidx.core.widget.NestedScrollView.OnScrollChangeListener?);
+ method public void setSmoothScrollingEnabled(boolean);
+ method public final void smoothScrollBy(int, int);
+ method public final void smoothScrollTo(int, int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ }
+
+ public static interface NestedScrollView.OnScrollChangeListener {
+ method public void onScrollChange(androidx.core.widget.NestedScrollView!, int, int, int, int);
+ }
+
+ public final class PopupMenuCompat {
+ method public static android.view.View.OnTouchListener? getDragToOpenListener(Object);
+ }
+
+ public final class PopupWindowCompat {
+ method public static boolean getOverlapAnchor(android.widget.PopupWindow);
+ method public static int getWindowLayoutType(android.widget.PopupWindow);
+ method public static void setOverlapAnchor(android.widget.PopupWindow, boolean);
+ method public static void setWindowLayoutType(android.widget.PopupWindow, int);
+ method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
+ }
+
+ @Deprecated public final class ScrollerCompat {
+ method @Deprecated public void abortAnimation();
+ method @Deprecated public boolean computeScrollOffset();
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!);
+ method @Deprecated public static androidx.core.widget.ScrollerCompat! create(android.content.Context!, android.view.animation.Interpolator!);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int);
+ method @Deprecated public void fling(int, int, int, int, int, int, int, int, int, int);
+ method @Deprecated public float getCurrVelocity();
+ method @Deprecated public int getCurrX();
+ method @Deprecated public int getCurrY();
+ method @Deprecated public int getFinalX();
+ method @Deprecated public int getFinalY();
+ method @Deprecated public boolean isFinished();
+ method @Deprecated public boolean isOverScrolled();
+ method @Deprecated public void notifyHorizontalEdgeReached(int, int, int);
+ method @Deprecated public void notifyVerticalEdgeReached(int, int, int);
+ method @Deprecated public boolean springBack(int, int, int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int);
+ method @Deprecated public void startScroll(int, int, int, int, int);
+ }
+
+ public final class TextViewCompat {
+ method public static int getAutoSizeMaxTextSize(android.widget.TextView);
+ method public static int getAutoSizeMinTextSize(android.widget.TextView);
+ method public static int getAutoSizeStepGranularity(android.widget.TextView);
+ method public static int[] getAutoSizeTextAvailableSizes(android.widget.TextView);
+ method public static int getAutoSizeTextType(android.widget.TextView);
+ method public static android.content.res.ColorStateList? getCompoundDrawableTintList(android.widget.TextView);
+ method public static android.graphics.PorterDuff.Mode? getCompoundDrawableTintMode(android.widget.TextView);
+ method public static android.graphics.drawable.Drawable[] getCompoundDrawablesRelative(android.widget.TextView);
+ method public static int getFirstBaselineToTopHeight(android.widget.TextView);
+ method public static int getLastBaselineToBottomHeight(android.widget.TextView);
+ method public static int getMaxLines(android.widget.TextView);
+ method public static int getMinLines(android.widget.TextView);
+ method public static androidx.core.text.PrecomputedTextCompat.Params getTextMetricsParams(android.widget.TextView);
+ method public static void setAutoSizeTextTypeUniformWithConfiguration(android.widget.TextView, int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeUniformWithPresetSizes(android.widget.TextView, int[], int) throws java.lang.IllegalArgumentException;
+ method public static void setAutoSizeTextTypeWithDefaults(android.widget.TextView, int);
+ method public static void setCompoundDrawableTintList(android.widget.TextView, android.content.res.ColorStateList?);
+ method public static void setCompoundDrawableTintMode(android.widget.TextView, android.graphics.PorterDuff.Mode?);
+ method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?, android.graphics.drawable.Drawable?);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, @DrawableRes int, @DrawableRes int, @DrawableRes int, @DrawableRes int);
+ method public static void setCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback);
+ method public static void setFirstBaselineToTopHeight(android.widget.TextView, @Px @IntRange(from=0) int);
+ method public static void setLastBaselineToBottomHeight(android.widget.TextView, @Px @IntRange(from=0) int);
+ method public static void setLineHeight(android.widget.TextView, @Px @IntRange(from=0) int);
+ method public static void setPrecomputedText(android.widget.TextView, androidx.core.text.PrecomputedTextCompat);
+ method public static void setTextAppearance(android.widget.TextView, @StyleRes int);
+ method public static void setTextMetricsParams(android.widget.TextView, androidx.core.text.PrecomputedTextCompat.Params);
+ field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
+ field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
+ }
+
+ public interface TintableCompoundButton {
+ method public android.content.res.ColorStateList? getSupportButtonTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportButtonTintMode();
+ method public void setSupportButtonTintList(android.content.res.ColorStateList?);
+ method public void setSupportButtonTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+ public interface TintableCompoundDrawablesView {
+ method public android.content.res.ColorStateList? getSupportCompoundDrawablesTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportCompoundDrawablesTintMode();
+ method public void setSupportCompoundDrawablesTintList(android.content.res.ColorStateList?);
+ method public void setSupportCompoundDrawablesTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+}
+
diff --git a/core/api/current.txt b/core/api/current.txt
index 0ece2e3..84939d0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -633,10 +633,9 @@
method public androidx.core.app.Person.Builder setUri(String?);
}
- public final class RemoteActionCompat {
+ public final class RemoteActionCompat implements androidx.versionedparcelable.VersionedParcelable {
ctor public RemoteActionCompat(androidx.core.graphics.drawable.IconCompat, CharSequence, CharSequence, android.app.PendingIntent);
ctor public RemoteActionCompat(androidx.core.app.RemoteActionCompat);
- method public static androidx.core.app.RemoteActionCompat createFromBundle(android.os.Bundle);
method @RequiresApi(26) public static androidx.core.app.RemoteActionCompat createFromRemoteAction(android.app.RemoteAction);
method public android.app.PendingIntent getActionIntent();
method public CharSequence getContentDescription();
@@ -646,7 +645,6 @@
method public void setEnabled(boolean);
method public void setShouldShowIcon(boolean);
method public boolean shouldShowIcon();
- method public android.os.Bundle toBundle();
method @RequiresApi(26) public android.app.RemoteAction toRemoteAction();
}
diff --git a/core/api/res-1.1.0-alpha06.txt b/core/api/res-1.1.0-alpha06.txt
new file mode 100644
index 0000000..e52dd8c
--- /dev/null
+++ b/core/api/res-1.1.0-alpha06.txt
@@ -0,0 +1,17 @@
+style TextAppearance_Compat_Notification
+style TextAppearance_Compat_Notification_Info
+style TextAppearance_Compat_Notification_Line2
+style TextAppearance_Compat_Notification_Time
+style TextAppearance_Compat_Notification_Title
+attr alpha
+attr font
+attr fontProviderAuthority
+attr fontProviderCerts
+attr fontProviderFetchStrategy
+attr fontProviderFetchTimeout
+attr fontProviderPackage
+attr fontProviderQuery
+attr fontStyle
+attr fontVariationSettings
+attr fontWeight
+attr ttcIndex
diff --git a/core/api/restricted_1.1.0-alpha06.ignore b/core/api/restricted_1.1.0-alpha06.ignore
new file mode 100644
index 0000000..c0d50ed
--- /dev/null
+++ b/core/api/restricted_1.1.0-alpha06.ignore
@@ -0,0 +1,55 @@
+// Baseline format: 1.0
+AddedAbstractMethod: androidx.core.internal.view.SupportMenuItem#requiresActionButton():
+ Added method androidx.core.internal.view.SupportMenuItem.requiresActionButton()
+AddedAbstractMethod: androidx.core.internal.view.SupportMenuItem#requiresOverflow():
+ Added method androidx.core.internal.view.SupportMenuItem.requiresOverflow()
+
+
+InvalidNullConversion: androidx.core.graphics.TypefaceCompatApi28Impl#createFromFontFamilyFilesResourceEntry(android.content.Context, androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry, android.content.res.Resources, int):
+ Attempted to remove @Nullable annotation from method androidx.core.graphics.TypefaceCompatApi28Impl.createFromFontFamilyFilesResourceEntry(android.content.Context,androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry,android.content.res.Resources,int)
+
+
+RemovedClass: androidx.core.graphics.PathParser:
+ Removed class androidx.core.graphics.PathParser
+RemovedClass: androidx.core.graphics.drawable.IconCompatParcelizer:
+ Removed class androidx.core.graphics.drawable.IconCompatParcelizer
+
+
+RemovedInterface: androidx.core.widget.NestedScrollView:
+ Class androidx.core.widget.NestedScrollView no longer implements androidx.core.view.NestedScrollingChild2
+
+
+RemovedMethod: androidx.core.app.ComponentActivity#getLifecycle():
+ Removed method androidx.core.app.ComponentActivity.getLifecycle()
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentFinite(float, String):
+ Removed method androidx.core.util.Preconditions.checkArgumentFinite(float,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentInRange(float, float, float, String):
+ Removed method androidx.core.util.Preconditions.checkArgumentInRange(float,float,float,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentInRange(long, long, long, String):
+ Removed method androidx.core.util.Preconditions.checkArgumentInRange(long,long,long,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentNonnegative(long):
+ Removed method androidx.core.util.Preconditions.checkArgumentNonnegative(long)
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentNonnegative(long, String):
+ Removed method androidx.core.util.Preconditions.checkArgumentNonnegative(long,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArgumentPositive(int, String):
+ Removed method androidx.core.util.Preconditions.checkArgumentPositive(int,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArrayElementsInRange(float[], float, float, String):
+ Removed method androidx.core.util.Preconditions.checkArrayElementsInRange(float[],float,float,String)
+RemovedMethod: androidx.core.util.Preconditions#checkArrayElementsNotNull(T[], String):
+ Removed method androidx.core.util.Preconditions.checkArrayElementsNotNull(T[],String)
+RemovedMethod: androidx.core.util.Preconditions#checkCollectionElementsNotNull(C, String):
+ Removed method androidx.core.util.Preconditions.checkCollectionElementsNotNull(C,String)
+RemovedMethod: androidx.core.util.Preconditions#checkCollectionNotEmpty(java.util.Collection<T>, String):
+ Removed method androidx.core.util.Preconditions.checkCollectionNotEmpty(java.util.Collection<T>,String)
+RemovedMethod: androidx.core.util.Preconditions#checkFlagsArgument(int, int):
+ Removed method androidx.core.util.Preconditions.checkFlagsArgument(int,int)
+RemovedMethod: androidx.core.util.Preconditions#checkStringNotEmpty(T):
+ Removed method androidx.core.util.Preconditions.checkStringNotEmpty(T)
+RemovedMethod: androidx.core.util.Preconditions#checkStringNotEmpty(T, Object):
+ Removed method androidx.core.util.Preconditions.checkStringNotEmpty(T,Object)
+
+
+RemovedPackage: android.support.v4.graphics.drawable:
+ Removed package android.support.v4.graphics.drawable
+
+
diff --git a/core/api/restricted_1.1.0-alpha06.txt b/core/api/restricted_1.1.0-alpha06.txt
new file mode 100644
index 0000000..5baf105
--- /dev/null
+++ b/core/api/restricted_1.1.0-alpha06.txt
@@ -0,0 +1,562 @@
+// Signature format: 3.0
+package android.support.v4.os {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ResultReceiver implements android.os.Parcelable {
+ ctor public ResultReceiver(android.os.Handler!);
+ method public int describeContents();
+ method protected void onReceiveResult(int, android.os.Bundle!);
+ method public void send(int, android.os.Bundle!);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.os.ResultReceiver>! CREATOR;
+ }
+
+}
+
+package androidx.core.app {
+
+ public class ActivityCompat extends androidx.core.content.ContextCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.ActivityCompat.PermissionCompatDelegate! getPermissionCompatDelegate();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface ActivityCompat.RequestPermissionsRequestCodeValidator {
+ method public void validateRequestPermissionsRequestCode(int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ComponentActivity extends android.app.Activity implements androidx.core.view.KeyEventDispatcher.Component {
+ ctor public ComponentActivity();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T extends androidx.core.app.ComponentActivity.ExtraData> T! getExtraData(Class<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void putExtraData(androidx.core.app.ComponentActivity.ExtraData!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean superDispatchKeyEvent(android.view.KeyEvent!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class ComponentActivity.ExtraData {
+ ctor public ComponentActivity.ExtraData();
+ }
+
+ @RequiresApi(api=28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CoreComponentFactory extends android.app.AppComponentFactory {
+ ctor public CoreComponentFactory();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface CoreComponentFactory.CompatWrapped {
+ method public Object! getWrapper();
+ }
+
+ @IntDef(flag=true, value={androidx.core.app.FrameMetricsAggregator.TOTAL_DURATION, androidx.core.app.FrameMetricsAggregator.INPUT_DURATION, androidx.core.app.FrameMetricsAggregator.LAYOUT_MEASURE_DURATION, androidx.core.app.FrameMetricsAggregator.DRAW_DURATION, androidx.core.app.FrameMetricsAggregator.SYNC_DURATION, androidx.core.app.FrameMetricsAggregator.COMMAND_DURATION, androidx.core.app.FrameMetricsAggregator.SWAP_DURATION, androidx.core.app.FrameMetricsAggregator.DELAY_DURATION, androidx.core.app.FrameMetricsAggregator.ANIMATION_DURATION, androidx.core.app.FrameMetricsAggregator.EVERY_DURATION}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FrameMetricsAggregator.MetricType {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface NotificationBuilderWithBuilderAccessor {
+ method public android.app.Notification.Builder! getBuilder();
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.BADGE_ICON_NONE, androidx.core.app.NotificationCompat.BADGE_ICON_SMALL, androidx.core.app.NotificationCompat.BADGE_ICON_LARGE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.BadgeIconType {
+ }
+
+ public static class NotificationCompat.Builder {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getBigContentView();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getColor();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getContentView();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! getHeadsUpContentView();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getPriority();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public long getWhenIfShowing();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.core.app.NotificationCompat.Action>! mActions;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.content.Context! mContext;
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.GROUP_ALERT_ALL, androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY, androidx.core.app.NotificationCompat.GROUP_ALERT_CHILDREN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.GroupAlertBehavior {
+ }
+
+ @IntDef({androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC, androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE, androidx.core.app.NotificationCompat.VISIBILITY_SECRET}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.NotificationVisibility {
+ }
+
+ @IntDef({android.media.AudioManager.STREAM_VOICE_CALL, android.media.AudioManager.STREAM_SYSTEM, android.media.AudioManager.STREAM_RING, android.media.AudioManager.STREAM_MUSIC, android.media.AudioManager.STREAM_ALARM, android.media.AudioManager.STREAM_NOTIFICATION, android.media.AudioManager.STREAM_DTMF, android.media.AudioManager.STREAM_ACCESSIBILITY}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NotificationCompat.StreamType {
+ }
+
+ public abstract static class NotificationCompat.Style {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addCompatExtras(android.os.Bundle!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void apply(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! applyStandardTemplate(boolean, int, boolean);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void buildIntoRemoteViews(android.widget.RemoteViews!, android.widget.RemoteViews!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.Bitmap! createColoredBitmap(int, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeBigContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.widget.RemoteViews! makeHeadsUpContentView(androidx.core.app.NotificationBuilderWithBuilderAccessor!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void restoreFromCompatExtras(android.os.Bundle!);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected androidx.core.app.NotificationCompat.Builder! mBuilder;
+ }
+
+ public class Person {
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromAndroidPerson(android.app.Person);
+ method @RequiresApi(22) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromPersistableBundle(android.os.PersistableBundle);
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.Person toAndroidPerson();
+ method @RequiresApi(22) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.os.PersistableBundle toPersistableBundle();
+ }
+
+ @IntDef({androidx.core.app.RemoteInput.SOURCE_FREE_FORM_INPUT, androidx.core.app.RemoteInput.SOURCE_CHOICE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RemoteInput.Source {
+ }
+
+ @IntDef(flag=true, value={androidx.core.app.ServiceCompat.STOP_FOREGROUND_REMOVE, androidx.core.app.ServiceCompat.STOP_FOREGROUND_DETACH}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ServiceCompat.StopForegroundFlags {
+ }
+
+}
+
+package androidx.core.content {
+
+ @IntDef({androidx.core.content.PermissionChecker.PERMISSION_GRANTED, androidx.core.content.PermissionChecker.PERMISSION_DENIED, androidx.core.content.PermissionChecker.PERMISSION_DENIED_APP_OP}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PermissionChecker.PermissionResult {
+ }
+
+}
+
+package androidx.core.content.pm {
+
+
+
+ public class ShortcutInfoCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.graphics.drawable.IconCompat! getIcon();
+ }
+
+ public static class ShortcutInfoCompat.Builder {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ShortcutInfoCompat.Builder(androidx.core.content.pm.ShortcutInfoCompat);
+ ctor @RequiresApi(25) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ShortcutInfoCompat.Builder(android.content.Context, android.content.pm.ShortcutInfo);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ShortcutInfoCompatSaver {
+ ctor public ShortcutInfoCompatSaver();
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void>! addShortcuts(java.util.List<androidx.core.content.pm.ShortcutInfoCompat>!);
+ method @WorkerThread public java.util.List<androidx.core.content.pm.ShortcutInfoCompat>! getShortcuts() throws java.lang.Exception;
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void>! removeAllShortcuts();
+ method @AnyThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void>! removeShortcuts(java.util.List<java.lang.String>!);
+ }
+
+}
+
+package androidx.core.content.res {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ColorStateListInflaterCompat {
+ method public static android.content.res.ColorStateList createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static android.content.res.ColorStateList createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme?) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static android.content.res.ColorStateList? inflate(android.content.res.Resources, @XmlRes int, android.content.res.Resources.Theme?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class ComplexColorCompat {
+ method @ColorInt public int getColor();
+ method public android.graphics.Shader? getShader();
+ method public static androidx.core.content.res.ComplexColorCompat? inflate(android.content.res.Resources, @ColorRes int, android.content.res.Resources.Theme?);
+ method public boolean isGradient();
+ method public boolean isStateful();
+ method public boolean onStateChanged(int[]!);
+ method public void setColor(@ColorInt int);
+ method public boolean willDraw();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FontResourcesParserCompat {
+ method public static androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry? parse(org.xmlpull.v1.XmlPullParser!, android.content.res.Resources!) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public static java.util.List<java.util.List<byte[]>>! readCerts(android.content.res.Resources!, @ArrayRes int);
+ field public static final int FETCH_STRATEGY_ASYNC = 1; // 0x1
+ field public static final int FETCH_STRATEGY_BLOCKING = 0; // 0x0
+ field public static final int INFINITE_TIMEOUT_VALUE = -1; // 0xffffffff
+ }
+
+ public static interface FontResourcesParserCompat.FamilyResourceEntry {
+ }
+
+ @IntDef({androidx.core.content.res.FontResourcesParserCompat.FETCH_STRATEGY_BLOCKING, androidx.core.content.res.FontResourcesParserCompat.FETCH_STRATEGY_ASYNC}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontResourcesParserCompat.FetchStrategy {
+ }
+
+ public static final class FontResourcesParserCompat.FontFamilyFilesResourceEntry implements androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry {
+ ctor public FontResourcesParserCompat.FontFamilyFilesResourceEntry(androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry[]);
+ method public androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry[] getEntries();
+ }
+
+ public static final class FontResourcesParserCompat.FontFileResourceEntry {
+ ctor public FontResourcesParserCompat.FontFileResourceEntry(String, int, boolean, String?, int, int);
+ method public String getFileName();
+ method public int getResourceId();
+ method public int getTtcIndex();
+ method public String? getVariationSettings();
+ method public int getWeight();
+ method public boolean isItalic();
+ }
+
+ public static final class FontResourcesParserCompat.ProviderResourceEntry implements androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry {
+ ctor public FontResourcesParserCompat.ProviderResourceEntry(androidx.core.provider.FontRequest, @androidx.core.content.res.FontResourcesParserCompat.FetchStrategy int, int);
+ method @androidx.core.content.res.FontResourcesParserCompat.FetchStrategy public int getFetchStrategy();
+ method public androidx.core.provider.FontRequest getRequest();
+ method public int getTimeout();
+ }
+
+ public final class ResourcesCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFont(android.content.Context, @FontRes int, android.util.TypedValue!, int, androidx.core.content.res.ResourcesCompat.FontCallback?) throws android.content.res.Resources.NotFoundException;
+ }
+
+ public abstract static class ResourcesCompat.FontCallback {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void callbackFailAsync(@androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason int, android.os.Handler?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final void callbackSuccessAsync(android.graphics.Typeface!, android.os.Handler?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypedArrayUtils {
+ method public static int getAttr(android.content.Context, int, int);
+ method public static boolean getBoolean(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, boolean);
+ method public static android.graphics.drawable.Drawable? getDrawable(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static int getInt(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, int);
+ method public static boolean getNamedBoolean(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, boolean);
+ method @ColorInt public static int getNamedColor(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, @ColorInt int);
+ method public static android.content.res.ColorStateList? getNamedColorStateList(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?, String, @StyleableRes int);
+ method public static androidx.core.content.res.ComplexColorCompat! getNamedComplexColor(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme?, String, @StyleableRes int, @ColorInt int);
+ method public static float getNamedFloat(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, float);
+ method public static int getNamedInt(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, int);
+ method @AnyRes public static int getNamedResourceId(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int, @AnyRes int);
+ method public static String? getNamedString(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, @StyleableRes int);
+ method @AnyRes public static int getResourceId(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int, @AnyRes int);
+ method public static String? getString(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static CharSequence? getText(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static CharSequence[]? getTextArray(android.content.res.TypedArray, @StyleableRes int, @StyleableRes int);
+ method public static boolean hasAttribute(org.xmlpull.v1.XmlPullParser, String);
+ method public static android.content.res.TypedArray obtainAttributes(android.content.res.Resources, android.content.res.Resources.Theme?, android.util.AttributeSet, int[]);
+ method public static android.util.TypedValue? peekNamedValue(android.content.res.TypedArray, org.xmlpull.v1.XmlPullParser, String, int);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public class TypefaceCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromFontInfo(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo[], int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromResourcesFamilyXml(android.content.Context, androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry, android.content.res.Resources, int, int, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? createFromResourcesFontFile(android.content.Context, android.content.res.Resources, int, String!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface? findFromCache(android.content.res.Resources, int, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(26) public class TypefaceCompatApi26Impl {
+ ctor public TypefaceCompatApi26Impl();
+ method protected android.graphics.Typeface? createFromFamiliesWithDefault(Object!);
+ method public android.graphics.Typeface? createFromFontFamilyFilesResourceEntry(android.content.Context!, androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry!, android.content.res.Resources!, int);
+ method public android.graphics.Typeface? createFromFontInfo(android.content.Context!, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo[], int);
+ method public android.graphics.Typeface? createFromResourcesFontFile(android.content.Context!, android.content.res.Resources!, int, String!, int);
+ method protected java.lang.reflect.Method! obtainAbortCreationMethod(Class!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainAddFontFromAssetManagerMethod(Class!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainAddFontFromBufferMethod(Class!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainCreateFromFamiliesWithDefaultMethod(Class!) throws java.lang.NoSuchMethodException;
+ method protected Class! obtainFontFamily() throws java.lang.ClassNotFoundException;
+ method protected java.lang.reflect.Constructor! obtainFontFamilyCtor(Class!) throws java.lang.NoSuchMethodException;
+ method protected java.lang.reflect.Method! obtainFreezeMethod(Class!) throws java.lang.NoSuchMethodException;
+ field protected final java.lang.reflect.Method! mAbortCreation;
+ field protected final java.lang.reflect.Method! mAddFontFromAssetManager;
+ field protected final java.lang.reflect.Method! mAddFontFromBuffer;
+ field protected final java.lang.reflect.Method! mCreateFromFamiliesWithDefault;
+ field protected final Class! mFontFamily;
+ field protected final java.lang.reflect.Constructor! mFontFamilyCtor;
+ field protected final java.lang.reflect.Method! mFreeze;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(28) public class TypefaceCompatApi28Impl extends androidx.core.graphics.TypefaceCompatApi26Impl {
+ ctor public TypefaceCompatApi28Impl();
+ method public android.graphics.Typeface! createFromFontFamilyFilesResourceEntry(android.content.Context!, androidx.core.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry!, android.content.res.Resources!, int);
+ method public android.graphics.Typeface! createFromFontInfo(android.content.Context!, android.os.CancellationSignal!, androidx.core.provider.FontsContractCompat.FontInfo[], int);
+ method public android.graphics.Typeface? createFromResourcesFontFile(android.content.Context!, android.content.res.Resources!, int, String!, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TypefaceCompatUtil {
+ method public static void closeQuietly(java.io.Closeable!);
+ method @RequiresApi(19) public static java.nio.ByteBuffer? copyToDirectBuffer(android.content.Context!, android.content.res.Resources!, int);
+ method public static boolean copyToFile(java.io.File!, java.io.InputStream!);
+ method public static boolean copyToFile(java.io.File!, android.content.res.Resources!, int);
+ method public static java.io.File? getTempFile(android.content.Context!);
+ method @RequiresApi(19) public static java.nio.ByteBuffer? mmap(android.content.Context!, android.os.CancellationSignal!, android.net.Uri!);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public class IconCompat extends androidx.versionedparcelable.CustomVersionedParcelable {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addToShortcutIntent(android.content.Intent, android.graphics.drawable.Drawable?, android.content.Context);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void checkResource(android.content.Context!);
+ method @RequiresApi(23) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat? createFromIcon(android.graphics.drawable.Icon);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.graphics.drawable.IconCompat! createWithResource(android.content.res.Resources!, String!, @DrawableRes int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.graphics.Bitmap? getBitmap();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int mType;
+ }
+
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface TintAwareDrawable {
+ method public void setTint(@ColorInt int);
+ method public void setTintList(android.content.res.ColorStateList!);
+ method public void setTintMode(android.graphics.PorterDuff.Mode!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface WrappedDrawable {
+ method public android.graphics.drawable.Drawable! getWrappedDrawable();
+ method public void setWrappedDrawable(android.graphics.drawable.Drawable!);
+ }
+
+}
+
+package androidx.core.internal.view {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportMenu extends android.view.Menu {
+ method public void setGroupDividerEnabled(boolean);
+ field public static final int CATEGORY_MASK = -65536; // 0xffff0000
+ field public static final int CATEGORY_SHIFT = 16; // 0x10
+ field public static final int FLAG_KEEP_OPEN_ON_SUBMENU_OPENED = 4; // 0x4
+ field public static final int SUPPORTED_MODIFIERS_MASK = 69647; // 0x1100f
+ field public static final int USER_MASK = 65535; // 0xffff
+ field public static final int USER_SHIFT = 0; // 0x0
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportMenuItem extends android.view.MenuItem {
+ method public int getAlphabeticModifiers();
+ method public CharSequence! getContentDescription();
+ method public android.content.res.ColorStateList! getIconTintList();
+ method public android.graphics.PorterDuff.Mode! getIconTintMode();
+ method public int getNumericModifiers();
+ method public androidx.core.view.ActionProvider! getSupportActionProvider();
+ method public CharSequence! getTooltipText();
+ method public boolean requiresActionButton();
+ method public boolean requiresOverflow();
+ method public android.view.MenuItem! setAlphabeticShortcut(char, int);
+ method public androidx.core.internal.view.SupportMenuItem! setContentDescription(CharSequence!);
+ method public android.view.MenuItem! setIconTintList(android.content.res.ColorStateList!);
+ method public android.view.MenuItem! setIconTintMode(android.graphics.PorterDuff.Mode!);
+ method public android.view.MenuItem! setNumericShortcut(char, int);
+ method public android.view.MenuItem! setShortcut(char, char, int, int);
+ method public androidx.core.internal.view.SupportMenuItem! setSupportActionProvider(androidx.core.view.ActionProvider!);
+ method public androidx.core.internal.view.SupportMenuItem! setTooltipText(CharSequence!);
+ field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SupportSubMenu extends androidx.core.internal.view.SupportMenu android.view.SubMenu {
+ }
+
+}
+
+package androidx.core.net {
+
+ @IntDef({androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_DISABLED, androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_WHITELISTED, androidx.core.net.ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ConnectivityManagerCompat.RestrictBackgroundStatus {
+ }
+
+}
+
+package androidx.core.provider {
+
+ public final class FontRequest {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
+ }
+
+ public class FontsContractCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static java.util.Map<android.net.Uri,java.nio.ByteBuffer>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo[]!, android.os.CancellationSignal!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
+ }
+
+ public static class FontsContractCompat.FontFamilyResult {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo[]?);
+ }
+
+ public static class FontsContractCompat.FontInfo {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
+ }
+
+ public static class FontsContractCompat.FontRequestCallback {
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
+ }
+
+ @IntDef({androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_UNAVAILABLE, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_MALFORMED_QUERY, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, androidx.core.provider.FontsContractCompat.FontRequestCallback.RESULT_OK}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontsContractCompat.FontRequestCallback.FontRequestFailReason {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
+ ctor public SelfDestructiveThread(String!, int, int);
+ method @VisibleForTesting public int getGeneration();
+ method @VisibleForTesting public boolean isRunning();
+ method public <T> void postAndReply(java.util.concurrent.Callable<T>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T>!);
+ method public <T> T! postAndWait(java.util.concurrent.Callable<T>!, int) throws java.lang.InterruptedException;
+ }
+
+ public static interface SelfDestructiveThread.ReplyCallback<T> {
+ method public void onReply(T!);
+ }
+
+}
+
+package androidx.core.text {
+
+ public class PrecomputedTextCompat implements android.text.Spannable {
+ method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.text.PrecomputedText? getPrecomputedText();
+ }
+
+ public static final class PrecomputedTextCompat.Params {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean equalsWithoutTextDirection(androidx.core.text.PrecomputedTextCompat.Params);
+ }
+
+}
+
+package androidx.core.text.util {
+
+ @IntDef(flag=true, value={android.text.util.Linkify.WEB_URLS, android.text.util.Linkify.EMAIL_ADDRESSES, android.text.util.Linkify.PHONE_NUMBERS, android.text.util.Linkify.MAP_ADDRESSES, android.text.util.Linkify.ALL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface LinkifyCompat.LinkifyMask {
+ }
+
+}
+
+package androidx.core.util {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DebugUtils {
+ method public static void buildShortClassTag(Object!, StringBuilder!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class LogWriter extends java.io.Writer {
+ ctor public LogWriter(String!);
+ method public void close();
+ method public void flush();
+ method public void write(char[]!, int, int);
+ }
+
+ public final class PatternsCompat {
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final java.util.regex.Pattern! AUTOLINK_EMAIL_ADDRESS;
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final java.util.regex.Pattern! AUTOLINK_WEB_URL;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class Preconditions {
+ method public static void checkArgument(boolean);
+ method public static void checkArgument(boolean, Object);
+ method public static int checkArgumentInRange(int, int, int, String);
+ method @IntRange(from=0) public static int checkArgumentNonnegative(int, String?);
+ method @IntRange(from=0) public static int checkArgumentNonnegative(int);
+ method public static <T> T checkNotNull(T?);
+ method public static <T> T checkNotNull(T?, Object);
+ method public static void checkState(boolean, String?);
+ method public static void checkState(boolean);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class TimeUtils {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, StringBuilder!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, java.io.PrintWriter!, int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, java.io.PrintWriter!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void formatDuration(long, long, java.io.PrintWriter!);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int HUNDRED_DAY_FIELD_LEN = 19; // 0x13
+ }
+
+}
+
+package androidx.core.view {
+
+ public class AccessibilityDelegateCompat {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityDelegateCompat(android.view.View.AccessibilityDelegate!);
+ }
+
+ public abstract class ActionProvider {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void reset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setSubUiVisibilityListener(androidx.core.view.ActionProvider.SubUiVisibilityListener!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void subUiVisibilityChanged(boolean);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface ActionProvider.SubUiVisibilityListener {
+ method public void onSubUiVisibilityChanged(boolean);
+ }
+
+ public final class DragAndDropPermissionsCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.view.DragAndDropPermissionsCompat? request(android.app.Activity!, android.view.DragEvent!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class KeyEventDispatcher {
+ method public static boolean dispatchBeforeHierarchy(android.view.View, android.view.KeyEvent);
+ method public static boolean dispatchKeyEvent(androidx.core.view.KeyEventDispatcher.Component, android.view.View?, android.view.Window.Callback?, android.view.KeyEvent);
+ }
+
+ public static interface KeyEventDispatcher.Component {
+ method public boolean superDispatchKeyEvent(android.view.KeyEvent!);
+ }
+
+ public final class PointerIconCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public Object! getPointerIcon();
+ }
+
+ @IntDef({android.view.View.FOCUS_LEFT, android.view.View.FOCUS_UP, android.view.View.FOCUS_RIGHT, android.view.View.FOCUS_DOWN, android.view.View.FOCUS_FORWARD, android.view.View.FOCUS_BACKWARD}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusDirection {
+ }
+
+ @IntDef({android.view.View.FOCUS_LEFT, android.view.View.FOCUS_UP, android.view.View.FOCUS_RIGHT, android.view.View.FOCUS_DOWN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusRealDirection {
+ }
+
+ @IntDef({android.view.View.FOCUS_FORWARD, android.view.View.FOCUS_BACKWARD}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.FocusRelativeDirection {
+ }
+
+ @IntDef({androidx.core.view.ViewCompat.TYPE_TOUCH, androidx.core.view.ViewCompat.TYPE_NON_TOUCH}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.NestedScrollType {
+ }
+
+ @IntDef(value={androidx.core.view.ViewCompat.SCROLL_AXIS_NONE, androidx.core.view.ViewCompat.SCROLL_AXIS_HORIZONTAL, androidx.core.view.ViewCompat.SCROLL_AXIS_VERTICAL}, flag=true) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.ScrollAxis {
+ }
+
+ @IntDef(flag=true, value={androidx.core.view.ViewCompat.SCROLL_INDICATOR_TOP, androidx.core.view.ViewCompat.SCROLL_INDICATOR_BOTTOM, androidx.core.view.ViewCompat.SCROLL_INDICATOR_LEFT, androidx.core.view.ViewCompat.SCROLL_INDICATOR_RIGHT, androidx.core.view.ViewCompat.SCROLL_INDICATOR_START, androidx.core.view.ViewCompat.SCROLL_INDICATOR_END}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ViewCompat.ScrollIndicators {
+ }
+
+}
+
+package androidx.core.view.accessibility {
+
+ public final class AccessibilityClickableSpanCompat extends android.text.style.ClickableSpan {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityClickableSpanCompat(int, androidx.core.view.accessibility.AccessibilityNodeInfoCompat!, int);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String SPAN_ID = "ACCESSIBILITY_CLICKABLE_SPAN_ID";
+ }
+
+ public class AccessibilityNodeInfoCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addSpansToExtras(CharSequence!, android.view.View!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.text.style.ClickableSpan[]! getClickableSpans(CharSequence!);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int mParentVirtualDescendantId;
+ }
+
+ public static class AccessibilityNodeInfoCompat.AccessibilityActionCompat {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, CharSequence!, androidx.core.view.accessibility.AccessibilityViewCommand!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat! createReplacementAction(CharSequence!, androidx.core.view.accessibility.AccessibilityViewCommand!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean perform(android.view.View!, android.os.Bundle!);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected final androidx.core.view.accessibility.AccessibilityViewCommand! mCommand;
+ }
+
+ public abstract static class AccessibilityViewCommand.CommandArguments {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setBundle(android.os.Bundle!);
+ }
+
+}
+
+package androidx.core.widget {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface AutoSizeableTextView {
+ method public int getAutoSizeMaxTextSize();
+ method public int getAutoSizeMinTextSize();
+ method public int getAutoSizeStepGranularity();
+ method public int[]! getAutoSizeTextAvailableSizes();
+ method @androidx.core.widget.TextViewCompat.AutoSizeTextType public int getAutoSizeTextType();
+ method public void setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeUniformWithPresetSizes(int[], int) throws java.lang.IllegalArgumentException;
+ method public void setAutoSizeTextTypeWithDefaults(@androidx.core.widget.TextViewCompat.AutoSizeTextType int);
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final boolean PLATFORM_SUPPORTS_AUTOSIZE;
+ }
+
+ public class NestedScrollView extends android.widget.FrameLayout implements androidx.core.view.NestedScrollingChild3 androidx.core.view.NestedScrollingParent3 androidx.core.view.ScrollingView {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeHorizontalScrollRange();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollExtent();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollOffset();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int computeVerticalScrollRange();
+ }
+
+ public final class TextViewCompat {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.view.ActionMode.Callback wrapCustomSelectionActionModeCallback(android.widget.TextView, android.view.ActionMode.Callback);
+ }
+
+ @IntDef({androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE, androidx.core.widget.TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface TextViewCompat.AutoSizeTextType {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface TintableImageSourceView {
+ method public android.content.res.ColorStateList? getSupportImageTintList();
+ method public android.graphics.PorterDuff.Mode? getSupportImageTintMode();
+ method public void setSupportImageTintList(android.content.res.ColorStateList?);
+ method public void setSupportImageTintMode(android.graphics.PorterDuff.Mode?);
+ }
+
+}
+
diff --git a/core/ktx/api/1.1.0-alpha06.txt b/core/ktx/api/1.1.0-alpha06.txt
new file mode 100644
index 0000000..63739d2
--- /dev/null
+++ b/core/ktx/api/1.1.0-alpha06.txt
@@ -0,0 +1,642 @@
+// Signature format: 3.0
+package androidx.core.animation {
+
+ public final class AnimatorKt {
+ ctor public AnimatorKt();
+ method public static inline android.animation.Animator.AnimatorListener addListener(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onEnd = {}, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onStart = {}, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onCancel = {}, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onRepeat = {});
+ method @RequiresApi(19) public static inline android.animation.Animator.AnimatorPauseListener addPauseListener(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onResume = {}, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> onPause = {});
+ method public static inline android.animation.Animator.AnimatorListener doOnCancel(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnEnd(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.animation.Animator.AnimatorPauseListener doOnPause(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnRepeat(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.animation.Animator.AnimatorPauseListener doOnResume(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ method public static inline android.animation.Animator.AnimatorListener doOnStart(android.animation.Animator, kotlin.jvm.functions.Function1<? super android.animation.Animator,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content {
+
+ public final class ContentValuesKt {
+ ctor public ContentValuesKt();
+ method public static android.content.ContentValues contentValuesOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class ContextKt {
+ ctor public ContextKt();
+ method public static inline <reified T> T! getSystemService(android.content.Context);
+ method public static inline void withStyledAttributes(android.content.Context, android.util.AttributeSet? set = null, int[] attrs, @AttrRes int defStyleAttr = 0, @StyleRes int defStyleRes = 0, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ method public static inline void withStyledAttributes(android.content.Context, @StyleRes int resourceId, int[] attrs, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,kotlin.Unit> block);
+ }
+
+ public final class SharedPreferencesKt {
+ ctor public SharedPreferencesKt();
+ method public static inline void edit(android.content.SharedPreferences, boolean commit = false, kotlin.jvm.functions.Function1<? super android.content.SharedPreferences.Editor,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.content.res {
+
+ public final class TypedArrayKt {
+ ctor public TypedArrayKt();
+ method public static boolean getBooleanOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @ColorInt public static int getColorOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.content.res.ColorStateList getColorStateListOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getDimensionOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelOffsetOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @Dimension public static int getDimensionPixelSizeOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static android.graphics.drawable.Drawable getDrawableOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static float getFloatOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @RequiresApi(26) public static android.graphics.Typeface getFontOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static int getIntegerOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method @AnyRes public static int getResourceIdOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static String getStringOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence[] getTextArrayOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static CharSequence getTextOrThrow(android.content.res.TypedArray, @StyleableRes int index);
+ method public static inline <R> R! use(android.content.res.TypedArray, kotlin.jvm.functions.Function1<? super android.content.res.TypedArray,? extends R> block);
+ }
+
+}
+
+package androidx.core.database {
+
+ public final class CursorKt {
+ ctor public CursorKt();
+ method public static inline byte[]? getBlobOrNull(android.database.Cursor, int index);
+ method public static inline Double? getDoubleOrNull(android.database.Cursor, int index);
+ method public static inline Float? getFloatOrNull(android.database.Cursor, int index);
+ method public static inline Integer? getIntOrNull(android.database.Cursor, int index);
+ method public static inline Long? getLongOrNull(android.database.Cursor, int index);
+ method public static inline Short? getShortOrNull(android.database.Cursor, int index);
+ method public static inline String? getStringOrNull(android.database.Cursor, int index);
+ }
+
+}
+
+package androidx.core.database.sqlite {
+
+ public final class SQLiteDatabaseKt {
+ ctor public SQLiteDatabaseKt();
+ method public static inline <T> T! transaction(android.database.sqlite.SQLiteDatabase, boolean exclusive = true, kotlin.jvm.functions.Function1<? super android.database.sqlite.SQLiteDatabase,? extends T> body);
+ }
+
+}
+
+package androidx.core.graphics {
+
+ public final class BitmapKt {
+ ctor public BitmapKt();
+ method public static inline android.graphics.Bitmap applyCanvas(android.graphics.Bitmap, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.Bitmap, android.graphics.PointF p);
+ method public static inline android.graphics.Bitmap createBitmap(int width, int height, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888);
+ method @RequiresApi(26) public static inline android.graphics.Bitmap createBitmap(int width, int height, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888, boolean hasAlpha = true, android.graphics.ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB));
+ method public static inline operator int get(android.graphics.Bitmap, int x, int y);
+ method public static inline android.graphics.Bitmap scale(android.graphics.Bitmap, int width, int height, boolean filter = true);
+ method public static inline operator void set(android.graphics.Bitmap, int x, int y, @ColorInt int color);
+ }
+
+ public final class CanvasKt {
+ ctor public CanvasKt();
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Rect clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.RectF clipRect, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, int left, int top, int right, int bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, float left, float top, float right, float bottom, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withClip(android.graphics.Canvas, android.graphics.Path clipPath, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withMatrix(android.graphics.Canvas, android.graphics.Matrix matrix = android.graphics.Matrix(), kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withRotation(android.graphics.Canvas, float degrees = 0.0f, float pivotX = 0.0f, float pivotY = 0.0f, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSave(android.graphics.Canvas, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withScale(android.graphics.Canvas, float x = 1.0f, float y = 1.0f, float pivotX = 0.0f, float pivotY = 0.0f, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withSkew(android.graphics.Canvas, float x = 0.0f, float y = 0.0f, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ method public static inline void withTranslation(android.graphics.Canvas, float x = 0.0f, float y = 0.0f, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class ColorKt {
+ ctor public ColorKt();
+ method @RequiresApi(26) public static inline operator float component1(android.graphics.Color);
+ method public static inline operator int component1(int);
+ method @RequiresApi(26) public static inline operator float component1(long);
+ method @RequiresApi(26) public static inline operator float component2(android.graphics.Color);
+ method public static inline operator int component2(int);
+ method @RequiresApi(26) public static inline operator float component2(long);
+ method @RequiresApi(26) public static inline operator float component3(android.graphics.Color);
+ method public static inline operator int component3(int);
+ method @RequiresApi(26) public static inline operator float component3(long);
+ method @RequiresApi(26) public static inline operator float component4(android.graphics.Color);
+ method public static inline operator int component4(int);
+ method @RequiresApi(26) public static inline operator float component4(long);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(int, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(int, android.graphics.ColorSpace colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(long, android.graphics.ColorSpace.Named colorSpace);
+ method @ColorLong @RequiresApi(26) public static inline infix long convertTo(long, android.graphics.ColorSpace colorSpace);
+ method @RequiresApi(26) public static inline infix android.graphics.Color! convertTo(android.graphics.Color, android.graphics.ColorSpace.Named colorSpace);
+ method @RequiresApi(26) public static inline infix android.graphics.Color! convertTo(android.graphics.Color, android.graphics.ColorSpace colorSpace);
+ method public static inline int getAlpha(int);
+ method @RequiresApi(26) public static inline float getAlpha(long);
+ method public static inline int getBlue(int);
+ method @RequiresApi(26) public static inline float getBlue(long);
+ method @RequiresApi(26) public static inline android.graphics.ColorSpace getColorSpace(long);
+ method public static inline int getGreen(int);
+ method @RequiresApi(26) public static inline float getGreen(long);
+ method @RequiresApi(26) public static inline float getLuminance(int);
+ method @RequiresApi(26) public static inline float getLuminance(long);
+ method public static inline int getRed(int);
+ method @RequiresApi(26) public static inline float getRed(long);
+ method @RequiresApi(26) public static inline boolean isSrgb(long);
+ method @RequiresApi(26) public static inline boolean isWideGamut(long);
+ method @RequiresApi(26) public static operator android.graphics.Color plus(android.graphics.Color, android.graphics.Color c);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(int);
+ method @RequiresApi(26) public static inline android.graphics.Color toColor(long);
+ method @ColorInt @RequiresApi(26) public static inline int toColorInt(long);
+ method @ColorInt public static inline int toColorInt(String);
+ method @ColorLong @RequiresApi(26) public static inline long toColorLong(int);
+ }
+
+ public final class ImageDecoderKt {
+ ctor public ImageDecoderKt();
+ method @RequiresApi(28) public static inline android.graphics.Bitmap decodeBitmap(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ method @RequiresApi(28) public static inline android.graphics.drawable.Drawable decodeDrawable(android.graphics.ImageDecoder.Source, kotlin.jvm.functions.Function3<? super android.graphics.ImageDecoder,? super android.graphics.ImageDecoder.ImageInfo,? super android.graphics.ImageDecoder.Source,kotlin.Unit> action);
+ }
+
+ public final class MatrixKt {
+ ctor public MatrixKt();
+ method public static android.graphics.Matrix rotationMatrix(float degrees, float px = 0.0f, float py = 0.0f);
+ method public static android.graphics.Matrix scaleMatrix(float sx = 1.0f, float sy = 1.0f);
+ method public static inline operator android.graphics.Matrix times(android.graphics.Matrix, android.graphics.Matrix m);
+ method public static android.graphics.Matrix translationMatrix(float tx = 0.0f, float ty = 0.0f);
+ method public static inline float[] values(android.graphics.Matrix);
+ }
+
+ public final class PathKt {
+ ctor public PathKt();
+ method @RequiresApi(19) public static inline infix android.graphics.Path and(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(26) public static Iterable<androidx.core.graphics.PathSegment> flatten(android.graphics.Path, float error = 0.5f);
+ method @RequiresApi(19) public static inline operator android.graphics.Path minus(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(19) public static inline infix android.graphics.Path or(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(19) public static inline operator android.graphics.Path plus(android.graphics.Path, android.graphics.Path p);
+ method @RequiresApi(19) public static inline infix android.graphics.Path xor(android.graphics.Path, android.graphics.Path p);
+ }
+
+ public final class PictureKt {
+ ctor public PictureKt();
+ method public static inline android.graphics.Picture record(android.graphics.Picture, int width, int height, kotlin.jvm.functions.Function1<? super android.graphics.Canvas,kotlin.Unit> block);
+ }
+
+ public final class PointKt {
+ ctor public PointKt();
+ method public static inline operator int component1(android.graphics.Point);
+ method public static inline operator float component1(android.graphics.PointF);
+ method public static inline operator int component2(android.graphics.Point);
+ method public static inline operator float component2(android.graphics.PointF);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.Point minus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF minus(android.graphics.PointF, float xy);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, android.graphics.Point p);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, android.graphics.PointF p);
+ method public static inline operator android.graphics.Point plus(android.graphics.Point, int xy);
+ method public static inline operator android.graphics.PointF plus(android.graphics.PointF, float xy);
+ method public static inline android.graphics.Point toPoint(android.graphics.PointF);
+ method public static inline android.graphics.PointF toPointF(android.graphics.Point);
+ method public static inline operator android.graphics.Point unaryMinus(android.graphics.Point);
+ method public static inline operator android.graphics.PointF unaryMinus(android.graphics.PointF);
+ }
+
+ public final class PorterDuffKt {
+ ctor public PorterDuffKt();
+ method public static inline android.graphics.PorterDuffColorFilter toColorFilter(android.graphics.PorterDuff.Mode, int color);
+ method public static inline android.graphics.PorterDuffXfermode toXfermode(android.graphics.PorterDuff.Mode);
+ }
+
+ public final class RectKt {
+ ctor public RectKt();
+ method public static inline infix android.graphics.Rect and(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF and(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator int component1(android.graphics.Rect);
+ method public static inline operator float component1(android.graphics.RectF);
+ method public static inline operator int component2(android.graphics.Rect);
+ method public static inline operator float component2(android.graphics.RectF);
+ method public static inline operator int component3(android.graphics.Rect);
+ method public static inline operator float component3(android.graphics.RectF);
+ method public static inline operator int component4(android.graphics.Rect);
+ method public static inline operator float component4(android.graphics.RectF);
+ method public static inline operator boolean contains(android.graphics.Rect, android.graphics.Point p);
+ method public static inline operator boolean contains(android.graphics.RectF, android.graphics.PointF p);
+ method public static inline operator android.graphics.Region minus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region minus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, float xy);
+ method public static inline operator android.graphics.Rect minus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.RectF minus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline infix android.graphics.Rect or(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.RectF or(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.RectF r);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, int xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, float xy);
+ method public static inline operator android.graphics.Rect plus(android.graphics.Rect, android.graphics.Point xy);
+ method public static inline operator android.graphics.RectF plus(android.graphics.RectF, android.graphics.PointF xy);
+ method public static inline android.graphics.Rect toRect(android.graphics.RectF);
+ method public static inline android.graphics.RectF toRectF(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.Rect);
+ method public static inline android.graphics.Region toRegion(android.graphics.RectF);
+ method public static inline android.graphics.RectF transform(android.graphics.RectF, android.graphics.Matrix m);
+ method public static inline infix android.graphics.Region xor(android.graphics.Rect, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.RectF, android.graphics.RectF r);
+ }
+
+ public final class RegionKt {
+ ctor public RegionKt();
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region and(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator boolean contains(android.graphics.Region, android.graphics.Point p);
+ method public static inline void forEach(android.graphics.Region, kotlin.jvm.functions.Function1<? super android.graphics.Rect,kotlin.Unit> action);
+ method public static operator java.util.Iterator<android.graphics.Rect> iterator(android.graphics.Region);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region minus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region not(android.graphics.Region);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region or(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Rect r);
+ method public static inline operator android.graphics.Region plus(android.graphics.Region, android.graphics.Region r);
+ method public static inline operator android.graphics.Region unaryMinus(android.graphics.Region);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Rect r);
+ method public static inline infix android.graphics.Region xor(android.graphics.Region, android.graphics.Region r);
+ }
+
+ public final class ShaderKt {
+ ctor public ShaderKt();
+ method public static inline void transform(android.graphics.Shader, kotlin.jvm.functions.Function1<? super android.graphics.Matrix,kotlin.Unit> block);
+ }
+
+}
+
+package androidx.core.graphics.drawable {
+
+ public final class BitmapDrawableKt {
+ ctor public BitmapDrawableKt();
+ method public static inline android.graphics.drawable.BitmapDrawable toDrawable(android.graphics.Bitmap, android.content.res.Resources resources);
+ }
+
+ public final class ColorDrawableKt {
+ ctor public ColorDrawableKt();
+ method public static inline android.graphics.drawable.ColorDrawable toDrawable(int);
+ method @RequiresApi(26) public static inline android.graphics.drawable.ColorDrawable toDrawable(android.graphics.Color);
+ }
+
+ public final class DrawableKt {
+ ctor public DrawableKt();
+ method public static android.graphics.Bitmap toBitmap(android.graphics.drawable.Drawable, @Px int width = intrinsicWidth, @Px int height = intrinsicHeight, android.graphics.Bitmap.Config? config = null);
+ method public static void updateBounds(android.graphics.drawable.Drawable, @Px int left = android.graphics.Rect.left, @Px int top = android.graphics.Rect.top, @Px int right = android.graphics.Rect.right, @Px int bottom = android.graphics.Rect.bottom);
+ }
+
+ public final class IconKt {
+ ctor public IconKt();
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toAdaptiveIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.graphics.Bitmap);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(android.net.Uri);
+ method @RequiresApi(26) public static inline android.graphics.drawable.Icon toIcon(byte[]);
+ }
+
+}
+
+package androidx.core.location {
+
+ public final class LocationKt {
+ ctor public LocationKt();
+ method public static inline operator double component1(android.location.Location);
+ method public static inline operator double component2(android.location.Location);
+ }
+
+}
+
+package androidx.core.net {
+
+ public final class UriKt {
+ ctor public UriKt();
+ method public static java.io.File toFile(android.net.Uri);
+ method public static inline android.net.Uri toUri(String);
+ method public static inline android.net.Uri toUri(java.io.File);
+ }
+
+}
+
+package androidx.core.os {
+
+ public final class BundleKt {
+ ctor public BundleKt();
+ method public static android.os.Bundle bundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class HandlerKt {
+ ctor public HandlerKt();
+ method public static inline Runnable postAtTime(android.os.Handler, long uptimeMillis, Object? token = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline Runnable postDelayed(android.os.Handler, long delayInMillis, Object? token = null, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ }
+
+ public final class PersistableBundleKt {
+ ctor public PersistableBundleKt();
+ method @RequiresApi(21) public static android.os.PersistableBundle persistableBundleOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class TraceKt {
+ ctor public TraceKt();
+ method public static inline <T> T! trace(String sectionName, kotlin.jvm.functions.Function0<? extends T> block);
+ }
+
+}
+
+package androidx.core.text {
+
+ public final class CharSequenceKt {
+ ctor public CharSequenceKt();
+ method public static inline boolean isDigitsOnly(CharSequence);
+ method public static inline int trimmedLength(CharSequence);
+ }
+
+ public final class HtmlKt {
+ ctor public HtmlKt();
+ method public static inline android.text.Spanned parseAsHtml(String, int flags = 0, android.text.Html.ImageGetter? imageGetter = null, android.text.Html.TagHandler? tagHandler = null);
+ method public static inline String toHtml(android.text.Spanned, int option = 0);
+ }
+
+ public final class LocaleKt {
+ ctor public LocaleKt();
+ method @RequiresApi(17) public static inline int getLayoutDirection(java.util.Locale);
+ }
+
+ public final class SpannableStringBuilderKt {
+ ctor public SpannableStringBuilderKt();
+ method public static inline android.text.SpannableStringBuilder backgroundColor(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder bold(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannedString buildSpannedString(kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder color(android.text.SpannableStringBuilder, @ColorInt int color, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object[] spans, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder inSpans(android.text.SpannableStringBuilder, Object span, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder italic(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder scale(android.text.SpannableStringBuilder, float proportion, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder strikeThrough(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder subscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder superscript(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ method public static inline android.text.SpannableStringBuilder underline(android.text.SpannableStringBuilder, kotlin.jvm.functions.Function1<? super android.text.SpannableStringBuilder,kotlin.Unit> builderAction);
+ }
+
+ public final class SpannableStringKt {
+ ctor public SpannableStringKt();
+ method public static inline void clearSpans(android.text.Spannable);
+ method public static inline operator void set(android.text.Spannable, int start, int end, Object span);
+ method public static inline operator void set(android.text.Spannable, kotlin.ranges.IntRange range, Object span);
+ method public static inline android.text.Spannable toSpannable(CharSequence);
+ }
+
+ public final class SpannedStringKt {
+ ctor public SpannedStringKt();
+ method public static inline <reified T> T[]! getSpans(android.text.Spanned, int start = 0, int end = length);
+ method public static inline android.text.Spanned toSpanned(CharSequence);
+ }
+
+ public final class StringKt {
+ ctor public StringKt();
+ method public static inline String htmlEncode(String);
+ }
+
+}
+
+package androidx.core.transition {
+
+ public final class TransitionKt {
+ ctor public TransitionKt();
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener addListener(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onEnd = {}, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onStart = {}, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onCancel = {}, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onResume = {}, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> onPause = {});
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener doOnCancel(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener doOnEnd(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener doOnPause(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener doOnResume(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ method @RequiresApi(19) public static inline android.transition.Transition.TransitionListener doOnStart(android.transition.Transition, kotlin.jvm.functions.Function1<? super android.transition.Transition,kotlin.Unit> action);
+ }
+
+}
+
+package androidx.core.util {
+
+ public final class AtomicFileKt {
+ ctor public AtomicFileKt();
+ method @RequiresApi(17) public static inline byte[] readBytes(android.util.AtomicFile);
+ method @RequiresApi(17) public static String readText(android.util.AtomicFile, java.nio.charset.Charset charset = Charsets.UTF_8);
+ method @RequiresApi(17) public static inline void tryWrite(android.util.AtomicFile, kotlin.jvm.functions.Function1<? super java.io.FileOutputStream,kotlin.Unit> block);
+ method @RequiresApi(17) public static void writeBytes(android.util.AtomicFile, byte[] array);
+ method @RequiresApi(17) public static void writeText(android.util.AtomicFile, String text, java.nio.charset.Charset charset = Charsets.UTF_8);
+ }
+
+ public final class HalfKt {
+ ctor public HalfKt();
+ method @RequiresApi(26) public static inline android.util.Half toHalf(short);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(float);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(double);
+ method @RequiresApi(26) public static inline android.util.Half toHalf(String);
+ }
+
+ public final class LongSparseArrayKt {
+ ctor public LongSparseArrayKt();
+ method @RequiresApi(16) public static inline operator <T> boolean contains(android.util.LongSparseArray<T>, long key);
+ method @RequiresApi(16) public static inline <T> boolean containsKey(android.util.LongSparseArray<T>, long key);
+ method @RequiresApi(16) public static inline <T> boolean containsValue(android.util.LongSparseArray<T>, T! value);
+ method @RequiresApi(16) public static inline <T> void forEach(android.util.LongSparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Long,? super T,kotlin.Unit> action);
+ method @RequiresApi(16) public static inline <T> T! getOrDefault(android.util.LongSparseArray<T>, long key, T! defaultValue);
+ method @RequiresApi(16) public static inline <T> T! getOrElse(android.util.LongSparseArray<T>, long key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method @RequiresApi(16) public static inline <T> int getSize(android.util.LongSparseArray<T>);
+ method @RequiresApi(16) public static inline <T> boolean isEmpty(android.util.LongSparseArray<T>);
+ method @RequiresApi(16) public static inline <T> boolean isNotEmpty(android.util.LongSparseArray<T>);
+ method @RequiresApi(16) public static <T> kotlin.collections.LongIterator keyIterator(android.util.LongSparseArray<T>);
+ method @RequiresApi(16) public static operator <T> android.util.LongSparseArray<T> plus(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method @RequiresApi(16) public static <T> void putAll(android.util.LongSparseArray<T>, android.util.LongSparseArray<T> other);
+ method @RequiresApi(16) public static <T> boolean remove(android.util.LongSparseArray<T>, long key, T! value);
+ method @RequiresApi(16) public static inline operator <T> void set(android.util.LongSparseArray<T>, long key, T! value);
+ method @RequiresApi(16) public static <T> java.util.Iterator<T> valueIterator(android.util.LongSparseArray<T>);
+ }
+
+ public final class LruCacheKt {
+ ctor public LruCacheKt();
+ method public static inline <K, V> android.util.LruCache<K,V> lruCache(int maxSize, kotlin.jvm.functions.Function2<? super K,? super V,java.lang.Integer> sizeOf = { _, _ -> 1 }, kotlin.jvm.functions.Function1<? super K,? extends V> create = { (V)null }, kotlin.jvm.functions.Function4<? super java.lang.Boolean,? super K,? super V,? super V,kotlin.Unit> onEntryRemoved = { _, _, _, _ -> });
+ }
+
+ public final class PairKt {
+ ctor public PairKt();
+ method public static inline operator <F, S> F! component1(android.util.Pair<F,S>);
+ method public static inline operator <F, S> S! component2(android.util.Pair<F,S>);
+ method public static inline <F, S> android.util.Pair<F,S> toAndroidPair(kotlin.Pair<? extends F,? extends S>);
+ method public static inline <F, S> kotlin.Pair<F,S> toKotlinPair(android.util.Pair<F,S>);
+ }
+
+ public final class RangeKt {
+ ctor public RangeKt();
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> and(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, T value);
+ method @RequiresApi(21) public static inline operator <T extends java.lang.Comparable<? super T>> android.util.Range<T> plus(android.util.Range<T>, android.util.Range<T> other);
+ method @RequiresApi(21) public static inline infix <T extends java.lang.Comparable<? super T>> android.util.Range<T> rangeTo(T, T that);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> kotlin.ranges.ClosedRange<T> toClosedRange(android.util.Range<T>);
+ method @RequiresApi(21) public static <T extends java.lang.Comparable<? super T>> android.util.Range<T> toRange(kotlin.ranges.ClosedRange<T>);
+ }
+
+ public final class SizeKt {
+ ctor public SizeKt();
+ method @RequiresApi(21) public static inline operator int component1(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component1(android.util.SizeF);
+ method @RequiresApi(21) public static inline operator int component2(android.util.Size);
+ method @RequiresApi(21) public static inline operator float component2(android.util.SizeF);
+ }
+
+ public final class SparseArrayKt {
+ ctor public SparseArrayKt();
+ method public static inline operator <T> boolean contains(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsKey(android.util.SparseArray<T>, int key);
+ method public static inline <T> boolean containsValue(android.util.SparseArray<T>, T! value);
+ method public static inline <T> void forEach(android.util.SparseArray<T>, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,kotlin.Unit> action);
+ method public static inline <T> T! getOrDefault(android.util.SparseArray<T>, int key, T! defaultValue);
+ method public static inline <T> T! getOrElse(android.util.SparseArray<T>, int key, kotlin.jvm.functions.Function0<? extends T> defaultValue);
+ method public static inline <T> int getSize(android.util.SparseArray<T>);
+ method public static inline <T> boolean isEmpty(android.util.SparseArray<T>);
+ method public static inline <T> boolean isNotEmpty(android.util.SparseArray<T>);
+ method public static <T> kotlin.collections.IntIterator keyIterator(android.util.SparseArray<T>);
+ method public static operator <T> android.util.SparseArray<T> plus(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> void putAll(android.util.SparseArray<T>, android.util.SparseArray<T> other);
+ method public static <T> boolean remove(android.util.SparseArray<T>, int key, T! value);
+ method public static inline operator <T> void set(android.util.SparseArray<T>, int key, T! value);
+ method public static <T> java.util.Iterator<T> valueIterator(android.util.SparseArray<T>);
+ }
+
+ public final class SparseBooleanArrayKt {
+ ctor public SparseBooleanArrayKt();
+ method public static inline operator boolean contains(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsKey(android.util.SparseBooleanArray, int key);
+ method public static inline boolean containsValue(android.util.SparseBooleanArray, boolean value);
+ method public static inline void forEach(android.util.SparseBooleanArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> action);
+ method public static inline boolean getOrDefault(android.util.SparseBooleanArray, int key, boolean defaultValue);
+ method public static inline boolean getOrElse(android.util.SparseBooleanArray, int key, kotlin.jvm.functions.Function0<java.lang.Boolean> defaultValue);
+ method public static inline int getSize(android.util.SparseBooleanArray);
+ method public static inline boolean isEmpty(android.util.SparseBooleanArray);
+ method public static inline boolean isNotEmpty(android.util.SparseBooleanArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseBooleanArray);
+ method public static operator android.util.SparseBooleanArray plus(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static void putAll(android.util.SparseBooleanArray, android.util.SparseBooleanArray other);
+ method public static boolean remove(android.util.SparseBooleanArray, int key, boolean value);
+ method public static inline operator void set(android.util.SparseBooleanArray, int key, boolean value);
+ method public static kotlin.collections.BooleanIterator valueIterator(android.util.SparseBooleanArray);
+ }
+
+ public final class SparseIntArrayKt {
+ ctor public SparseIntArrayKt();
+ method public static inline operator boolean contains(android.util.SparseIntArray, int key);
+ method public static inline boolean containsKey(android.util.SparseIntArray, int key);
+ method public static inline boolean containsValue(android.util.SparseIntArray, int value);
+ method public static inline void forEach(android.util.SparseIntArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline int getOrDefault(android.util.SparseIntArray, int key, int defaultValue);
+ method public static inline int getOrElse(android.util.SparseIntArray, int key, kotlin.jvm.functions.Function0<java.lang.Integer> defaultValue);
+ method public static inline int getSize(android.util.SparseIntArray);
+ method public static inline boolean isEmpty(android.util.SparseIntArray);
+ method public static inline boolean isNotEmpty(android.util.SparseIntArray);
+ method public static kotlin.collections.IntIterator keyIterator(android.util.SparseIntArray);
+ method public static operator android.util.SparseIntArray plus(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static void putAll(android.util.SparseIntArray, android.util.SparseIntArray other);
+ method public static boolean remove(android.util.SparseIntArray, int key, int value);
+ method public static inline operator void set(android.util.SparseIntArray, int key, int value);
+ method public static kotlin.collections.IntIterator valueIterator(android.util.SparseIntArray);
+ }
+
+ public final class SparseLongArrayKt {
+ ctor public SparseLongArrayKt();
+ method @RequiresApi(18) public static inline operator boolean contains(android.util.SparseLongArray, int key);
+ method @RequiresApi(18) public static inline boolean containsKey(android.util.SparseLongArray, int key);
+ method @RequiresApi(18) public static inline boolean containsValue(android.util.SparseLongArray, long value);
+ method @RequiresApi(18) public static inline void forEach(android.util.SparseLongArray, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> action);
+ method @RequiresApi(18) public static inline long getOrDefault(android.util.SparseLongArray, int key, long defaultValue);
+ method @RequiresApi(18) public static inline long getOrElse(android.util.SparseLongArray, int key, kotlin.jvm.functions.Function0<java.lang.Long> defaultValue);
+ method @RequiresApi(18) public static inline int getSize(android.util.SparseLongArray);
+ method @RequiresApi(18) public static inline boolean isEmpty(android.util.SparseLongArray);
+ method @RequiresApi(18) public static inline boolean isNotEmpty(android.util.SparseLongArray);
+ method @RequiresApi(18) public static kotlin.collections.IntIterator keyIterator(android.util.SparseLongArray);
+ method @RequiresApi(18) public static operator android.util.SparseLongArray plus(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method @RequiresApi(18) public static void putAll(android.util.SparseLongArray, android.util.SparseLongArray other);
+ method @RequiresApi(18) public static boolean remove(android.util.SparseLongArray, int key, long value);
+ method @RequiresApi(18) public static inline operator void set(android.util.SparseLongArray, int key, long value);
+ method @RequiresApi(18) public static kotlin.collections.LongIterator valueIterator(android.util.SparseLongArray);
+ }
+
+}
+
+package androidx.core.view {
+
+ public final class MenuKt {
+ ctor public MenuKt();
+ method public static operator boolean contains(android.view.Menu, android.view.MenuItem item);
+ method public static inline void forEach(android.view.Menu, kotlin.jvm.functions.Function1<? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.Menu, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.MenuItem,kotlin.Unit> action);
+ method public static inline operator android.view.MenuItem get(android.view.Menu, int index);
+ method public static kotlin.sequences.Sequence<android.view.MenuItem> getChildren(android.view.Menu);
+ method public static inline int getSize(android.view.Menu);
+ method public static inline boolean isEmpty(android.view.Menu);
+ method public static inline boolean isNotEmpty(android.view.Menu);
+ method public static operator java.util.Iterator<android.view.MenuItem> iterator(android.view.Menu);
+ method public static inline operator void minusAssign(android.view.Menu, android.view.MenuItem item);
+ }
+
+ public final class ViewGroupKt {
+ ctor public ViewGroupKt();
+ method public static inline operator boolean contains(android.view.ViewGroup, android.view.View view);
+ method public static inline void forEach(android.view.ViewGroup, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void forEachIndexed(android.view.ViewGroup, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super android.view.View,kotlin.Unit> action);
+ method public static operator android.view.View get(android.view.ViewGroup, int index);
+ method public static kotlin.sequences.Sequence<android.view.View> getChildren(android.view.ViewGroup);
+ method public static inline int getSize(android.view.ViewGroup);
+ method public static inline boolean isEmpty(android.view.ViewGroup);
+ method public static inline boolean isNotEmpty(android.view.ViewGroup);
+ method public static operator java.util.Iterator<android.view.View> iterator(android.view.ViewGroup);
+ method public static inline operator void minusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline operator void plusAssign(android.view.ViewGroup, android.view.View view);
+ method public static inline void setMargins(android.view.ViewGroup.MarginLayoutParams, @Px int size);
+ method public static inline void updateMargins(android.view.ViewGroup.MarginLayoutParams, @Px int left = android.view.ViewGroup.MarginLayoutParams.leftMargin, @Px int top = android.view.ViewGroup.MarginLayoutParams.topMargin, @Px int right = android.view.ViewGroup.MarginLayoutParams.rightMargin, @Px int bottom = android.view.ViewGroup.MarginLayoutParams.bottomMargin);
+ method @RequiresApi(17) public static inline void updateMarginsRelative(android.view.ViewGroup.MarginLayoutParams, @Px int start = marginStart, @Px int top = android.view.ViewGroup.MarginLayoutParams.topMargin, @Px int end = marginEnd, @Px int bottom = android.view.ViewGroup.MarginLayoutParams.bottomMargin);
+ }
+
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static inline void doOnLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline void doOnNextLayout(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static inline androidx.core.view.OneShotPreDrawListener doOnPreDraw(android.view.View, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> action);
+ method public static android.graphics.Bitmap drawToBitmap(android.view.View, android.graphics.Bitmap.Config config = android.graphics.Bitmap.Config.ARGB_8888);
+ method public static inline int getMarginBottom(android.view.View);
+ method public static inline int getMarginEnd(android.view.View);
+ method public static inline int getMarginLeft(android.view.View);
+ method public static inline int getMarginRight(android.view.View);
+ method public static inline int getMarginStart(android.view.View);
+ method public static inline int getMarginTop(android.view.View);
+ method public static inline boolean isGone(android.view.View);
+ method public static inline boolean isInvisible(android.view.View);
+ method public static inline boolean isVisible(android.view.View);
+ method public static inline Runnable postDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method @RequiresApi(16) public static inline Runnable postOnAnimationDelayed(android.view.View, long delayInMillis, kotlin.jvm.functions.Function0<kotlin.Unit> action);
+ method public static inline void setGone(android.view.View, boolean value);
+ method public static inline void setInvisible(android.view.View, boolean value);
+ method public static inline void setPadding(android.view.View, @Px int size);
+ method public static inline void setVisible(android.view.View, boolean value);
+ method public static inline void updateLayoutParams(android.view.View, kotlin.jvm.functions.Function1<? super android.view.ViewGroup.LayoutParams,kotlin.Unit> block);
+ method public static inline <reified T extends android.view.ViewGroup.LayoutParams> void updateLayoutParamsTyped(android.view.View, kotlin.jvm.functions.Function1<? super T,kotlin.Unit>! block);
+ method public static inline void updatePadding(android.view.View, @Px int left = paddingLeft, @Px int top = paddingTop, @Px int right = paddingRight, @Px int bottom = paddingBottom);
+ method @RequiresApi(17) public static inline void updatePaddingRelative(android.view.View, @Px int start = paddingStart, @Px int top = paddingTop, @Px int end = paddingEnd, @Px int bottom = paddingBottom);
+ }
+
+}
+
+package androidx.core.widget {
+
+ public final class TextViewKt {
+ ctor public TextViewKt();
+ method public static inline android.text.TextWatcher addTextChangedListener(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> beforeTextChanged = { _, _, _, _ -> }, kotlin.jvm.functions.Function4<? super java.lang.CharSequence,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> onTextChanged = { _, _, _, _ -> }, kotlin.jvm.functions.Function1<? super android.text.Editable,kotlin.Unit> afterTextChanged = {});
+ method public static inline android.text.TextWatcher doAfterTextChanged(android.widget.TextView, kotlin.jvm.functions.Function1<? super android.text.Editable,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doBeforeTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ method public static inline android.text.TextWatcher doOnTextChanged(android.widget.TextView, kotlin.jvm.functions.Function4<? super java.lang.CharSequence,? super java.lang.Integer,? super java.lang.Integer,? super java.lang.Integer,kotlin.Unit> action);
+ }
+
+}
+
diff --git a/core/ktx/api/res-1.1.0-alpha06.txt b/core/ktx/api/res-1.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/ktx/api/res-1.1.0-alpha06.txt
diff --git a/core/ktx/api/restricted_1.1.0-alpha06.txt b/core/ktx/api/restricted_1.1.0-alpha06.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/core/ktx/api/restricted_1.1.0-alpha06.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java b/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
index 47da629..a299a46 100644
--- a/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
+++ b/core/src/androidTest/java/androidx/core/app/RemoteActionCompatTest.java
@@ -21,65 +21,65 @@
import android.app.PendingIntent;
import android.content.Intent;
+import android.os.Parcel;
import android.support.v4.BaseInstrumentationTestCase;
import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import androidx.versionedparcelable.ParcelUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class RemoteActionCompatTest extends BaseInstrumentationTestCase<TestActivity> {
+ private static final IconCompat ICON = IconCompat.createWithContentUri("content://test");
+ private static final String TITLE = "title";
+ private static final String DESCRIPTION = "description";
+ private static final PendingIntent ACTION = PendingIntent.getBroadcast(
+ InstrumentationRegistry.getContext(), 0, new Intent("TESTACTION"), 0);
public RemoteActionCompatTest() {
super(TestActivity.class);
}
@Test
- public void testRemoteAction_bundle() throws Throwable {
- IconCompat icon = IconCompat.createWithContentUri("content://test");
- String title = "title";
- String description = "description";
- PendingIntent action = PendingIntent.getBroadcast(
- ApplicationProvider.getApplicationContext(), 0,
- new Intent("TESTACTION"), 0);
- RemoteActionCompat reference = new RemoteActionCompat(icon, title, description, action);
- reference.setEnabled(false);
- reference.setShouldShowIcon(false);
-
- RemoteActionCompat result = RemoteActionCompat.createFromBundle(reference.toBundle());
-
- assertEquals(icon.getUri(), result.getIcon().getUri());
- assertEquals(title, result.getTitle());
- assertEquals(description, result.getContentDescription());
- assertEquals(action.getTargetPackage(), result.getActionIntent().getTargetPackage());
- assertFalse(result.isEnabled());
- assertFalse(result.shouldShowIcon());
+ public void testRemoteAction_shallowCopy() throws Throwable {
+ RemoteActionCompat reference = createTestRemoteActionCompat();
+ RemoteActionCompat result = new RemoteActionCompat(reference);
+ assertEqualsToTestRemoteActionCompat(result);
}
@Test
- public void testRemoteAction_shallowCopy() throws Throwable {
- IconCompat icon = IconCompat.createWithContentUri("content://test");
- String title = "title";
- String description = "description";
- PendingIntent action = PendingIntent.getBroadcast(
- ApplicationProvider.getApplicationContext(), 0,
- new Intent("TESTACTION"), 0);
- RemoteActionCompat reference = new RemoteActionCompat(icon, title, description, action);
+ public void testRemoteAction_parcel() {
+ RemoteActionCompat reference = createTestRemoteActionCompat();
+
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(ParcelUtils.toParcelable(reference), 0);
+ p.setDataPosition(0);
+ RemoteActionCompat result = ParcelUtils.fromParcelable(
+ p.readParcelable(getClass().getClassLoader()));
+
+ assertEqualsToTestRemoteActionCompat(result);
+ }
+
+ private RemoteActionCompat createTestRemoteActionCompat() {
+ RemoteActionCompat reference = new RemoteActionCompat(ICON, TITLE, DESCRIPTION, ACTION);
reference.setEnabled(false);
reference.setShouldShowIcon(false);
-
- RemoteActionCompat result = new RemoteActionCompat(reference);
-
- assertEquals(icon.getUri(), result.getIcon().getUri());
- assertEquals(title, result.getTitle());
- assertEquals(description, result.getContentDescription());
- assertEquals(action.getTargetPackage(), result.getActionIntent().getTargetPackage());
- assertFalse(result.isEnabled());
- assertFalse(result.shouldShowIcon());
+ return reference;
}
-}
+
+ private void assertEqualsToTestRemoteActionCompat(RemoteActionCompat remoteAction) {
+ assertEquals(ICON.getUri(), remoteAction.getIcon().getUri());
+ assertEquals(TITLE, remoteAction.getTitle());
+ assertEquals(DESCRIPTION, remoteAction.getContentDescription());
+ assertEquals(ACTION.getTargetPackage(), remoteAction.getActionIntent().getTargetPackage());
+ assertFalse(remoteAction.isEnabled());
+ assertFalse(remoteAction.shouldShowIcon());
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/androidx/core/app/RemoteActionCompat.java b/core/src/main/java/androidx/core/app/RemoteActionCompat.java
index faf676c..28f89c7 100644
--- a/core/src/main/java/androidx/core/app/RemoteActionCompat.java
+++ b/core/src/main/java/androidx/core/app/RemoteActionCompat.java
@@ -19,12 +19,14 @@
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.os.Build;
-import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.graphics.drawable.IconCompat;
import androidx.core.util.Preconditions;
+import androidx.versionedparcelable.ParcelField;
+import androidx.versionedparcelable.VersionedParcelable;
+import androidx.versionedparcelable.VersionedParcelize;
/**
* Represents a remote action that can be called from another process. The action can have an
@@ -32,21 +34,20 @@
* <p>
* This is a backward-compatible version of {@link RemoteAction}.
*/
-public final class RemoteActionCompat {
-
- private static final String EXTRA_ICON = "icon";
- private static final String EXTRA_TITLE = "title";
- private static final String EXTRA_CONTENT_DESCRIPTION = "desc";
- private static final String EXTRA_ACTION_INTENT = "action";
- private static final String EXTRA_ENABLED = "enabled";
- private static final String EXTRA_SHOULD_SHOW_ICON = "showicon";
-
- private final IconCompat mIcon;
- private final CharSequence mTitle;
- private final CharSequence mContentDescription;
- private final PendingIntent mActionIntent;
- private boolean mEnabled;
- private boolean mShouldShowIcon;
+@VersionedParcelize(jetifyAs = "android.support.v4.app.RemoteActionCompat")
+public final class RemoteActionCompat implements VersionedParcelable {
+ @ParcelField(1)
+ IconCompat mIcon;
+ @ParcelField(2)
+ CharSequence mTitle;
+ @ParcelField(3)
+ CharSequence mContentDescription;
+ @ParcelField(4)
+ PendingIntent mActionIntent;
+ @ParcelField(5)
+ boolean mEnabled;
+ @ParcelField(6)
+ boolean mShouldShowIcon;
public RemoteActionCompat(@NonNull IconCompat icon, @NonNull CharSequence title,
@NonNull CharSequence contentDescription, @NonNull PendingIntent intent) {
@@ -59,6 +60,11 @@
}
/**
+ * Used for VersionedParcelable.
+ */
+ RemoteActionCompat() {}
+
+ /**
* Constructs a {@link RemoteActionCompat} using data from {@code other}.
*/
public RemoteActionCompat(@NonNull RemoteActionCompat other) {
@@ -160,35 +166,4 @@
}
return action;
}
-
- /**
- * Converts this into a Bundle that can be converted back to a {@link RemoteActionCompat}
- * by calling {@link #createFromBundle(Bundle)}.
- */
- @NonNull
- public Bundle toBundle() {
- Bundle bundle = new Bundle();
- bundle.putBundle(EXTRA_ICON, mIcon.toBundle());
- bundle.putCharSequence(EXTRA_TITLE, mTitle);
- bundle.putCharSequence(EXTRA_CONTENT_DESCRIPTION, mContentDescription);
- bundle.putParcelable(EXTRA_ACTION_INTENT, mActionIntent);
- bundle.putBoolean(EXTRA_ENABLED, mEnabled);
- bundle.putBoolean(EXTRA_SHOULD_SHOW_ICON, mShouldShowIcon);
- return bundle;
- }
-
- /**
- * Converts the bundle created by {@link #toBundle()} back to {@link RemoteActionCompat}.
- */
- @NonNull
- public static RemoteActionCompat createFromBundle(@NonNull Bundle bundle) {
- RemoteActionCompat action = new RemoteActionCompat(
- IconCompat.createFromBundle(bundle.getBundle(EXTRA_ICON)),
- bundle.getCharSequence(EXTRA_TITLE),
- bundle.getCharSequence(EXTRA_CONTENT_DESCRIPTION),
- bundle.<PendingIntent>getParcelable(EXTRA_ACTION_INTENT));
- action.setEnabled(bundle.getBoolean(EXTRA_ENABLED));
- action.setShouldShowIcon(bundle.getBoolean(EXTRA_SHOULD_SHOW_ICON));
- return action;
- }
}
diff --git a/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java b/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
index 1e6ca0e..8bf3e30 100644
--- a/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
+++ b/core/src/main/java/androidx/core/graphics/TypefaceCompatUtil.java
@@ -60,9 +60,14 @@
*/
@Nullable
public static File getTempFile(Context context) {
+ File cacheDir = context.getCacheDir();
+ if (cacheDir == null) {
+ return null;
+ }
+
final String prefix = CACHE_FILE_PREFIX + Process.myPid() + "-" + Process.myTid() + "-";
for (int i = 0; i < 100; ++i) {
- final File file = new File(context.getCacheDir(), prefix + i);
+ final File file = new File(cacheDir, prefix + i);
try {
if (file.createNewFile()) {
return file;
diff --git a/docs-fake/build.gradle b/docs-fake/build.gradle
index 4750de6..df7e903 100644
--- a/docs-fake/build.gradle
+++ b/docs-fake/build.gradle
@@ -15,6 +15,7 @@
*/
import androidx.build.SupportConfig
+import static androidx.build.dependencies.DependenciesKt.CHECKER_FRAMEWORK
plugins {
id("AndroidXPlugin")
@@ -25,7 +26,11 @@
// it is more effective then task.enabled = false, because we avoid executing deps as well
def reentrance = false
project.tasks.whenTaskAdded { task ->
- if (task instanceof org.gradle.api.tasks.testing.Test || task.name.startsWith("assemble")) {
+ if (task instanceof org.gradle.api.tasks.testing.Test
+ || task.name.startsWith("assemble")
+ || task.name == "transformDexArchiveWithExternalLibsDexMergerForPublicDebug"
+ || task.name == "transformResourcesWithMergeJavaResForPublicDebug"
+ || task.name == "checkPublicDebugDuplicateClasses") {
if (!reentrance) {
reentrance = true
project.tasks.replace(task.name)
@@ -47,3 +52,13 @@
}
flavorDimensions "library-group"
}
+
+dependencies {
+ /*
+ * This is needed to build Public Docs for media2:1.0.0-alpha04 and higher
+ * Our current guess is that Doclava isn't checking which dependencies are unique to
+ * the compileOnly configuration, since they're not supposed to be needed at runtime.
+ */
+ compileOnly(CHECKER_FRAMEWORK)
+}
+
diff --git a/dynamic-animation/api/1.1.0-alpha01.txt b/dynamic-animation/api/1.1.0-alpha01.txt
index 9a947b0..bfc7230 100644
--- a/dynamic-animation/api/1.1.0-alpha01.txt
+++ b/dynamic-animation/api/1.1.0-alpha01.txt
@@ -63,7 +63,7 @@
method public abstract void setValue(T!, float);
}
- public final class FloatValueHolder {
+ public class FloatValueHolder {
ctor public FloatValueHolder();
ctor public FloatValueHolder(float);
method public float getValue();
@@ -72,6 +72,7 @@
public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation> {
ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
+ ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!, float);
ctor public SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K>!);
ctor public SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K>!, float);
method public void animateToFinalPosition(float);
diff --git a/dynamic-animation/api/current.txt b/dynamic-animation/api/current.txt
index 9a947b0..bfc7230 100644
--- a/dynamic-animation/api/current.txt
+++ b/dynamic-animation/api/current.txt
@@ -63,7 +63,7 @@
method public abstract void setValue(T!, float);
}
- public final class FloatValueHolder {
+ public class FloatValueHolder {
ctor public FloatValueHolder();
ctor public FloatValueHolder(float);
method public float getValue();
@@ -72,6 +72,7 @@
public final class SpringAnimation extends androidx.dynamicanimation.animation.DynamicAnimation<androidx.dynamicanimation.animation.SpringAnimation> {
ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!);
+ ctor public SpringAnimation(androidx.dynamicanimation.animation.FloatValueHolder!, float);
ctor public SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K>!);
ctor public SpringAnimation(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K>!, float);
method public void animateToFinalPosition(float);
diff --git a/dynamic-animation/ktx/api/1.0.0-alpha02.txt b/dynamic-animation/ktx/api/1.0.0-alpha02.txt
index cda5dad..e250ffe 100644
--- a/dynamic-animation/ktx/api/1.0.0-alpha02.txt
+++ b/dynamic-animation/ktx/api/1.0.0-alpha02.txt
@@ -3,8 +3,8 @@
public final class DynamicAnimationKt {
ctor public DynamicAnimationKt();
- method public static inline <K> androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property);
- method public static inline <K> androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property, float finalPosition = Float.NaN);
+ method public static androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> setter, kotlin.jvm.functions.Function0<java.lang.Float> getter);
+ method public static androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> setter, kotlin.jvm.functions.Function0<java.lang.Float> getter, float finalPosition = Float.NaN);
method public static inline androidx.dynamicanimation.animation.SpringAnimation withSpringForceProperties(androidx.dynamicanimation.animation.SpringAnimation, kotlin.jvm.functions.Function1<? super androidx.dynamicanimation.animation.SpringForce,kotlin.Unit> func);
}
diff --git a/dynamic-animation/ktx/api/current.txt b/dynamic-animation/ktx/api/current.txt
index cda5dad..e250ffe 100644
--- a/dynamic-animation/ktx/api/current.txt
+++ b/dynamic-animation/ktx/api/current.txt
@@ -3,8 +3,8 @@
public final class DynamicAnimationKt {
ctor public DynamicAnimationKt();
- method public static inline <K> androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property);
- method public static inline <K> androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(K!, androidx.dynamicanimation.animation.FloatPropertyCompat<K> property, float finalPosition = Float.NaN);
+ method public static androidx.dynamicanimation.animation.FlingAnimation flingAnimationOf(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> setter, kotlin.jvm.functions.Function0<java.lang.Float> getter);
+ method public static androidx.dynamicanimation.animation.SpringAnimation springAnimationOf(kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> setter, kotlin.jvm.functions.Function0<java.lang.Float> getter, float finalPosition = Float.NaN);
method public static inline androidx.dynamicanimation.animation.SpringAnimation withSpringForceProperties(androidx.dynamicanimation.animation.SpringAnimation, kotlin.jvm.functions.Function1<? super androidx.dynamicanimation.animation.SpringForce,kotlin.Unit> func);
}
diff --git a/dynamic-animation/ktx/build.gradle b/dynamic-animation/ktx/build.gradle
index 2511749..b14cc2d 100644
--- a/dynamic-animation/ktx/build.gradle
+++ b/dynamic-animation/ktx/build.gradle
@@ -38,12 +38,14 @@
api(KOTLIN_STDLIB)
api(project(":dynamicanimation"))
- androidTestImplementation(JUNIT)
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(TEST_RULES)
- androidTestImplementation(TRUTH)
+
+ androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
supportLibrary {
diff --git a/dynamic-animation/ktx/src/androidTest/AndroidManifest.xml b/dynamic-animation/ktx/src/androidTest/AndroidManifest.xml
new file mode 100755
index 0000000..8cc5dd5
--- /dev/null
+++ b/dynamic-animation/ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.dynamicanimation.ktx.test">
+ <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+
+ <application android:supportsRtl="true">
+ <activity android:name="androidx.dynamicanimation.tests.AnimationActivity"/>
+ </application>
+</manifest>
diff --git a/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt
deleted file mode 100644
index 1cce0dd..0000000
--- a/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/animation/DynamicAnimationTest.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.dynamicanimation.animation
-
-import androidx.test.filters.SmallTest
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-
-@SmallTest
-class DynamicAnimationTest {
-
- private val animObject = Any()
- private lateinit var floatPropertyCompat: FloatPropertyCompat<Any>
-
- /**
- * Setup [FloatPropertyCompat] before test start
- */
- @Before fun setup() {
- floatPropertyCompat = object : FloatPropertyCompat<Any>("") {
-
- private var value = 0f
-
- override fun getValue(`object`: Any?): Float {
- return value
- }
-
- override fun setValue(`object`: Any?, value: Float) {
- this.value = value
- }
- }
- }
-
- /**
- * Test extension for creating [FlingAnimation]
- */
- @Test fun testCreateFlingAnimation() {
- val flingAnimation = animObject.flingAnimationOf(floatPropertyCompat)
- assertNotNull(flingAnimation)
- assertEquals(flingAnimation.mTarget, animObject)
- assertEquals(flingAnimation.mProperty, floatPropertyCompat)
- assertTrue(flingAnimation.friction == 1f)
- flingAnimation.friction = 1.5f
- assertTrue(flingAnimation.friction == 1.5f)
- }
-
- /**
- * Test extension for creating [SpringAnimation]
- */
- @Test fun testCreateSpringAnimation() {
- val springAnimationWithoutFinalPosition = animObject.springAnimationOf(floatPropertyCompat)
- assertNotNull(springAnimationWithoutFinalPosition)
- assertEquals(springAnimationWithoutFinalPosition.mTarget, animObject)
- assertEquals(springAnimationWithoutFinalPosition.mProperty, floatPropertyCompat)
- assertNull(springAnimationWithoutFinalPosition.spring)
- val springAnimationWithFinalPosition = animObject.springAnimationOf(floatPropertyCompat,
- 100f)
- assertNotNull(springAnimationWithFinalPosition)
- assertEquals(springAnimationWithFinalPosition.mTarget, animObject)
- assertEquals(springAnimationWithFinalPosition.mProperty, floatPropertyCompat)
- assertNotNull(springAnimationWithFinalPosition.spring)
- assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 100f)
- }
-
- /**
- * Test extension for setting up [SpringForce]
- */
- @Test fun testSpringForce() {
- val springAnimationWithoutFinalPosition = animObject
- .springAnimationOf(floatPropertyCompat)
- .withSpringForceProperties {
- finalPosition = 100f
- dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
- stiffness = SpringForce.STIFFNESS_HIGH
- }
- assertNotNull(springAnimationWithoutFinalPosition.spring)
- assertEquals(springAnimationWithoutFinalPosition.spring.finalPosition, 100f)
- assertEquals(springAnimationWithoutFinalPosition.spring.dampingRatio,
- SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
- assertEquals(springAnimationWithoutFinalPosition.spring.stiffness,
- SpringForce.STIFFNESS_HIGH)
- val springAnimationWithFinalPosition = animObject
- .springAnimationOf(floatPropertyCompat, 120f)
- .withSpringForceProperties {
- dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
- stiffness = SpringForce.STIFFNESS_LOW
- }
- assertNotNull(springAnimationWithFinalPosition.spring)
- assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 120f)
- assertEquals(springAnimationWithFinalPosition.spring.dampingRatio,
- SpringForce.DAMPING_RATIO_LOW_BOUNCY)
- assertEquals(springAnimationWithFinalPosition.spring.stiffness,
- SpringForce.STIFFNESS_LOW)
- }
-}
\ No newline at end of file
diff --git a/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/AnimationActivity.java b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/AnimationActivity.java
new file mode 100644
index 0000000..5e0dd54
--- /dev/null
+++ b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/AnimationActivity.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.dynamicanimation.tests;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.dynamicanimation.ktx.test.R;
+
+public class AnimationActivity extends Activity {
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.anim_layout);
+ }
+}
diff --git a/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/DynamicAnimationTest.kt b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/DynamicAnimationTest.kt
new file mode 100644
index 0000000..8cdf5ea
--- /dev/null
+++ b/dynamic-animation/ktx/src/androidTest/java/androidx/dynamicanimation/tests/DynamicAnimationTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.dynamicanimation.tests
+
+import android.view.View
+
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import androidx.dynamicanimation.animation.flingAnimationOf
+import androidx.dynamicanimation.animation.springAnimationOf
+import androidx.dynamicanimation.animation.withSpringForceProperties
+import androidx.dynamicanimation.ktx.test.R
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DynamicAnimationTest {
+
+ @get:Rule val activityTestRule = ActivityTestRule<AnimationActivity>(
+ AnimationActivity::class.java)
+
+ private lateinit var view: View
+
+ @Before fun setup() {
+ view = activityTestRule.getActivity().findViewById(R.id.anim_view)
+ }
+
+ /**
+ * Test extension for creating [FlingAnimation]
+ */
+ @Test fun testCreateFlingAnimation() {
+ val flingAnimation = flingAnimationOf(view::setAlpha, view::getAlpha)
+ assertNotNull(flingAnimation)
+ assertTrue(flingAnimation.friction == 1f)
+ flingAnimation.friction = 1.5f
+ assertTrue(flingAnimation.friction == 1.5f)
+ }
+
+ /**
+ * Test extension for creating [SpringAnimation]
+ */
+ @Test fun testCreateSpringAnimation() {
+ val springAnimationWithoutFinalPosition =
+ springAnimationOf(view::setScaleX, view::getScaleX)
+ assertNotNull(springAnimationWithoutFinalPosition)
+ assertNull(springAnimationWithoutFinalPosition.spring)
+ val springAnimationWithFinalPosition =
+ springAnimationOf(view::setScaleX, view::getScaleX, 100f)
+ assertNotNull(springAnimationWithFinalPosition)
+ assertNotNull(springAnimationWithFinalPosition.spring)
+ assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 100f)
+
+ val listener: DynamicAnimation.OnAnimationEndListener = mock(
+ DynamicAnimation.OnAnimationEndListener::class.java)
+ springAnimationWithFinalPosition.addEndListener(listener)
+ InstrumentationRegistry.getInstrumentation().runOnMainSync({
+ springAnimationWithFinalPosition.setStartValue(0f).start()
+ })
+ assertTrue(springAnimationWithFinalPosition.isRunning())
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync({
+ springAnimationWithFinalPosition.skipToEnd()
+ })
+
+ verify(listener, timeout(2000)).onAnimationEnd(springAnimationWithFinalPosition, false,
+ 100f, 0f)
+
+ assertEquals(100f, view.getScaleX())
+ }
+
+ /**
+ * Test extension for setting up [SpringForce]
+ */
+ @Test fun testSpringForce() {
+ val springAnimationWithoutFinalPosition = springAnimationOf(
+ view::setTranslationX, view::getTranslationX)
+ .withSpringForceProperties {
+ finalPosition = 100f
+ dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
+ stiffness = SpringForce.STIFFNESS_HIGH
+ }
+ assertNotNull(springAnimationWithoutFinalPosition.spring)
+ assertEquals(springAnimationWithoutFinalPosition.spring.finalPosition, 100f)
+ assertEquals(
+ springAnimationWithoutFinalPosition.spring.dampingRatio,
+ SpringForce.DAMPING_RATIO_HIGH_BOUNCY
+ )
+ assertEquals(
+ springAnimationWithoutFinalPosition.spring.stiffness,
+ SpringForce.STIFFNESS_HIGH
+ )
+ val springAnimationWithFinalPosition = springAnimationOf(
+ view::setTranslationX, view::getTranslationX, 120f)
+ .withSpringForceProperties {
+ dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ stiffness = SpringForce.STIFFNESS_LOW
+ }
+ assertNotNull(springAnimationWithFinalPosition.spring)
+ assertEquals(springAnimationWithFinalPosition.spring.finalPosition, 120f)
+ assertEquals(
+ springAnimationWithFinalPosition.spring.dampingRatio,
+ SpringForce.DAMPING_RATIO_LOW_BOUNCY
+ )
+ assertEquals(
+ springAnimationWithFinalPosition.spring.stiffness,
+ SpringForce.STIFFNESS_LOW
+ )
+ }
+}
diff --git a/dynamic-animation/ktx/src/androidTest/res/layout/anim_layout.xml b/dynamic-animation/ktx/src/androidTest/res/layout/anim_layout.xml
new file mode 100644
index 0000000..5db7936
--- /dev/null
+++ b/dynamic-animation/ktx/src/androidTest/res/layout/anim_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/anim_view"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+
+ <View
+ android:id="@+id/anim_another_view"
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
+
+</FrameLayout>
diff --git a/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt b/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
index bf0eaef..068b162 100644
--- a/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
+++ b/dynamic-animation/ktx/src/main/java/androidx/dynamicanimation/animation/DynamicAnimation.kt
@@ -17,32 +17,39 @@
package androidx.dynamicanimation.animation
/**
- * Creates [FlingAnimation] for object.
+ * Creates [FlingAnimation] for a property that can be accessed via the provided setter and getter.
+ * For example, the following sample code creates a [FlingAnimation] for the alpha property of a
+ * [View] object named `view`:
+ * `flingAnimationOf(view::setAlpha, view::getAlpha)`
*
- * @param property object's property to be animated.
+ * @param setter The function that mutates the property being animated
+ * @param getter The function that returns the value of the property
* @return [FlingAnimation]
*/
-inline fun <K> K.flingAnimationOf(property: FloatPropertyCompat<K>): FlingAnimation {
- return FlingAnimation(this, property)
+fun flingAnimationOf(setter: (Float) -> Unit, getter: () -> Float): FlingAnimation {
+ return FlingAnimation(createFloatValueHolder(setter, getter))
}
/**
- * Creates [SpringAnimation] for object.
+ * Creates [SpringAnimation] for a property that can be accessed via the provided setter and getter.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
- * @param property object's property to be animated.
+ * @param setter The function that mutates the property being animated
+ * @param getter The function that returns the value of the property
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
-inline fun <K> K.springAnimationOf(
- property: FloatPropertyCompat<K>,
+fun springAnimationOf(
+ setter: (Float) -> Unit,
+ getter: () -> Float,
finalPosition: Float = Float.NaN
): SpringAnimation {
+ val valueHolder = createFloatValueHolder(setter, getter)
return if (finalPosition.isNaN()) {
- SpringAnimation(this, property)
+ SpringAnimation(valueHolder)
} else {
- SpringAnimation(this, property, finalPosition)
+ SpringAnimation(valueHolder, finalPosition)
}
}
@@ -64,4 +71,16 @@
}
spring.func()
return this
+}
+
+private fun createFloatValueHolder(setter: (Float) -> Unit, getter: () -> Float): FloatValueHolder {
+ return object : FloatValueHolder() {
+ override fun getValue(): Float {
+ return getter.invoke()
+ }
+
+ override fun setValue(value: Float) {
+ setter.invoke(value)
+ }
+ }
}
\ No newline at end of file
diff --git a/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/FloatValueHolder.java b/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/FloatValueHolder.java
index e85905d..bd25f7b 100644
--- a/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/FloatValueHolder.java
+++ b/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/FloatValueHolder.java
@@ -36,7 +36,7 @@
* @see FlingAnimation#FlingAnimation(FloatValueHolder)
*/
-public final class FloatValueHolder {
+public class FloatValueHolder {
private float mValue = 0.0f;
/**
diff --git a/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/SpringAnimation.java b/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/SpringAnimation.java
index 0ff9ce0..2b75cc6 100644
--- a/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/SpringAnimation.java
+++ b/dynamic-animation/src/main/java/androidx/dynamicanimation/animation/SpringAnimation.java
@@ -79,6 +79,27 @@
}
/**
+ * <p>This creates a SpringAnimation that animates a {@link FloatValueHolder} instance. During
+ * the animation, the {@link FloatValueHolder} instance will be updated via
+ * {@link FloatValueHolder#setValue(float)} each frame. The caller can obtain the up-to-date
+ * animation value via {@link FloatValueHolder#getValue()}.
+ *
+ * A Spring will be created with the given final position and default stiffness and damping
+ * ratio. This spring can be accessed and reconfigured through {@link #setSpring(SpringForce)}.
+ *
+ * <p><strong>Note:</strong> changing the value in the {@link FloatValueHolder} via
+ * {@link FloatValueHolder#setValue(float)} outside of the animation during an
+ * animation run will not have any effect on the on-going animation.
+ *
+ * @param floatValueHolder the property to be animated
+ * @param finalPosition the final position of the spring to be created.
+ */
+ public SpringAnimation(FloatValueHolder floatValueHolder, float finalPosition) {
+ super(floatValueHolder);
+ mSpring = new SpringForce(finalPosition);
+ }
+
+ /**
* This creates a SpringAnimation that animates the property of the given object.
* Note, a spring will need to setup through {@link #setSpring(SpringForce)} before
* the animation starts.
diff --git a/enterprise/feedback/build.gradle b/enterprise/feedback/build.gradle
index 01318c7..aa8ad4c 100644
--- a/enterprise/feedback/build.gradle
+++ b/enterprise/feedback/build.gradle
@@ -6,8 +6,8 @@
id("SupportAndroidLibraryPlugin")
}
dependencies {
- api("androidx.core:core:1.0.0")
- implementation(AUTO_VALUE_ANNOTATIONS)
+ api("androidx.annotation:annotation:1.0.1")
+ api(AUTO_VALUE_ANNOTATIONS)
annotationProcessor(AUTO_VALUE)
testImplementation(TEST_CORE)
testImplementation(JUNIT)
diff --git a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/BufferedServiceConnection.java b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/BufferedServiceConnection.java
index fb3a361..6b657a0 100644
--- a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/BufferedServiceConnection.java
+++ b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/BufferedServiceConnection.java
@@ -18,7 +18,6 @@
import static androidx.enterprise.feedback.KeyedAppStatesReporter.canPackageReceiveAppStates;
-import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -96,8 +95,6 @@
*
* <p>This can only be called once per instance.
*/
- @SuppressLint("UntrackedBindService")
- // Not dependent on GMS
void bindService() {
if (mHasBound) {
throw new IllegalStateException(
diff --git a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
index 2a40b1c..0fb7aa5 100644
--- a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
+++ b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesReporter.java
@@ -374,7 +374,7 @@
private static Message createStateMessage(Bundle appStatesBundle, boolean immediate) {
Message message = Message.obtain();
message.what = immediate ? WHAT_IMMEDIATE_STATE : WHAT_STATE;
- message.setData(appStatesBundle);
+ message.obj = appStatesBundle;
return message;
}
}
diff --git a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesService.java b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesService.java
index 52d8151..e10a5aa 100644
--- a/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesService.java
+++ b/enterprise/feedback/src/main/java/androidx/enterprise/feedback/KeyedAppStatesService.java
@@ -135,7 +135,14 @@
private static Collection<ReceivedKeyedAppState> extractReceivedKeyedAppStates(
Message message, String packageName, long timestamp) {
- Bundle bundle = message.getData();
+ Bundle bundle;
+
+ try {
+ bundle = (Bundle) message.obj;
+ } catch (ClassCastException e) {
+ Log.e(LOG_TAG, "Could not extract state bundles from message");
+ return Collections.emptyList();
+ }
if (bundle == null) {
Log.e(LOG_TAG, "Could not extract state bundles from message");
@@ -158,7 +165,7 @@
states.add(ReceivedKeyedAppState.fromBundle(stateBundle, packageName, timestamp));
}
- return states;
+ return Collections.unmodifiableCollection(states);
}
private static Collection<ReceivedKeyedAppState> deduplicateStates(
diff --git a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java
index 56a8441..d094c16 100644
--- a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java
+++ b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesReporterTest.java
@@ -148,7 +148,7 @@
reporter.set(singletonList(mState));
Bundle appStatesBundle = buildStatesBundle(singleton(mState));
- assertAppStateBundlesEqual(appStatesBundle, mTestHandler.latestMessage().getData());
+ assertAppStateBundlesEqual(appStatesBundle, (Bundle) mTestHandler.latestMessage().obj);
}
private static Bundle buildStatesBundle(Collection<KeyedAppState> keyedAppStates) {
diff --git a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesServiceTest.java b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesServiceTest.java
index bfe4dba..d0043dd 100644
--- a/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesServiceTest.java
+++ b/enterprise/feedback/src/test/java/androidx/enterprise/feedback/KeyedAppStatesServiceTest.java
@@ -240,9 +240,9 @@
@Test
@SmallTest
- public void send_messageWithNullData_doesNotCallback() throws RemoteException {
+ public void send_messageWithIncorrectObj_doesNotCallback() throws RemoteException {
Message message = createStateMessage(null);
- message.setData(null);
+ message.obj = "";
mMessenger.send(message);
@@ -325,7 +325,7 @@
private static Message createStateMessage(Bundle appStatesBundle, boolean immediate) {
Message message = Message.obtain();
message.what = immediate ? WHAT_IMMEDIATE_STATE : WHAT_STATE;
- message.setData(appStatesBundle);
+ message.obj = appStatesBundle;
return message;
}
diff --git a/exifinterface/api/1.1.0-alpha01.txt b/exifinterface/api/1.1.0-alpha01.txt
index 75772d7..b714925 100644
--- a/exifinterface/api/1.1.0-alpha01.txt
+++ b/exifinterface/api/1.1.0-alpha01.txt
@@ -10,8 +10,10 @@
method public void flipVertically();
method public double getAltitude(double);
method public String? getAttribute(String);
+ method public byte[]? getAttributeBytes(String);
method public double getAttributeDouble(String, double);
method public int getAttributeInt(String, int);
+ method public long[]? getAttributeRange(String);
method @Deprecated public boolean getLatLong(float[]!);
method public double[]? getLatLong();
method public int getRotationDegrees();
@@ -19,6 +21,7 @@
method public android.graphics.Bitmap? getThumbnailBitmap();
method public byte[]? getThumbnailBytes();
method public long[]? getThumbnailRange();
+ method public boolean hasAttribute(String);
method public boolean hasThumbnail();
method public boolean isFlipped();
method public boolean isThumbnailCompressed();
diff --git a/exifinterface/api/1.1.0-alpha02.txt b/exifinterface/api/1.1.0-alpha02.txt
new file mode 100644
index 0000000..87a26c7
--- /dev/null
+++ b/exifinterface/api/1.1.0-alpha02.txt
@@ -0,0 +1,340 @@
+// Signature format: 3.0
+package androidx.exifinterface.media {
+
+ public class ExifInterface {
+ ctor public ExifInterface(java.io.File) throws java.io.IOException;
+ ctor public ExifInterface(String) throws java.io.IOException;
+ ctor public ExifInterface(java.io.FileDescriptor) throws java.io.IOException;
+ ctor public ExifInterface(java.io.InputStream) throws java.io.IOException;
+ method public void flipHorizontally();
+ method public void flipVertically();
+ method public double getAltitude(double);
+ method public String? getAttribute(String);
+ method public byte[]? getAttributeBytes(String);
+ method public double getAttributeDouble(String, double);
+ method public int getAttributeInt(String, int);
+ method public long[]? getAttributeRange(String);
+ method @Deprecated public boolean getLatLong(float[]!);
+ method public double[]? getLatLong();
+ method public int getRotationDegrees();
+ method public byte[]? getThumbnail();
+ method public android.graphics.Bitmap? getThumbnailBitmap();
+ method public byte[]? getThumbnailBytes();
+ method public long[]? getThumbnailRange();
+ method public boolean hasAttribute(String);
+ method public boolean hasThumbnail();
+ method public boolean isFlipped();
+ method public boolean isThumbnailCompressed();
+ method public void resetOrientation();
+ method public void rotate(int);
+ method public void saveAttributes() throws java.io.IOException;
+ method public void setAltitude(double);
+ method public void setAttribute(String, String?);
+ method public void setGpsInfo(android.location.Location!);
+ method public void setLatLong(double, double);
+ field public static final short ALTITUDE_ABOVE_SEA_LEVEL = 0; // 0x0
+ field public static final short ALTITUDE_BELOW_SEA_LEVEL = 1; // 0x1
+ field public static final int[]! BITS_PER_SAMPLE_GREYSCALE_1;
+ field public static final int[]! BITS_PER_SAMPLE_GREYSCALE_2;
+ field public static final int[]! BITS_PER_SAMPLE_RGB;
+ field public static final int COLOR_SPACE_S_RGB = 1; // 0x1
+ field public static final int COLOR_SPACE_UNCALIBRATED = 65535; // 0xffff
+ field public static final short CONTRAST_HARD = 2; // 0x2
+ field public static final short CONTRAST_NORMAL = 0; // 0x0
+ field public static final short CONTRAST_SOFT = 1; // 0x1
+ field public static final int DATA_DEFLATE_ZIP = 8; // 0x8
+ field public static final int DATA_HUFFMAN_COMPRESSED = 2; // 0x2
+ field public static final int DATA_JPEG = 6; // 0x6
+ field public static final int DATA_JPEG_COMPRESSED = 7; // 0x7
+ field public static final int DATA_LOSSY_JPEG = 34892; // 0x884c
+ field public static final int DATA_PACK_BITS_COMPRESSED = 32773; // 0x8005
+ field public static final int DATA_UNCOMPRESSED = 1; // 0x1
+ field public static final short EXPOSURE_MODE_AUTO = 0; // 0x0
+ field public static final short EXPOSURE_MODE_AUTO_BRACKET = 2; // 0x2
+ field public static final short EXPOSURE_MODE_MANUAL = 1; // 0x1
+ field public static final short EXPOSURE_PROGRAM_ACTION = 6; // 0x6
+ field public static final short EXPOSURE_PROGRAM_APERTURE_PRIORITY = 3; // 0x3
+ field public static final short EXPOSURE_PROGRAM_CREATIVE = 5; // 0x5
+ field public static final short EXPOSURE_PROGRAM_LANDSCAPE_MODE = 8; // 0x8
+ field public static final short EXPOSURE_PROGRAM_MANUAL = 1; // 0x1
+ field public static final short EXPOSURE_PROGRAM_NORMAL = 2; // 0x2
+ field public static final short EXPOSURE_PROGRAM_NOT_DEFINED = 0; // 0x0
+ field public static final short EXPOSURE_PROGRAM_PORTRAIT_MODE = 7; // 0x7
+ field public static final short EXPOSURE_PROGRAM_SHUTTER_PRIORITY = 4; // 0x4
+ field public static final short FILE_SOURCE_DSC = 3; // 0x3
+ field public static final short FILE_SOURCE_OTHER = 0; // 0x0
+ field public static final short FILE_SOURCE_REFLEX_SCANNER = 2; // 0x2
+ field public static final short FILE_SOURCE_TRANSPARENT_SCANNER = 1; // 0x1
+ field public static final short FLAG_FLASH_FIRED = 1; // 0x1
+ field public static final short FLAG_FLASH_MODE_AUTO = 24; // 0x18
+ field public static final short FLAG_FLASH_MODE_COMPULSORY_FIRING = 8; // 0x8
+ field public static final short FLAG_FLASH_MODE_COMPULSORY_SUPPRESSION = 16; // 0x10
+ field public static final short FLAG_FLASH_NO_FLASH_FUNCTION = 32; // 0x20
+ field public static final short FLAG_FLASH_RED_EYE_SUPPORTED = 64; // 0x40
+ field public static final short FLAG_FLASH_RETURN_LIGHT_DETECTED = 6; // 0x6
+ field public static final short FLAG_FLASH_RETURN_LIGHT_NOT_DETECTED = 4; // 0x4
+ field public static final short FORMAT_CHUNKY = 1; // 0x1
+ field public static final short FORMAT_PLANAR = 2; // 0x2
+ field public static final short GAIN_CONTROL_HIGH_GAIN_DOWN = 4; // 0x4
+ field public static final short GAIN_CONTROL_HIGH_GAIN_UP = 2; // 0x2
+ field public static final short GAIN_CONTROL_LOW_GAIN_DOWN = 3; // 0x3
+ field public static final short GAIN_CONTROL_LOW_GAIN_UP = 1; // 0x1
+ field public static final short GAIN_CONTROL_NONE = 0; // 0x0
+ field public static final String GPS_DIRECTION_MAGNETIC = "M";
+ field public static final String GPS_DIRECTION_TRUE = "T";
+ field public static final String GPS_DISTANCE_KILOMETERS = "K";
+ field public static final String GPS_DISTANCE_MILES = "M";
+ field public static final String GPS_DISTANCE_NAUTICAL_MILES = "N";
+ field public static final String GPS_MEASUREMENT_2D = "2";
+ field public static final String GPS_MEASUREMENT_3D = "3";
+ field public static final short GPS_MEASUREMENT_DIFFERENTIAL_CORRECTED = 1; // 0x1
+ field public static final String GPS_MEASUREMENT_INTERRUPTED = "V";
+ field public static final String GPS_MEASUREMENT_IN_PROGRESS = "A";
+ field public static final short GPS_MEASUREMENT_NO_DIFFERENTIAL = 0; // 0x0
+ field public static final String GPS_SPEED_KILOMETERS_PER_HOUR = "K";
+ field public static final String GPS_SPEED_KNOTS = "N";
+ field public static final String GPS_SPEED_MILES_PER_HOUR = "M";
+ field public static final String LATITUDE_NORTH = "N";
+ field public static final String LATITUDE_SOUTH = "S";
+ field public static final short LIGHT_SOURCE_CLOUDY_WEATHER = 10; // 0xa
+ field public static final short LIGHT_SOURCE_COOL_WHITE_FLUORESCENT = 14; // 0xe
+ field public static final short LIGHT_SOURCE_D50 = 23; // 0x17
+ field public static final short LIGHT_SOURCE_D55 = 20; // 0x14
+ field public static final short LIGHT_SOURCE_D65 = 21; // 0x15
+ field public static final short LIGHT_SOURCE_D75 = 22; // 0x16
+ field public static final short LIGHT_SOURCE_DAYLIGHT = 1; // 0x1
+ field public static final short LIGHT_SOURCE_DAYLIGHT_FLUORESCENT = 12; // 0xc
+ field public static final short LIGHT_SOURCE_DAY_WHITE_FLUORESCENT = 13; // 0xd
+ field public static final short LIGHT_SOURCE_FINE_WEATHER = 9; // 0x9
+ field public static final short LIGHT_SOURCE_FLASH = 4; // 0x4
+ field public static final short LIGHT_SOURCE_FLUORESCENT = 2; // 0x2
+ field public static final short LIGHT_SOURCE_ISO_STUDIO_TUNGSTEN = 24; // 0x18
+ field public static final short LIGHT_SOURCE_OTHER = 255; // 0xff
+ field public static final short LIGHT_SOURCE_SHADE = 11; // 0xb
+ field public static final short LIGHT_SOURCE_STANDARD_LIGHT_A = 17; // 0x11
+ field public static final short LIGHT_SOURCE_STANDARD_LIGHT_B = 18; // 0x12
+ field public static final short LIGHT_SOURCE_STANDARD_LIGHT_C = 19; // 0x13
+ field public static final short LIGHT_SOURCE_TUNGSTEN = 3; // 0x3
+ field public static final short LIGHT_SOURCE_UNKNOWN = 0; // 0x0
+ field public static final short LIGHT_SOURCE_WARM_WHITE_FLUORESCENT = 16; // 0x10
+ field public static final short LIGHT_SOURCE_WHITE_FLUORESCENT = 15; // 0xf
+ field public static final String LONGITUDE_EAST = "E";
+ field public static final String LONGITUDE_WEST = "W";
+ field public static final short METERING_MODE_AVERAGE = 1; // 0x1
+ field public static final short METERING_MODE_CENTER_WEIGHT_AVERAGE = 2; // 0x2
+ field public static final short METERING_MODE_MULTI_SPOT = 4; // 0x4
+ field public static final short METERING_MODE_OTHER = 255; // 0xff
+ field public static final short METERING_MODE_PARTIAL = 6; // 0x6
+ field public static final short METERING_MODE_PATTERN = 5; // 0x5
+ field public static final short METERING_MODE_SPOT = 3; // 0x3
+ field public static final short METERING_MODE_UNKNOWN = 0; // 0x0
+ field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
+ field public static final int ORIENTATION_FLIP_VERTICAL = 4; // 0x4
+ field public static final int ORIENTATION_NORMAL = 1; // 0x1
+ field public static final int ORIENTATION_ROTATE_180 = 3; // 0x3
+ field public static final int ORIENTATION_ROTATE_270 = 8; // 0x8
+ field public static final int ORIENTATION_ROTATE_90 = 6; // 0x6
+ field public static final int ORIENTATION_TRANSPOSE = 5; // 0x5
+ field public static final int ORIENTATION_TRANSVERSE = 7; // 0x7
+ field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
+ field public static final int ORIGINAL_RESOLUTION_IMAGE = 0; // 0x0
+ field public static final int PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 1; // 0x1
+ field public static final int PHOTOMETRIC_INTERPRETATION_RGB = 2; // 0x2
+ field public static final int PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0; // 0x0
+ field public static final int PHOTOMETRIC_INTERPRETATION_YCBCR = 6; // 0x6
+ field public static final int REDUCED_RESOLUTION_IMAGE = 1; // 0x1
+ field public static final short RENDERED_PROCESS_CUSTOM = 1; // 0x1
+ field public static final short RENDERED_PROCESS_NORMAL = 0; // 0x0
+ field public static final short RESOLUTION_UNIT_CENTIMETERS = 3; // 0x3
+ field public static final short RESOLUTION_UNIT_INCHES = 2; // 0x2
+ field public static final short SATURATION_HIGH = 0; // 0x0
+ field public static final short SATURATION_LOW = 0; // 0x0
+ field public static final short SATURATION_NORMAL = 0; // 0x0
+ field public static final short SCENE_CAPTURE_TYPE_LANDSCAPE = 1; // 0x1
+ field public static final short SCENE_CAPTURE_TYPE_NIGHT = 3; // 0x3
+ field public static final short SCENE_CAPTURE_TYPE_PORTRAIT = 2; // 0x2
+ field public static final short SCENE_CAPTURE_TYPE_STANDARD = 0; // 0x0
+ field public static final short SCENE_TYPE_DIRECTLY_PHOTOGRAPHED = 1; // 0x1
+ field public static final short SENSITIVITY_TYPE_ISO_SPEED = 3; // 0x3
+ field public static final short SENSITIVITY_TYPE_REI = 2; // 0x2
+ field public static final short SENSITIVITY_TYPE_REI_AND_ISO = 6; // 0x6
+ field public static final short SENSITIVITY_TYPE_SOS = 1; // 0x1
+ field public static final short SENSITIVITY_TYPE_SOS_AND_ISO = 5; // 0x5
+ field public static final short SENSITIVITY_TYPE_SOS_AND_REI = 4; // 0x4
+ field public static final short SENSITIVITY_TYPE_SOS_AND_REI_AND_ISO = 7; // 0x7
+ field public static final short SENSITIVITY_TYPE_UNKNOWN = 0; // 0x0
+ field public static final short SENSOR_TYPE_COLOR_SEQUENTIAL = 5; // 0x5
+ field public static final short SENSOR_TYPE_COLOR_SEQUENTIAL_LINEAR = 8; // 0x8
+ field public static final short SENSOR_TYPE_NOT_DEFINED = 1; // 0x1
+ field public static final short SENSOR_TYPE_ONE_CHIP = 2; // 0x2
+ field public static final short SENSOR_TYPE_THREE_CHIP = 4; // 0x4
+ field public static final short SENSOR_TYPE_TRILINEAR = 7; // 0x7
+ field public static final short SENSOR_TYPE_TWO_CHIP = 3; // 0x3
+ field public static final short SHARPNESS_HARD = 2; // 0x2
+ field public static final short SHARPNESS_NORMAL = 0; // 0x0
+ field public static final short SHARPNESS_SOFT = 1; // 0x1
+ field public static final short SUBJECT_DISTANCE_RANGE_CLOSE_VIEW = 2; // 0x2
+ field public static final short SUBJECT_DISTANCE_RANGE_DISTANT_VIEW = 3; // 0x3
+ field public static final short SUBJECT_DISTANCE_RANGE_MACRO = 1; // 0x1
+ field public static final short SUBJECT_DISTANCE_RANGE_UNKNOWN = 0; // 0x0
+ field public static final String TAG_APERTURE_VALUE = "ApertureValue";
+ field public static final String TAG_ARTIST = "Artist";
+ field public static final String TAG_BITS_PER_SAMPLE = "BitsPerSample";
+ field public static final String TAG_BODY_SERIAL_NUMBER = "BodySerialNumber";
+ field public static final String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
+ field @Deprecated public static final String TAG_CAMARA_OWNER_NAME = "CameraOwnerName";
+ field public static final String TAG_CAMERA_OWNER_NAME = "CameraOwnerName";
+ field public static final String TAG_CFA_PATTERN = "CFAPattern";
+ field public static final String TAG_COLOR_SPACE = "ColorSpace";
+ field public static final String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
+ field public static final String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
+ field public static final String TAG_COMPRESSION = "Compression";
+ field public static final String TAG_CONTRAST = "Contrast";
+ field public static final String TAG_COPYRIGHT = "Copyright";
+ field public static final String TAG_CUSTOM_RENDERED = "CustomRendered";
+ field public static final String TAG_DATETIME = "DateTime";
+ field public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+ field public static final String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
+ field public static final String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize";
+ field public static final String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
+ field public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
+ field public static final String TAG_DNG_VERSION = "DNGVersion";
+ field public static final String TAG_EXIF_VERSION = "ExifVersion";
+ field public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
+ field public static final String TAG_EXPOSURE_INDEX = "ExposureIndex";
+ field public static final String TAG_EXPOSURE_MODE = "ExposureMode";
+ field public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
+ field public static final String TAG_EXPOSURE_TIME = "ExposureTime";
+ field public static final String TAG_FILE_SOURCE = "FileSource";
+ field public static final String TAG_FLASH = "Flash";
+ field public static final String TAG_FLASHPIX_VERSION = "FlashpixVersion";
+ field public static final String TAG_FLASH_ENERGY = "FlashEnergy";
+ field public static final String TAG_FOCAL_LENGTH = "FocalLength";
+ field public static final String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
+ field public static final String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
+ field public static final String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution";
+ field public static final String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution";
+ field public static final String TAG_F_NUMBER = "FNumber";
+ field public static final String TAG_GAIN_CONTROL = "GainControl";
+ field public static final String TAG_GAMMA = "Gamma";
+ field public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
+ field public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
+ field public static final String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
+ field public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
+ field public static final String TAG_GPS_DEST_BEARING = "GPSDestBearing";
+ field public static final String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
+ field public static final String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
+ field public static final String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
+ field public static final String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
+ field public static final String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
+ field public static final String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
+ field public static final String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
+ field public static final String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
+ field public static final String TAG_GPS_DOP = "GPSDOP";
+ field public static final String TAG_GPS_H_POSITIONING_ERROR = "GPSHPositioningError";
+ field public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
+ field public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
+ field public static final String TAG_GPS_LATITUDE = "GPSLatitude";
+ field public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+ field public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+ field public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+ field public static final String TAG_GPS_MAP_DATUM = "GPSMapDatum";
+ field public static final String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
+ field public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
+ field public static final String TAG_GPS_SATELLITES = "GPSSatellites";
+ field public static final String TAG_GPS_SPEED = "GPSSpeed";
+ field public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef";
+ field public static final String TAG_GPS_STATUS = "GPSStatus";
+ field public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+ field public static final String TAG_GPS_TRACK = "GPSTrack";
+ field public static final String TAG_GPS_TRACK_REF = "GPSTrackRef";
+ field public static final String TAG_GPS_VERSION_ID = "GPSVersionID";
+ field public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription";
+ field public static final String TAG_IMAGE_LENGTH = "ImageLength";
+ field public static final String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
+ field public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+ field public static final String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
+ field public static final String TAG_ISO_SPEED = "ISOSpeed";
+ field public static final String TAG_ISO_SPEED_LATITUDE_YYY = "ISOSpeedLatitudeyyy";
+ field public static final String TAG_ISO_SPEED_LATITUDE_ZZZ = "ISOSpeedLatitudezzz";
+ field @Deprecated public static final String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings";
+ field public static final String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
+ field public static final String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
+ field public static final String TAG_LENS_MAKE = "LensMake";
+ field public static final String TAG_LENS_MODEL = "LensModel";
+ field public static final String TAG_LENS_SERIAL_NUMBER = "LensSerialNumber";
+ field public static final String TAG_LENS_SPECIFICATION = "LensSpecification";
+ field public static final String TAG_LIGHT_SOURCE = "LightSource";
+ field public static final String TAG_MAKE = "Make";
+ field public static final String TAG_MAKER_NOTE = "MakerNote";
+ field public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
+ field public static final String TAG_METERING_MODE = "MeteringMode";
+ field public static final String TAG_MODEL = "Model";
+ field public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
+ field public static final String TAG_OECF = "OECF";
+ field public static final String TAG_ORF_ASPECT_FRAME = "AspectFrame";
+ field public static final String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength";
+ field public static final String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart";
+ field public static final String TAG_ORF_THUMBNAIL_IMAGE = "ThumbnailImage";
+ field public static final String TAG_ORIENTATION = "Orientation";
+ field public static final String TAG_PHOTOGRAPHIC_SENSITIVITY = "PhotographicSensitivity";
+ field public static final String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+ field public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
+ field public static final String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
+ field public static final String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
+ field public static final String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
+ field public static final String TAG_RECOMMENDED_EXPOSURE_INDEX = "RecommendedExposureIndex";
+ field public static final String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
+ field public static final String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
+ field public static final String TAG_RESOLUTION_UNIT = "ResolutionUnit";
+ field public static final String TAG_ROWS_PER_STRIP = "RowsPerStrip";
+ field public static final String TAG_RW2_ISO = "ISO";
+ field public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw";
+ field public static final String TAG_RW2_SENSOR_BOTTOM_BORDER = "SensorBottomBorder";
+ field public static final String TAG_RW2_SENSOR_LEFT_BORDER = "SensorLeftBorder";
+ field public static final String TAG_RW2_SENSOR_RIGHT_BORDER = "SensorRightBorder";
+ field public static final String TAG_RW2_SENSOR_TOP_BORDER = "SensorTopBorder";
+ field public static final String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
+ field public static final String TAG_SATURATION = "Saturation";
+ field public static final String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
+ field public static final String TAG_SCENE_TYPE = "SceneType";
+ field public static final String TAG_SENSING_METHOD = "SensingMethod";
+ field public static final String TAG_SENSITIVITY_TYPE = "SensitivityType";
+ field public static final String TAG_SHARPNESS = "Sharpness";
+ field public static final String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
+ field public static final String TAG_SOFTWARE = "Software";
+ field public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
+ field public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
+ field public static final String TAG_STANDARD_OUTPUT_SENSITIVITY = "StandardOutputSensitivity";
+ field public static final String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
+ field public static final String TAG_STRIP_OFFSETS = "StripOffsets";
+ field public static final String TAG_SUBFILE_TYPE = "SubfileType";
+ field public static final String TAG_SUBJECT_AREA = "SubjectArea";
+ field public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
+ field public static final String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
+ field public static final String TAG_SUBJECT_LOCATION = "SubjectLocation";
+ field public static final String TAG_SUBSEC_TIME = "SubSecTime";
+ field public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized";
+ field public static final String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal";
+ field public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
+ field public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
+ field public static final String TAG_TRANSFER_FUNCTION = "TransferFunction";
+ field public static final String TAG_USER_COMMENT = "UserComment";
+ field public static final String TAG_WHITE_BALANCE = "WhiteBalance";
+ field public static final String TAG_WHITE_POINT = "WhitePoint";
+ field public static final String TAG_XMP = "Xmp";
+ field public static final String TAG_X_RESOLUTION = "XResolution";
+ field public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
+ field public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
+ field public static final String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
+ field public static final String TAG_Y_RESOLUTION = "YResolution";
+ field @Deprecated public static final int WHITEBALANCE_AUTO = 0; // 0x0
+ field @Deprecated public static final int WHITEBALANCE_MANUAL = 1; // 0x1
+ field public static final short WHITE_BALANCE_AUTO = 0; // 0x0
+ field public static final short WHITE_BALANCE_MANUAL = 1; // 0x1
+ field public static final short Y_CB_CR_POSITIONING_CENTERED = 1; // 0x1
+ field public static final short Y_CB_CR_POSITIONING_CO_SITED = 2; // 0x2
+ }
+
+}
+
diff --git a/exifinterface/api/current.txt b/exifinterface/api/current.txt
index 75772d7..87a26c7 100644
--- a/exifinterface/api/current.txt
+++ b/exifinterface/api/current.txt
@@ -10,8 +10,10 @@
method public void flipVertically();
method public double getAltitude(double);
method public String? getAttribute(String);
+ method public byte[]? getAttributeBytes(String);
method public double getAttributeDouble(String, double);
method public int getAttributeInt(String, int);
+ method public long[]? getAttributeRange(String);
method @Deprecated public boolean getLatLong(float[]!);
method public double[]? getLatLong();
method public int getRotationDegrees();
@@ -19,6 +21,7 @@
method public android.graphics.Bitmap? getThumbnailBitmap();
method public byte[]? getThumbnailBytes();
method public long[]? getThumbnailRange();
+ method public boolean hasAttribute(String);
method public boolean hasThumbnail();
method public boolean isFlipped();
method public boolean isThumbnailCompressed();
@@ -319,6 +322,7 @@
field public static final String TAG_USER_COMMENT = "UserComment";
field public static final String TAG_WHITE_BALANCE = "WhiteBalance";
field public static final String TAG_WHITE_POINT = "WhitePoint";
+ field public static final String TAG_XMP = "Xmp";
field public static final String TAG_X_RESOLUTION = "XResolution";
field public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
field public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
diff --git a/exifinterface/api/res-1.1.0-alpha02.txt b/exifinterface/api/res-1.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/exifinterface/api/res-1.1.0-alpha02.txt
diff --git a/exifinterface/api/restricted_1.1.0-alpha02.txt b/exifinterface/api/restricted_1.1.0-alpha02.txt
new file mode 100644
index 0000000..1cdbaf4
--- /dev/null
+++ b/exifinterface/api/restricted_1.1.0-alpha02.txt
@@ -0,0 +1,9 @@
+// Signature format: 3.0
+package androidx.exifinterface.media {
+
+ public class ExifInterface {
+ }
+
+
+}
+
diff --git a/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 906eebd..1031526 100644
--- a/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -19,8 +19,10 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.content.res.TypedArray;
@@ -54,6 +56,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@@ -69,11 +72,13 @@
private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
- private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
+ private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800_dng.dng";
+ private static final String LG_G4_ISO_800_JPG = "lg_g4_iso_800_jpg.jpg";
private static final int[] IMAGE_RESOURCES = new int[] {
- R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800};
+ R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800_dng,
+ R.raw.lg_g4_iso_800_jpg};
private static final String[] IMAGE_FILENAMES = new String[] {
- EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG};
+ EXIF_BYTE_ORDER_II_JPEG, EXIF_BYTE_ORDER_MM_JPEG, LG_G4_ISO_800_DNG, LG_G4_ISO_800_JPG};
private static final int USER_READ_WRITE = 0600;
private static final String TEST_TEMP_FILE_NAME = "testImage";
@@ -228,10 +233,15 @@
public final boolean hasThumbnail;
public final int thumbnailWidth;
public final int thumbnailHeight;
+ public final boolean isThumbnailCompressed;
+ public final int thumbnailOffset;
+ public final int thumbnailLength;
// GPS information.
public final boolean hasLatLong;
public final float latitude;
+ public final int latitudeOffset;
+ public final int latitudeLength;
public final float longitude;
public final float altitude;
@@ -258,6 +268,11 @@
public final int orientation;
public final int whiteBalance;
+ // XMP information.
+ public final boolean hasXmp;
+ public final int xmpOffset;
+ public final int xmpLength;
+
private static String getString(TypedArray typedArray, int index) {
String stringValue = typedArray.getString(index);
if (stringValue == null || stringValue.equals("")) {
@@ -267,39 +282,51 @@
}
ExpectedValue(TypedArray typedArray) {
+ int index = 0;
+
// Reads thumbnail information.
- hasThumbnail = typedArray.getBoolean(0, false);
- thumbnailWidth = typedArray.getInt(1, 0);
- thumbnailHeight = typedArray.getInt(2, 0);
+ hasThumbnail = typedArray.getBoolean(index++, false);
+ thumbnailOffset = typedArray.getInt(index++, -1);
+ thumbnailLength = typedArray.getInt(index++, -1);
+ thumbnailWidth = typedArray.getInt(index++, 0);
+ thumbnailHeight = typedArray.getInt(index++, 0);
+ isThumbnailCompressed = typedArray.getBoolean(index++, false);
// Reads GPS information.
- hasLatLong = typedArray.getBoolean(3, false);
- latitude = typedArray.getFloat(4, 0f);
- longitude = typedArray.getFloat(5, 0f);
- altitude = typedArray.getFloat(6, 0f);
+ hasLatLong = typedArray.getBoolean(index++, false);
+ latitudeOffset = typedArray.getInt(index++, -1);
+ latitudeLength = typedArray.getInt(index++, -1);
+ latitude = typedArray.getFloat(index++, 0f);
+ longitude = typedArray.getFloat(index++, 0f);
+ altitude = typedArray.getFloat(index++, 0f);
// Reads values.
- make = getString(typedArray, 7);
- model = getString(typedArray, 8);
- aperture = typedArray.getFloat(9, 0f);
- dateTimeOriginal = getString(typedArray, 10);
- exposureTime = typedArray.getFloat(11, 0f);
- flash = typedArray.getFloat(12, 0f);
- focalLength = getString(typedArray, 13);
- gpsAltitude = getString(typedArray, 14);
- gpsAltitudeRef = getString(typedArray, 15);
- gpsDatestamp = getString(typedArray, 16);
- gpsLatitude = getString(typedArray, 17);
- gpsLatitudeRef = getString(typedArray, 18);
- gpsLongitude = getString(typedArray, 19);
- gpsLongitudeRef = getString(typedArray, 20);
- gpsProcessingMethod = getString(typedArray, 21);
- gpsTimestamp = getString(typedArray, 22);
- imageLength = typedArray.getInt(23, 0);
- imageWidth = typedArray.getInt(24, 0);
- iso = getString(typedArray, 25);
- orientation = typedArray.getInt(26, 0);
- whiteBalance = typedArray.getInt(27, 0);
+ make = getString(typedArray, index++);
+ model = getString(typedArray, index++);
+ aperture = typedArray.getFloat(index++, 0f);
+ dateTimeOriginal = getString(typedArray, index++);
+ exposureTime = typedArray.getFloat(index++, 0f);
+ flash = typedArray.getFloat(index++, 0f);
+ focalLength = getString(typedArray, index++);
+ gpsAltitude = getString(typedArray, index++);
+ gpsAltitudeRef = getString(typedArray, index++);
+ gpsDatestamp = getString(typedArray, index++);
+ gpsLatitude = getString(typedArray, index++);
+ gpsLatitudeRef = getString(typedArray, index++);
+ gpsLongitude = getString(typedArray, index++);
+ gpsLongitudeRef = getString(typedArray, index++);
+ gpsProcessingMethod = getString(typedArray, index++);
+ gpsTimestamp = getString(typedArray, index++);
+ imageLength = typedArray.getInt(index++, 0);
+ imageWidth = typedArray.getInt(index++, 0);
+ iso = getString(typedArray, index++);
+ orientation = typedArray.getInt(index++, 0);
+ whiteBalance = typedArray.getInt(index++, 0);
+
+ // Reads XMP information.
+ hasXmp = typedArray.getBoolean(index++, false);
+ xmpOffset = typedArray.getInt(index++, 0);
+ xmpLength = typedArray.getInt(index++, 0);
typedArray.recycle();
}
@@ -359,6 +386,12 @@
@Test
@LargeTest
+ public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
+ testExifInterfaceForJpeg(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
+ }
+
+ @Test
+ @LargeTest
public void testDoNotFailOnCorruptedImage() throws Throwable {
// ExifInterface shouldn't raise any exceptions except an IOException when unable to open
// a file, even with a corrupted image. Generates randomly corrupted image stream for
@@ -719,20 +752,29 @@
}
private void compareWithExpectedValue(ExifInterface exifInterface,
- ExpectedValue expectedValue, String verboseTag) {
+ ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
if (VERBOSE) {
printExifTagsAndValues(verboseTag, exifInterface);
}
// Checks a thumbnail image.
assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
if (expectedValue.hasThumbnail) {
+ assertNotNull(exifInterface.getThumbnailRange());
+ if (assertRanges) {
+ final long[] thumbnailRange = exifInterface.getThumbnailRange();
+ assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
+ assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
+ }
byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
assertNotNull(thumbnailBytes);
Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
assertNotNull(thumbnailBitmap);
assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+ assertEquals(expectedValue.isThumbnailCompressed,
+ exifInterface.isThumbnailCompressed());
} else {
+ assertNull(exifInterface.getThumbnailRange());
assertNull(exifInterface.getThumbnail());
}
@@ -740,8 +782,21 @@
double[] latLong = exifInterface.getLatLong();
assertEquals(expectedValue.hasLatLong, latLong != null);
if (expectedValue.hasLatLong) {
+ assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
+ if (assertRanges) {
+ final long[] latitudeRange = exifInterface
+ .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
+ assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
+ assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
+ }
assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
+ assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+ assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+ } else {
+ assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
+ assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+ assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
}
assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
@@ -773,6 +828,27 @@
expectedValue.iso);
assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
+
+ if (expectedValue.hasXmp) {
+ assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
+ if (assertRanges) {
+ final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP);
+ assertEquals(expectedValue.xmpOffset, xmpRange[0]);
+ assertEquals(expectedValue.xmpLength, xmpRange[1]);
+ }
+ final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP),
+ Charset.forName("UTF-8"));
+ // We're only interested in confirming that we were able to extract
+ // valid XMP data, which must always include this XML tag; a full
+ // XMP parser is beyond the scope of ExifInterface. See XMP
+ // Specification Part 1, Section C.2.2 for additional details.
+ if (!xmp.contains("<rdf:RDF")) {
+ fail("Invalid XMP: " + xmp);
+ }
+ } else {
+ assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
+ }
+
}
private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
@@ -783,20 +859,20 @@
// Creates via file.
ExifInterface exifInterface = new ExifInterface(imageFile);
assertNotNull(exifInterface);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
// Creates via path.
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
assertNotNull(exifInterface);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
InputStream in = null;
// Creates via InputStream.
try {
in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
exifInterface = new ExifInterface(in);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
} finally {
closeQuietly(in);
}
@@ -808,7 +884,7 @@
fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY,
OsConstants.S_IRWXU);
exifInterface = new ExifInterface(fd);
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
} catch (Exception e) {
throw new IOException("Failed to open file descriptor", e);
} finally {
@@ -825,7 +901,7 @@
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
// Test for modifying one attribute.
String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
@@ -837,7 +913,7 @@
exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
exifInterface.saveAttributes();
exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- compareWithExpectedValue(exifInterface, expectedValue, verboseTag);
+ compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
}
private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
diff --git a/exifinterface/src/androidTest/res/raw/lg_g4_iso_800.dng b/exifinterface/src/androidTest/res/raw/lg_g4_iso_800_dng.dng
similarity index 100%
rename from exifinterface/src/androidTest/res/raw/lg_g4_iso_800.dng
rename to exifinterface/src/androidTest/res/raw/lg_g4_iso_800_dng.dng
Binary files differ
diff --git a/exifinterface/src/androidTest/res/raw/lg_g4_iso_800_jpg.jpg b/exifinterface/src/androidTest/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 0000000..d264196
--- /dev/null
+++ b/exifinterface/src/androidTest/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/exifinterface/src/androidTest/res/values/arrays.xml b/exifinterface/src/androidTest/res/values/arrays.xml
index d7b645a..9b7c604 100644
--- a/exifinterface/src/androidTest/res/values/arrays.xml
+++ b/exifinterface/src/androidTest/res/values/arrays.xml
@@ -17,9 +17,14 @@
<resources>
<array name="exifbyteorderii_jpg">
<item>true</item>
+ <item>3500</item>
+ <item>6265</item>
<item>512</item>
<item>288</item>
+ <item>true</item>
<item>false</item>
+ <item>0</item>
+ <item>0</item>
<item>0.0</item>
<item>0.0</item>
<item>0.0</item>
@@ -44,12 +49,20 @@
<item>50</item>
<item>6</item>
<item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
</array>
<array name="exifbyteordermm_jpg">
<item>false</item>
<item>0</item>
<item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>false</item>
<item>true</item>
+ <item>572</item>
+ <item>24</item>
<item>0.0</item>
<item>0.0</item>
<item>0.0</item>
@@ -74,12 +87,20 @@
<item>146</item>
<item>0</item>
<item>0</item>
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
</array>
<array name="lg_g4_iso_800_dng">
<item>true</item>
+ <item>0</item>
+ <item>15179</item>
<item>256</item>
<item>144</item>
<item>true</item>
+ <item>true</item>
+ <item>12486</item>
+ <item>24</item>
<item>53.834507</item>
<item>10.69585</item>
<item>0.0</item>
@@ -104,5 +125,46 @@
<item>800</item>
<item>1</item>
<item>0</item>
+ <item>true</item>
+ <item>826</item>
+ <item>10067</item>
+ </array>
+ <array name="lg_g4_iso_800_jpg">
+ <item>false</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>0</item>
+ <item>false</item>
+ <item>true</item>
+ <item>1662</item>
+ <item>24</item>
+ <item>53.834507</item>
+ <item>10.69585</item>
+ <item>0.0</item>
+ <item>LGE</item>
+ <item>LG-H815</item>
+ <item>1.800</item>
+ <item>2015:11:12 16:46:18</item>
+ <item>0.0040</item>
+ <item>0.0</item>
+ <item>442/100</item>
+ <item />
+ <item />
+ <item>1970:01:17</item>
+ <item>53/1,50/1,423/100</item>
+ <item>N</item>
+ <item>10/1,41/1,4506/100</item>
+ <item>E</item>
+ <item />
+ <item>18:08:10</item>
+ <item>337</item>
+ <item>600</item>
+ <item>800</item>
+ <item>1</item>
+ <item>0</item>
+ <item>true</item>
+ <item>1809</item>
+ <item>13197</item>
</array>
</resources>
\ No newline at end of file
diff --git a/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index c8bdf798..2d540d5 100644
--- a/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -2102,6 +2102,12 @@
* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html
*/
public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw";
+ /**
+ * Type is byte[]. See <a href=
+ * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform">Extensible
+ * Metadata Platform (XMP)</a> for details on contents.
+ */
+ public static final String TAG_XMP = "Xmp";
/** Type is int. See JEITA CP-3451C Spec Section 3: Bilevel Images. */
public static final String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
/** Type is int. See JEITA CP-3451C Spec Section 3: Bilevel Images. */
@@ -2962,14 +2968,23 @@
// A class for indicating EXIF attribute.
private static class ExifAttribute {
+ public static final long BYTES_OFFSET_UNKNOWN = -1;
+
public final int format;
public final int numberOfComponents;
+ public final long bytesOffset;
public final byte[] bytes;
@SuppressWarnings("WeakerAccess") /* synthetic access */
ExifAttribute(int format, int numberOfComponents, byte[] bytes) {
+ this(format, numberOfComponents, BYTES_OFFSET_UNKNOWN, bytes);
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ ExifAttribute(int format, int numberOfComponents, long bytesOffset, byte[] bytes) {
this.format = format;
this.numberOfComponents = numberOfComponents;
+ this.bytesOffset = bytesOffset;
this.bytes = bytes;
}
@@ -3415,7 +3430,8 @@
new ExifTag(TAG_RW2_SENSOR_BOTTOM_BORDER, 6, IFD_FORMAT_ULONG),
new ExifTag(TAG_RW2_SENSOR_RIGHT_BORDER, 7, IFD_FORMAT_ULONG),
new ExifTag(TAG_RW2_ISO, 23, IFD_FORMAT_USHORT),
- new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED)
+ new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED),
+ new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE),
};
// Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels)
@@ -3652,6 +3668,9 @@
static final Charset ASCII = Charset.forName("US-ASCII");
// Identifier for EXIF APP1 segment in JPEG
static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII);
+ // Identifier for XMP APP1 segment in JPEG
+ private static final byte[] IDENTIFIER_XMP_APP1 =
+ "http://ns.adobe.com/xap/1.0/\0".getBytes(ASCII);
// JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with
// the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start
// of frame(baseline DCT) and the image size info exists in its beginning part.
@@ -3733,6 +3752,7 @@
private int mOrfThumbnailLength;
private int mRw2JpgFromRawOffset;
private boolean mIsSupportedFile;
+ private boolean mModified;
// Pattern to check non zero timestamp
private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
@@ -3754,6 +3774,9 @@
* Reads Exif tags from the specified image file.
*/
public ExifInterface(@NonNull String filename) throws IOException {
+ if (filename == null) {
+ throw new NullPointerException("filename cannot be null");
+ }
initForFilename(filename);
}
@@ -3823,6 +3846,9 @@
*/
@Nullable
private ExifAttribute getExifAttribute(@NonNull String tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
if (DEBUG) {
Log.d(TAG, "getExifAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -3849,6 +3875,9 @@
*/
@Nullable
public String getAttribute(@NonNull String tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
ExifAttribute attribute = getExifAttribute(tag);
if (attribute != null) {
if (!sTagSetForCompatibility.contains(tag)) {
@@ -3889,6 +3918,9 @@
* @param defaultValue the value to return if the tag is not available.
*/
public int getAttributeInt(@NonNull String tag, int defaultValue) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
ExifAttribute exifAttribute = getExifAttribute(tag);
if (exifAttribute == null) {
return defaultValue;
@@ -3910,6 +3942,9 @@
* @param defaultValue the value to return if the tag is not available.
*/
public double getAttributeDouble(@NonNull String tag, double defaultValue) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
ExifAttribute exifAttribute = getExifAttribute(tag);
if (exifAttribute == null) {
return defaultValue;
@@ -3929,6 +3964,9 @@
* @param value the value of the tag.
*/
public void setAttribute(@NonNull String tag, @Nullable String value) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
if (TAG_ISO_SPEED_RATINGS.equals(tag)) {
if (DEBUG) {
Log.d(TAG, "setAttribute: Replacing TAG_ISO_SPEED_RATINGS with "
@@ -4269,6 +4307,9 @@
* determine whether the image data format is JPEG or not.
*/
private void loadAttributes(@NonNull InputStream in) throws IOException {
+ if (in == null) {
+ throw new NullPointerException("inputstream shouldn't be null");
+ }
try {
// Initialize mAttributes.
for (int i = 0; i < EXIF_TAGS.length; ++i) {
@@ -4370,6 +4411,11 @@
* and make a single call rather than multiple calls for each attribute.
* <p>
* This method is only supported for JPEG files.
+ * <p class="note">
+ * Note: after calling this method, any attempts to obtain range information
+ * from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
+ * will throw {@link IllegalStateException}, since the offsets may have
+ * changed in the newly written file.
* </p>
*/
public void saveAttributes() throws IOException {
@@ -4381,6 +4427,10 @@
"ExifInterface does not support saving attributes for the current input.");
}
+ // Remember the fact that we've changed the file on disk from what was
+ // originally parsed, meaning we can't answer range questions
+ mModified = true;
+
// Keep the thumbnail in memory
mThumbnailBytes = getThumbnail();
@@ -4441,6 +4491,15 @@
}
/**
+ * Returns true if the image file has the given attribute defined.
+ *
+ * @param tag the name of the tag.
+ */
+ public boolean hasAttribute(@NonNull String tag) {
+ return getExifAttribute(tag) != null;
+ }
+
+ /**
* Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no
* JPEG compressed thumbnail.
* The returned data can be decoded using
@@ -4548,7 +4607,13 @@
* not exist or thumbnail image is uncompressed.
*/
public boolean isThumbnailCompressed() {
- return mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED;
+ if (!mHasThumbnail) {
+ return false;
+ }
+ if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) {
+ return true;
+ }
+ return false;
}
/**
@@ -4557,18 +4622,70 @@
*
* @return two-element array, the offset in the first value, and length in
* the second, or {@code null} if no thumbnail was found.
+ * @throws IllegalStateException if {@link #saveAttributes()} has been
+ * called since the underlying file was initially parsed, since
+ * that means offsets may have changed.
*/
@Nullable
public long[] getThumbnailRange() {
- if (!mHasThumbnail) {
- return null;
+ if (mModified) {
+ throw new IllegalStateException(
+ "The underlying file has been modified since being parsed");
}
- long[] range = new long[2];
- range[0] = mThumbnailOffset;
- range[1] = mThumbnailLength;
+ if (mHasThumbnail) {
+ return new long[] { mThumbnailOffset, mThumbnailLength };
+ } else {
+ return null;
+ }
+ }
- return range;
+ /**
+ * Returns the offset and length of the requested tag inside the image file,
+ * or {@code null} if the tag is not contained.
+ *
+ * @return two-element array, the offset in the first value, and length in
+ * the second, or {@code null} if no tag was found.
+ * @throws IllegalStateException if {@link #saveAttributes()} has been
+ * called since the underlying file was initially parsed, since
+ * that means offsets may have changed.
+ */
+ @Nullable
+ public long[] getAttributeRange(@NonNull String tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
+ if (mModified) {
+ throw new IllegalStateException(
+ "The underlying file has been modified since being parsed");
+ }
+
+ final ExifAttribute attribute = getExifAttribute(tag);
+ if (attribute != null) {
+ return new long[] { attribute.bytesOffset, attribute.bytes.length };
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the raw bytes for the value of the requested tag inside the image
+ * file, or {@code null} if the tag is not contained.
+ *
+ * @return raw bytes for the value of the requested tag, or {@code null} if
+ * no tag was found.
+ */
+ @Nullable
+ public byte[] getAttributeBytes(@NonNull String tag) {
+ if (tag == null) {
+ throw new NullPointerException("tag shouldn't be null");
+ }
+ final ExifAttribute attribute = getExifAttribute(tag);
+ if (attribute != null) {
+ return attribute.bytes;
+ } else {
+ return null;
+ }
}
/**
@@ -4702,13 +4819,45 @@
}
/**
- * Returns number of milliseconds since Jan. 1, 1970, midnight local time.
- * Returns -1 if the date time information if not available.
+ * Returns parsed {@link ExifInterface#TAG_DATETIME} value as number of milliseconds since
+ * Jan. 1, 1970, midnight local time.
+ * Returns -1 if date time information is unavailable or invalid.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public long getDateTime() {
- String dateTimeString = getAttribute(TAG_DATETIME);
+ return parseDateTime(getAttribute(TAG_DATETIME),
+ getAttribute(TAG_SUBSEC_TIME));
+ }
+
+ /**
+ * Returns parsed {@link ExifInterface#TAG_DATETIME_DIGITIZED} value as number of
+ * milliseconds since Jan. 1, 1970, midnight local time.
+ * Returns -1 if digitized date time information is unavailable or invalid.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public long getDateTimeDigitized() {
+ return parseDateTime(getAttribute(TAG_DATETIME_DIGITIZED),
+ getAttribute(TAG_SUBSEC_TIME_DIGITIZED));
+ }
+
+ /**
+ * Returns parsed {@link ExifInterface#TAG_DATETIME_ORIGINAL} value as number of
+ * milliseconds since Jan. 1, 1970, midnight local time.
+ * Returns -1 if original date time information is unavailable or invalid.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public long getDateTimeOriginal() {
+ return parseDateTime(getAttribute(TAG_DATETIME_ORIGINAL),
+ getAttribute(TAG_SUBSEC_TIME_ORIGINAL));
+ }
+
+ private static long parseDateTime(@Nullable String dateTimeString, @Nullable String subSecs) {
if (dateTimeString == null
|| !sNonZeroTimePattern.matcher(dateTimeString).matches()) return -1;
@@ -4720,7 +4869,6 @@
if (datetime == null) return -1;
long msecs = datetime.getTime();
- String subSecs = getAttribute(TAG_SUBSEC_TIME);
if (subSecs != null) {
try {
long sub = Long.parseLong(subSecs);
@@ -5045,41 +5193,32 @@
}
switch (marker) {
case MARKER_APP1: {
- if (DEBUG) {
- Log.d(TAG, "MARKER_APP1");
- }
- if (length < 6) {
- // Skip if it's not an EXIF APP1 segment.
- break;
- }
- byte[] identifier = new byte[6];
- if (in.read(identifier) != 6) {
- throw new IOException("Invalid exif");
- }
- bytesRead += 6;
- length -= 6;
- if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
- // Skip if it's not an EXIF APP1 segment.
- break;
- }
- if (length <= 0) {
- throw new IOException("Invalid exif");
- }
- if (DEBUG) {
- Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")");
- }
- // Save offset values for createJpegThumbnailBitmap() function
- mExifOffset = bytesRead;
-
- byte[] bytes = new byte[length];
- if (in.read(bytes) != length) {
- throw new IOException("Invalid exif");
- }
+ final int start = bytesRead;
+ final byte[] bytes = new byte[length];
+ in.readFully(bytes);
bytesRead += length;
length = 0;
- readExifSegment(bytes, imageType);
- break;
+ if (startsWith(bytes, IDENTIFIER_EXIF_APP1)) {
+ final long offset = start + IDENTIFIER_EXIF_APP1.length;
+ final byte[] value = Arrays.copyOfRange(bytes, IDENTIFIER_EXIF_APP1.length,
+ bytes.length);
+
+ readExifSegment(value, imageType);
+
+ // Save offset values for createJpegThumbnailBitmap() function
+ mExifOffset = (int) offset;
+ } else if (startsWith(bytes, IDENTIFIER_XMP_APP1)) {
+ // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6
+ final long offset = start + IDENTIFIER_XMP_APP1.length;
+ final byte[] value = Arrays.copyOfRange(bytes,
+ IDENTIFIER_XMP_APP1.length, bytes.length);
+
+ if (getAttribute(TAG_XMP) == null) {
+ mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute(
+ IFD_FORMAT_BYTE, value.length, offset, value));
+ }
+ }
}
case MARKER_COM: {
@@ -5856,9 +5995,11 @@
continue;
}
- byte[] bytes = new byte[(int) byteCount];
+ final int bytesOffset = dataInputStream.peek();
+ final byte[] bytes = new byte[(int) byteCount];
dataInputStream.readFully(bytes);
- ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents, bytes);
+ ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents,
+ bytesOffset, bytes);
mAttributes[ifdType].put(tag.name, attribute);
// DNG files have a DNG Version tag specifying the version of specifications that the
@@ -6887,4 +7028,19 @@
}
return null;
}
+
+ private static boolean startsWith(byte[] cur, byte[] val) {
+ if (cur == null || val == null) {
+ return false;
+ }
+ if (cur.length < val.length) {
+ return false;
+ }
+ for (int i = 0; i < val.length; i++) {
+ if (cur[i] != val[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/fragment/api/1.1.0-alpha06.txt b/fragment/api/1.1.0-alpha06.txt
new file mode 100644
index 0000000..a5c77ae
--- /dev/null
+++ b/fragment/api/1.1.0-alpha06.txt
@@ -0,0 +1,403 @@
+// Signature format: 3.0
+package androidx.fragment.app {
+
+ public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ ctor public DialogFragment();
+ method public void dismiss();
+ method public void dismissAllowingStateLoss();
+ method public android.app.Dialog? getDialog();
+ method public boolean getShowsDialog();
+ method @StyleRes public int getTheme();
+ method public boolean isCancelable();
+ method public void onCancel(android.content.DialogInterface);
+ method public android.app.Dialog onCreateDialog(android.os.Bundle?);
+ method public void onDismiss(android.content.DialogInterface);
+ method public final android.app.Dialog requireDialog();
+ method public void setCancelable(boolean);
+ method public void setShowsDialog(boolean);
+ method public void setStyle(int, @StyleRes int);
+ method public void show(androidx.fragment.app.FragmentManager, String?);
+ method public int show(androidx.fragment.app.FragmentTransaction, String?);
+ method public void showNow(androidx.fragment.app.FragmentManager, String?);
+ field public static final int STYLE_NORMAL = 0; // 0x0
+ field public static final int STYLE_NO_FRAME = 2; // 0x2
+ field public static final int STYLE_NO_INPUT = 3; // 0x3
+ field public static final int STYLE_NO_TITLE = 1; // 0x1
+ }
+
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ ctor public Fragment();
+ ctor @ContentView public Fragment(@LayoutRes int);
+ method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?);
+ method public final boolean equals(Object?);
+ method public final androidx.fragment.app.FragmentActivity? getActivity();
+ method public boolean getAllowEnterTransitionOverlap();
+ method public boolean getAllowReturnTransitionOverlap();
+ method public final android.os.Bundle? getArguments();
+ method public final androidx.fragment.app.FragmentManager getChildFragmentManager();
+ method public android.content.Context? getContext();
+ method public Object? getEnterTransition();
+ method public Object? getExitTransition();
+ method public final androidx.fragment.app.FragmentManager? getFragmentManager();
+ method public final Object? getHost();
+ method public final int getId();
+ method public final android.view.LayoutInflater getLayoutInflater();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method @Deprecated public androidx.loader.app.LoaderManager getLoaderManager();
+ method public final androidx.fragment.app.Fragment? getParentFragment();
+ method public Object? getReenterTransition();
+ method public final android.content.res.Resources getResources();
+ method public final boolean getRetainInstance();
+ method public Object? getReturnTransition();
+ method public final androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method public Object? getSharedElementEnterTransition();
+ method public Object? getSharedElementReturnTransition();
+ method public final String getString(@StringRes int);
+ method public final String getString(@StringRes int, java.lang.Object...?);
+ method public final String? getTag();
+ method public final androidx.fragment.app.Fragment? getTargetFragment();
+ method public final int getTargetRequestCode();
+ method public final CharSequence getText(@StringRes int);
+ method public boolean getUserVisibleHint();
+ method public android.view.View? getView();
+ method @MainThread public androidx.lifecycle.LifecycleOwner getViewLifecycleOwner();
+ method public androidx.lifecycle.LiveData<androidx.lifecycle.LifecycleOwner> getViewLifecycleOwnerLiveData();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ method public final int hashCode();
+ method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String);
+ method @Deprecated public static androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+ method public final boolean isAdded();
+ method public final boolean isDetached();
+ method public final boolean isHidden();
+ method public final boolean isInLayout();
+ method public final boolean isRemoving();
+ method public final boolean isResumed();
+ method public final boolean isStateSaved();
+ method public final boolean isVisible();
+ method @CallSuper public void onActivityCreated(android.os.Bundle?);
+ method public void onActivityResult(int, int, android.content.Intent?);
+ method @CallSuper public void onAttach(android.content.Context);
+ method @Deprecated @CallSuper public void onAttach(android.app.Activity);
+ method public void onAttachFragment(androidx.fragment.app.Fragment);
+ method @CallSuper public void onConfigurationChanged(android.content.res.Configuration);
+ method public boolean onContextItemSelected(android.view.MenuItem);
+ method @CallSuper public void onCreate(android.os.Bundle?);
+ method public android.view.animation.Animation? onCreateAnimation(int, boolean, int);
+ method public android.animation.Animator? onCreateAnimator(int, boolean, int);
+ method public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo?);
+ method public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method public android.view.View? onCreateView(android.view.LayoutInflater, android.view.ViewGroup?, android.os.Bundle?);
+ method @CallSuper public void onDestroy();
+ method public void onDestroyOptionsMenu();
+ method @CallSuper public void onDestroyView();
+ method @CallSuper public void onDetach();
+ method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle?);
+ method public void onHiddenChanged(boolean);
+ method @CallSuper public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle?);
+ method @Deprecated @CallSuper public void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle?);
+ method @CallSuper public void onLowMemory();
+ method public void onMultiWindowModeChanged(boolean);
+ method public boolean onOptionsItemSelected(android.view.MenuItem);
+ method public void onOptionsMenuClosed(android.view.Menu);
+ method @CallSuper public void onPause();
+ method public void onPictureInPictureModeChanged(boolean);
+ method public void onPrepareOptionsMenu(android.view.Menu);
+ method public void onRequestPermissionsResult(int, String[], int[]);
+ method @CallSuper public void onResume();
+ method public void onSaveInstanceState(android.os.Bundle);
+ method @CallSuper public void onStart();
+ method @CallSuper public void onStop();
+ method public void onViewCreated(android.view.View, android.os.Bundle?);
+ method @CallSuper public void onViewStateRestored(android.os.Bundle?);
+ method public void postponeEnterTransition();
+ method public void registerForContextMenu(android.view.View);
+ method public final void requestPermissions(String[], int);
+ method public final androidx.fragment.app.FragmentActivity requireActivity();
+ method public final android.os.Bundle requireArguments();
+ method public final android.content.Context requireContext();
+ method public final androidx.fragment.app.FragmentManager requireFragmentManager();
+ method public final Object requireHost();
+ method public final androidx.fragment.app.Fragment requireParentFragment();
+ method public final android.view.View requireView();
+ method public void setAllowEnterTransitionOverlap(boolean);
+ method public void setAllowReturnTransitionOverlap(boolean);
+ method public void setArguments(android.os.Bundle?);
+ method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+ method public void setEnterTransition(Object?);
+ method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+ method public void setExitTransition(Object?);
+ method public void setHasOptionsMenu(boolean);
+ method public void setInitialSavedState(androidx.fragment.app.Fragment.SavedState?);
+ method public void setMenuVisibility(boolean);
+ method public void setReenterTransition(Object?);
+ method public void setRetainInstance(boolean);
+ method public void setReturnTransition(Object?);
+ method public void setSharedElementEnterTransition(Object?);
+ method public void setSharedElementReturnTransition(Object?);
+ method public void setTargetFragment(androidx.fragment.app.Fragment?, int);
+ method public void setUserVisibleHint(boolean);
+ method public boolean shouldShowRequestPermissionRationale(String);
+ method public void startActivity(android.content.Intent!);
+ method public void startActivity(android.content.Intent!, android.os.Bundle?);
+ method public void startActivityForResult(android.content.Intent!, int);
+ method public void startActivityForResult(android.content.Intent!, int, android.os.Bundle?);
+ method public void startIntentSenderForResult(android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public void startPostponedEnterTransition();
+ method public void unregisterForContextMenu(android.view.View);
+ }
+
+ public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+ ctor public Fragment.InstantiationException(String, Exception?);
+ }
+
+ public static class Fragment.SavedState implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<androidx.fragment.app.Fragment.SavedState> CREATOR;
+ }
+
+ public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
+ ctor public FragmentActivity();
+ ctor @ContentView public FragmentActivity(@LayoutRes int);
+ method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+ method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
+ method public void onAttachFragment(androidx.fragment.app.Fragment);
+ method @CallSuper public void onMultiWindowModeChanged(boolean);
+ method @CallSuper public void onPictureInPictureModeChanged(boolean);
+ method protected void onResumeFragments();
+ method public void setEnterSharedElementCallback(androidx.core.app.SharedElementCallback?);
+ method public void setExitSharedElementCallback(androidx.core.app.SharedElementCallback?);
+ method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+ method public void startActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+ method public void startIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public void supportFinishAfterTransition();
+ method @Deprecated public void supportInvalidateOptionsMenu();
+ method public void supportPostponeEnterTransition();
+ method public void supportStartPostponedEnterTransition();
+ method public final void validateRequestPermissionsRequestCode(int);
+ }
+
+ public abstract class FragmentContainer {
+ ctor public FragmentContainer();
+ method @Deprecated public androidx.fragment.app.Fragment instantiate(android.content.Context, String, android.os.Bundle?);
+ method public abstract android.view.View? onFindViewById(@IdRes int);
+ method public abstract boolean onHasView();
+ }
+
+ public class FragmentController {
+ method public void attachHost(androidx.fragment.app.Fragment?);
+ method public static androidx.fragment.app.FragmentController createController(androidx.fragment.app.FragmentHostCallback<?>);
+ method public void dispatchActivityCreated();
+ method public void dispatchConfigurationChanged(android.content.res.Configuration);
+ method public boolean dispatchContextItemSelected(android.view.MenuItem);
+ method public void dispatchCreate();
+ method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method public void dispatchDestroy();
+ method public void dispatchDestroyView();
+ method public void dispatchLowMemory();
+ method public void dispatchMultiWindowModeChanged(boolean);
+ method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+ method public void dispatchOptionsMenuClosed(android.view.Menu);
+ method public void dispatchPause();
+ method public void dispatchPictureInPictureModeChanged(boolean);
+ method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+ method @Deprecated public void dispatchReallyStop();
+ method public void dispatchResume();
+ method public void dispatchStart();
+ method public void dispatchStop();
+ method @Deprecated public void doLoaderDestroy();
+ method @Deprecated public void doLoaderRetain();
+ method @Deprecated public void doLoaderStart();
+ method @Deprecated public void doLoaderStop(boolean);
+ method @Deprecated public void dumpLoaders(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?);
+ method public boolean execPendingActions();
+ method public androidx.fragment.app.Fragment? findFragmentByWho(String);
+ method public java.util.List<androidx.fragment.app.Fragment> getActiveFragments(java.util.List<androidx.fragment.app.Fragment>!);
+ method public int getActiveFragmentsCount();
+ method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
+ method @Deprecated public androidx.loader.app.LoaderManager! getSupportLoaderManager();
+ method public void noteStateNotSaved();
+ method public android.view.View? onCreateView(android.view.View?, String, android.content.Context, android.util.AttributeSet);
+ method @Deprecated public void reportLoaderStart();
+ method @Deprecated public void restoreAllState(android.os.Parcelable?, java.util.List<androidx.fragment.app.Fragment>?);
+ method @Deprecated public void restoreAllState(android.os.Parcelable?, androidx.fragment.app.FragmentManagerNonConfig?);
+ method @Deprecated public void restoreLoaderNonConfig(androidx.collection.SimpleArrayMap<java.lang.String,androidx.loader.app.LoaderManager>!);
+ method public void restoreSaveState(android.os.Parcelable?);
+ method @Deprecated public androidx.collection.SimpleArrayMap<java.lang.String,androidx.loader.app.LoaderManager>? retainLoaderNonConfig();
+ method @Deprecated public androidx.fragment.app.FragmentManagerNonConfig? retainNestedNonConfig();
+ method @Deprecated public java.util.List<androidx.fragment.app.Fragment>? retainNonConfig();
+ method public android.os.Parcelable? saveAllState();
+ }
+
+ public class FragmentFactory {
+ ctor public FragmentFactory();
+ method @Deprecated public androidx.fragment.app.Fragment instantiate(ClassLoader, String, android.os.Bundle?);
+ method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
+ method public static Class<? extends androidx.fragment.app.Fragment> loadFragmentClass(ClassLoader, String);
+ }
+
+ public abstract class FragmentHostCallback<E> extends androidx.fragment.app.FragmentContainer {
+ ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
+ method public void onDump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?);
+ method public android.view.View? onFindViewById(int);
+ method public abstract E? onGetHost();
+ method public android.view.LayoutInflater onGetLayoutInflater();
+ method public int onGetWindowAnimations();
+ method public boolean onHasView();
+ method public boolean onHasWindowAnimations();
+ method public void onRequestPermissionsFromFragment(androidx.fragment.app.Fragment, String[], int);
+ method public boolean onShouldSaveFragmentState(androidx.fragment.app.Fragment);
+ method public boolean onShouldShowRequestPermissionRationale(String);
+ method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int);
+ method public void onStartActivityFromFragment(androidx.fragment.app.Fragment, android.content.Intent!, int, android.os.Bundle?);
+ method public void onStartIntentSenderFromFragment(androidx.fragment.app.Fragment, android.content.IntentSender!, int, android.content.Intent?, int, int, int, android.os.Bundle?) throws android.content.IntentSender.SendIntentException;
+ method public void onSupportInvalidateOptionsMenu();
+ }
+
+ public abstract class FragmentManager {
+ ctor public FragmentManager();
+ method public abstract void addOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+ method public abstract androidx.fragment.app.FragmentTransaction beginTransaction();
+ method public abstract void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?);
+ method public static void enableDebugLogging(boolean);
+ method public abstract boolean executePendingTransactions();
+ method public abstract androidx.fragment.app.Fragment? findFragmentById(@IdRes int);
+ method public abstract androidx.fragment.app.Fragment? findFragmentByTag(String?);
+ method public abstract androidx.fragment.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+ method public abstract int getBackStackEntryCount();
+ method public abstract androidx.fragment.app.Fragment? getFragment(android.os.Bundle, String);
+ method public androidx.fragment.app.FragmentFactory getFragmentFactory();
+ method public abstract java.util.List<androidx.fragment.app.Fragment> getFragments();
+ method public abstract androidx.fragment.app.Fragment? getPrimaryNavigationFragment();
+ method public abstract boolean isDestroyed();
+ method public abstract boolean isStateSaved();
+ method public abstract void popBackStack();
+ method public abstract void popBackStack(String?, int);
+ method public abstract void popBackStack(int, int);
+ method public abstract boolean popBackStackImmediate();
+ method public abstract boolean popBackStackImmediate(String?, int);
+ method public abstract boolean popBackStackImmediate(int, int);
+ method public abstract void putFragment(android.os.Bundle, String, androidx.fragment.app.Fragment);
+ method public abstract void registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, boolean);
+ method public abstract void removeOnBackStackChangedListener(androidx.fragment.app.FragmentManager.OnBackStackChangedListener);
+ method public abstract androidx.fragment.app.Fragment.SavedState? saveFragmentInstanceState(androidx.fragment.app.Fragment);
+ method public void setFragmentFactory(androidx.fragment.app.FragmentFactory);
+ method public abstract void unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks);
+ field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+ }
+
+ public static interface FragmentManager.BackStackEntry {
+ method public CharSequence? getBreadCrumbShortTitle();
+ method @StringRes public int getBreadCrumbShortTitleRes();
+ method public CharSequence? getBreadCrumbTitle();
+ method @StringRes public int getBreadCrumbTitleRes();
+ method public int getId();
+ method public String? getName();
+ }
+
+ public abstract static class FragmentManager.FragmentLifecycleCallbacks {
+ ctor public FragmentManager.FragmentLifecycleCallbacks();
+ method public void onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+ method public void onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+ method public void onFragmentCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+ method public void onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentDetached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentPaused(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentPreAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context);
+ method public void onFragmentPreCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?);
+ method public void onFragmentResumed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentSaveInstanceState(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle);
+ method public void onFragmentStarted(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentStopped(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ method public void onFragmentViewCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.view.View, android.os.Bundle?);
+ method public void onFragmentViewDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment);
+ }
+
+ public static interface FragmentManager.OnBackStackChangedListener {
+ method public void onBackStackChanged();
+ }
+
+ @Deprecated public class FragmentManagerNonConfig {
+ }
+
+ public abstract class FragmentPagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+ ctor public FragmentPagerAdapter(androidx.fragment.app.FragmentManager);
+ method public abstract androidx.fragment.app.Fragment getItem(int);
+ method public long getItemId(int);
+ method public boolean isViewFromObject(android.view.View, Object);
+ }
+
+ public abstract class FragmentStatePagerAdapter extends androidx.viewpager.widget.PagerAdapter {
+ ctor public FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager);
+ method public abstract androidx.fragment.app.Fragment getItem(int);
+ method public boolean isViewFromObject(android.view.View, Object);
+ }
+
+ @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+ ctor @Deprecated public FragmentTabHost(android.content.Context);
+ ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+ method @Deprecated public void onTabChanged(String?);
+ method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+ method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+ }
+
+ public abstract class FragmentTransaction {
+ ctor public FragmentTransaction();
+ method public abstract androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.Fragment, String?);
+ method public abstract androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment);
+ method public abstract androidx.fragment.app.FragmentTransaction add(@IdRes int, androidx.fragment.app.Fragment, String?);
+ method public abstract androidx.fragment.app.FragmentTransaction addSharedElement(android.view.View, String);
+ method public abstract androidx.fragment.app.FragmentTransaction addToBackStack(String?);
+ method public abstract androidx.fragment.app.FragmentTransaction attach(androidx.fragment.app.Fragment);
+ method public abstract int commit();
+ method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
+ method public abstract androidx.fragment.app.FragmentTransaction detach(androidx.fragment.app.Fragment);
+ method public abstract androidx.fragment.app.FragmentTransaction disallowAddToBackStack();
+ method public abstract androidx.fragment.app.FragmentTransaction hide(androidx.fragment.app.Fragment);
+ method public abstract boolean isAddToBackStackAllowed();
+ method public abstract boolean isEmpty();
+ method public abstract androidx.fragment.app.FragmentTransaction remove(androidx.fragment.app.Fragment);
+ method public abstract androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment);
+ method public abstract androidx.fragment.app.FragmentTransaction replace(@IdRes int, androidx.fragment.app.Fragment, String?);
+ method public abstract androidx.fragment.app.FragmentTransaction runOnCommit(Runnable);
+ method @Deprecated public abstract androidx.fragment.app.FragmentTransaction setAllowOptimization(boolean);
+ method public abstract androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(@StringRes int);
+ method public abstract androidx.fragment.app.FragmentTransaction setBreadCrumbShortTitle(CharSequence?);
+ method public abstract androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(@StringRes int);
+ method public abstract androidx.fragment.app.FragmentTransaction setBreadCrumbTitle(CharSequence?);
+ method public abstract androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
+ method public abstract androidx.fragment.app.FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int, @AnimatorRes @AnimRes int);
+ method public abstract androidx.fragment.app.FragmentTransaction setPrimaryNavigationFragment(androidx.fragment.app.Fragment?);
+ method public abstract androidx.fragment.app.FragmentTransaction setReorderingAllowed(boolean);
+ method public abstract androidx.fragment.app.FragmentTransaction setTransition(int);
+ method public abstract androidx.fragment.app.FragmentTransaction setTransitionStyle(@StyleRes int);
+ method public abstract androidx.fragment.app.FragmentTransaction show(androidx.fragment.app.Fragment);
+ field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+ field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+ field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+ field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+ field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+ field public static final int TRANSIT_NONE = 0; // 0x0
+ field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+ }
+
+ public class ListFragment extends androidx.fragment.app.Fragment {
+ ctor public ListFragment();
+ method public android.widget.ListAdapter? getListAdapter();
+ method public android.widget.ListView getListView();
+ method public long getSelectedItemId();
+ method public int getSelectedItemPosition();
+ method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+ method public final android.widget.ListAdapter requireListAdapter();
+ method public void setEmptyText(CharSequence?);
+ method public void setListAdapter(android.widget.ListAdapter?);
+ method public void setListShown(boolean);
+ method public void setListShownNoAnimation(boolean);
+ method public void setSelection(int);
+ }
+
+}
+
diff --git a/fragment/api/current.txt b/fragment/api/current.txt
index e586a2f..a5c77ae 100644
--- a/fragment/api/current.txt
+++ b/fragment/api/current.txt
@@ -27,6 +27,7 @@
public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
ctor public Fragment();
+ ctor @ContentView public Fragment(@LayoutRes int);
method public void dump(String, java.io.FileDescriptor?, java.io.PrintWriter, String[]?);
method public final boolean equals(Object?);
method public final androidx.fragment.app.FragmentActivity? getActivity();
@@ -157,9 +158,9 @@
public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
ctor public FragmentActivity();
+ ctor @ContentView public FragmentActivity(@LayoutRes int);
method public androidx.fragment.app.FragmentManager getSupportFragmentManager();
method @Deprecated public androidx.loader.app.LoaderManager getSupportLoaderManager();
- method public android.content.Context getThemedContext();
method public void onAttachFragment(androidx.fragment.app.Fragment);
method @CallSuper public void onMultiWindowModeChanged(boolean);
method @CallSuper public void onPictureInPictureModeChanged(boolean);
@@ -230,7 +231,8 @@
public class FragmentFactory {
ctor public FragmentFactory();
- method public androidx.fragment.app.Fragment instantiate(ClassLoader, String, android.os.Bundle?);
+ method @Deprecated public androidx.fragment.app.Fragment instantiate(ClassLoader, String, android.os.Bundle?);
+ method public androidx.fragment.app.Fragment instantiate(ClassLoader, String);
method public static Class<? extends androidx.fragment.app.Fragment> loadFragmentClass(ClassLoader, String);
}
@@ -331,14 +333,13 @@
method public boolean isViewFromObject(android.view.View, Object);
}
- public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
- ctor public FragmentTabHost(android.content.Context);
- ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
- method public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
- method public void onTabChanged(String?);
- method @Deprecated public void setup();
- method public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
- method public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
+ @Deprecated public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+ ctor @Deprecated public FragmentTabHost(android.content.Context);
+ ctor @Deprecated public FragmentTabHost(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public void addTab(android.widget.TabHost.TabSpec, Class<?>, android.os.Bundle?);
+ method @Deprecated public void onTabChanged(String?);
+ method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager);
+ method @Deprecated public void setup(android.content.Context, androidx.fragment.app.FragmentManager, int);
}
public abstract class FragmentTransaction {
diff --git a/fragment/api/res-1.1.0-alpha06.txt b/fragment/api/res-1.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/api/res-1.1.0-alpha06.txt
diff --git a/fragment/api/restricted_1.1.0-alpha06.txt b/fragment/api/restricted_1.1.0-alpha06.txt
new file mode 100644
index 0000000..59d618c
--- /dev/null
+++ b/fragment/api/restricted_1.1.0-alpha06.txt
@@ -0,0 +1,47 @@
+// Signature format: 3.0
+package androidx.fragment.app {
+
+ public class DialogFragment extends androidx.fragment.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setupDialog(android.app.Dialog, int);
+ }
+
+ public class Fragment implements android.content.ComponentCallbacks androidx.lifecycle.LifecycleOwner androidx.savedstate.SavedStateRegistryOwner android.view.View.OnCreateContextMenuListener androidx.lifecycle.ViewModelStoreOwner {
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.view.LayoutInflater getLayoutInflater(android.os.Bundle?);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean hasOptionsMenu();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final boolean isMenuVisible();
+ }
+
+ public class FragmentActivity extends androidx.activity.ComponentActivity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback androidx.core.app.ActivityCompat.RequestPermissionsRequestCodeValidator {
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected boolean onPrepareOptionsPanel(android.view.View?, android.view.Menu);
+ method public final void validateRequestPermissionsRequestCode(int);
+ }
+
+ public abstract class FragmentManager {
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.fragment.app.FragmentTransaction openTransaction();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class FragmentTransitionImpl {
+ ctor public FragmentTransitionImpl();
+ method public abstract void addTarget(Object!, android.view.View!);
+ method public abstract void addTargets(Object!, java.util.ArrayList<android.view.View>!);
+ method public abstract void beginDelayedTransition(android.view.ViewGroup!, Object!);
+ method protected static void bfsAddViewChildren(java.util.List<android.view.View>!, android.view.View!);
+ method public abstract boolean canHandle(Object!);
+ method public abstract Object! cloneTransition(Object!);
+ method protected void getBoundsOnScreen(android.view.View!, android.graphics.Rect!);
+ method protected static boolean isNullOrEmpty(java.util.List!);
+ method public abstract Object! mergeTransitionsInSequence(Object!, Object!, Object!);
+ method public abstract Object! mergeTransitionsTogether(Object!, Object!, Object!);
+ method public abstract void removeTarget(Object!, android.view.View!);
+ method public abstract void replaceTargets(Object!, java.util.ArrayList<android.view.View>!, java.util.ArrayList<android.view.View>!);
+ method public abstract void scheduleHideFragmentView(Object!, android.view.View!, java.util.ArrayList<android.view.View>!);
+ method public abstract void scheduleRemoveTargets(Object!, Object!, java.util.ArrayList<android.view.View>!, Object!, java.util.ArrayList<android.view.View>!, Object!, java.util.ArrayList<android.view.View>!);
+ method public abstract void setEpicenter(Object!, android.view.View!);
+ method public abstract void setEpicenter(Object!, android.graphics.Rect!);
+ method public abstract void setSharedElementTargets(Object!, android.view.View!, java.util.ArrayList<android.view.View>!);
+ method public abstract void swapSharedElementTargets(Object!, java.util.ArrayList<android.view.View>!, java.util.ArrayList<android.view.View>!);
+ method public abstract Object! wrapTransitionInSet(Object!);
+ }
+
+}
+
diff --git a/fragment/ktx/api/1.1.0-alpha06.txt b/fragment/ktx/api/1.1.0-alpha06.txt
new file mode 100644
index 0000000..03e8624
--- /dev/null
+++ b/fragment/ktx/api/1.1.0-alpha06.txt
@@ -0,0 +1,19 @@
+// Signature format: 3.0
+package androidx.fragment.app {
+
+ public final class FragmentManagerKt {
+ ctor public FragmentManagerKt();
+ method public static inline void commit(androidx.fragment.app.FragmentManager, boolean allowStateLoss = false, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+ method public static inline void commitNow(androidx.fragment.app.FragmentManager, boolean allowStateLoss = false, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+ method @Deprecated public static inline void transaction(androidx.fragment.app.FragmentManager, boolean now = false, boolean allowStateLoss = false, kotlin.jvm.functions.Function1<? super androidx.fragment.app.FragmentTransaction,kotlin.Unit> body);
+ }
+
+ public final class FragmentViewModelLazyKt {
+ ctor public FragmentViewModelLazyKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
+ method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>! ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
+ }
+
+}
+
diff --git a/fragment/ktx/api/current.txt b/fragment/ktx/api/current.txt
index 54ffec7..03e8624 100644
--- a/fragment/ktx/api/current.txt
+++ b/fragment/ktx/api/current.txt
@@ -11,7 +11,7 @@
public final class FragmentViewModelLazyKt {
ctor public FragmentViewModelLazyKt();
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! activityViewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
- method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
+ method @MainThread public static <VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM> createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer = null);
method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! viewModels(androidx.fragment.app.Fragment, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner>! ownerProducer = { this }, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
}
diff --git a/fragment/ktx/api/res-1.1.0-alpha06.txt b/fragment/ktx/api/res-1.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/ktx/api/res-1.1.0-alpha06.txt
diff --git a/fragment/ktx/api/restricted_1.1.0-alpha06.txt b/fragment/ktx/api/restricted_1.1.0-alpha06.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/fragment/ktx/api/restricted_1.1.0-alpha06.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/fragment/ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt b/fragment/ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
index ccc3033..3566fc2 100644
--- a/fragment/ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
+++ b/fragment/ktx/src/main/java/androidx/fragment/app/FragmentViewModelLazy.kt
@@ -22,6 +22,7 @@
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.ViewModelProvider.Factory
+import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import kotlin.reflect.KClass
@@ -55,7 +56,7 @@
inline fun <reified VM : ViewModel> Fragment.viewModels(
noinline ownerProducer: () -> ViewModelStoreOwner = { this },
noinline factoryProducer: (() -> Factory)? = null
-) = createViewModelLazy(VM::class, ownerProducer, factoryProducer)
+) = createViewModelLazy(VM::class, { ownerProducer().viewModelStore }, factoryProducer)
/**
* Returns a property delegate to access parent activity's [ViewModel],
@@ -74,7 +75,7 @@
@MainThread
inline fun <reified VM : ViewModel> Fragment.activityViewModels(
noinline factoryProducer: (() -> Factory)? = null
-) = createViewModelLazy(VM::class, ::requireActivity, factoryProducer)
+) = createViewModelLazy(VM::class, { requireActivity().viewModelStore }, factoryProducer)
/**
* Helper method for creation of [ViewModelLazy], that resolves `null` passed as [factoryProducer]
@@ -83,7 +84,7 @@
@MainThread
fun <VM : ViewModel> Fragment.createViewModelLazy(
viewModelClass: KClass<VM>,
- ownerProducer: () -> ViewModelStoreOwner,
+ storeProducer: () -> ViewModelStore,
factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
val factoryPromise = factoryProducer ?: {
@@ -92,5 +93,5 @@
)
AndroidViewModelFactory.getInstance(application)
}
- return ViewModelLazy(viewModelClass, ownerProducer, factoryPromise)
+ return ViewModelLazy(viewModelClass, storeProducer, factoryPromise)
}
\ No newline at end of file
diff --git a/fragment/src/androidTest/AndroidManifest.xml b/fragment/src/androidTest/AndroidManifest.xml
index fb640bf..30b60d1b 100644
--- a/fragment/src/androidTest/AndroidManifest.xml
+++ b/fragment/src/androidTest/AndroidManifest.xml
@@ -42,6 +42,7 @@
<activity android:name="androidx.fragment.app.test.ViewModelActivity" />
<activity android:name="androidx.fragment.app.OnResumeTestActivity" />
<activity android:name="androidx.fragment.app.FragmentSavedStateActivity" />
+ <activity android:name="androidx.fragment.app.FragmentFinishEarlyTestActivity" />
<activity android:name="androidx.fragment.app.DialogActivity"
android:theme="@style/DialogTheme"/>
</application>
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java
deleted file mode 100644
index 900ec9d..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Counts the number of onCreateView, onHiddenChanged (onHide, onShow), onAttach, and onDetach
- * calls.
- */
-public class CountCallsFragment extends StrictViewFragment {
- public int onCreateViewCount = 0;
- public int onDestroyViewCount = 0;
- public int onHideCount = 0;
- public int onShowCount = 0;
- public int onAttachCount = 0;
- public int onDetachCount = 0;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- onCreateViewCount++;
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onHiddenChanged(boolean hidden) {
- if (hidden) {
- onHideCount++;
- } else {
- onShowCount++;
- }
- super.onHiddenChanged(hidden);
- }
-
- @Override
- public void onAttach(Context context) {
- onAttachCount++;
- super.onAttach(context);
- }
-
- @Override
- public void onDetach() {
- onDetachCount++;
- super.onDetach();
- }
-
- @Override
- public void onDestroyView() {
- onDestroyViewCount++;
- super.onDestroyView();
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt
new file mode 100644
index 0000000..e1e3378
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/CountCallsFragment.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+
+/**
+ * Counts the number of onCreateView, onHiddenChanged (onHide, onShow), onAttach, and onDetach
+ * calls.
+ */
+class CountCallsFragment(
+ @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictViewFragment(contentLayoutId) {
+ var onCreateViewCount = 0
+ var onDestroyViewCount = 0
+ var onHideCount = 0
+ var onShowCount = 0
+ var onAttachCount = 0
+ var onDetachCount = 0
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ onCreateViewCount++
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+
+ override fun onHiddenChanged(hidden: Boolean) {
+ if (hidden) {
+ onHideCount++
+ } else {
+ onShowCount++
+ }
+ super.onHiddenChanged(hidden)
+ }
+
+ override fun onAttach(context: Context) {
+ onAttachCount++
+ super.onAttach(context)
+ }
+
+ override fun onDetach() {
+ onDetachCount++
+ super.onDetach()
+ }
+
+ override fun onDestroyView() {
+ onDestroyViewCount++
+ super.onDestroyView()
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
index f17f8ec..c53870c 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentDismissTest.kt
@@ -138,7 +138,9 @@
operation.run(fragment)
}
- countDownLatch.await(1, TimeUnit.SECONDS)
+ assertWithMessage("Timed out waiting for ON_DESTROY")
+ .that(countDownLatch.await(5, TimeUnit.SECONDS))
+ .isTrue()
assertWithMessage("Dialog should be dismissed")
.that(dismissCalled)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index e4a3be9..300fad6 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -355,8 +355,7 @@
val fm1 = fc1.supportFragmentManager
- val fragment1 = StrictViewFragment()
- fragment1.setLayoutId(R.layout.scene1)
+ val fragment1 = StrictViewFragment(R.layout.scene1)
fm1.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.commit()
@@ -596,7 +595,7 @@
@Throws(InterruptedException::class)
private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
- assertThat(fragment.mOnCreateViewCalled).isTrue()
+ assertThat(fragment.onCreateViewCalled).isTrue()
assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
index b96e10b..b71b846 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimatorTest.kt
@@ -415,8 +415,7 @@
val fm1 = fc1.supportFragmentManager
- val fragment1 = StrictViewFragment()
- fragment1.setLayoutId(R.layout.scene1)
+ val fragment1 = StrictViewFragment(R.layout.scene1)
fm1.beginTransaction()
.add(R.id.fragmentContainer, fragment1, "1")
.setReorderingAllowed(true)
@@ -509,7 +508,7 @@
}
private fun assertPostponed(fragment: AnimatorFragment, expectedAnimators: Int) {
- assertThat(fragment.mOnCreateViewCalled).isTrue()
+ assertThat(fragment.onCreateViewCalled).isTrue()
assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
assertThat(fragment.requireView().alpha).isWithin(0f).of(0f)
assertThat(fragment.numAnimators).isEqualTo(expectedAnimators)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt
index f885d53..a90cccf 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFactoryTest.kt
@@ -17,7 +17,6 @@
package androidx.fragment.app
import android.os.Bundle
-import androidx.annotation.ContentView
import androidx.fragment.app.test.EmptyFragmentTestActivity
import androidx.fragment.test.R
import androidx.test.annotation.UiThreadTest
@@ -87,8 +86,7 @@
}
}
-@ContentView(R.layout.nested_inflated_fragment_parent)
-class ParentFragment : Fragment() {
+class ParentFragment : Fragment(R.layout.nested_inflated_fragment_parent) {
var factory: FragmentFactory? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -102,8 +100,8 @@
class TestFragmentFactory : FragmentFactory() {
var instantiateCount = 0
- override fun instantiate(classLoader: ClassLoader, className: String, args: Bundle?): Fragment {
+ override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
instantiateCount++
- return super.instantiate(classLoader, className, args)
+ return super.instantiate(classLoader, className)
}
}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt
new file mode 100644
index 0000000..7cc8940
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentFinishEarlyTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Build
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class FragmentFinishEarlyTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentFinishEarlyTestActivity::class.java, false, false)
+
+ /**
+ * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
+ */
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ @Test
+ fun fragmentActivityFinishEarly() {
+ val activity = activityRule.launchActivity(null)
+
+ assertThat(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(activity.fragment.activityDestroyed).isFalse()
+ }
+}
+
+/**
+ * A simple activity used for testing an Early Finishing Activity
+ */
+class FragmentFinishEarlyTestActivity : FragmentActivity() {
+ val onDestroyLatch = CountDownLatch(1)
+ val fragment = AssertNotDestroyed()
+
+ public override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+ finish()
+ supportFragmentManager.beginTransaction()
+ .add(fragment, "not destroyed")
+ .commit()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ onDestroyLatch.countDown()
+ }
+
+ class AssertNotDestroyed : Fragment() {
+ var activityDestroyed: Boolean = false
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ activityDestroyed = requireActivity().isDestroyed
+ }
+ }
+}
\ No newline at end of file
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
deleted file mode 100644
index a52ef71..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.java
+++ /dev/null
@@ -1,2038 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import static androidx.fragment.app.FragmentTestUtil.HostCallbacks;
-import static androidx.fragment.app.FragmentTestUtil.restartFragmentController;
-import static androidx.fragment.app.FragmentTestUtil.shutdownFragmentController;
-import static androidx.fragment.app.FragmentTestUtil.startupFragmentController;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.view.ViewCompat;
-import androidx.fragment.app.test.EmptyFragmentTestActivity;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.ViewModelStore;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class FragmentLifecycleTest {
-
- @Rule
- public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
- new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
-
- @Test
- public void basicLifecycle() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment strictFragment = new StrictFragment();
-
- // Add fragment; StrictFragment will throw if it detects any violation
- // in standard lifecycle method ordering or expected preconditions.
- fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment is not added", strictFragment.isAdded());
- assertFalse("fragment is detached", strictFragment.isDetached());
- assertTrue("fragment is not resumed", strictFragment.isResumed());
- Lifecycle lifecycle = strictFragment.getLifecycle();
- assertThat(lifecycle.getCurrentState())
- .isEqualTo(Lifecycle.State.RESUMED);
-
- // Test removal as well; StrictFragment will throw here too.
- fm.beginTransaction().remove(strictFragment).commit();
- executePendingTransactions(fm);
-
- assertFalse("fragment is added", strictFragment.isAdded());
- assertFalse("fragment is resumed", strictFragment.isResumed());
- assertThat(lifecycle.getCurrentState())
- .isEqualTo(Lifecycle.State.DESTROYED);
- // Once removed, a new Lifecycle should be created just in case
- // the developer reuses the same Fragment
- assertThat(strictFragment.getLifecycle().getCurrentState())
- .isEqualTo(Lifecycle.State.INITIALIZED);
-
- // This one is perhaps counterintuitive; "detached" means specifically detached
- // but still managed by a FragmentManager. The .remove call above
- // should not enter this state.
- assertFalse("fragment is detached", strictFragment.isDetached());
- }
-
- @Test
- public void detachment() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment f1 = new StrictFragment();
- final StrictFragment f2 = new StrictFragment();
-
- fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
- assertTrue("fragment 2 is not added", f2.isAdded());
-
- // Test detaching fragments using StrictFragment to throw on errors.
- fm.beginTransaction().detach(f1).detach(f2).commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not detached", f1.isDetached());
- assertTrue("fragment 2 is not detached", f2.isDetached());
- assertFalse("fragment 1 is added", f1.isAdded());
- assertFalse("fragment 2 is added", f2.isAdded());
-
- // Only reattach f1; leave v2 detached.
- fm.beginTransaction().attach(f1).commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
- assertFalse("fragment 1 is detached", f1.isDetached());
- assertTrue("fragment 2 is not detached", f2.isDetached());
-
- // Remove both from the FragmentManager.
- fm.beginTransaction().remove(f1).remove(f2).commit();
- executePendingTransactions(fm);
-
- assertFalse("fragment 1 is added", f1.isAdded());
- assertFalse("fragment 2 is added", f2.isAdded());
- assertFalse("fragment 1 is detached", f1.isDetached());
- assertFalse("fragment 2 is detached", f2.isDetached());
- }
-
- @Test
- public void basicBackStack() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment f1 = new StrictFragment();
- final StrictFragment f2 = new StrictFragment();
-
- // Add a fragment normally to set up
- fm.beginTransaction().add(f1, "1").commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
-
- // Remove the first one and add a second. We're not using replace() here since
- // these fragments are headless and as of this test writing, replace() only works
- // for fragments with views and a container view id.
- // Add it to the back stack so we can pop it afterwards.
- fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
- executePendingTransactions(fm);
-
- assertFalse("fragment 1 is added", f1.isAdded());
- assertTrue("fragment 2 is not added", f2.isAdded());
-
- // Test popping the stack
- fm.popBackStack();
- executePendingTransactions(fm);
-
- assertFalse("fragment 2 is added", f2.isAdded());
- assertTrue("fragment 1 is not added", f1.isAdded());
- }
-
- @Test
- public void attachBackStack() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment f1 = new StrictFragment();
- final StrictFragment f2 = new StrictFragment();
-
- // Add a fragment normally to set up
- fm.beginTransaction().add(f1, "1").commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
-
- fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not detached", f1.isDetached());
- assertFalse("fragment 2 is detached", f2.isDetached());
- assertFalse("fragment 1 is added", f1.isAdded());
- assertTrue("fragment 2 is not added", f2.isAdded());
- }
-
- @Test
- public void viewLifecycle() throws Throwable {
- // Test basic lifecycle when the fragment creates a view
-
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment f1 = new StrictViewFragment();
-
- fm.beginTransaction().add(android.R.id.content, f1).commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
- final View view = f1.getView();
- assertNotNull("fragment 1 returned null from getView", view);
- assertTrue("fragment 1's view is not attached to a window",
- ViewCompat.isAttachedToWindow(view));
-
- fm.beginTransaction().remove(f1).commit();
- executePendingTransactions(fm);
-
- assertFalse("fragment 1 is added", f1.isAdded());
- assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
- assertFalse("fragment 1's previous view is still attached to a window",
- ViewCompat.isAttachedToWindow(view));
- }
-
- @Test
- public void viewReplace() throws Throwable {
- // Replace one view with another, then reverse it with the back stack
-
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment f1 = new StrictViewFragment();
- final StrictViewFragment f2 = new StrictViewFragment();
-
- fm.beginTransaction().add(android.R.id.content, f1).commit();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
-
- View origView1 = f1.getView();
- assertNotNull("fragment 1 returned null view", origView1);
- assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(origView1));
-
- fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
- executePendingTransactions(fm);
-
- assertFalse("fragment 1 is added", f1.isAdded());
- assertTrue("fragment 2 is added", f2.isAdded());
- assertNull("fragment 1 returned non-null view", f1.getView());
- assertFalse("fragment 1's old view still attached",
- ViewCompat.isAttachedToWindow(origView1));
- View origView2 = f2.getView();
- assertNotNull("fragment 2 returned null view", origView2);
- assertTrue("fragment 2's view not attached", ViewCompat.isAttachedToWindow(origView2));
-
- fm.popBackStack();
- executePendingTransactions(fm);
-
- assertTrue("fragment 1 is not added", f1.isAdded());
- assertFalse("fragment 2 is added", f2.isAdded());
- assertNull("fragment 2 returned non-null view", f2.getView());
- assertFalse("fragment 2's view still attached", ViewCompat.isAttachedToWindow(origView2));
- View newView1 = f1.getView();
- assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
- assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(newView1));
- }
-
- @Test
- @UiThreadTest
- public void setInitialSavedState() throws Throwable {
- FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- // Add a StateSaveFragment
- StateSaveFragment fragment = new StateSaveFragment("Saved", "");
- fm.beginTransaction().add(fragment, "tag").commit();
- executePendingTransactions(fm);
-
- // Change the user visible hint before we save state
- fragment.setUserVisibleHint(false);
-
- // Save its state and remove it
- Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
- fm.beginTransaction().remove(fragment).commit();
- executePendingTransactions(fm);
-
- // Create a new instance, calling setInitialSavedState
- fragment = new StateSaveFragment("", "");
- fragment.setInitialSavedState(state);
-
- // Add the new instance
- fm.beginTransaction().add(fragment, "tag").commit();
- executePendingTransactions(fm);
-
- assertEquals("setInitialSavedState did not restore saved state",
- "Saved", fragment.getSavedState());
- assertEquals("setInitialSavedState did not restore user visible hint",
- false, fragment.getUserVisibleHint());
- }
-
- @Test
- @UiThreadTest
- public void setInitialSavedStateWithSetUserVisibleHint() throws Throwable {
- FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- // Add a StateSaveFragment
- StateSaveFragment fragment = new StateSaveFragment("Saved", "");
- fm.beginTransaction().add(fragment, "tag").commit();
- executePendingTransactions(fm);
-
- // Save its state and remove it
- Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
- fm.beginTransaction().remove(fragment).commit();
- executePendingTransactions(fm);
-
- // Create a new instance, calling setInitialSavedState
- fragment = new StateSaveFragment("", "");
- fragment.setInitialSavedState(state);
-
- // Change the user visible hint after we call setInitialSavedState
- fragment.setUserVisibleHint(false);
-
- // Add the new instance
- fm.beginTransaction().add(fragment, "tag").commit();
- executePendingTransactions(fm);
-
- assertEquals("setInitialSavedState did not restore saved state",
- "Saved", fragment.getSavedState());
- assertEquals("setUserVisibleHint should override setInitialSavedState",
- false, fragment.getUserVisibleHint());
- }
-
- @Test
- @UiThreadTest
- public void testSavedInstanceStateAfterRestore() {
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- // Add the initial state
- final StrictFragment parentFragment = new StrictFragment();
- parentFragment.setRetainInstance(true);
- final StrictFragment childFragment = new StrictFragment();
- fm1.beginTransaction().add(parentFragment, "parent").commitNow();
- final FragmentManager childFragmentManager = parentFragment.getChildFragmentManager();
- childFragmentManager.beginTransaction().add(childFragment, "child").commitNow();
-
- // Confirm the initial state
- assertWithMessage("Initial parent saved instance state should be null")
- .that(parentFragment.mSavedInstanceState)
- .isNull();
- assertWithMessage("Initial child saved instance state should be null")
- .that(childFragment.mSavedInstanceState)
- .isNull();
-
- // Bring the state back down to destroyed, simulating an activity restart
- fc1.dispatchPause();
- final Parcelable savedState = fc1.saveAllState();
- fc1.dispatchStop();
- fc1.dispatchDestroy();
-
- // Create the new controller and restore state
- final FragmentController fc2 =
- startupFragmentController(mActivityRule.getActivity(), savedState, viewModelStore);
- final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
- final StrictFragment restoredParentFragment = (StrictFragment) fm2
- .findFragmentByTag("parent");
- assertNotNull("Parent fragment was not restored", restoredParentFragment);
- final StrictFragment restoredChildFragment = (StrictFragment) restoredParentFragment
- .getChildFragmentManager().findFragmentByTag("child");
- assertNotNull("Child fragment was not restored", restoredChildFragment);
-
- assertWithMessage("Parent fragment saved instance state should still be null "
- + "since it is a retained Fragment")
- .that(restoredParentFragment.mSavedInstanceState)
- .isNull();
- assertWithMessage("Child fragment saved instance state should be non-null")
- .that(restoredChildFragment.mSavedInstanceState)
- .isNotNull();
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
- }
-
- @Test
- @UiThreadTest
- public void restoreNestedFragmentsOnBackStack() {
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- fc1.attachHost(null);
- fc1.dispatchCreate();
-
- // Add the initial state
- final StrictFragment parentFragment = new StrictFragment();
- final StrictFragment childFragment = new StrictFragment();
- fm1.beginTransaction().add(parentFragment, "parent").commitNow();
- final FragmentManager childFragmentManager = parentFragment.getChildFragmentManager();
- childFragmentManager.beginTransaction().add(childFragment, "child").commitNow();
-
- // Now add a Fragment to the back stack
- final StrictFragment replacementChildFragment = new StrictFragment();
- childFragmentManager.beginTransaction()
- .remove(childFragment)
- .add(replacementChildFragment, "child")
- .addToBackStack("back_stack").commit();
- childFragmentManager.executePendingTransactions();
-
- // Move the activity to resumed
- fc1.dispatchActivityCreated();
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
- fc1.dispatchResume();
- fc1.execPendingActions();
-
- // Now bring the state back down
- fc1.dispatchPause();
- final Parcelable savedState = fc1.saveAllState();
- fc1.dispatchStop();
- fc1.dispatchDestroy();
-
- // Create the new controller and restore state
- final FragmentController fc2 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
- fc2.attachHost(null);
- fc2.restoreSaveState(savedState);
- fc2.dispatchCreate();
-
- final StrictFragment restoredParentFragment = (StrictFragment) fm2
- .findFragmentByTag("parent");
- assertNotNull("Parent fragment was not restored", restoredParentFragment);
- final StrictFragment restoredChildFragment = (StrictFragment) restoredParentFragment
- .getChildFragmentManager().findFragmentByTag("child");
- assertNotNull("Child fragment was not restored", restoredChildFragment);
-
- fc2.dispatchActivityCreated();
- fc2.noteStateNotSaved();
- fc2.execPendingActions();
- fc2.dispatchStart();
- fc2.dispatchResume();
- fc2.execPendingActions();
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
- }
-
- @Test
- @UiThreadTest
- public void restoreRetainedInstanceFragments() throws Throwable {
- // Create a new FragmentManager in isolation, nest some assorted fragments
- // and then restore them to a second new FragmentManager.
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- fc1.attachHost(null);
- fc1.dispatchCreate();
-
- // Configure fragments.
-
- // This retained fragment will be added, then removed. After being removed, it
- // should no longer be retained by the FragmentManager
- final StateSaveFragment removedFragment = new StateSaveFragment("Removed",
- "UnsavedRemoved");
- removedFragment.setRetainInstance(true);
- fm1.beginTransaction().add(removedFragment, "tag:removed").commitNow();
- fm1.beginTransaction().remove(removedFragment).commitNow();
-
- // This retained fragment will be added, then detached. After being detached, it
- // should continue to be retained by the FragmentManager
- final StateSaveFragment detachedFragment = new StateSaveFragment("Detached",
- "UnsavedDetached");
- removedFragment.setRetainInstance(true);
- fm1.beginTransaction().add(detachedFragment, "tag:detached").commitNow();
- fm1.beginTransaction().detach(detachedFragment).commitNow();
-
- // Grandparent fragment will not retain instance
- final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent",
- "UnsavedGrandparent");
- assertNotNull("grandparent fragment saved state not initialized",
- grandparentFragment.getSavedState());
- assertNotNull("grandparent fragment unsaved state not initialized",
- grandparentFragment.getUnsavedState());
- fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow();
-
- // Parent fragment will retain instance
- final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent");
- assertNotNull("parent fragment saved state not initialized",
- parentFragment.getSavedState());
- assertNotNull("parent fragment unsaved state not initialized",
- parentFragment.getUnsavedState());
- parentFragment.setRetainInstance(true);
- grandparentFragment.getChildFragmentManager().beginTransaction()
- .add(parentFragment, "tag:parent").commitNow();
- assertSame("parent fragment is not a child of grandparent",
- grandparentFragment, parentFragment.getParentFragment());
-
- // Child fragment will not retain instance
- final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild");
- assertNotNull("child fragment saved state not initialized",
- childFragment.getSavedState());
- assertNotNull("child fragment unsaved state not initialized",
- childFragment.getUnsavedState());
- parentFragment.getChildFragmentManager().beginTransaction()
- .add(childFragment, "tag:child").commitNow();
- assertSame("child fragment is not a child of grandpanret",
- parentFragment, childFragment.getParentFragment());
-
- // Saved for comparison later
- final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager();
-
- fc1.dispatchActivityCreated();
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
- fc1.dispatchResume();
- fc1.execPendingActions();
-
- // Bring the state back down to destroyed, simulating an activity restart
- fc1.dispatchPause();
- final Parcelable savedState = fc1.saveAllState();
- fc1.dispatchStop();
- fc1.dispatchDestroy();
-
- // Create the new controller and restore state
- final FragmentController fc2 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
- fc2.attachHost(null);
- fc2.restoreSaveState(savedState);
- fc2.dispatchCreate();
-
- // Confirm that the restored fragments are available and in the expected states
- final StateSaveFragment restoredRemovedFragment = (StateSaveFragment)
- fm2.findFragmentByTag("tag:removed");
- assertNull(restoredRemovedFragment);
- assertTrue("Removed Fragment should be destroyed", removedFragment.mCalledOnDestroy);
-
- final StateSaveFragment restoredDetachedFragment = (StateSaveFragment)
- fm2.findFragmentByTag("tag:detached");
- assertNotNull(restoredDetachedFragment);
-
- final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag(
- "tag:grandparent");
- assertNotNull("grandparent fragment not restored", restoredGrandparent);
-
- assertNotSame("grandparent fragment instance was saved",
- grandparentFragment, restoredGrandparent);
- assertEquals("grandparent fragment saved state was not equal",
- grandparentFragment.getSavedState(), restoredGrandparent.getSavedState());
- assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved",
- grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState());
-
- final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent
- .getChildFragmentManager().findFragmentByTag("tag:parent");
- assertNotNull("parent fragment not restored", restoredParent);
-
- assertSame("parent fragment instance was not saved", parentFragment, restoredParent);
- assertEquals("parent fragment saved state was not equal",
- parentFragment.getSavedState(), restoredParent.getSavedState());
- assertEquals("parent fragment unsaved state was not equal",
- parentFragment.getUnsavedState(), restoredParent.getUnsavedState());
- assertNotSame("parent fragment has the same child FragmentManager",
- parentChildFragmentManager, restoredParent.getChildFragmentManager());
-
- final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent
- .getChildFragmentManager().findFragmentByTag("tag:child");
- assertNotNull("child fragment not restored", restoredChild);
-
- assertNotSame("child fragment instance state was saved", childFragment, restoredChild);
- assertEquals("child fragment saved state was not equal",
- childFragment.getSavedState(), restoredChild.getSavedState());
- assertNotEquals("child fragment saved state was unexpectedly equal",
- childFragment.getUnsavedState(), restoredChild.getUnsavedState());
-
- fc2.dispatchActivityCreated();
- fc2.noteStateNotSaved();
- fc2.execPendingActions();
- fc2.dispatchStart();
- fc2.dispatchResume();
- fc2.execPendingActions();
-
- // Test that the fragments are in the configuration we expect
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
-
- assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy);
- assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy);
- assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
- }
-
- @Test
- @UiThreadTest
- public void restoreRetainedInstanceFragmentWithTransparentActivityConfigChange() {
- // Create a new FragmentManager in isolation, add a retained instance Fragment,
- // then mimic the following scenario:
- // 1. Activity A adds retained Fragment F
- // 2. Activity A starts translucent Activity B
- // 3. Activity B start opaque Activity C
- // 4. Rotate phone
- // 5. Finish Activity C
- // 6. Finish Activity B
-
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- fc1.attachHost(null);
- fc1.dispatchCreate();
-
- // Add the retained Fragment
- final StateSaveFragment retainedFragment = new StateSaveFragment("Retained",
- "UnsavedRetained");
- retainedFragment.setRetainInstance(true);
- fm1.beginTransaction().add(retainedFragment, "tag:retained").commitNow();
-
- // Move the activity to resumed
- fc1.dispatchActivityCreated();
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
- fc1.dispatchResume();
- fc1.execPendingActions();
-
- // Launch the transparent activity on top
- fc1.dispatchPause();
-
- // Launch the opaque activity on top
- final Parcelable savedState = fc1.saveAllState();
- fc1.dispatchStop();
-
- // Finish the opaque activity, making our Activity visible i.e., started
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
-
- // Finish the transparent activity, causing a config change
- fc1.dispatchStop();
- fc1.dispatchDestroy();
-
- // Create the new controller and restore state
- final FragmentController fc2 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm2 = fc2.getSupportFragmentManager();
-
- fc2.attachHost(null);
- fc2.restoreSaveState(savedState);
- fc2.dispatchCreate();
-
- final StateSaveFragment restoredFragment = (StateSaveFragment) fm2
- .findFragmentByTag("tag:retained");
- assertNotNull("retained fragment not restored", restoredFragment);
- assertEquals("The retained Fragment shouldn't be recreated",
- retainedFragment, restoredFragment);
-
- fc2.dispatchActivityCreated();
- fc2.noteStateNotSaved();
- fc2.execPendingActions();
- fc2.dispatchStart();
- fc2.dispatchResume();
- fc2.execPendingActions();
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
- }
-
- @Test
- @UiThreadTest
- public void saveAnimationState() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
- viewModelStore);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- fm.beginTransaction()
- .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
- .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
- // Causes save and restore of fragments and back stack
- fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
- fm = fc.getSupportFragmentManager();
-
- assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
- fm.beginTransaction()
- .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
- .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
-
- // Causes save and restore of fragments and back stack
- fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
- fm = fc.getSupportFragmentManager();
-
- assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
-
- fm.popBackStackImmediate();
-
- assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
-
- shutdownFragmentController(fc, viewModelStore);
- }
-
- /**
- * This test confirms that as long as a parent fragment has called super.onCreate,
- * any child fragments added, committed and with transactions executed will be brought
- * to at least the CREATED state by the time the parent fragment receives onCreateView.
- * This means the child fragment will have received onAttach/onCreate.
- */
- @Test
- @UiThreadTest
- public void childFragmentManagerAttach() throws Throwable {
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
- fc.attachHost(null);
- fc.dispatchCreate();
-
- FragmentManager.FragmentLifecycleCallbacks
- mockLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
- FragmentManager.FragmentLifecycleCallbacks
- mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
-
- FragmentManager fm = fc.getSupportFragmentManager();
- fm.registerFragmentLifecycleCallbacks(mockLc, false);
- fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true);
-
- ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
- fm.beginTransaction()
- .add(android.R.id.content, fragment)
- .commitNow();
-
- verify(mockLc, times(1)).onFragmentCreated(fm, fragment, null);
-
- fc.dispatchActivityCreated();
-
- Fragment childFragment = fragment.getChildFragment();
-
- verify(mockLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
- verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
- verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, childFragment, null);
-
- fc.dispatchStart();
-
- verify(mockLc, times(1)).onFragmentStarted(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, childFragment);
-
- fc.dispatchResume();
-
- verify(mockLc, times(1)).onFragmentResumed(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, childFragment);
-
- // Confirm that the parent fragment received onAttachFragment
- assertTrue("parent fragment did not receive onAttachFragment",
- fragment.mCalledOnAttachFragment);
-
- fc.dispatchStop();
-
- verify(mockLc, times(1)).onFragmentStopped(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, childFragment);
-
- viewModelStore.clear();
- fc.dispatchDestroy();
-
- verify(mockLc, times(1)).onFragmentDestroyed(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, fragment);
- verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, childFragment);
- }
-
- /**
- * This test checks that FragmentLifecycleCallbacks are invoked when expected.
- */
- @Test
- @UiThreadTest
- public void fragmentLifecycleCallbacks() throws Throwable {
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
- fc.attachHost(null);
- fc.dispatchCreate();
-
- FragmentManager fm = fc.getSupportFragmentManager();
-
- ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
- fm.beginTransaction()
- .add(android.R.id.content, fragment)
- .commitNow();
-
- fc.dispatchActivityCreated();
-
- fc.dispatchStart();
- fc.dispatchResume();
-
- // Confirm that the parent fragment received onAttachFragment
- assertTrue("parent fragment did not receive onAttachFragment",
- fragment.mCalledOnAttachFragment);
-
- shutdownFragmentController(fc, viewModelStore);
- }
-
- /**
- * This tests that fragments call onDestroy when the activity finishes.
- */
- @Test
- @UiThreadTest
- public void fragmentDestroyedOnFinish() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
- viewModelStore);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- StrictViewFragment fragmentA = StrictViewFragment.create(R.layout.fragment_a);
- StrictViewFragment fragmentB = StrictViewFragment.create(R.layout.fragment_b);
- fm.beginTransaction()
- .add(android.R.id.content, fragmentA)
- .commit();
- fm.executePendingTransactions();
- fm.beginTransaction()
- .replace(android.R.id.content, fragmentB)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- shutdownFragmentController(fc, viewModelStore);
- assertTrue(fragmentB.mCalledOnDestroy);
- assertTrue(fragmentA.mCalledOnDestroy);
- }
-
- // Make sure that executing transactions during activity lifecycle events
- // is properly prevented.
- @Test
- public void preventReentrantCalls() throws Throwable {
- testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.CREATED);
- testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ACTIVITY_CREATED);
- testLifecycleTransitionFailure(StrictFragment.ACTIVITY_CREATED, StrictFragment.STARTED);
- testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.RESUMED);
-
- testLifecycleTransitionFailure(StrictFragment.RESUMED, StrictFragment.STARTED);
- testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.CREATED);
- testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ATTACHED);
- testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.DETACHED);
- }
-
- private void testLifecycleTransitionFailure(final int fromState,
- final int toState) throws Throwable {
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- final ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = startupFragmentController(
- mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- final Fragment reentrantFragment = ReentrantFragment.create(fromState, toState);
-
- fm1.beginTransaction()
- .add(reentrantFragment, "reentrant")
- .commit();
- try {
- fm1.executePendingTransactions();
- } catch (IllegalStateException e) {
- fail("An exception shouldn't happen when initially adding the fragment");
- }
-
- // Now shut down the fragment controller. When fromState > toState, this should
- // result in an exception
- Parcelable savedState;
- try {
- fc1.dispatchPause();
- savedState = fc1.saveAllState();
- fc1.dispatchStop();
- fc1.dispatchDestroy();
- if (fromState > toState) {
- fail("Expected IllegalStateException when moving from "
- + StrictFragment.stateToString(fromState) + " to "
- + StrictFragment.stateToString(toState));
- }
- } catch (IllegalStateException e) {
- if (fromState < toState) {
- fail("Unexpected IllegalStateException when moving from "
- + StrictFragment.stateToString(fromState) + " to "
- + StrictFragment.stateToString(toState));
- }
- return; // test passed!
- }
-
- // now restore from saved state. This will be reached when
- // fromState < toState. We want to catch the fragment while it
- // is being restored as the fragment controller state is being brought up.
-
- try {
- startupFragmentController(mActivityRule.getActivity(), savedState,
- viewModelStore);
-
- fail("Expected IllegalStateException when moving from "
- + StrictFragment.stateToString(fromState) + " to "
- + StrictFragment.stateToString(toState));
- } catch (IllegalStateException e) {
- // expected, so the test passed!
- }
- }
- });
- }
-
- /**
- * Test to ensure that when dispatch* is called that the fragment manager
- * doesn't cause the contained fragment states to change even if no state changes.
- */
- @Test
- @UiThreadTest
- public void noPrematureStateChange() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
- viewModelStore);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- fm.beginTransaction()
- .add(new StrictFragment(), "1")
- .commitNow();
-
- fc = restartFragmentController(mActivityRule.getActivity(), fc, viewModelStore);
-
- fm = fc.getSupportFragmentManager();
-
- StrictFragment fragment1 = (StrictFragment) fm.findFragmentByTag("1");
- assertWithMessage("Fragment should be resumed after restart")
- .that(fragment1.mCalledOnResume)
- .isTrue();
- fragment1.mCalledOnResume = false;
- fc.dispatchResume();
-
- assertWithMessage("Fragment should not get onResume() after second dispatchResume()")
- .that(fragment1.mCalledOnResume)
- .isFalse();
- }
-
- @Test
- @UiThreadTest
- public void testIsStateSaved() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
- viewModelStore);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- Fragment f = new StrictFragment();
- fm.beginTransaction()
- .add(f, "1")
- .commitNow();
-
- assertFalse("fragment reported state saved while resumed", f.isStateSaved());
-
- fc.dispatchPause();
- fc.saveAllState();
-
- assertTrue("fragment reported state not saved after saveAllState", f.isStateSaved());
-
- fc.dispatchStop();
-
- assertTrue("fragment reported state not saved after stop", f.isStateSaved());
-
- viewModelStore.clear();
- fc.dispatchDestroy();
-
- assertFalse("fragment reported state saved after destroy", f.isStateSaved());
- }
-
- @Test
- @UiThreadTest
- public void testSetArgumentsLifecycle() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- FragmentController fc = startupFragmentController(mActivityRule.getActivity(), null,
- viewModelStore);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- Fragment f = new StrictFragment();
- f.setArguments(new Bundle());
-
- fm.beginTransaction()
- .add(f, "1")
- .commitNow();
-
- f.setArguments(new Bundle());
-
- fc.dispatchPause();
- fc.saveAllState();
-
- boolean threw = false;
- try {
- f.setArguments(new Bundle());
- } catch (IllegalStateException ise) {
- threw = true;
- }
- assertTrue("fragment allowed setArguments after state save", threw);
-
- fc.dispatchStop();
-
- threw = false;
- try {
- f.setArguments(new Bundle());
- } catch (IllegalStateException ise) {
- threw = true;
- }
- assertTrue("fragment allowed setArguments after stop", threw);
-
- viewModelStore.clear();
- fc.dispatchDestroy();
-
- // Fully destroyed, so fragments have been removed.
- f.setArguments(new Bundle());
- }
-
- /*
- * Test that target fragments are in a useful state when we restore them, even if they're
- * on the back stack.
- */
-
- @Test
- @UiThreadTest
- public void targetFragmentRestoreLifecycleStateBackStack() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- fc1.attachHost(null);
- fc1.dispatchCreate();
-
- final Fragment target = new TargetFragment();
- fm1.beginTransaction().add(target, "target").commitNow();
-
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
-
- fm1.beginTransaction()
- .remove(target)
- .add(referrer, "referrer")
- .addToBackStack(null)
- .commit();
-
- fc1.dispatchActivityCreated();
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
- fc1.dispatchResume();
- fc1.execPendingActions();
-
- // Simulate an activity restart
- final FragmentController fc2 =
- restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
- }
-
- @Test
- @UiThreadTest
- public void targetFragmentRestoreLifecycleStateManagerOrder() throws Throwable {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc1 = FragmentController.createController(
- new HostCallbacks(mActivityRule.getActivity(), viewModelStore));
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- fc1.attachHost(null);
- fc1.dispatchCreate();
-
- final Fragment target1 = new TargetFragment();
- final Fragment referrer1 = new ReferrerFragment();
- referrer1.setTargetFragment(target1, 0);
-
- fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow();
-
- final Fragment target2 = new TargetFragment();
- final Fragment referrer2 = new ReferrerFragment();
- referrer2.setTargetFragment(target2, 0);
-
- // Order shouldn't matter.
- fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow();
-
- fc1.dispatchActivityCreated();
- fc1.noteStateNotSaved();
- fc1.execPendingActions();
- fc1.dispatchStart();
- fc1.dispatchResume();
- fc1.execPendingActions();
-
- // Simulate an activity restart
- final FragmentController fc2 =
- restartFragmentController(mActivityRule.getActivity(), fc1, viewModelStore);
-
- // Bring the state back down to destroyed before we finish the test
- shutdownFragmentController(fc2, viewModelStore);
- }
-
- @Test
- @UiThreadTest
- public void targetFragmentClearedWhenSetToNull() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- referrer.setTargetFragment(null, 0);
-
- assertWithMessage("Target Fragment should cleared after setTargetFragment with null")
- .that(referrer.getTargetFragment())
- .isNull();
-
- fm.beginTransaction()
- .remove(referrer)
- .commitNow();
-
- assertWithMessage("Target Fragment should still be cleared after being removed")
- .that(referrer.getTargetFragment())
- .isNull();
-
- shutdownFragmentController(fc, viewModelStore);
- }
-
- /**
- * Test the availability of getTargetFragment() when the target Fragment is already
- * attached to a FragmentManager, but the referrer Fragment is not attached.
- */
- @Test
- @UiThreadTest
- public void targetFragmentOnlyTargetAdded() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- // Add just the target Fragment to the FragmentManager
- fm.beginTransaction().add(target, "target").commitNow();
-
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction()
- .remove(referrer)
- .commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being removed")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- shutdownFragmentController(fc, viewModelStore);
- }
-
- /**
- * Test the availability of getTargetFragment() when the target fragment is
- * not retained and the referrer fragment is not retained.
- */
- @Test
- @UiThreadTest
- public void targetFragmentNonRetainedNonRetained() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction()
- .remove(referrer)
- .commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being removed")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- shutdownFragmentController(fc, viewModelStore);
-
- assertWithMessage("Target Fragment should be accessible after destruction")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
- }
-
- /**
- * Test the availability of getTargetFragment() when the target fragment is
- * retained and the referrer fragment is not retained.
- */
- @Test
- @UiThreadTest
- public void targetFragmentRetainedNonRetained() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- target.setRetainInstance(true);
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction()
- .remove(referrer)
- .commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being removed")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- shutdownFragmentController(fc, viewModelStore);
-
- assertWithMessage("Target Fragment should be accessible after destruction")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
- }
-
- /**
- * Test the availability of getTargetFragment() when the target fragment is
- * not retained and the referrer fragment is retained.
- */
- @Test
- @UiThreadTest
- public void targetFragmentNonRetainedRetained() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- final Fragment referrer = new ReferrerFragment();
- referrer.setTargetFragment(target, 0);
- referrer.setRetainInstance(true);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- // Save the state
- fc.dispatchPause();
- fc.saveAllState();
- fc.dispatchStop();
- fc.dispatchDestroy();
-
- assertWithMessage("Target Fragment should be accessible after target Fragment destruction")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
- }
-
- /**
- * Test the availability of getTargetFragment() when the target fragment is
- * retained and the referrer fragment is also retained.
- */
- @Test
- @UiThreadTest
- public void targetFragmentRetainedRetained() {
- ViewModelStore viewModelStore = new ViewModelStore();
- final FragmentController fc =
- startupFragmentController(mActivityRule.getActivity(), null, viewModelStore);
-
- final FragmentManager fm = fc.getSupportFragmentManager();
-
- final Fragment target = new TargetFragment();
- target.setRetainInstance(true);
- final Fragment referrer = new ReferrerFragment();
- referrer.setRetainInstance(true);
- referrer.setTargetFragment(target, 0);
-
- assertWithMessage("Target Fragment should be accessible before being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow();
-
- assertWithMessage("Target Fragment should be accessible after being added")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
-
- // Save the state
- fc.dispatchPause();
- fc.saveAllState();
- fc.dispatchStop();
- fc.dispatchDestroy();
-
- assertWithMessage("Target Fragment should be accessible after FragmentManager destruction")
- .that(referrer.getTargetFragment())
- .isSameAs(target);
- }
-
- @Test
- public void targetFragmentNoCycles() throws Throwable {
- final Fragment one = new Fragment();
- final Fragment two = new Fragment();
- final Fragment three = new Fragment();
-
- try {
- one.setTargetFragment(two, 0);
- two.setTargetFragment(three, 0);
- three.setTargetFragment(one, 0);
- assertTrue("creating a fragment target cycle did not throw IllegalArgumentException",
- false);
- } catch (IllegalArgumentException e) {
- // Success!
- }
- }
-
- @Test
- public void targetFragmentSetClear() throws Throwable {
- final Fragment one = new Fragment();
- final Fragment two = new Fragment();
-
- one.setTargetFragment(two, 0);
- one.setTargetFragment(null, 0);
- }
-
- /**
- * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
- */
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
- @Test
- public void fragmentActivityFinishEarly() throws Throwable {
- Intent intent = new Intent(mActivityRule.getActivity(), FragmentTestActivity.class);
- intent.putExtra("finishEarly", true);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- FragmentTestActivity activity = (FragmentTestActivity)
- InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
-
- assertTrue(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS));
- }
-
- /**
- * When a fragment is saved in non-config, it should be restored to the same index.
- */
- @Test
- @UiThreadTest
- public void restoreNonConfig() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- Fragment backStackRetainedFragment = new StrictFragment();
- backStackRetainedFragment.setRetainInstance(true);
- Fragment fragment1 = new StrictFragment();
- fm.beginTransaction()
- .add(backStackRetainedFragment, "backStack")
- .add(fragment1, "1")
- .setPrimaryNavigationFragment(fragment1)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- Fragment fragment2 = new StrictFragment();
- fragment2.setRetainInstance(true);
- fragment2.setTargetFragment(fragment1, 0);
- Fragment fragment3 = new StrictFragment();
- fm.beginTransaction()
- .remove(backStackRetainedFragment)
- .remove(fragment1)
- .add(fragment2, "2")
- .add(fragment3, "3")
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- boolean foundFragment2 = false;
- for (Fragment fragment : fc.getSupportFragmentManager().getFragments()) {
- if (fragment == fragment2) {
- foundFragment2 = true;
- assertNotNull(fragment.getTargetFragment());
- assertEquals("1", fragment.getTargetFragment().getTag());
- } else {
- assertNotEquals("2", fragment.getTag());
- }
- }
- assertTrue(foundFragment2);
- fc.getSupportFragmentManager().popBackStackImmediate();
- Fragment foundBackStackRetainedFragment = fc.getSupportFragmentManager()
- .findFragmentByTag("backStack");
- assertEquals("Retained Fragment on the back stack was not retained",
- backStackRetainedFragment, foundBackStackRetainedFragment);
- }
-
- /**
- * Check that retained fragments in the backstack correctly restored after two "configChanges"
- */
- @Test
- @UiThreadTest
- public void retainedFragmentInBackstack() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- Fragment fragment1 = new StrictFragment();
- fm.beginTransaction()
- .add(fragment1, "1")
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- Fragment child = new StrictFragment();
- child.setRetainInstance(true);
- fragment1.getChildFragmentManager().beginTransaction()
- .add(child, "child").commit();
- fragment1.getChildFragmentManager().executePendingTransactions();
-
- Fragment fragment2 = new StrictFragment();
- fm.beginTransaction()
- .remove(fragment1)
- .add(fragment2, "2")
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- savedState = FragmentTestUtil.destroy(mActivityRule, fc);
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- fm = fc.getSupportFragmentManager();
- fm.popBackStackImmediate();
- Fragment retainedChild = fm.findFragmentByTag("1")
- .getChildFragmentManager().findFragmentByTag("child");
- assertEquals(child, retainedChild);
- }
-
- /**
- * When a fragment has been optimized out, it state should still be saved during
- * save and restore instance state.
- */
- @Test
- @UiThreadTest
- public void saveRemovedFragment() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- SaveStateFragment fragment1 = SaveStateFragment.create(1);
- fm.beginTransaction()
- .add(android.R.id.content, fragment1, "1")
- .addToBackStack(null)
- .commit();
- SaveStateFragment fragment2 = SaveStateFragment.create(2);
- fm.beginTransaction()
- .replace(android.R.id.content, fragment2, "2")
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- fm = fc.getSupportFragmentManager();
- fragment2 = (SaveStateFragment) fm.findFragmentByTag("2");
- assertNotNull(fragment2);
- assertEquals(2, fragment2.getValue());
- fm.popBackStackImmediate();
- fragment1 = (SaveStateFragment) fm.findFragmentByTag("1");
- assertNotNull(fragment1);
- assertEquals(1, fragment1.getValue());
- }
-
- /**
- * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments
- * should be null
- */
- @Test
- @UiThreadTest
- public void nullNonConfig() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- Fragment fragment1 = new StrictFragment();
- fm.beginTransaction()
- .add(fragment1, "1")
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
- assertNull(savedState.second);
- }
-
- /**
- * When the FragmentManager state changes, the pending transactions should execute.
- */
- @Test
- @UiThreadTest
- public void runTransactionsOnChange() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- RemoveHelloInOnResume fragment1 = new RemoveHelloInOnResume();
- StrictFragment fragment2 = new StrictFragment();
- fm.beginTransaction()
- .add(fragment1, "1")
- .setReorderingAllowed(false)
- .commit();
- fm.beginTransaction()
- .add(fragment2, "Hello")
- .setReorderingAllowed(false)
- .commit();
- fm.executePendingTransactions();
-
- assertEquals(2, fm.getFragments().size());
- assertTrue(fm.getFragments().contains(fragment1));
- assertTrue(fm.getFragments().contains(fragment2));
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- fm = fc.getSupportFragmentManager();
-
- assertEquals(1, fm.getFragments().size());
- for (Fragment fragment : fm.getFragments()) {
- assertTrue(fragment instanceof RemoveHelloInOnResume);
- }
- }
-
- @Test
- @UiThreadTest
- public void optionsMenu() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- InvalidateOptionFragment fragment = new InvalidateOptionFragment();
- fm.beginTransaction()
- .add(android.R.id.content, fragment)
- .commit();
- fm.executePendingTransactions();
-
- Menu menu = mock(Menu.class);
- fc.dispatchPrepareOptionsMenu(menu);
- assertTrue(fragment.onPrepareOptionsMenuCalled);
- fragment.onPrepareOptionsMenuCalled = false;
- FragmentTestUtil.destroy(mActivityRule, fc);
- fc.dispatchPrepareOptionsMenu(menu);
- assertFalse(fragment.onPrepareOptionsMenuCalled);
- }
-
- /**
- * When a retained instance fragment is saved while in the back stack, it should go
- * through onCreate() when it is popped back.
- */
- @Test
- @UiThreadTest
- public void retainInstanceWithOnCreate() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- OnCreateFragment fragment1 = new OnCreateFragment();
-
- fm.beginTransaction()
- .add(fragment1, "1")
- .commit();
- fm.beginTransaction()
- .remove(fragment1)
- .addToBackStack(null)
- .commit();
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
- Pair<Parcelable, FragmentManagerNonConfig> restartState =
- Pair.create(savedState.first, null);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, restartState);
-
- // Save again, but keep the state
- savedState = FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
-
- fm = fc.getSupportFragmentManager();
-
- fm.popBackStackImmediate();
- OnCreateFragment fragment2 = (OnCreateFragment) fm.findFragmentByTag("1");
- assertTrue(fragment2.onCreateCalled);
- fm.popBackStackImmediate();
- }
-
- /**
- * A retained instance fragment should go through onCreate() once, even through save and
- * restore.
- */
- @Test
- @UiThreadTest
- public void retainInstanceOneOnCreate() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- OnCreateFragment fragment = new OnCreateFragment();
-
- fm.beginTransaction()
- .add(fragment, "fragment")
- .commit();
- fm.executePendingTransactions();
-
- fm.beginTransaction()
- .remove(fragment)
- .addToBackStack(null)
- .commit();
-
- assertTrue(fragment.onCreateCalled);
- fragment.onCreateCalled = false;
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- fm = fc.getSupportFragmentManager();
-
- fm.popBackStackImmediate();
- assertFalse(fragment.onCreateCalled);
- }
-
- /**
- * A retained instance fragment added via XML should go through onCreate() once, but should get
- * onInflate calls for each inflation.
- */
- @Test
- @UiThreadTest
- public void retainInstanceLayoutOnInflate() throws Throwable {
- FragmentController fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, null);
- FragmentManager fm = fc.getSupportFragmentManager();
-
- RetainedInflatedParentFragment parentFragment = new RetainedInflatedParentFragment();
-
- fm.beginTransaction()
- .add(android.R.id.content, parentFragment)
- .commit();
- fm.executePendingTransactions();
-
- RetainedInflatedChildFragment childFragment = (RetainedInflatedChildFragment)
- parentFragment.getChildFragmentManager().findFragmentById(R.id.child_fragment);
-
- fm.beginTransaction()
- .remove(parentFragment)
- .addToBackStack(null)
- .commit();
-
- Pair<Parcelable, FragmentManagerNonConfig> savedState =
- FragmentTestUtil.destroy(mActivityRule, fc);
-
- fc = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc, savedState);
- fm = fc.getSupportFragmentManager();
-
- fm.popBackStackImmediate();
-
- parentFragment = (RetainedInflatedParentFragment) fm.findFragmentById(android.R.id.content);
- RetainedInflatedChildFragment childFragment2 = (RetainedInflatedChildFragment)
- parentFragment.getChildFragmentManager().findFragmentById(R.id.child_fragment);
-
- assertEquals("Child Fragment should be retained", childFragment, childFragment2);
- assertEquals("Child Fragment should have onInflate called twice",
- 2, childFragment2.mOnInflateCount);
- }
-
- private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
- int popExit) {
- FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
- BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
-
- Assert.assertEquals(enter, record.mEnterAnim);
- Assert.assertEquals(exit, record.mExitAnim);
- Assert.assertEquals(popEnter, record.mPopEnterAnim);
- Assert.assertEquals(popExit, record.mPopExitAnim);
- }
-
- private void executePendingTransactions(final FragmentManager fm) throws Throwable {
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fm.executePendingTransactions();
- }
- });
- }
-
- public static class StateSaveFragment extends StrictFragment {
- private static final String STATE_KEY = "state";
-
- private String mSavedState;
- private String mUnsavedState;
-
- public StateSaveFragment() {
- }
-
- public StateSaveFragment(String savedState, String unsavedState) {
- mSavedState = savedState;
- mUnsavedState = unsavedState;
- }
-
- public String getSavedState() {
- return mSavedState;
- }
-
- public String getUnsavedState() {
- return mUnsavedState;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mSavedState = savedInstanceState.getString(STATE_KEY);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putString(STATE_KEY, mSavedState);
- }
- }
-
- /**
- * This tests a deliberately odd use of a child fragment, added in onCreateView instead
- * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy
- * created by this fragment.
- */
- public static class ChildFragmentManagerFragment extends StrictFragment {
- private FragmentManager mSavedChildFragmentManager;
- private ChildFragmentManagerChildFragment mChildFragment;
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mSavedChildFragmentManager = getChildFragmentManager();
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager,
- getChildFragmentManager());
- ChildFragmentManagerChildFragment child =
- (ChildFragmentManagerChildFragment) mSavedChildFragmentManager
- .findFragmentByTag("tag");
- if (child == null) {
- child = new ChildFragmentManagerChildFragment("foo");
- mSavedChildFragmentManager.beginTransaction()
- .add(child, "tag")
- .commitNow();
- assertEquals("argument strings don't match", "foo", child.getString());
- }
- mChildFragment = child;
- return new TextView(container.getContext());
- }
-
- @Nullable
- public Fragment getChildFragment() {
- return mChildFragment;
- }
- }
-
- public static class ChildFragmentManagerChildFragment extends StrictFragment {
- private String mString;
-
- public ChildFragmentManagerChildFragment() {
- }
-
- public ChildFragmentManagerChildFragment(String arg) {
- final Bundle b = new Bundle();
- b.putString("string", arg);
- setArguments(b);
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mString = requireArguments().getString("string", "NO VALUE");
- }
-
- public String getString() {
- return mString;
- }
- }
-
- public static class SimpleFragment extends Fragment {
- private int mLayoutId;
- private static final String LAYOUT_ID = "layoutId";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(LAYOUT_ID, mLayoutId);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(mLayoutId, container, false);
- }
-
- public static SimpleFragment create(int layoutId) {
- SimpleFragment fragment = new SimpleFragment();
- fragment.mLayoutId = layoutId;
- return fragment;
- }
- }
-
- public static class TargetFragment extends Fragment {
- public boolean calledCreate;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- calledCreate = true;
- }
- }
-
- public static class ReferrerFragment extends Fragment {
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Fragment target = getTargetFragment();
- assertNotNull("target fragment was null during referrer onCreate", target);
-
- if (!(target instanceof TargetFragment)) {
- throw new IllegalStateException("target fragment was not a TargetFragment");
- }
-
- assertTrue("target fragment has not yet been created",
- ((TargetFragment) target).calledCreate);
- }
- }
-
- public static class SaveStateFragment extends Fragment {
- private static final String VALUE_KEY = "SaveStateFragment.mValue";
- private int mValue;
-
- public static SaveStateFragment create(int value) {
- SaveStateFragment saveStateFragment = new SaveStateFragment();
- saveStateFragment.mValue = value;
- return saveStateFragment;
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(VALUE_KEY, mValue);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mValue = savedInstanceState.getInt(VALUE_KEY, mValue);
- }
- }
-
- public int getValue() {
- return mValue;
- }
- }
-
- public static class RemoveHelloInOnResume extends Fragment {
- @Override
- public void onResume() {
- super.onResume();
- Fragment fragment = getFragmentManager().findFragmentByTag("Hello");
- if (fragment != null) {
- getFragmentManager().beginTransaction().remove(fragment).commit();
- }
- }
- }
-
- public static class InvalidateOptionFragment extends Fragment {
- public boolean onPrepareOptionsMenuCalled;
-
- public InvalidateOptionFragment() {
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
- onPrepareOptionsMenuCalled = true;
- assertNotNull(getContext());
- super.onPrepareOptionsMenu(menu);
- }
- }
-
- public static class OnCreateFragment extends Fragment {
- public boolean onCreateCalled;
-
- public OnCreateFragment() {
- setRetainInstance(true);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- onCreateCalled = true;
- }
- }
-
- @ContentView(R.layout.nested_retained_inflated_fragment_parent)
- public static class RetainedInflatedParentFragment extends Fragment {
- }
-
- @ContentView(R.layout.nested_inflated_fragment_child)
- public static class RetainedInflatedChildFragment extends Fragment {
-
- int mOnInflateCount = 0;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setRetainInstance(true);
- }
-
- @Override
- public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
- @Nullable Bundle savedInstanceState) {
- super.onInflate(context, attrs, savedInstanceState);
- mOnInflateCount++;
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
new file mode 100644
index 0000000..bda50e0
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentLifecycleTest.kt
@@ -0,0 +1,808 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Parcelable
+import android.util.AttributeSet
+import android.util.Pair
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.core.view.ViewCompat
+import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
+import androidx.fragment.app.FragmentTestUtil.HostCallbacks
+import androidx.fragment.app.FragmentTestUtil.shutdownFragmentController
+import androidx.fragment.app.FragmentTestUtil.startupFragmentController
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class FragmentLifecycleTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ @Test
+ fun basicLifecycle() {
+ val fm = activityRule.activity.supportFragmentManager
+ val strictFragment = StrictFragment()
+
+ // Add fragment; StrictFragment will throw if it detects any violation
+ // in standard lifecycle method ordering or expected preconditions.
+ fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment is not added").that(strictFragment.isAdded).isTrue()
+ assertWithMessage("fragment is detached").that(strictFragment.isDetached).isFalse()
+ assertWithMessage("fragment is not resumed").that(strictFragment.isResumed).isTrue()
+ val lifecycle = strictFragment.lifecycle
+ assertThat(lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+
+ // Test removal as well; StrictFragment will throw here too.
+ fm.beginTransaction().remove(strictFragment).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment is added").that(strictFragment.isAdded).isFalse()
+ assertWithMessage("fragment is resumed").that(strictFragment.isResumed).isFalse()
+ assertThat(lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+ // Once removed, a new Lifecycle should be created just in case
+ // the developer reuses the same Fragment
+ assertThat(strictFragment.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+
+ // This one is perhaps counterintuitive; "detached" means specifically detached
+ // but still managed by a FragmentManager. The .remove call above
+ // should not enter this state.
+ assertWithMessage("fragment is detached").that(strictFragment.isDetached).isFalse()
+ }
+
+ @Test
+ fun detachment() {
+ val fm = activityRule.activity.supportFragmentManager
+ val f1 = StrictFragment()
+ val f2 = StrictFragment()
+
+ fm.beginTransaction().add(f1, "1").add(f2, "2").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+ assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+
+ // Test detaching fragments using StrictFragment to throw on errors.
+ fm.beginTransaction().detach(f1).detach(f2).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not detached").that(f1.isDetached).isTrue()
+ assertWithMessage("fragment 2 is not detached").that(f2.isDetached).isTrue()
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+
+ // Only reattach f1; leave v2 detached.
+ fm.beginTransaction().attach(f1).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+ assertWithMessage("fragment 1 is detached").that(f1.isDetached).isFalse()
+ assertWithMessage("fragment 2 is not detached").that(f2.isDetached).isTrue()
+
+ // Remove both from the FragmentManager.
+ fm.beginTransaction().remove(f1).remove(f2).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+ assertWithMessage("fragment 1 is detached").that(f1.isDetached).isFalse()
+ assertWithMessage("fragment 2 is detached").that(f2.isDetached).isFalse()
+ }
+
+ @Test
+ fun basicBackStack() {
+ val fm = activityRule.activity.supportFragmentManager
+ val f1 = StrictFragment()
+ val f2 = StrictFragment()
+
+ // Add a fragment normally to set up
+ fm.beginTransaction().add(f1, "1").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+ // Remove the first one and add a second. We're not using replace() here since
+ // these fragments are headless and as of this test writing, replace() only works
+ // for fragments with views and a container view id.
+ // Add it to the back stack so we can pop it afterwards.
+ fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+
+ // Test popping the stack
+ fm.popBackStack()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+ }
+
+ @Test
+ fun attachBackStack() {
+ val fm = activityRule.activity.supportFragmentManager
+ val f1 = StrictFragment()
+ val f2 = StrictFragment()
+
+ // Add a fragment normally to set up
+ fm.beginTransaction().add(f1, "1").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+ fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not detached").that(f1.isDetached).isTrue()
+ assertWithMessage("fragment 2 is detached").that(f2.isDetached).isFalse()
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 2 is not added").that(f2.isAdded).isTrue()
+ }
+
+ @Test
+ fun viewLifecycle() {
+ // Test basic lifecycle when the fragment creates a view
+
+ val fm = activityRule.activity.supportFragmentManager
+ val f1 = StrictViewFragment()
+
+ fm.beginTransaction().add(android.R.id.content, f1).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+ val view = f1.requireView()
+ assertWithMessage("fragment 1 returned null from getView").that(view).isNotNull()
+ assertWithMessage("fragment 1's view is not attached to a window")
+ .that(ViewCompat.isAttachedToWindow(view)).isTrue()
+
+ fm.beginTransaction().remove(f1).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 1 returned non-null from getView after removal")
+ .that(f1.view).isNull()
+ assertWithMessage("fragment 1's previous view is still attached to a window")
+ .that(ViewCompat.isAttachedToWindow(view)).isFalse()
+ }
+
+ @Test
+ fun viewReplace() {
+ // Replace one view with another, then reverse it with the back stack
+
+ val fm = activityRule.activity.supportFragmentManager
+ val f1 = StrictViewFragment()
+ val f2 = StrictViewFragment()
+
+ fm.beginTransaction().add(android.R.id.content, f1).commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+
+ val origView1 = f1.requireView()
+ assertWithMessage("fragment 1 returned null view").that(origView1).isNotNull()
+ assertWithMessage("fragment 1's view not attached")
+ .that(ViewCompat.isAttachedToWindow(origView1)).isTrue()
+
+ fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is added").that(f1.isAdded).isFalse()
+ assertWithMessage("fragment 2 is added").that(f2.isAdded).isTrue()
+ assertWithMessage("fragment 1 returned non-null view").that(f1.view).isNull()
+ assertWithMessage("fragment 1's old view still attached")
+ .that(ViewCompat.isAttachedToWindow(origView1)).isFalse()
+ val origView2 = f2.requireView()
+ assertWithMessage("fragment 2 returned null view").that(origView2).isNotNull()
+ assertWithMessage("fragment 2's view not attached")
+ .that(ViewCompat.isAttachedToWindow(origView2)).isTrue()
+
+ fm.popBackStack()
+ executePendingTransactions(fm)
+
+ assertWithMessage("fragment 1 is not added").that(f1.isAdded).isTrue()
+ assertWithMessage("fragment 2 is added").that(f2.isAdded).isFalse()
+ assertWithMessage("fragment 2 returned non-null view").that(f2.view).isNull()
+ assertWithMessage("fragment 2's view still attached")
+ .that(ViewCompat.isAttachedToWindow(origView2)).isFalse()
+ val newView1 = f1.requireView()
+ assertWithMessage("fragment 1 had same view from last attachment")
+ .that(newView1).isNotSameAs(origView1)
+ assertWithMessage("fragment 1's view not attached")
+ .that(ViewCompat.isAttachedToWindow(newView1)).isTrue()
+ }
+
+ /**
+ * This test confirms that as long as a parent fragment has called super.onCreate,
+ * any child fragments added, committed and with transactions executed will be brought
+ * to at least the CREATED state by the time the parent fragment receives onCreateView.
+ * This means the child fragment will have received onAttach/onCreate.
+ */
+ @Test
+ @UiThreadTest
+ fun childFragmentManagerAttach() {
+ val viewModelStore = ViewModelStore()
+ val fc = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+ fc.attachHost(null)
+ fc.dispatchCreate()
+
+ val mockLc = mock(FragmentManager.FragmentLifecycleCallbacks::class.java)
+ val mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks::class.java)
+
+ val fm = fc.supportFragmentManager
+ fm.registerFragmentLifecycleCallbacks(mockLc, false)
+ fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true)
+
+ val fragment = ChildFragmentManagerFragment()
+ fm.beginTransaction()
+ .add(android.R.id.content, fragment)
+ .commitNow()
+
+ verify<FragmentManager.FragmentLifecycleCallbacks>(mockLc, times(1))
+ .onFragmentCreated(fm, fragment, null)
+
+ fc.dispatchActivityCreated()
+
+ val childFragment = fragment.childFragment!!
+
+ verify<FragmentLifecycleCallbacks>(mockLc, times(1))
+ .onFragmentActivityCreated(fm, fragment, null)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentActivityCreated(fm, fragment, null)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentActivityCreated(fm, childFragment, null)
+
+ fc.dispatchStart()
+
+ verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentStarted(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentStarted(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentStarted(fm, childFragment)
+
+ fc.dispatchResume()
+
+ verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentResumed(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentResumed(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentResumed(fm, childFragment)
+
+ // Confirm that the parent fragment received onAttachFragment
+ assertWithMessage("parent fragment did not receive onAttachFragment")
+ .that(fragment.calledOnAttachFragment).isTrue()
+
+ fc.dispatchStop()
+
+ verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentStopped(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentStopped(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentStopped(fm, childFragment)
+
+ viewModelStore.clear()
+ fc.dispatchDestroy()
+
+ verify<FragmentLifecycleCallbacks>(mockLc, times(1)).onFragmentDestroyed(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentDestroyed(fm, fragment)
+ verify<FragmentLifecycleCallbacks>(mockRecursiveLc, times(1))
+ .onFragmentDestroyed(fm, childFragment)
+ }
+
+ /**
+ * This test checks that FragmentLifecycleCallbacks are invoked when expected.
+ */
+ @Test
+ @UiThreadTest
+ fun fragmentLifecycleCallbacks() {
+ val viewModelStore = ViewModelStore()
+ val fc = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+ fc.attachHost(null)
+ fc.dispatchCreate()
+
+ val fm = fc.supportFragmentManager
+
+ val fragment = ChildFragmentManagerFragment()
+ fm.beginTransaction()
+ .add(android.R.id.content, fragment)
+ .commitNow()
+
+ fc.dispatchActivityCreated()
+
+ fc.dispatchStart()
+ fc.dispatchResume()
+
+ // Confirm that the parent fragment received onAttachFragment
+ assertWithMessage("parent fragment did not receive onAttachFragment")
+ .that(fragment.calledOnAttachFragment).isTrue()
+
+ shutdownFragmentController(fc, viewModelStore)
+ }
+
+ /**
+ * This tests that fragments call onDestroy when the activity finishes.
+ */
+ @Test
+ @UiThreadTest
+ fun fragmentDestroyedOnFinish() {
+ val viewModelStore = ViewModelStore()
+ val fc = startupFragmentController(activityRule.activity, null, viewModelStore)
+ val fm = fc.supportFragmentManager
+
+ val fragmentA = StrictViewFragment(R.layout.fragment_a)
+ val fragmentB = StrictViewFragment(R.layout.fragment_b)
+ fm.beginTransaction()
+ .add(android.R.id.content, fragmentA)
+ .commit()
+ fm.executePendingTransactions()
+ fm.beginTransaction()
+ .replace(android.R.id.content, fragmentB)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ shutdownFragmentController(fc, viewModelStore)
+ assertThat(fragmentB.calledOnDestroy).isTrue()
+ assertThat(fragmentA.calledOnDestroy).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSetArgumentsLifecycle() {
+ val viewModelStore = ViewModelStore()
+ val fc = startupFragmentController(activityRule.activity, null, viewModelStore)
+ val fm = fc.supportFragmentManager
+
+ val f = StrictFragment()
+ f.arguments = Bundle()
+
+ fm.beginTransaction().add(f, "1").commitNow()
+
+ f.arguments = Bundle()
+
+ fc.dispatchPause()
+ fc.saveAllState()
+
+ try {
+ f.arguments = Bundle()
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("Fragment already added and state has been saved")
+ }
+
+ fc.dispatchStop()
+
+ try {
+ f.arguments = Bundle()
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("Fragment already added and state has been saved")
+ }
+
+ viewModelStore.clear()
+ fc.dispatchDestroy()
+
+ // Fully destroyed, so fragments have been removed.
+ f.arguments = Bundle()
+ }
+
+ /**
+ * When a fragment is saved in non-config, it should be restored to the same index.
+ */
+ @Test
+ @UiThreadTest
+ fun restoreNonConfig() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ val fm = fc.supportFragmentManager
+
+ val backStackRetainedFragment = StrictFragment()
+ backStackRetainedFragment.retainInstance = true
+ val fragment1 = StrictFragment()
+ fm.beginTransaction()
+ .add(backStackRetainedFragment, "backStack")
+ .add(fragment1, "1")
+ .setPrimaryNavigationFragment(fragment1)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ val fragment2 = StrictFragment()
+ fragment2.retainInstance = true
+ fragment2.setTargetFragment(fragment1, 0)
+ val fragment3 = StrictFragment()
+ fm.beginTransaction()
+ .remove(backStackRetainedFragment)
+ .remove(fragment1)
+ .add(fragment2, "2")
+ .add(fragment3, "3")
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+
+ val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ var foundFragment2 = false
+ for (fragment in fc.supportFragmentManager.fragments) {
+ if (fragment === fragment2) {
+ foundFragment2 = true
+ assertThat(fragment.getTargetFragment()).isNotNull()
+ assertThat(fragment.getTargetFragment()!!.tag).isEqualTo("1")
+ } else {
+ assertThat(fragment.tag).isNotEqualTo("2")
+ }
+ }
+ assertThat(foundFragment2).isTrue()
+ fc.supportFragmentManager.popBackStackImmediate()
+ val foundBackStackRetainedFragment = fc.supportFragmentManager
+ .findFragmentByTag("backStack")
+ assertWithMessage("Retained Fragment on the back stack was not retained")
+ .that(foundBackStackRetainedFragment).isEqualTo(backStackRetainedFragment)
+ }
+
+ /**
+ * Check that retained fragments in the backstack correctly restored after two "configChanges"
+ */
+ @Test
+ @UiThreadTest
+ fun retainedFragmentInBackstack() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ val fragment1 = StrictFragment()
+ fm.beginTransaction().add(fragment1, "1").addToBackStack(null).commit()
+ fm.executePendingTransactions()
+
+ val child = StrictFragment()
+ child.retainInstance = true
+ fragment1.childFragmentManager.beginTransaction().add(child, "child").commit()
+ fragment1.childFragmentManager.executePendingTransactions()
+
+ val fragment2 = StrictFragment()
+ fm.beginTransaction().remove(fragment1).add(fragment2, "2").addToBackStack(null).commit()
+ fm.executePendingTransactions()
+
+ var savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ savedState = FragmentTestUtil.destroy(activityRule, fc)
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ fm = fc.supportFragmentManager
+ fm.popBackStackImmediate()
+ val retainedChild = fm.findFragmentByTag("1")!!
+ .childFragmentManager.findFragmentByTag("child")
+ assertThat(retainedChild).isEqualTo(child)
+ }
+
+ /**
+ * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments
+ * should be null
+ */
+ @Test
+ @UiThreadTest
+ fun nullNonConfig() {
+ val fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ val fm = fc.supportFragmentManager
+
+ val fragment1 = StrictFragment()
+ fm.beginTransaction().add(fragment1, "1").addToBackStack(null).commit()
+ fm.executePendingTransactions()
+ val savedState = FragmentTestUtil.destroy(activityRule, fc)
+ assertThat(savedState.second).isNull()
+ }
+
+ /**
+ * When the FragmentManager state changes, the pending transactions should execute.
+ */
+ @Test
+ @UiThreadTest
+ fun runTransactionsOnChange() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ val fragment1 = RemoveHelloInOnResume()
+ val fragment2 = StrictFragment()
+ fm.beginTransaction().add(fragment1, "1").setReorderingAllowed(false).commit()
+ fm.beginTransaction().add(fragment2, "Hello").setReorderingAllowed(false).commit()
+ fm.executePendingTransactions()
+
+ assertThat(fm.fragments.size).isEqualTo(2)
+ assertThat(fm.fragments.contains(fragment1)).isTrue()
+ assertThat(fm.fragments.contains(fragment2)).isTrue()
+
+ val savedState = FragmentTestUtil.destroy(activityRule, fc)
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ fm = fc.supportFragmentManager
+
+ assertThat(fm.fragments.size).isEqualTo(1)
+ for (fragment in fm.fragments) {
+ assertThat(fragment is RemoveHelloInOnResume).isTrue()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun optionsMenu() {
+ val fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ val fm = fc.supportFragmentManager
+
+ val fragment = InvalidateOptionFragment()
+ fm.beginTransaction().add(android.R.id.content, fragment).commit()
+ fm.executePendingTransactions()
+
+ val menu = mock(Menu::class.java)
+ fc.dispatchPrepareOptionsMenu(menu)
+ assertThat(fragment.onPrepareOptionsMenuCalled).isTrue()
+ fragment.onPrepareOptionsMenuCalled = false
+ FragmentTestUtil.destroy(activityRule, fc)
+ fc.dispatchPrepareOptionsMenu(menu)
+ assertThat(fragment.onPrepareOptionsMenuCalled).isFalse()
+ }
+
+ /**
+ * When a retained instance fragment is saved while in the back stack, it should go
+ * through onCreate() when it is popped back.
+ */
+ @Test
+ @UiThreadTest
+ fun retainInstanceWithOnCreate() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ val fragment1 = OnCreateFragment()
+
+ fm.beginTransaction().add(fragment1, "1").commit()
+ fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+
+ var savedState = FragmentTestUtil.destroy(activityRule, fc)
+ val restartState = Pair.create<Parcelable, FragmentManagerNonConfig>(savedState.first, null)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, restartState)
+
+ // Save again, but keep the state
+ savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+
+ fm = fc.supportFragmentManager
+
+ fm.popBackStackImmediate()
+ val fragment2 = fm.findFragmentByTag("1") as OnCreateFragment
+ assertThat(fragment2.onCreateCalled).isTrue()
+ fm.popBackStackImmediate()
+ }
+
+ /**
+ * A retained instance fragment should go through onCreate() once, even through save and
+ * restore.
+ */
+ @Test
+ @UiThreadTest
+ fun retainInstanceOneOnCreate() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ val fragment = OnCreateFragment()
+
+ fm.beginTransaction().add(fragment, "fragment").commit()
+ fm.executePendingTransactions()
+
+ fm.beginTransaction().remove(fragment).addToBackStack(null).commit()
+
+ assertThat(fragment.onCreateCalled).isTrue()
+ fragment.onCreateCalled = false
+
+ val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ fm = fc.supportFragmentManager
+
+ fm.popBackStackImmediate()
+ assertThat(fragment.onCreateCalled).isFalse()
+ }
+
+ /**
+ * A retained instance fragment added via XML should go through onCreate() once, but should get
+ * onInflate calls for each inflation.
+ */
+ @Test
+ @UiThreadTest
+ fun retainInstanceLayoutOnInflate() {
+ var fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ var parentFragment = RetainedInflatedParentFragment()
+
+ fm.beginTransaction().add(android.R.id.content, parentFragment).commit()
+ fm.executePendingTransactions()
+
+ val childFragment = parentFragment.childFragmentManager
+ .findFragmentById(R.id.child_fragment) as RetainedInflatedChildFragment
+
+ fm.beginTransaction().remove(parentFragment).addToBackStack(null).commit()
+
+ val savedState = FragmentTestUtil.destroy(activityRule, fc)
+
+ fc = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc, savedState)
+ fm = fc.supportFragmentManager
+
+ fm.popBackStackImmediate()
+
+ parentFragment = fm.findFragmentById(android.R.id.content) as RetainedInflatedParentFragment
+ val childFragment2 = parentFragment.childFragmentManager
+ .findFragmentById(R.id.child_fragment) as RetainedInflatedChildFragment
+
+ assertWithMessage("Child Fragment should be retained")
+ .that(childFragment2).isEqualTo(childFragment)
+ assertWithMessage("Child Fragment should have onInflate called twice")
+ .that(childFragment2.mOnInflateCount).isEqualTo(2)
+ }
+
+ private fun executePendingTransactions(fm: FragmentManager) {
+ activityRule.runOnUiThread { fm.executePendingTransactions() }
+ }
+
+ /**
+ * This tests a deliberately odd use of a child fragment, added in onCreateView instead
+ * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy
+ * created by this fragment.
+ */
+ class ChildFragmentManagerFragment : StrictFragment() {
+ private lateinit var savedChildFragmentManager: FragmentManager
+ private lateinit var childFragmentManagerChildFragment: ChildFragmentManagerChildFragment
+
+ val childFragment: Fragment?
+ get() = childFragmentManagerChildFragment
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ savedChildFragmentManager = childFragmentManager
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = TextView(inflater.context).also {
+ assertWithMessage("child FragmentManagers not the same instance")
+ .that(childFragmentManager).isSameAs(savedChildFragmentManager)
+ var child = savedChildFragmentManager
+ .findFragmentByTag("tag") as ChildFragmentManagerChildFragment?
+ if (child == null) {
+ child = ChildFragmentManagerChildFragment("foo")
+ savedChildFragmentManager.beginTransaction().add(child, "tag").commitNow()
+ assertWithMessage("argument strings don't match")
+ .that(child.string).isEqualTo("foo")
+ }
+ childFragmentManagerChildFragment = child
+ }
+ }
+
+ class ChildFragmentManagerChildFragment : StrictFragment {
+ lateinit var string: String
+ private set
+
+ constructor()
+
+ constructor(arg: String) {
+ val b = Bundle()
+ b.putString("string", arg)
+ arguments = b
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ string = requireArguments().getString("string", "NO VALUE")
+ }
+ }
+
+ class RemoveHelloInOnResume : Fragment() {
+ override fun onResume() {
+ super.onResume()
+ val fragment = fragmentManager!!.findFragmentByTag("Hello")
+ if (fragment != null) {
+ fragmentManager!!.beginTransaction().remove(fragment).commit()
+ }
+ }
+ }
+
+ class InvalidateOptionFragment : Fragment() {
+ var onPrepareOptionsMenuCalled: Boolean = false
+
+ init {
+ setHasOptionsMenu(true)
+ }
+
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ onPrepareOptionsMenuCalled = true
+ assertThat(context).isNotNull()
+ super.onPrepareOptionsMenu(menu)
+ }
+ }
+
+ class OnCreateFragment : Fragment() {
+ var onCreateCalled: Boolean = false
+
+ init {
+ retainInstance = true
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ onCreateCalled = true
+ }
+ }
+
+ class RetainedInflatedParentFragment :
+ Fragment(R.layout.nested_retained_inflated_fragment_parent)
+
+ class RetainedInflatedChildFragment : Fragment(R.layout.nested_inflated_fragment_child) {
+ internal var mOnInflateCount = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ retainInstance = true
+ }
+
+ override fun onInflate(context: Context, attrs: AttributeSet, savedInstanceState: Bundle?) {
+ super.onInflate(context, attrs, savedInstanceState)
+ mOnInflateCount++
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.java
deleted file mode 100644
index b65b191..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import android.os.Build;
-
-import androidx.fragment.app.test.NonConfigOnStopActivity;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.rule.ActivityTestRule;
-import androidx.testutils.FragmentActivityUtils;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentManagerNonConfigTest {
-
- @Rule
- public ActivityTestRule<NonConfigOnStopActivity> mActivityRule =
- new ActivityTestRule<>(NonConfigOnStopActivity.class);
-
- /**
- * When a fragment is added during onStop(), it shouldn't show up in non-config
- * state when restored before P, because OnSaveInstanceState was already called.
- */
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
- public void nonConfigStop() throws Throwable {
- FragmentActivity activity = FragmentActivityUtils.recreateActivity(mActivityRule,
- mActivityRule.getActivity());
-
- // A fragment was added in onStop(), but we shouldn't see it here...
- assertTrue(activity.getSupportFragmentManager().getFragments().isEmpty());
- }
-
- /**
- * When a fragment is added during onStop(), it shouldn't show up in non-config
- * state when restored after (>=) P, because OnSaveInstanceState isn't yet called.
- */
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
- public void nonConfigStopSavingFragment() throws Throwable {
- FragmentActivity activity = FragmentActivityUtils.recreateActivity(mActivityRule,
- mActivityRule.getActivity());
-
- assertThat(activity.getSupportFragmentManager().getFragments().size(), is(1));
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.kt
new file mode 100644
index 0000000..6d47436
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentManagerNonConfigTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Build
+import androidx.fragment.app.test.NonConfigOnStopActivity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import androidx.testutils.FragmentActivityUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class FragmentManagerNonConfigTest {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(NonConfigOnStopActivity::class.java)
+
+ /**
+ * When a fragment is added during onStop(), it shouldn't show up in non-config
+ * state when restored before P, because OnSaveInstanceState was already called.
+ */
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.O_MR1)
+ fun nonConfigStop() {
+ val activity = FragmentActivityUtils.recreateActivity(
+ activityRule,
+ activityRule.activity
+ )
+
+ // A fragment was added in onStop(), but we shouldn't see it here...
+ assertThat(activity.supportFragmentManager.fragments.isEmpty()).isTrue()
+ }
+
+ /**
+ * When a fragment is added during onStop(), it shouldn't show up in non-config
+ * state when restored after (>=) P, because OnSaveInstanceState isn't yet called.
+ */
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P)
+ fun nonConfigStopSavingFragment() {
+ val activity = FragmentActivityUtils.recreateActivity(
+ activityRule,
+ activityRule.activity
+ )
+
+ assertThat(activity.supportFragmentManager.fragments.size).isEqualTo(1)
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.java
deleted file mode 100644
index 1c47bc8..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.java
+++ /dev/null
@@ -1,683 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Instrumentation;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentReorderingTest {
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private ViewGroup mContainer;
- private FragmentManager mFM;
- private Instrumentation mInstrumentation;
-
- @Before
- public void setup() {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- mContainer = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- mFM = mActivityRule.getActivity().getSupportFragmentManager();
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- }
-
- // Test that when you add and replace a fragment that only the replace's add
- // actually creates a View.
- @Test
- public void addReplace() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final StrictViewFragment fragment2 = new StrictViewFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
- assertEquals(0, fragment1.onCreateViewCount);
- FragmentTestUtil.assertChildren(mContainer, fragment2);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.popBackStack();
- mFM.popBackStack();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer);
- }
-
- // Test that it is possible to merge a transaction that starts with pop and adds
- // the same view back again.
- @Test
- public void startWithPop() throws Throwable {
- // Start with a single fragment on the back stack
- final CountCallsFragment fragment1 = new CountCallsFragment();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- // Now pop and add
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.popBackStack();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- assertEquals(1, fragment1.onCreateViewCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer);
- assertEquals(1, fragment1.onCreateViewCount);
- }
-
- // Popping the back stack in the middle of other operations doesn't fool it.
- @Test
- public void middlePop() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final CountCallsFragment fragment2 = new CountCallsFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.popBackStack();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer, fragment2);
- assertEquals(0, fragment1.onAttachCount);
- assertEquals(1, fragment2.onCreateViewCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer);
- assertEquals(1, fragment2.onDetachCount);
- }
-
- // ensure that removing a view after adding it is optimized into no
- // View being created. Hide still gets notified.
- @Test
- public void removeRedundantRemove() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final int[] id = new int[1];
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- id[0] = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .remove(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer);
- assertEquals(0, fragment1.onCreateViewCount);
- assertEquals(1, fragment1.onHideCount);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(0, fragment1.onDetachCount);
- assertEquals(1, fragment1.onAttachCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id[0],
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
- FragmentTestUtil.assertChildren(mContainer);
- assertEquals(0, fragment1.onCreateViewCount);
- assertEquals(1, fragment1.onHideCount);
- assertEquals(1, fragment1.onShowCount);
- assertEquals(1, fragment1.onDetachCount);
- assertEquals(1, fragment1.onAttachCount);
- }
-
- // Ensure that removing and adding the same view results in no operation
- @Test
- public void removeRedundantAdd() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(1, fragment1.onCreateViewCount);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .remove(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- // should be optimized out
- assertEquals(1, fragment1.onCreateViewCount);
-
- mFM.popBackStack(id, 0);
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- // optimize out going back, too
- assertEquals(1, fragment1.onCreateViewCount);
- }
-
- // detaching, then attaching results in on change. Hide still functions
- @Test
- public void removeRedundantAttach() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(1, fragment1.onAttachCount);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .detach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .attach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- // can optimize out the detach/attach
- assertEquals(0, fragment1.onDestroyViewCount);
- assertEquals(1, fragment1.onHideCount);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onCreateViewCount);
- assertEquals(1, fragment1.onAttachCount);
- assertEquals(0, fragment1.onDetachCount);
-
- mFM.popBackStack(id, 0);
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- // optimized out again, but not the show
- assertEquals(0, fragment1.onDestroyViewCount);
- assertEquals(1, fragment1.onHideCount);
- assertEquals(1, fragment1.onShowCount);
- assertEquals(1, fragment1.onCreateViewCount);
- assertEquals(1, fragment1.onAttachCount);
- assertEquals(0, fragment1.onDetachCount);
- }
-
- // attaching, then detaching shouldn't result in a View being created
- @Test
- public void removeRedundantDetach() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .detach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- // the add detach is not fully optimized out
- assertEquals(1, fragment1.onAttachCount);
- assertEquals(0, fragment1.onDetachCount);
- assertTrue(fragment1.isDetached());
- assertEquals(0, fragment1.onCreateViewCount);
- FragmentTestUtil.assertChildren(mContainer);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .attach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .detach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- FragmentTestUtil.assertChildren(mContainer);
- // can optimize out the attach/detach, and the hide call
- assertEquals(1, fragment1.onAttachCount);
- assertEquals(0, fragment1.onDetachCount);
- assertEquals(1, fragment1.onHideCount);
- assertTrue(fragment1.isHidden());
- assertEquals(0, fragment1.onShowCount);
-
- mFM.popBackStack(id, 0);
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer);
-
- // we can optimize out the attach/detach on the way back
- assertEquals(1, fragment1.onAttachCount);
- assertEquals(0, fragment1.onDetachCount);
- assertEquals(1, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
- assertFalse(fragment1.isHidden());
- }
-
- // show, then hide should optimize out
- @Test
- public void removeRedundantHide() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .show(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .remove(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- // optimize out hide/show
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- // still optimized out
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .show(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- // The show/hide can be optimized out and nothing should change.
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .show(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .detach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .attach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- // the detach/attach should not affect the show/hide, so show/hide should cancel each other
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- assertEquals(0, fragment1.onShowCount);
- assertEquals(1, fragment1.onHideCount);
- }
-
- // hiding and showing the same view should optimize out
- @Test
- public void removeRedundantShow() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(0, fragment1.onHideCount);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .hide(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .detach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .attach(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .show(fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- }
- });
-
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- // can optimize out the show/hide
- assertEquals(0, fragment1.onShowCount);
- assertEquals(0, fragment1.onHideCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id,
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
- assertEquals(0, fragment1.onShowCount);
- assertEquals(0, fragment1.onHideCount);
- }
-
- // The View order shouldn't be messed up by reordering -- a view that
- // is optimized to not remove/add should be in its correct position after
- // the transaction completes.
- @Test
- public void viewOrder() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- int id = mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
-
- final CountCallsFragment fragment2 = new CountCallsFragment();
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer, fragment2, fragment1);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, id, 0);
- FragmentTestUtil.assertChildren(mContainer, fragment1);
- }
-
- // Popping an added transaction results in no operation
- @Test
- public void addPopBackStack() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.popBackStack();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer);
-
- // Was never instantiated because it was popped before anything could happen
- assertEquals(0, fragment1.onCreateViewCount);
- }
-
- // A non-back-stack transaction doesn't interfere with back stack add/pop
- // optimization.
- @Test
- public void popNonBackStack() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final CountCallsFragment fragment2 = new CountCallsFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .setReorderingAllowed(true)
- .commit();
- mFM.popBackStack();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer, fragment2);
-
- // It should be optimized with the replace, so no View creation
- assertEquals(0, fragment1.onCreateViewCount);
- }
-
- // When reordering is disabled, the transaction prior to the disabled reordering
- // transaction should all be run prior to running the ordered transaction.
- @Test
- public void noReordering() throws Throwable {
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final CountCallsFragment fragment2 = new CountCallsFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFM.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(false)
- .commit();
- mFM.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(mContainer, fragment2);
-
- // No reordering, so fragment1 should have created its View
- assertEquals(1, fragment1.onCreateViewCount);
- }
-
- // Test that a fragment view that is created with focus has focus after the transaction
- // completes.
- @UiThreadTest
- @Test
- public void focusedView() throws Throwable {
- mActivityRule.getActivity().setContentView(R.layout.double_container);
- mContainer = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
- EditText firstEditText = new EditText(mContainer.getContext());
- mContainer.addView(firstEditText);
- firstEditText.requestFocus();
-
- assertTrue(firstEditText.isFocused());
- final CountCallsFragment fragment1 = new CountCallsFragment();
- final CountCallsFragment fragment2 = new CountCallsFragment();
- fragment2.setLayoutId(R.layout.with_edit_text);
- mFM.beginTransaction()
- .add(R.id.fragmentContainer2, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.beginTransaction()
- .replace(R.id.fragmentContainer2, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- mFM.executePendingTransactions();
- final View editText = fragment2.requireView().findViewById(R.id.editText);
- assertTrue(editText.isFocused());
- assertFalse(firstEditText.isFocused());
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
new file mode 100644
index 0000000..6877430
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReorderingTest.kt
@@ -0,0 +1,633 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.app.Instrumentation
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FragmentReorderingTest {
+ @get:Rule
+ var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private lateinit var container: ViewGroup
+ private lateinit var fm: FragmentManager
+ private lateinit var instrumentation: Instrumentation
+
+ @Before
+ fun setup() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ container = activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ fm = activityRule.activity.supportFragmentManager
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ }
+
+ // Test that when you add and replace a fragment that only the replace's add
+ // actually creates a View.
+ @Test
+ fun addReplace() {
+ val fragment1 = CountCallsFragment()
+ val fragment2 = StrictViewFragment()
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ FragmentTestUtil.assertChildren(container, fragment2)
+
+ instrumentation.runOnMainSync {
+ fm.popBackStack()
+ fm.popBackStack()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container)
+ }
+
+ // Test that it is possible to merge a transaction that starts with pop and adds
+ // the same view back again.
+ @Test
+ fun startWithPop() {
+ // Start with a single fragment on the back stack
+ val fragment1 = CountCallsFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ // Now pop and add
+ instrumentation.runOnMainSync {
+ fm.popBackStack()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment1)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ }
+
+ // Popping the back stack in the middle of other operations doesn't fool it.
+ @Test
+ fun middlePop() {
+ val fragment1 = CountCallsFragment()
+ val fragment2 = CountCallsFragment()
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.popBackStack()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment2)
+ assertThat(fragment1.onAttachCount).isEqualTo(0)
+ assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment2.onDetachCount).isEqualTo(1)
+ }
+
+ // ensure that removing a view after adding it is optimized into no
+ // View being created. Hide still gets notified.
+ @Test
+ fun removeRedundantRemove() {
+ val fragment1 = CountCallsFragment()
+ var id = -1
+ instrumentation.runOnMainSync {
+ id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .remove(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(
+ activityRule,
+ id,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.onShowCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(1)
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ }
+
+ // Ensure that removing and adding the same view results in no operation
+ @Test
+ fun removeRedundantAdd() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .remove(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+ // should be optimized out
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+
+ fm.popBackStack(id, 0)
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+ // optimize out going back, too
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ }
+
+ // detaching, then attaching results in on change. Hide still functions
+ @Test
+ fun removeRedundantAttach() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+ // can optimize out the detach/attach
+ assertThat(fragment1.onDestroyViewCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+
+ fm.popBackStack(id, 0)
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ // optimized out again, but not the show
+ assertThat(fragment1.onDestroyViewCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.onShowCount).isEqualTo(1)
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+ }
+
+ // attaching, then detaching shouldn't result in a View being created
+ @Test
+ fun removeRedundantDetach() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ // the add detach is not fully optimized out
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+ assertThat(fragment1.isDetached).isTrue()
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ FragmentTestUtil.assertChildren(container)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ FragmentTestUtil.assertChildren(container)
+ // can optimize out the attach/detach, and the hide call
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.isHidden).isTrue()
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+
+ fm.popBackStack(id, 0)
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+
+ // we can optimize out the attach/detach on the way back
+ assertThat(fragment1.onAttachCount).isEqualTo(1)
+ assertThat(fragment1.onDetachCount).isEqualTo(0)
+ assertThat(fragment1.onShowCount).isEqualTo(1)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ assertThat(fragment1.isHidden).isFalse()
+ }
+
+ // show, then hide should optimize out
+ @Test
+ fun removeRedundantHide() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .remove(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+ // optimize out hide/show
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ // still optimized out
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ // The show/hide can be optimized out and nothing should change.
+ FragmentTestUtil.assertChildren(container, fragment1)
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ // the detach/attach should not affect the show/hide, so show/hide should cancel each other
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(1)
+ }
+
+ // hiding and showing the same view should optimize out
+ @Test
+ fun removeRedundantShow() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(0)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .hide(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .detach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .attach(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .show(fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ }
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+ // can optimize out the show/hide
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(0)
+
+ FragmentTestUtil.popBackStackImmediate(
+ activityRule, id,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+ assertThat(fragment1.onShowCount).isEqualTo(0)
+ assertThat(fragment1.onHideCount).isEqualTo(0)
+ }
+
+ // The View order shouldn't be messed up by reordering -- a view that
+ // is optimized to not remove/add should be in its correct position after
+ // the transaction completes.
+ @Test
+ fun viewOrder() {
+ val fragment1 = CountCallsFragment()
+ val id = fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ val fragment2 = CountCallsFragment()
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment2, fragment1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule, id, 0)
+ FragmentTestUtil.assertChildren(container, fragment1)
+ }
+
+ // Popping an added transaction results in no operation
+ @Test
+ fun addPopBackStack() {
+ val fragment1 = CountCallsFragment()
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.popBackStack()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container)
+
+ // Was never instantiated because it was popped before anything could happen
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ }
+
+ // A non-back-stack transaction doesn't interfere with back stack add/pop
+ // optimization.
+ @Test
+ fun popNonBackStack() {
+ val fragment1 = CountCallsFragment()
+ val fragment2 = CountCallsFragment()
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.popBackStack()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment2)
+
+ // It should be optimized with the replace, so no View creation
+ assertThat(fragment1.onCreateViewCount).isEqualTo(0)
+ }
+
+ // When reordering is disabled, the transaction prior to the disabled reordering
+ // transaction should all be run prior to running the ordered transaction.
+ @Test
+ fun noReordering() {
+ val fragment1 = CountCallsFragment()
+ val fragment2 = CountCallsFragment()
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(false)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment2)
+
+ // No reordering, so fragment1 should have created its View
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ }
+
+ // Test that a fragment view that is created with focus has focus after the transaction
+ // completes.
+ @UiThreadTest
+ @Test
+ fun focusedView() {
+ activityRule.activity.setContentView(R.layout.double_container)
+ container = activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+ val firstEditText = EditText(container.context)
+ container.addView(firstEditText)
+ firstEditText.requestFocus()
+
+ assertThat(firstEditText.isFocused).isTrue()
+ val fragment1 = CountCallsFragment()
+ val fragment2 = CountCallsFragment(R.layout.with_edit_text)
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer2, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer2, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ fm.executePendingTransactions()
+ val editText = fragment2.requireView().findViewById<View>(R.id.editText)
+ assertThat(editText.isFocused).isTrue()
+ assertThat(firstEditText.isFocused).isFalse()
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
index 1430927..2284c44 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentReplaceTest.kt
@@ -19,7 +19,6 @@
import android.view.KeyEvent
import android.view.View
import androidx.fragment.app.test.FragmentTestActivity
-import androidx.fragment.app.test.FragmentTestActivity.TestFragment
import androidx.fragment.test.R
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
@@ -53,7 +52,7 @@
val fm = activity.supportFragmentManager
fm.beginTransaction()
- .add(R.id.content, TestFragment.create(R.layout.fragment_a))
+ .add(R.id.content, StrictViewFragment(R.layout.fragment_a))
.addToBackStack(null)
.commit()
executePendingTransactions(fm)
@@ -62,7 +61,7 @@
assertThat(activity.findViewById<View>(R.id.textC)).isNull()
fm.beginTransaction()
- .add(R.id.content, TestFragment.create(R.layout.fragment_b))
+ .add(R.id.content, StrictViewFragment(R.layout.fragment_b))
.addToBackStack(null)
.commit()
executePendingTransactions(fm)
@@ -71,7 +70,7 @@
assertThat(activity.findViewById<View>(R.id.textC)).isNull()
activity.supportFragmentManager.beginTransaction()
- .replace(R.id.content, TestFragment.create(R.layout.fragment_c))
+ .replace(R.id.content, StrictViewFragment(R.layout.fragment_c))
.addToBackStack(null)
.commit()
executePendingTransactions(fm)
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java
deleted file mode 100644
index 0558eee..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.Animation;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.RequiresApi;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Miscellaneous tests for fragments that aren't big enough to belong to their own classes.
- */
-@RunWith(AndroidJUnit4.class)
-public class FragmentTest {
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private FragmentTestActivity mActivity;
- private Instrumentation mInstrumentation;
-
- @Before
- public void setup() {
- mActivity = mActivityRule.getActivity();
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- }
-
- @SmallTest
- @UiThreadTest
- @Test
- public void testRequireView() {
- StrictViewFragment fragment1 = new StrictViewFragment();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.content, fragment1)
- .commitNow();
- assertThat(fragment1.requireView())
- .isNotNull();
- }
-
- @SmallTest
- @UiThreadTest
- @Test(expected = IllegalStateException.class)
- public void testRequireViewWithoutView() {
- StrictFragment fragment1 = new StrictFragment();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .add(fragment1, "fragment")
- .commitNow();
- fragment1.requireView();
- }
-
- @SmallTest
- @UiThreadTest
- @Test
- public void testOnCreateOrder() throws Throwable {
- OrderFragment fragment1 = new OrderFragment();
- OrderFragment fragment2 = new OrderFragment();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.content, fragment1)
- .add(R.id.content, fragment2)
- .commitNow();
- assertEquals(0, fragment1.createOrder);
- assertEquals(1, fragment2.createOrder);
- }
-
- @LargeTest
- @Test
- @SdkSuppress(minSdkVersion = 16) // waitForHalfFadeIn requires API 16
- public void testChildFragmentManagerGone() throws Throwable {
- final FragmentA fragmentA = new FragmentA();
- final FragmentB fragmentB = new FragmentB();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragmentA)
- .commitNow();
- }
- });
- mInstrumentation.waitForIdleSync();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mActivity.getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
- R.anim.long_fade_in, R.anim.long_fade_out)
- .replace(R.id.content, fragmentB)
- .addToBackStack(null)
- .commit();
- }
- });
- // Wait for the middle of the animation
- waitForHalfFadeIn(fragmentB);
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mActivity.getSupportFragmentManager().beginTransaction()
- .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
- R.anim.long_fade_in, R.anim.long_fade_out)
- .replace(R.id.content, fragmentA)
- .addToBackStack(null)
- .commit();
- }
- });
- // Wait for the middle of the animation
- waitForHalfFadeIn(fragmentA);
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- // Wait for the middle of the animation
- waitForHalfFadeIn(fragmentB);
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- }
-
- @RequiresApi(16) // ViewTreeObserver.OnDrawListener was added in API 16
- private void waitForHalfFadeIn(Fragment fragment) throws Throwable {
- if (fragment.getView() == null) {
- FragmentTestUtil.waitForExecution(mActivityRule);
- }
- final View view = fragment.requireView();
- final Animation animation = view.getAnimation();
- if (animation == null || animation.hasEnded()) {
- // animation has already completed
- return;
- }
-
- final long startTime = animation.getStartTime();
- if (view.getDrawingTime() > animation.getStartTime()) {
- return; // We've already done at least one frame
- }
- final CountDownLatch latch = new CountDownLatch(1);
- final ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
- ViewTreeObserver.OnDrawListener listener = new ViewTreeObserver.OnDrawListener() {
- @Override
- public void onDraw() {
- if (animation.hasEnded() || view.getDrawingTime() > startTime) {
- final ViewTreeObserver.OnDrawListener onDrawListener = this;
- latch.countDown();
- view.post(new Runnable() {
- @Override
- public void run() {
- viewTreeObserver.removeOnDrawListener(onDrawListener);
- }
- });
- }
- }
- };
- viewTreeObserver.addOnDrawListener(listener);
- latch.await(5, TimeUnit.SECONDS);
- }
-
- @MediumTest
- @UiThreadTest
- @Test
- public void testViewOrder() throws Throwable {
- FragmentA fragmentA = new FragmentA();
- FragmentB fragmentB = new FragmentB();
- FragmentC fragmentC = new FragmentC();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.content, fragmentA)
- .add(R.id.content, fragmentB)
- .add(R.id.content, fragmentC)
- .commitNow();
- ViewGroup content = (ViewGroup) mActivity.findViewById(R.id.content);
- assertEquals(3, content.getChildCount());
- assertNotNull(content.getChildAt(0).findViewById(R.id.textA));
- assertNotNull(content.getChildAt(1).findViewById(R.id.textB));
- assertNotNull(content.getChildAt(2).findViewById(R.id.textC));
- }
-
- @SmallTest
- @UiThreadTest
- @Test
- public void testRequireParentFragment() {
- StrictFragment parentFragment = new StrictFragment();
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(parentFragment, "parent")
- .commitNow();
-
- FragmentManager childFragmentManager = parentFragment.getChildFragmentManager();
- StrictFragment childFragment = new StrictFragment();
- childFragmentManager.beginTransaction()
- .add(childFragment, "child")
- .commitNow();
- assertThat(childFragment.requireParentFragment())
- .isSameAs(parentFragment);
- }
-
- @SmallTest
- @Test
- public void requireMethodsThrowsWhenNotAttached() {
- Fragment fragment = new Fragment();
- try {
- fragment.requireContext();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- fragment.requireActivity();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- fragment.requireHost();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- fragment.requireParentFragment();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- fragment.requireView();
- fail();
- } catch (IllegalStateException expected) {
- }
- try {
- fragment.requireFragmentManager();
- fail();
- } catch (IllegalStateException expected) {
- }
- }
-
- @SmallTest
- @Test
- public void requireArguments() {
- Fragment fragment = new Fragment();
- try {
- fragment.requireArguments();
- fail();
- } catch (IllegalStateException expected) {
- }
- Bundle arguments = new Bundle();
- fragment.setArguments(arguments);
- assertWithMessage("requireArguments should return the arguments")
- .that(fragment.requireArguments())
- .isSameAs(arguments);
- }
-
- public static class OrderFragment extends Fragment {
- private static AtomicInteger sOrder = new AtomicInteger();
- public int createOrder = -1;
-
- public OrderFragment() {}
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- createOrder = sOrder.getAndIncrement();
- super.onCreate(savedInstanceState);
- }
- }
-
- @ContentView(R.layout.fragment_a)
- public static class FragmentA extends Fragment {
- }
-
- @ContentView(R.layout.fragment_b)
- public static class FragmentB extends Fragment {
- }
-
- @ContentView(R.layout.fragment_c)
- public static class FragmentC extends Fragment {
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt
new file mode 100644
index 0000000..85adb6c7
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTest.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Miscellaneous tests for fragments that aren't big enough to belong to their own classes.
+ */
+@RunWith(AndroidJUnit4::class)
+class FragmentTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private lateinit var activity: FragmentTestActivity
+ private lateinit var instrumentation: Instrumentation
+
+ @Before
+ fun setup() {
+ activity = activityRule.activity
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ }
+
+ @SmallTest
+ @UiThreadTest
+ @Test
+ fun testRequireView() {
+ val fragment1 = StrictViewFragment()
+ activity.supportFragmentManager.beginTransaction().add(R.id.content, fragment1).commitNow()
+ assertThat(fragment1.requireView()).isNotNull()
+ }
+
+ @SmallTest
+ @UiThreadTest
+ @Test(expected = IllegalStateException::class)
+ fun testRequireViewWithoutView() {
+ val fragment1 = StrictFragment()
+ activity.supportFragmentManager.beginTransaction().add(fragment1, "fragment").commitNow()
+ fragment1.requireView()
+ }
+
+ @SmallTest
+ @UiThreadTest
+ @Test
+ fun testOnCreateOrder() {
+ val fragment1 = OrderFragment()
+ val fragment2 = OrderFragment()
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment1)
+ .add(R.id.content, fragment2)
+ .commitNow()
+ assertThat(fragment1.createOrder).isEqualTo(0)
+ assertThat(fragment2.createOrder).isEqualTo(1)
+ }
+
+ @LargeTest
+ @Test
+ @SdkSuppress(minSdkVersion = 16) // waitForHalfFadeIn requires API 16
+ fun testChildFragmentManagerGone() {
+ val fragmentA = FragmentA()
+ val fragmentB = FragmentB()
+ activityRule.runOnUiThread {
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragmentA)
+ .commitNow()
+ }
+ instrumentation.waitForIdleSync()
+ activityRule.runOnUiThread {
+ activity.supportFragmentManager.beginTransaction()
+ .setCustomAnimations(
+ R.anim.long_fade_in, R.anim.long_fade_out,
+ R.anim.long_fade_in, R.anim.long_fade_out
+ )
+ .replace(R.id.content, fragmentB)
+ .addToBackStack(null)
+ .commit()
+ }
+ // Wait for the middle of the animation
+ waitForHalfFadeIn(fragmentB)
+
+ activityRule.runOnUiThread {
+ activity.supportFragmentManager.beginTransaction()
+ .setCustomAnimations(
+ R.anim.long_fade_in, R.anim.long_fade_out,
+ R.anim.long_fade_in, R.anim.long_fade_out
+ )
+ .replace(R.id.content, fragmentA)
+ .addToBackStack(null)
+ .commit()
+ }
+ // Wait for the middle of the animation
+ waitForHalfFadeIn(fragmentA)
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ // Wait for the middle of the animation
+ waitForHalfFadeIn(fragmentB)
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ }
+
+ @SmallTest
+ @Test
+ fun testChildFragmentManagerNotAttached() {
+ val fragment = Fragment()
+ try {
+ fragment.childFragmentManager
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment has not been attached yet.")
+ }
+ }
+
+ @RequiresApi(16) // ViewTreeObserver.OnDrawListener was added in API 16
+ private fun waitForHalfFadeIn(fragment: Fragment) {
+ if (fragment.view == null) {
+ FragmentTestUtil.waitForExecution(activityRule)
+ }
+ val view = fragment.requireView()
+ val animation = view.animation
+ if (animation == null || animation.hasEnded()) {
+ // animation has already completed
+ return
+ }
+
+ val startTime = animation.startTime
+ if (view.drawingTime > animation.startTime) {
+ return // We've already done at least one frame
+ }
+ val latch = CountDownLatch(1)
+ val viewTreeObserver = view.viewTreeObserver
+ val listener = object : ViewTreeObserver.OnDrawListener {
+ override fun onDraw() {
+ if (animation.hasEnded() || view.drawingTime > startTime) {
+ val onDrawListener = this
+ latch.countDown()
+ view.post { viewTreeObserver.removeOnDrawListener(onDrawListener) }
+ }
+ }
+ }
+ viewTreeObserver.addOnDrawListener(listener)
+ latch.await(5, TimeUnit.SECONDS)
+ }
+
+ @MediumTest
+ @UiThreadTest
+ @Test
+ fun testViewOrder() {
+ val fragmentA = FragmentA()
+ val fragmentB = FragmentB()
+ val fragmentC = FragmentC()
+ activity.supportFragmentManager
+ .beginTransaction()
+ .add(R.id.content, fragmentA)
+ .add(R.id.content, fragmentB)
+ .add(R.id.content, fragmentC)
+ .commitNow()
+ val content = activity.findViewById(R.id.content) as ViewGroup
+ assertThat(content.childCount.toLong()).isEqualTo(3)
+ assertThat(content.getChildAt(0).findViewById<View>(R.id.textA)).isNotNull()
+ assertThat(content.getChildAt(1).findViewById<View>(R.id.textB)).isNotNull()
+ assertThat(content.getChildAt(2).findViewById<View>(R.id.textC)).isNotNull()
+ }
+
+ @SmallTest
+ @UiThreadTest
+ @Test
+ fun testRequireParentFragment() {
+ val parentFragment = StrictFragment()
+ activity.supportFragmentManager.beginTransaction().add(parentFragment, "parent").commitNow()
+
+ val childFragmentManager = parentFragment.childFragmentManager
+ val childFragment = StrictFragment()
+ childFragmentManager.beginTransaction().add(childFragment, "child").commitNow()
+ assertThat(childFragment.requireParentFragment()).isSameAs(parentFragment)
+ }
+
+ @SmallTest
+ @Test
+ fun requireMethodsThrowsWhenNotAttached() {
+ val fragment = Fragment()
+ try {
+ fragment.requireContext()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment not attached to a context.")
+ }
+
+ try {
+ fragment.requireActivity()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment not attached to an activity.")
+ }
+
+ try {
+ fragment.requireHost()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment not attached to a host.")
+ }
+
+ try {
+ fragment.requireParentFragment()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment is not attached to any Fragment or host")
+ }
+
+ try {
+ fragment.requireView()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains(
+ "Fragment $fragment did not return a View from onCreateView() or this was " +
+ "called before onCreateView()."
+ )
+ }
+
+ try {
+ fragment.requireFragmentManager()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment not associated with a fragment manager.")
+ }
+ }
+
+ @SmallTest
+ @Test
+ fun requireArguments() {
+ val fragment = Fragment()
+ try {
+ fragment.requireArguments()
+ fail()
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Fragment $fragment does not have any arguments.")
+ }
+
+ val arguments = Bundle()
+ fragment.arguments = arguments
+ assertWithMessage("requireArguments should return the arguments")
+ .that(fragment.requireArguments())
+ .isSameAs(arguments)
+ }
+
+ class OrderFragment : Fragment() {
+ var createOrder = -1
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ createOrder = order.getAndIncrement()
+ super.onCreate(savedInstanceState)
+ }
+
+ companion object {
+ private val order = AtomicInteger()
+ }
+ }
+
+ class FragmentA : Fragment(R.layout.fragment_a)
+
+ class FragmentB : Fragment(R.layout.fragment_b)
+
+ class FragmentC : Fragment(R.layout.fragment_c)
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.java
deleted file mode 100644
index bae9332..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.view.LayoutInflater;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.app.test.NewIntentActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collection;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests usage of the {@link FragmentTransaction} class.
- */
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentTransactionTest {
-
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<>(FragmentTestActivity.class);
-
- private FragmentTestActivity mActivity;
- private int mOnBackStackChangedTimes;
- private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener;
-
- @Before
- public void setUp() {
- mActivity = mActivityRule.getActivity();
- mOnBackStackChangedTimes = 0;
- mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
- @Override
- public void onBackStackChanged() {
- mOnBackStackChangedTimes++;
- }
- };
- mActivity.getSupportFragmentManager()
- .addOnBackStackChangedListener(mOnBackStackChangedListener);
- }
-
- @After
- public void tearDown() {
- mActivity.getSupportFragmentManager()
- .removeOnBackStackChangedListener(mOnBackStackChangedListener);
- mOnBackStackChangedListener = null;
- }
-
- @Test
- @UiThreadTest
- public void testAddTransactionWithValidFragment() {
- final Fragment fragment = new CorrectFragment();
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, mOnBackStackChangedTimes);
- assertTrue(fragment.isAdded());
- }
-
- @Test
- @UiThreadTest
- public void testAddTransactionWithPrivateFragment() {
- final Fragment fragment = new PrivateFragment();
- boolean exceptionThrown = false;
- try {
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, mOnBackStackChangedTimes);
- } catch (IllegalStateException e) {
- exceptionThrown = true;
- } finally {
- assertTrue("Exception should be thrown", exceptionThrown);
- assertFalse("Fragment shouldn't be added", fragment.isAdded());
- }
- }
-
- @Test
- @UiThreadTest
- public void testAddTransactionWithPackagePrivateFragment() {
- final Fragment fragment = new PackagePrivateFragment();
- boolean exceptionThrown = false;
- try {
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, mOnBackStackChangedTimes);
- } catch (IllegalStateException e) {
- exceptionThrown = true;
- } finally {
- assertTrue("Exception should be thrown", exceptionThrown);
- assertFalse("Fragment shouldn't be added", fragment.isAdded());
- }
- }
-
- @Test
- @UiThreadTest
- public void testAddTransactionWithAnonymousFragment() {
- final Fragment fragment = new Fragment() {};
- boolean exceptionThrown = false;
- try {
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, mOnBackStackChangedTimes);
- } catch (IllegalStateException e) {
- exceptionThrown = true;
- } finally {
- assertTrue("Exception should be thrown", exceptionThrown);
- assertFalse("Fragment shouldn't be added", fragment.isAdded());
- }
- }
-
- @Test
- @UiThreadTest
- public void testGetLayoutInflater() {
- final OnGetLayoutInflaterFragment fragment1 = new OnGetLayoutInflaterFragment();
- assertEquals(0, fragment1.onGetLayoutInflaterCalls);
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment1)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, fragment1.onGetLayoutInflaterCalls);
- assertEquals(fragment1.layoutInflater, fragment1.getLayoutInflater());
- // getLayoutInflater() didn't force onGetLayoutInflater()
- assertEquals(1, fragment1.onGetLayoutInflaterCalls);
-
- LayoutInflater layoutInflater = fragment1.layoutInflater;
- // Replacing fragment1 won't detach it, so the value won't be cleared
- final OnGetLayoutInflaterFragment fragment2 = new OnGetLayoutInflaterFragment();
- mActivity.getSupportFragmentManager().beginTransaction()
- .replace(R.id.content, fragment2)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
-
- assertSame(layoutInflater, fragment1.getLayoutInflater());
- assertEquals(1, fragment1.onGetLayoutInflaterCalls);
-
- // Popping it should cause onCreateView again, so a new LayoutInflater...
- mActivity.getSupportFragmentManager().popBackStackImmediate();
- assertNotSame(layoutInflater, fragment1.getLayoutInflater());
- assertEquals(2, fragment1.onGetLayoutInflaterCalls);
- layoutInflater = fragment1.layoutInflater;
- assertSame(layoutInflater, fragment1.getLayoutInflater());
-
- // Popping it should detach it, clearing the cached value again
- mActivity.getSupportFragmentManager().popBackStackImmediate();
-
- // once it is detached, the getLayoutInflater() will default to throw
- // an exception, but we've made it return null instead.
- assertEquals(2, fragment1.onGetLayoutInflaterCalls);
- try {
- fragment1.getLayoutInflater();
- fail("getLayoutInflater should throw when the Fragment is detached");
- } catch (IllegalStateException e) {
- // Expected
- }
- assertEquals(3, fragment1.onGetLayoutInflaterCalls);
- }
-
- @Test
- @UiThreadTest
- public void testAddTransactionWithNonStaticFragment() {
- final Fragment fragment = new NonStaticFragment();
- boolean exceptionThrown = false;
- try {
- mActivity.getSupportFragmentManager().beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
- mActivity.getSupportFragmentManager().executePendingTransactions();
- assertEquals(1, mOnBackStackChangedTimes);
- } catch (IllegalStateException e) {
- exceptionThrown = true;
- } finally {
- assertTrue("Exception should be thrown", exceptionThrown);
- assertFalse("Fragment shouldn't be added", fragment.isAdded());
- }
- }
-
- @Test
- @UiThreadTest
- public void testPostOnCommit() {
- final boolean[] ran = new boolean[1];
- FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction().runOnCommit(new Runnable() {
- @Override
- public void run() {
- ran[0] = true;
- }
- }).commit();
- fm.executePendingTransactions();
-
- assertTrue("runOnCommit runnable never ran", ran[0]);
-
- ran[0] = false;
-
- boolean threw = false;
- try {
- fm.beginTransaction().runOnCommit(new Runnable() {
- @Override
- public void run() {
- ran[0] = true;
- }
- }).addToBackStack(null).commit();
- } catch (IllegalStateException ise) {
- threw = true;
- }
-
- fm.executePendingTransactions();
-
- assertTrue("runOnCommit was allowed to be called for back stack transaction",
- threw);
- assertFalse("runOnCommit runnable for back stack transaction was run", ran[0]);
- }
-
- // Ensure that getFragments() works during transactions, even if it is run off thread
- @Test
- public void getFragmentsOffThread() throws Throwable {
- final FragmentManager fm = mActivity.getSupportFragmentManager();
-
- // Make sure that adding a fragment works
- Fragment fragment = new CorrectFragment();
- fm.beginTransaction()
- .add(R.id.content, fragment)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- Collection<Fragment> fragments = fm.getFragments();
- assertEquals(1, fragments.size());
- assertTrue(fragments.contains(fragment));
-
- // Removed fragments shouldn't show
- fm.beginTransaction()
- .remove(fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertTrue(fm.getFragments().isEmpty());
-
- // Now try detached fragments
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fm.beginTransaction()
- .detach(fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertTrue(fm.getFragments().isEmpty());
-
- // Now try hidden fragments
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fm.beginTransaction()
- .hide(fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- fragments = fm.getFragments();
- assertEquals(1, fragments.size());
- assertTrue(fragments.contains(fragment));
-
- // And showing it again shouldn't change anything:
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragments = fm.getFragments();
- assertEquals(1, fragments.size());
- assertTrue(fragments.contains(fragment));
-
- // Now pop back to the start state
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- // We can't force concurrency, but we can do it lots of times and hope that
- // we hit it.
- // Reset count here to verify afterwards
-
- // Wait until we receive a OnBackStackChange callback for the total number of times
- // specified by transactionCount times 2 (1 for adding, 1 for removal)
- final int transactionCount = 100;
- final CountDownLatch backStackLatch = new CountDownLatch(transactionCount * 2);
- final FragmentManager.OnBackStackChangedListener countDownListener =
- new FragmentManager.OnBackStackChangedListener() {
-
- @Override
- public void onBackStackChanged() {
- backStackLatch.countDown();
- }
- };
-
- fm.addOnBackStackChangedListener(countDownListener);
-
- for (int i = 0; i < transactionCount; i++) {
- Fragment fragment2 = new CorrectFragment();
- fm.beginTransaction()
- .add(R.id.content, fragment2)
- .addToBackStack(null)
- .commit();
- getFragmentsUntilSize(1);
-
- fm.popBackStack();
- getFragmentsUntilSize(0);
- }
-
- backStackLatch.await();
-
- fm.removeOnBackStackChangedListener(countDownListener);
- }
-
- /**
- * When a FragmentManager is detached, it should allow commitAllowingStateLoss()
- * and commitNowAllowingStateLoss() by just dropping the transaction.
- */
- @Test
- public void commitAllowStateLossDetached() throws Throwable {
- Fragment fragment1 = new CorrectFragment();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .add(fragment1, "1")
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final FragmentManager fm = fragment1.getChildFragmentManager();
- mActivity.getSupportFragmentManager()
- .beginTransaction()
- .remove(fragment1)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- Assert.assertEquals(0, mActivity.getSupportFragmentManager().getFragments().size());
- assertEquals(0, fm.getFragments().size());
-
- // Now the fragment1's fragment manager should allow commitAllowingStateLoss
- // by doing nothing since it has been detached.
- Fragment fragment2 = new CorrectFragment();
- fm.beginTransaction()
- .add(fragment2, "2")
- .commitAllowingStateLoss();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(0, fm.getFragments().size());
-
- // It should also allow commitNowAllowingStateLoss by doing nothing
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Fragment fragment3 = new CorrectFragment();
- fm.beginTransaction()
- .add(fragment3, "3")
- .commitNowAllowingStateLoss();
- assertEquals(0, fm.getFragments().size());
- }
- });
- }
-
- /**
- * onNewIntent() should note that the state is not saved so that child fragment
- * managers can execute transactions.
- */
- @Test
- public void newIntentUnlocks() throws Throwable {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent1 = new Intent(mActivity, NewIntentActivity.class)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- NewIntentActivity newIntentActivity =
- (NewIntentActivity) instrumentation.startActivitySync(intent1);
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- Intent intent2 = new Intent(mActivity, FragmentTestActivity.class);
- intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Activity coveringActivity = instrumentation.startActivitySync(intent2);
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- Intent intent3 = new Intent(mActivity, NewIntentActivity.class)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mActivity.startActivity(intent3);
- assertTrue(newIntentActivity.newIntent.await(1, TimeUnit.SECONDS));
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- for (Fragment fragment : newIntentActivity.getSupportFragmentManager().getFragments()) {
- // There really should only be one fragment in newIntentActivity.
- assertEquals(1, fragment.getChildFragmentManager().getFragments().size());
- }
- }
-
- private void getFragmentsUntilSize(int expectedSize) {
- final long endTime = SystemClock.uptimeMillis() + 3000;
-
- do {
- assertTrue(SystemClock.uptimeMillis() < endTime);
- } while (mActivity.getSupportFragmentManager().getFragments().size() != expectedSize);
- }
-
- public static class CorrectFragment extends Fragment {}
-
- private static class PrivateFragment extends Fragment {}
-
- static class PackagePrivateFragment extends Fragment {}
-
- private class NonStaticFragment extends Fragment {}
-
- @ContentView(R.layout.fragment_a)
- public static class OnGetLayoutInflaterFragment extends Fragment {
- public int onGetLayoutInflaterCalls = 0;
- public LayoutInflater layoutInflater;
-
- @NonNull
- @Override
- public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
- onGetLayoutInflaterCalls++;
- layoutInflater = super.onGetLayoutInflater(savedInstanceState);
- return layoutInflater;
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt
new file mode 100644
index 0000000..b66c15f
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransactionTest.kt
@@ -0,0 +1,416 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app.test
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.SystemClock
+import android.view.LayoutInflater
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTestUtil
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests usage of the [FragmentTransaction] class.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class FragmentTransactionTest {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private lateinit var activity: FragmentTestActivity
+ private var onBackStackChangedTimes: Int = 0
+ private lateinit var onBackStackChangedListener: FragmentManager.OnBackStackChangedListener
+
+ @Before
+ fun setUp() {
+ activity = activityRule.activity
+ onBackStackChangedTimes = 0
+ onBackStackChangedListener =
+ FragmentManager.OnBackStackChangedListener { onBackStackChangedTimes++ }
+ activity.supportFragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ @After
+ fun tearDown() {
+ activity.supportFragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testAddTransactionWithValidFragment() {
+ val fragment = CorrectFragment()
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ assertThat(fragment.isAdded).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testAddTransactionWithPrivateFragment() {
+ val fragment = PrivateFragment()
+ try {
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+ " must be a public static class to be properly recreated from instance " +
+ "state.")
+ } finally {
+ assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testAddTransactionWithPackagePrivateFragment() {
+ val fragment = OuterPackagePrivateFragment.PackagePrivateFragment()
+ try {
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+ " must be a public static class to be properly recreated from instance " +
+ "state.")
+ } finally {
+ assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testAddTransactionWithAnonymousFragment() {
+ val fragment = object : Fragment() {}
+ try {
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+ " must be a public static class to be properly recreated from instance state.")
+ } finally {
+ assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testGetLayoutInflater() {
+ val fragment1 = OnGetLayoutInflaterFragment()
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(0)
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment1)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+ assertThat(fragment1.layoutInflater).isEqualTo(fragment1.baseLayoutInflater)
+ // getBaseLayoutInflater() didn't force onGetLayoutInflater()
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+
+ var layoutInflater = fragment1.baseLayoutInflater
+ // Replacing fragment1 won't detach it, so the value won't be cleared
+ val fragment2 = OnGetLayoutInflaterFragment()
+ activity.supportFragmentManager.beginTransaction()
+ .replace(R.id.content, fragment2)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+
+ assertThat(fragment1.layoutInflater).isSameAs(layoutInflater)
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(1)
+
+ // Popping it should cause onCreateView again, so a new LayoutInflater...
+ activity.supportFragmentManager.popBackStackImmediate()
+ assertThat(fragment1.layoutInflater).isNotSameAs(layoutInflater)
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(2)
+ layoutInflater = fragment1.baseLayoutInflater
+ assertThat(fragment1.layoutInflater).isSameAs(layoutInflater)
+
+ // Popping it should detach it, clearing the cached value again
+ activity.supportFragmentManager.popBackStackImmediate()
+
+ // once it is detached, the getBaseLayoutInflater() will default to throw
+ // an exception, but we've made it return null instead.
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(2)
+ try {
+ fragment1.layoutInflater
+ fail("getLayoutInflater should throw when the Fragment is detached")
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains("onGetLayoutInflater() cannot be executed " +
+ "until the Fragment is attached to the FragmentManager.")
+ }
+
+ assertThat(fragment1.onGetLayoutInflaterCalls).isEqualTo(3)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testAddTransactionWithNonStaticFragment() {
+ val fragment = NonStaticFragment()
+ try {
+ activity.supportFragmentManager.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+ activity.supportFragmentManager.executePendingTransactions()
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains("Fragment " + fragment.javaClass.canonicalName +
+ " must be a public static class to be properly recreated from instance state.")
+ } finally {
+ assertWithMessage("Fragment shouldn't be added").that(fragment.isAdded).isFalse()
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testPostOnCommit() {
+ var ran = false
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction().runOnCommit { ran = true }.commit()
+ fm.executePendingTransactions()
+
+ assertWithMessage("runOnCommit runnable never ran").that(ran).isTrue()
+
+ ran = false
+
+ try {
+ fm.beginTransaction().runOnCommit { ran = true }.addToBackStack(null).commit()
+ } catch (e: IllegalStateException) {
+ assertThat(e).hasMessageThat().contains("This FragmentTransaction is not allowed to" +
+ " be added to the back stack.")
+ }
+
+ fm.executePendingTransactions()
+
+ assertWithMessage("runOnCommit runnable for back stack transaction was run")
+ .that(ran)
+ .isFalse()
+ }
+
+ // Ensure that getFragments() works during transactions, even if it is run off thread
+ @Test
+ fun getFragmentsOffThread() {
+ val fm = activity.supportFragmentManager
+
+ // Make sure that adding a fragment works
+ val fragment = CorrectFragment()
+ fm.beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ var fragments: Collection<Fragment> = fm.fragments
+ assertThat(fragments.size).isEqualTo(1)
+ assertThat(fragments.contains(fragment)).isTrue()
+
+ // Removed fragments shouldn't show
+ fm.beginTransaction()
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fm.fragments.isEmpty()).isTrue()
+
+ // Now try detached fragments
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fm.beginTransaction()
+ .detach(fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fm.fragments.isEmpty()).isTrue()
+
+ // Now try hidden fragments
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fm.beginTransaction()
+ .hide(fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ fragments = fm.fragments
+ assertThat(fragments.size).isEqualTo(1)
+ assertThat(fragments.contains(fragment)).isTrue()
+
+ // And showing it again shouldn't change anything:
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragments = fm.fragments
+ assertThat(fragments.size).isEqualTo(1)
+ assertThat(fragments.contains(fragment)).isTrue()
+
+ // Now pop back to the start state
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ // We can't force concurrency, but we can do it lots of times and hope that
+ // we hit it.
+ // Reset count here to verify afterwards
+
+ // Wait until we receive a OnBackStackChange callback for the total number of times
+ // specified by transactionCount times 2 (1 for adding, 1 for removal)
+ val transactionCount = 100
+ val backStackLatch = CountDownLatch(transactionCount * 2)
+ val countDownListener =
+ FragmentManager.OnBackStackChangedListener { backStackLatch.countDown() }
+
+ fm.addOnBackStackChangedListener(countDownListener)
+
+ for (i in 0 until transactionCount) {
+ val fragment2 = CorrectFragment()
+ fm.beginTransaction()
+ .add(R.id.content, fragment2)
+ .addToBackStack(null)
+ .commit()
+ getFragmentsUntilSize(1)
+
+ fm.popBackStack()
+ getFragmentsUntilSize(0)
+ }
+
+ backStackLatch.await()
+
+ fm.removeOnBackStackChangedListener(countDownListener)
+ }
+
+ /**
+ * When a FragmentManager is detached, it should allow commitAllowingStateLoss()
+ * and commitNowAllowingStateLoss() by just dropping the transaction.
+ */
+ @Test
+ fun commitAllowStateLossDetached() {
+ val fragment1 = CorrectFragment()
+ activity.supportFragmentManager
+ .beginTransaction()
+ .add(fragment1, "1")
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val fm = fragment1.childFragmentManager
+ activity.supportFragmentManager
+ .beginTransaction()
+ .remove(fragment1)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(activity.supportFragmentManager.fragments.size).isEqualTo(0)
+ assertThat(fm.fragments.size).isEqualTo(0)
+
+ // Now the fragment1's fragment manager should allow commitAllowingStateLoss
+ // by doing nothing since it has been detached.
+ val fragment2 = CorrectFragment()
+ fm.beginTransaction()
+ .add(fragment2, "2")
+ .commitAllowingStateLoss()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(fm.fragments.size).isEqualTo(0)
+
+ // It should also allow commitNowAllowingStateLoss by doing nothing
+ activityRule.runOnUiThread {
+ val fragment3 = CorrectFragment()
+ fm.beginTransaction()
+ .add(fragment3, "3")
+ .commitNowAllowingStateLoss()
+ assertThat(fm.fragments.size).isEqualTo(0)
+ }
+ }
+
+ /**
+ * onNewIntent() should note that the state is not saved so that child fragment
+ * managers can execute transactions.
+ */
+ @Test
+ fun newIntentUnlocks() {
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ val intent1 = Intent(activity, NewIntentActivity::class.java)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ val newIntentActivity = instrumentation.startActivitySync(intent1) as NewIntentActivity
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val intent2 = Intent(activity, FragmentTestActivity::class.java)
+ intent2.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ instrumentation.startActivitySync(intent2)
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val intent3 = Intent(activity, NewIntentActivity::class.java)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ activity.startActivity(intent3)
+ assertThat(newIntentActivity.newIntent.await(1, TimeUnit.SECONDS)).isTrue()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ for (fragment in newIntentActivity.supportFragmentManager.fragments) {
+ // There really should only be one fragment in newIntentActivity.
+ assertThat(fragment.childFragmentManager.fragments.size).isEqualTo(1)
+ }
+ }
+
+ private fun getFragmentsUntilSize(expectedSize: Int) {
+ val endTime = SystemClock.uptimeMillis() + 3000
+
+ do {
+ assertThat(SystemClock.uptimeMillis() < endTime).isTrue()
+ } while (activity.supportFragmentManager.fragments.size != expectedSize)
+ }
+
+ class CorrectFragment : Fragment()
+
+ private class PrivateFragment : Fragment()
+
+ private inner class NonStaticFragment : Fragment()
+
+ class OnGetLayoutInflaterFragment : Fragment(R.layout.fragment_a) {
+ var onGetLayoutInflaterCalls = 0
+ lateinit var baseLayoutInflater: LayoutInflater
+
+ override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
+ onGetLayoutInflaterCalls++
+ baseLayoutInflater = super.onGetLayoutInflater(savedInstanceState)
+ return baseLayoutInflater
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java
deleted file mode 100644
index 04cb401..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.java
+++ /dev/null
@@ -1,1155 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.app.Instrumentation;
-import android.graphics.Rect;
-import android.os.Build;
-import android.os.Bundle;
-import android.transition.TransitionSet;
-import android.view.View;
-
-import androidx.core.app.SharedElementCallback;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-@MediumTest
-@RunWith(Parameterized.class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-public class FragmentTransitionTest {
- private final boolean mReorderingAllowed;
-
- @Parameterized.Parameters
- public static Object[] data() {
- return new Boolean[] {
- false, true
- };
- }
-
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private Instrumentation mInstrumentation;
- private FragmentManager mFragmentManager;
- private int mOnBackStackChangedTimes;
- private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener;
-
- public FragmentTransitionTest(final boolean reordering) {
- mReorderingAllowed = reordering;
- }
-
- @Before
- public void setup() throws Throwable {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mFragmentManager = mActivityRule.getActivity().getSupportFragmentManager();
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- mOnBackStackChangedTimes = 0;
- mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
- @Override
- public void onBackStackChanged() {
- mOnBackStackChangedTimes++;
- }
- };
- mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
- }
-
- @After
- public void teardown() throws Throwable {
- mFragmentManager.removeOnBackStackChangedListener(mOnBackStackChangedListener);
- mOnBackStackChangedListener = null;
- }
-
- // Test that normal view transitions (enter, exit, reenter, return) run with
- // a single fragment.
- @Test
- public void enterExitTransitions() throws Throwable {
- // enter transition
- TransitionFragment fragment = setupInitialFragment();
- final View blue = findBlue();
- final View green = findBlue();
-
- // exit transition
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .remove(fragment)
- .addToBackStack(null)
- .commit();
-
- fragment.waitForTransition();
- verifyAndClearTransition(fragment.exitTransition, null, green, blue);
- verifyNoOtherTransitions(fragment);
- assertEquals(2, mOnBackStackChangedTimes);
-
- // reenter transition
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragment.waitForTransition();
- final View green2 = findGreen();
- final View blue2 = findBlue();
- verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2);
- verifyNoOtherTransitions(fragment);
- assertEquals(3, mOnBackStackChangedTimes);
-
- // return transition
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragment.waitForTransition();
- verifyAndClearTransition(fragment.returnTransition, null, green2, blue2);
- verifyNoOtherTransitions(fragment);
- assertEquals(4, mOnBackStackChangedTimes);
- }
-
- // Test that shared elements transition from one fragment to the next
- // and back during pop.
- @Test
- public void sharedElement() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- verifyTransition(fragment1, fragment2, "blueSquare");
-
- // Now pop the back stack
- verifyPopTransition(1, fragment2, fragment1);
- }
-
- // Test that shared element transitions through multiple fragments work together
- @Test
- public void intermediateFragment() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- final TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene3);
-
- verifyTransition(fragment1, fragment2, "shared");
-
- final TransitionFragment fragment3 = new TransitionFragment();
- fragment3.setLayoutId(R.layout.scene2);
-
- verifyTransition(fragment2, fragment3, "blueSquare");
-
- // Should transfer backwards when popping multiple:
- verifyPopTransition(2, fragment3, fragment1, fragment2);
- }
-
- // Adding/removing the same fragment multiple times shouldn't mess anything up
- @Test
- public void removeAdded() throws Throwable {
- final TransitionFragment fragment1 = setupInitialFragment();
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
-
- final TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .replace(R.id.fragmentContainer, fragment2)
- .replace(R.id.fragmentContainer, fragment1)
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
- }
- });
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(2, mOnBackStackChangedTimes);
-
- // should be a normal transition from fragment1 to fragment2
- fragment2.waitForTransition();
- final View endBlue = findBlue();
- final View endGreen = findGreen();
- verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen);
- verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen);
- verifyNoOtherTransitions(fragment1);
- verifyNoOtherTransitions(fragment2);
-
- // Pop should also do the same thing
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- assertEquals(3, mOnBackStackChangedTimes);
-
- fragment1.waitForTransition();
- final View popBlue = findBlue();
- final View popGreen = findGreen();
- verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen);
- verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen);
- verifyNoOtherTransitions(fragment1);
- verifyNoOtherTransitions(fragment2);
- }
-
- // Make sure that shared elements on two different fragment containers don't interact
- @Test
- public void crossContainer() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
- TransitionFragment fragment1 = new TransitionFragment();
- fragment1.setLayoutId(R.layout.scene1);
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene1);
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .add(R.id.fragmentContainer1, fragment1)
- .add(R.id.fragmentContainer2, fragment2)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(1, mOnBackStackChangedTimes);
-
- fragment1.waitForTransition();
- final View greenSquare1 = findViewById(fragment1, R.id.greenSquare);
- final View blueSquare1 = findViewById(fragment1, R.id.blueSquare);
- verifyAndClearTransition(fragment1.enterTransition, null, greenSquare1, blueSquare1);
- verifyNoOtherTransitions(fragment1);
- fragment2.waitForTransition();
- final View greenSquare2 = findViewById(fragment2, R.id.greenSquare);
- final View blueSquare2 = findViewById(fragment2, R.id.blueSquare);
- verifyAndClearTransition(fragment2.enterTransition, null, greenSquare2, blueSquare2);
- verifyNoOtherTransitions(fragment2);
-
- // Make sure the correct transitions are run when the target names
- // are different in both shared elements. We may fool the system.
- verifyCrossTransition(false, fragment1, fragment2);
-
- // Make sure the correct transitions are run when the source names
- // are different in both shared elements. We may fool the system.
- verifyCrossTransition(true, fragment1, fragment2);
- }
-
- // Make sure that onSharedElementStart and onSharedElementEnd are called
- @Test
- public void callStartEndWithSharedElements() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- SharedElementCallback enterCallback = mock(SharedElementCallback.class);
- fragment2.setEnterSharedElementCallback(enterCallback);
-
- final View startBlue = findBlue();
-
- verifyTransition(fragment1, fragment2, "blueSquare");
-
- ArgumentCaptor<List> names = ArgumentCaptor.forClass(List.class);
- ArgumentCaptor<List> views = ArgumentCaptor.forClass(List.class);
- ArgumentCaptor<List> snapshots = ArgumentCaptor.forClass(List.class);
- verify(enterCallback).onSharedElementStart(names.capture(), views.capture(),
- snapshots.capture());
- assertEquals(1, names.getValue().size());
- assertEquals(1, views.getValue().size());
- assertNull(snapshots.getValue());
- assertEquals("blueSquare", names.getValue().get(0));
- assertEquals(startBlue, views.getValue().get(0));
-
- final View endBlue = findBlue();
-
- verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(),
- snapshots.capture());
- assertEquals(1, names.getValue().size());
- assertEquals(1, views.getValue().size());
- assertNull(snapshots.getValue());
- assertEquals("blueSquare", names.getValue().get(0));
- assertEquals(endBlue, views.getValue().get(0));
-
- // Now pop the back stack
- reset(enterCallback);
- verifyPopTransition(1, fragment2, fragment1);
-
- verify(enterCallback).onSharedElementStart(names.capture(), views.capture(),
- snapshots.capture());
- assertEquals(1, names.getValue().size());
- assertEquals(1, views.getValue().size());
- assertNull(snapshots.getValue());
- assertEquals("blueSquare", names.getValue().get(0));
- assertEquals(endBlue, views.getValue().get(0));
-
- final View reenterBlue = findBlue();
-
- verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(),
- snapshots.capture());
- assertEquals(1, names.getValue().size());
- assertEquals(1, views.getValue().size());
- assertNull(snapshots.getValue());
- assertEquals("blueSquare", names.getValue().get(0));
- assertEquals(reenterBlue, views.getValue().get(0));
- }
-
- // Make sure that onMapSharedElement works to change the shared element going out
- @Test
- public void onMapSharedElementOut() throws Throwable {
- final TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
-
- final Rect startGreenBounds = getBoundsOnScreen(startGreen);
-
- SharedElementCallback mapOut = new SharedElementCallback() {
- @Override
- public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
- assertEquals(1, names.size());
- assertEquals("blueSquare", names.get(0));
- assertEquals(1, sharedElements.size());
- assertEquals(startBlue, sharedElements.get("blueSquare"));
- sharedElements.put("blueSquare", startGreen);
- }
- };
- fragment1.setExitSharedElementCallback(mapOut);
-
- mFragmentManager.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .setReorderingAllowed(mReorderingAllowed)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View endBlue = findBlue();
- final Rect endBlueBounds = getBoundsOnScreen(endBlue);
-
- verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
- endBlue);
-
- SharedElementCallback mapBack = new SharedElementCallback() {
- @Override
- public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
- assertEquals(1, names.size());
- assertEquals("blueSquare", names.get(0));
- assertEquals(1, sharedElements.size());
- final View expectedBlue = findViewById(fragment1, R.id.blueSquare);
- assertEquals(expectedBlue, sharedElements.get("blueSquare"));
- final View greenSquare = findViewById(fragment1, R.id.greenSquare);
- sharedElements.put("blueSquare", greenSquare);
- }
- };
- fragment1.setExitSharedElementCallback(mapBack);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View reenterGreen = findGreen();
- verifyAndClearTransition(fragment2.sharedElementReturn, endBlueBounds, endBlue,
- reenterGreen);
- }
-
- // Make sure that onMapSharedElement works to change the shared element target
- @Test
- public void onMapSharedElementIn() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- final TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
- SharedElementCallback mapIn = new SharedElementCallback() {
- @Override
- public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
- assertEquals(1, names.size());
- assertEquals("blueSquare", names.get(0));
- assertEquals(1, sharedElements.size());
- final View blueSquare = findViewById(fragment2, R.id.blueSquare);
- assertEquals(blueSquare, sharedElements.get("blueSquare"));
- final View greenSquare = findViewById(fragment2, R.id.greenSquare);
- sharedElements.put("blueSquare", greenSquare);
- }
- };
- fragment2.setEnterSharedElementCallback(mapIn);
-
- mFragmentManager.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .setReorderingAllowed(mReorderingAllowed)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View endGreen = findGreen();
- final View endBlue = findBlue();
- final Rect endGreenBounds = getBoundsOnScreen(endGreen);
-
- verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue,
- endGreen);
-
- SharedElementCallback mapBack = new SharedElementCallback() {
- @Override
- public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
- assertEquals(1, names.size());
- assertEquals("blueSquare", names.get(0));
- assertEquals(1, sharedElements.size());
- assertEquals(endBlue, sharedElements.get("blueSquare"));
- sharedElements.put("blueSquare", endGreen);
- }
- };
- fragment2.setEnterSharedElementCallback(mapBack);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View reenterBlue = findBlue();
- verifyAndClearTransition(fragment2.sharedElementReturn, endGreenBounds, endGreen,
- reenterBlue);
- }
-
- // Ensure that shared element transitions that have targets properly target the views
- @Test
- public void complexSharedElementTransition() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- ComplexTransitionFragment fragment2 = new ComplexTransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
- mFragmentManager.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .addSharedElement(startGreen, "greenSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(2, mOnBackStackChangedTimes);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View endBlue = findBlue();
- final View endGreen = findGreen();
- final Rect endBlueBounds = getBoundsOnScreen(endBlue);
-
- verifyAndClearTransition(fragment2.sharedElementEnterTransition1, startBlueBounds,
- startBlue, endBlue);
- verifyAndClearTransition(fragment2.sharedElementEnterTransition2, startBlueBounds,
- startGreen, endGreen);
-
- // Now see if it works when popped
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- assertEquals(3, mOnBackStackChangedTimes);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View reenterBlue = findBlue();
- final View reenterGreen = findGreen();
-
- verifyAndClearTransition(fragment2.sharedElementReturnTransition1, endBlueBounds,
- endBlue, reenterBlue);
- verifyAndClearTransition(fragment2.sharedElementReturnTransition2, endBlueBounds,
- endGreen, reenterGreen);
- }
-
- // Ensure that after transitions have executed that they don't have any targets or other
- // unfortunate modifications.
- @Test
- public void transitionsEndUnchanged() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- verifyTransition(fragment1, fragment2, "blueSquare");
- assertEquals(0, fragment1.exitTransition.getTargets().size());
- assertEquals(0, fragment2.sharedElementEnter.getTargets().size());
- assertEquals(0, fragment2.enterTransition.getTargets().size());
- assertNull(fragment1.exitTransition.getEpicenterCallback());
- assertNull(fragment2.enterTransition.getEpicenterCallback());
- assertNull(fragment2.sharedElementEnter.getEpicenterCallback());
-
- // Now pop the back stack
- verifyPopTransition(1, fragment2, fragment1);
-
- assertEquals(0, fragment2.returnTransition.getTargets().size());
- assertEquals(0, fragment2.sharedElementReturn.getTargets().size());
- assertEquals(0, fragment1.reenterTransition.getTargets().size());
- assertNull(fragment2.returnTransition.getEpicenterCallback());
- assertNull(fragment2.sharedElementReturn.getEpicenterCallback());
- assertNull(fragment2.reenterTransition.getEpicenterCallback());
- }
-
- // Ensure that transitions are done when a fragment is shown and hidden
- @Test
- public void showHideTransition() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
-
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .add(R.id.fragmentContainer, fragment2)
- .hide(fragment1)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View endGreen = findViewById(fragment2, R.id.greenSquare);
- final View endBlue = findViewById(fragment2, R.id.blueSquare);
-
- assertEquals(View.GONE, fragment1.requireView().getVisibility());
- assertEquals(View.VISIBLE, startGreen.getVisibility());
- assertEquals(View.VISIBLE, startBlue.getVisibility());
-
- verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
- verifyNoOtherTransitions(fragment1);
-
- verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
- verifyNoOtherTransitions(fragment2);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- verifyAndClearTransition(fragment1.reenterTransition, null, startGreen, startBlue);
- verifyNoOtherTransitions(fragment1);
-
- assertEquals(View.VISIBLE, fragment1.requireView().getVisibility());
- assertEquals(View.VISIBLE, startGreen.getVisibility());
- assertEquals(View.VISIBLE, startBlue.getVisibility());
-
- verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue);
- verifyNoOtherTransitions(fragment2);
- }
-
- // Ensure that transitions are done when a fragment is attached and detached
- @Test
- public void attachDetachTransition() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
-
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .add(R.id.fragmentContainer, fragment2)
- .detach(fragment1)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- final View endGreen = findViewById(fragment2, R.id.greenSquare);
- final View endBlue = findViewById(fragment2, R.id.blueSquare);
-
- verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
- verifyNoOtherTransitions(fragment1);
-
- verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
- verifyNoOtherTransitions(fragment2);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- final View reenterBlue = findBlue();
- final View reenterGreen = findGreen();
-
- verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue);
- verifyNoOtherTransitions(fragment1);
-
- verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue);
- verifyNoOtherTransitions(fragment2);
- }
-
- // Ensure that shared element without matching transition name doesn't error out
- @Test
- public void sharedElementMismatch() throws Throwable {
- final TransitionFragment fragment1 = setupInitialFragment();
-
- // Now do a transition to scene2
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
- mFragmentManager.beginTransaction()
- .addSharedElement(startBlue, "fooSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .setReorderingAllowed(mReorderingAllowed)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
-
- final View endBlue = findBlue();
- final View endGreen = findGreen();
-
- if (mReorderingAllowed) {
- verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
- } else {
- verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
- verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue);
- }
- verifyNoOtherTransitions(fragment1);
-
- verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
- verifyNoOtherTransitions(fragment2);
- }
-
- // Ensure that using the same source or target shared element results in an exception.
- @Test
- public void sharedDuplicateTargetNames() throws Throwable {
- setupInitialFragment();
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
-
- FragmentTransaction ft = mFragmentManager.beginTransaction();
- ft.addSharedElement(startBlue, "blueSquare");
- try {
- ft.addSharedElement(startGreen, "blueSquare");
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected
- }
-
- try {
- ft.addSharedElement(startBlue, "greenSquare");
- fail("Expected IllegalArgumentException");
- } catch (IllegalArgumentException e) {
- // expected
- }
- }
-
- // Test that invisible fragment views don't participate in transitions
- @Test
- public void invisibleNoTransitions() throws Throwable {
- if (!mReorderingAllowed) {
- return; // only reordered transitions can avoid interaction
- }
- // enter transition
- TransitionFragment fragment = new InvisibleFragment();
- fragment.setLayoutId(R.layout.scene1);
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .add(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment.waitForNoTransition();
- verifyNoOtherTransitions(fragment);
-
- // exit transition
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .remove(fragment)
- .addToBackStack(null)
- .commit();
-
- fragment.waitForNoTransition();
- verifyNoOtherTransitions(fragment);
-
- // reenter transition
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragment.waitForNoTransition();
- verifyNoOtherTransitions(fragment);
-
- // return transition
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragment.waitForNoTransition();
- verifyNoOtherTransitions(fragment);
- }
-
- // No crash when transitioning a shared element and there is no shared element transition.
- @Test
- public void noSharedElementTransition() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final Rect startBlueBounds = getBoundsOnScreen(startBlue);
-
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene2);
-
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
-
- fragment1.waitForTransition();
- fragment2.waitForTransition();
- final View midGreen = findGreen();
- final View midBlue = findBlue();
- final Rect midBlueBounds = getBoundsOnScreen(midBlue);
- verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
- verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue);
- verifyAndClearTransition(fragment2.enterTransition, midBlueBounds, midGreen);
- verifyNoOtherTransitions(fragment1);
- verifyNoOtherTransitions(fragment2);
-
- final TransitionFragment fragment3 = new TransitionFragment();
- fragment3.setLayoutId(R.layout.scene3);
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.popBackStack();
- fm.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack(null)
- .commit();
- }
- });
-
- // This shouldn't give an error.
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- fragment2.waitForTransition();
- // It does not transition properly for ordered transactions, though.
- if (mReorderingAllowed) {
- verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue);
- final View endGreen = findGreen();
- final View endBlue = findBlue();
- final View endRed = findRed();
- verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed);
- verifyNoOtherTransitions(fragment2);
- verifyNoOtherTransitions(fragment3);
- } else {
- // fragment3 doesn't get a transition since it conflicts with the pop transition
- verifyNoOtherTransitions(fragment3);
- // Everything else is just doing its best. Ordered transactions can't handle
- // multiple transitions acting together except for popping multiple together.
- }
- }
-
- // When there is no matching shared element, the transition name should not be changed
- @Test
- public void noMatchingSharedElementRetainName() throws Throwable {
- TransitionFragment fragment1 = setupInitialFragment();
-
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final Rect startGreenBounds = getBoundsOnScreen(startGreen);
-
- TransitionFragment fragment2 = new TransitionFragment();
- fragment2.setLayoutId(R.layout.scene3);
-
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .addSharedElement(startGreen, "greenSquare")
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
-
- fragment2.waitForTransition();
- final View midGreen = findGreen();
- final View midBlue = findBlue();
- final View midRed = findRed();
- final Rect midGreenBounds = getBoundsOnScreen(midGreen);
- if (mReorderingAllowed) {
- verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
- midGreen);
- } else {
- verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen,
- midGreen, startBlue);
- }
- verifyAndClearTransition(fragment2.enterTransition, midGreenBounds, midBlue, midRed);
- verifyNoOtherTransitions(fragment2);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
- fragment2.waitForTransition();
- fragment1.waitForTransition();
-
- final View endBlue = findBlue();
- final View endGreen = findGreen();
-
- assertEquals("blueSquare", endBlue.getTransitionName());
- assertEquals("greenSquare", endGreen.getTransitionName());
- }
-
- private TransitionFragment setupInitialFragment() throws Throwable {
- TransitionFragment fragment1 = new TransitionFragment();
- fragment1.setLayoutId(R.layout.scene1);
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(1, mOnBackStackChangedTimes);
- fragment1.waitForTransition();
- final View blueSquare1 = findBlue();
- final View greenSquare1 = findGreen();
- verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1);
- verifyNoOtherTransitions(fragment1);
- return fragment1;
- }
-
- private View findViewById(Fragment fragment, int id) {
- return fragment.requireView().findViewById(id);
- }
-
- private View findGreen() {
- return mActivityRule.getActivity().findViewById(R.id.greenSquare);
- }
-
- private View findBlue() {
- return mActivityRule.getActivity().findViewById(R.id.blueSquare);
- }
-
- private View findRed() {
- return mActivityRule.getActivity().findViewById(R.id.redSquare);
- }
-
- private void verifyAndClearTransition(TargetTracking transition, Rect epicenter,
- View... expected) {
- if (epicenter == null) {
- assertNull(transition.getCapturedEpicenter());
- } else {
- assertEquals(epicenter, transition.getCapturedEpicenter());
- }
- ArrayList<View> targets = transition.getTrackedTargets();
- StringBuilder sb = new StringBuilder();
- sb
- .append("Expected: [")
- .append(expected.length)
- .append("] {");
- boolean isFirst = true;
- for (View view : expected) {
- if (isFirst) {
- isFirst = false;
- } else {
- sb.append(", ");
- }
- sb.append(view);
- }
- sb
- .append("}, but got: [")
- .append(targets.size())
- .append("] {");
- isFirst = true;
- for (View view : targets) {
- if (isFirst) {
- isFirst = false;
- } else {
- sb.append(", ");
- }
- sb.append(view);
- }
- sb.append("}");
- String errorMessage = sb.toString();
-
- assertEquals(errorMessage, expected.length, targets.size());
- for (View view : expected) {
- assertTrue(errorMessage, targets.contains(view));
- }
- transition.clearTargets();
- }
-
- private void verifyNoOtherTransitions(TransitionFragment fragment) {
- assertEquals(0, fragment.enterTransition.targets.size());
- assertEquals(0, fragment.exitTransition.targets.size());
- assertEquals(0, fragment.reenterTransition.targets.size());
- assertEquals(0, fragment.returnTransition.targets.size());
- assertEquals(0, fragment.sharedElementEnter.targets.size());
- assertEquals(0, fragment.sharedElementReturn.targets.size());
- }
-
- private void verifyTransition(TransitionFragment from, TransitionFragment to,
- String sharedElementName) throws Throwable {
- final int startOnBackStackChanged = mOnBackStackChangedTimes;
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final View startRed = findRed();
-
- final Rect startBlueRect = getBoundsOnScreen(startBlue);
-
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .addSharedElement(startBlue, sharedElementName)
- .replace(R.id.fragmentContainer, to)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes);
-
- to.waitForTransition();
- final View endGreen = findGreen();
- final View endBlue = findBlue();
- final View endRed = findRed();
- final Rect endBlueRect = getBoundsOnScreen(endBlue);
-
- if (startRed != null) {
- verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen, startRed);
- } else {
- verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen);
- }
- verifyNoOtherTransitions(from);
-
- if (endRed != null) {
- verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen, endRed);
- } else {
- verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen);
- }
- verifyAndClearTransition(to.sharedElementEnter, startBlueRect, startBlue, endBlue);
- verifyNoOtherTransitions(to);
- }
-
- private void verifyCrossTransition(boolean swapSource,
- TransitionFragment from1, TransitionFragment from2) throws Throwable {
- final int startNumOnBackStackChanged = mOnBackStackChangedTimes;
- final int changesPerOperation = mReorderingAllowed ? 1 : 2;
-
- final TransitionFragment to1 = new TransitionFragment();
- to1.setLayoutId(R.layout.scene2);
- final TransitionFragment to2 = new TransitionFragment();
- to2.setLayoutId(R.layout.scene2);
-
- final View fromExit1 = findViewById(from1, R.id.greenSquare);
- final View fromShared1 = findViewById(from1, R.id.blueSquare);
- final Rect fromSharedRect1 = getBoundsOnScreen(fromShared1);
-
- final int fromExitId2 = swapSource ? R.id.blueSquare : R.id.greenSquare;
- final int fromSharedId2 = swapSource ? R.id.greenSquare : R.id.blueSquare;
- final View fromExit2 = findViewById(from2, fromExitId2);
- final View fromShared2 = findViewById(from2, fromSharedId2);
- final Rect fromSharedRect2 = getBoundsOnScreen(fromShared2);
-
- final String sharedElementName = swapSource ? "blueSquare" : "greenSquare";
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .addSharedElement(fromShared1, "blueSquare")
- .replace(R.id.fragmentContainer1, to1)
- .addToBackStack(null)
- .commit();
- mFragmentManager.beginTransaction()
- .setReorderingAllowed(mReorderingAllowed)
- .addSharedElement(fromShared2, sharedElementName)
- .replace(R.id.fragmentContainer2, to2)
- .addToBackStack(null)
- .commit();
- }
- });
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(startNumOnBackStackChanged + changesPerOperation, mOnBackStackChangedTimes);
-
- from1.waitForTransition();
- from2.waitForTransition();
- to1.waitForTransition();
- to2.waitForTransition();
-
- final View toEnter1 = findViewById(to1, R.id.greenSquare);
- final View toShared1 = findViewById(to1, R.id.blueSquare);
- final Rect toSharedRect1 = getBoundsOnScreen(toShared1);
-
- final View toEnter2 = findViewById(to2, fromSharedId2);
- final View toShared2 = findViewById(to2, fromExitId2);
- final Rect toSharedRect2 = getBoundsOnScreen(toShared2);
-
- verifyAndClearTransition(from1.exitTransition, fromSharedRect1, fromExit1);
- verifyAndClearTransition(from2.exitTransition, fromSharedRect2, fromExit2);
- verifyNoOtherTransitions(from1);
- verifyNoOtherTransitions(from2);
-
- verifyAndClearTransition(to1.enterTransition, toSharedRect1, toEnter1);
- verifyAndClearTransition(to2.enterTransition, toSharedRect2, toEnter2);
- verifyAndClearTransition(to1.sharedElementEnter, fromSharedRect1, fromShared1, toShared1);
- verifyAndClearTransition(to2.sharedElementEnter, fromSharedRect2, fromShared2, toShared2);
- verifyNoOtherTransitions(to1);
- verifyNoOtherTransitions(to2);
-
- // Now pop it back
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mFragmentManager.popBackStack();
- mFragmentManager.popBackStack();
- }
- });
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(startNumOnBackStackChanged + changesPerOperation + 1,
- mOnBackStackChangedTimes);
-
- from1.waitForTransition();
- from2.waitForTransition();
- to1.waitForTransition();
- to2.waitForTransition();
-
- final View returnEnter1 = findViewById(from1, R.id.greenSquare);
- final View returnShared1 = findViewById(from1, R.id.blueSquare);
-
- final View returnEnter2 = findViewById(from2, fromExitId2);
- final View returnShared2 = findViewById(from2, fromSharedId2);
-
- verifyAndClearTransition(to1.returnTransition, toSharedRect1, toEnter1);
- verifyAndClearTransition(to2.returnTransition, toSharedRect2, toEnter2);
- verifyAndClearTransition(to1.sharedElementReturn, toSharedRect1, toShared1, returnShared1);
- verifyAndClearTransition(to2.sharedElementReturn, toSharedRect2, toShared2, returnShared2);
- verifyNoOtherTransitions(to1);
- verifyNoOtherTransitions(to2);
-
- verifyAndClearTransition(from1.reenterTransition, fromSharedRect1, returnEnter1);
- verifyAndClearTransition(from2.reenterTransition, fromSharedRect2, returnEnter2);
- verifyNoOtherTransitions(from1);
- verifyNoOtherTransitions(from2);
- }
-
- private void verifyPopTransition(final int numPops, TransitionFragment from,
- TransitionFragment to, TransitionFragment... others) throws Throwable {
- final int startOnBackStackChanged = mOnBackStackChangedTimes;
- final View startBlue = findBlue();
- final View startGreen = findGreen();
- final View startRed = findRed();
- final Rect startSharedRect = getBoundsOnScreen(startBlue);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < numPops; i++) {
- mFragmentManager.popBackStack();
- }
- }
- });
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes);
-
- to.waitForTransition();
- final View endGreen = findGreen();
- final View endBlue = findBlue();
- final View endRed = findRed();
- final Rect endSharedRect = getBoundsOnScreen(endBlue);
-
- if (startRed != null) {
- verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen, startRed);
- } else {
- verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen);
- }
- verifyAndClearTransition(from.sharedElementReturn, startSharedRect, startBlue, endBlue);
- verifyNoOtherTransitions(from);
-
- if (endRed != null) {
- verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen, endRed);
- } else {
- verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen);
- }
- verifyNoOtherTransitions(to);
-
- if (others != null) {
- for (TransitionFragment fragment : others) {
- verifyNoOtherTransitions(fragment);
- }
- }
- }
-
- private static Rect getBoundsOnScreen(View view) {
- final int[] loc = new int[2];
- view.getLocationOnScreen(loc);
- return new Rect(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
- }
-
- public static class ComplexTransitionFragment extends TransitionFragment {
- public final TrackingTransition sharedElementEnterTransition1 = new TrackingTransition();
- public final TrackingTransition sharedElementEnterTransition2 = new TrackingTransition();
- public final TrackingTransition sharedElementReturnTransition1 = new TrackingTransition();
- public final TrackingTransition sharedElementReturnTransition2 = new TrackingTransition();
-
- public final TransitionSet sharedElementEnterTransition = new TransitionSet()
- .addTransition(sharedElementEnterTransition1)
- .addTransition(sharedElementEnterTransition2);
- public final TransitionSet sharedElementReturnTransition = new TransitionSet()
- .addTransition(sharedElementReturnTransition1)
- .addTransition(sharedElementReturnTransition2);
-
- public ComplexTransitionFragment() {
- sharedElementEnterTransition1.addTarget(R.id.blueSquare);
- sharedElementEnterTransition2.addTarget(R.id.greenSquare);
- sharedElementReturnTransition1.addTarget(R.id.blueSquare);
- sharedElementReturnTransition2.addTarget(R.id.greenSquare);
- setSharedElementEnterTransition(sharedElementEnterTransition);
- setSharedElementReturnTransition(sharedElementReturnTransition);
- }
- }
-
- public static class InvisibleFragment extends TransitionFragment {
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- view.setVisibility(View.INVISIBLE);
- super.onViewCreated(view, savedInstanceState);
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
new file mode 100644
index 0000000..83d8465
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentTransitionTest.kt
@@ -0,0 +1,1147 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.graphics.Rect
+import android.os.Build
+import android.os.Bundle
+import android.transition.TransitionSet
+import android.view.View
+import androidx.core.app.SharedElementCallback
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@MediumTest
+@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+class FragmentTransitionTest(private val reorderingAllowed: Boolean) {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private lateinit var fragmentManager: FragmentManager
+ private var onBackStackChangedTimes: Int = 0
+ private val onBackStackChangedListener =
+ FragmentManager.OnBackStackChangedListener { onBackStackChangedTimes++ }
+
+ @Before
+ fun setup() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ onBackStackChangedTimes = 0
+ fragmentManager = activityRule.activity.supportFragmentManager
+ fragmentManager.addOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ @After
+ fun teardown() {
+ fragmentManager.removeOnBackStackChangedListener(onBackStackChangedListener)
+ }
+
+ // Test that normal view transitions (enter, exit, reenter, return) run with
+ // a single fragment.
+ @Test
+ fun enterExitTransitions() {
+ // enter transition
+ val fragment = setupInitialFragment()
+ val blue = findBlue()
+ val green = findBlue()
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.exitTransition, null, green, blue)
+ verifyNoOtherTransitions(fragment)
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+ // reenter transition
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragment.waitForTransition()
+ val green2 = findGreen()
+ val blue2 = findBlue()
+ verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2)
+ verifyNoOtherTransitions(fragment)
+ assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+ // return transition
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragment.waitForTransition()
+ verifyAndClearTransition(fragment.returnTransition, null, green2, blue2)
+ verifyNoOtherTransitions(fragment)
+ assertThat(onBackStackChangedTimes).isEqualTo(4)
+ }
+
+ // Test that shared elements transition from one fragment to the next
+ // and back during pop.
+ @Test
+ fun sharedElement() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ verifyTransition(fragment1, fragment2, "blueSquare")
+
+ // Now pop the back stack
+ verifyPopTransition(1, fragment2, fragment1)
+ }
+
+ // Test that shared element transitions through multiple fragments work together
+ @Test
+ fun intermediateFragment() {
+ val fragment1 = setupInitialFragment()
+
+ val fragment2 = TransitionFragment(R.layout.scene3)
+
+ verifyTransition(fragment1, fragment2, "shared")
+
+ val fragment3 = TransitionFragment(R.layout.scene2)
+
+ verifyTransition(fragment2, fragment3, "blueSquare")
+
+ // Should transfer backwards when popping multiple:
+ verifyPopTransition(2, fragment3, fragment1, fragment2)
+ }
+
+ // Adding/removing the same fragment multiple times shouldn't mess anything up
+ @Test
+ fun removeAdded() {
+ val fragment1 = setupInitialFragment()
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ instrumentation.runOnMainSync {
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .replace(R.id.fragmentContainer, fragment2)
+ .replace(R.id.fragmentContainer, fragment1)
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ }
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+ // should be a normal transition from fragment1 to fragment2
+ fragment2.waitForTransition()
+ val endBlue = findBlue()
+ val endGreen = findGreen()
+ verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen)
+ verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen)
+ verifyNoOtherTransitions(fragment1)
+ verifyNoOtherTransitions(fragment2)
+
+ // Pop should also do the same thing
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+ fragment1.waitForTransition()
+ val popBlue = findBlue()
+ val popGreen = findGreen()
+ verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen)
+ verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen)
+ verifyNoOtherTransitions(fragment1)
+ verifyNoOtherTransitions(fragment2)
+ }
+
+ // Make sure that shared elements on two different fragment containers don't interact
+ @Test
+ fun crossContainer() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ val fragment2 = TransitionFragment(R.layout.scene1)
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer1, fragment1)
+ .add(R.id.fragmentContainer2, fragment2)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+
+ fragment1.waitForTransition()
+ val greenSquare1 = findViewById(fragment1, R.id.greenSquare)
+ val blueSquare1 = findViewById(fragment1, R.id.blueSquare)
+ verifyAndClearTransition(fragment1.enterTransition, null, greenSquare1, blueSquare1)
+ verifyNoOtherTransitions(fragment1)
+ fragment2.waitForTransition()
+ val greenSquare2 = findViewById(fragment2, R.id.greenSquare)
+ val blueSquare2 = findViewById(fragment2, R.id.blueSquare)
+ verifyAndClearTransition(fragment2.enterTransition, null, greenSquare2, blueSquare2)
+ verifyNoOtherTransitions(fragment2)
+
+ // Make sure the correct transitions are run when the target names
+ // are different in both shared elements. We may fool the system.
+ verifyCrossTransition(false, fragment1, fragment2)
+
+ // Make sure the correct transitions are run when the source names
+ // are different in both shared elements. We may fool the system.
+ verifyCrossTransition(true, fragment1, fragment2)
+ }
+
+ // Make sure that onSharedElementStart and onSharedElementEnd are called
+ @Test
+ fun callStartEndWithSharedElements() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val enterCallback = mock(SharedElementCallback::class.java)
+ fragment2.setEnterSharedElementCallback(enterCallback)
+
+ val startBlue = findBlue()
+
+ verifyTransition(fragment1, fragment2, "blueSquare")
+
+ val names = ArgumentCaptor.forClass(List::class.java as Class<List<String>>)
+ val views = ArgumentCaptor.forClass(List::class.java as Class<List<View>>)
+ val snapshots = ArgumentCaptor.forClass(List::class.java as Class<List<View>>)
+ verify(enterCallback).onSharedElementStart(
+ names.capture(), views.capture(),
+ snapshots.capture()
+ )
+ assertThat(names.value.size).isEqualTo(1)
+ assertThat(views.value.size).isEqualTo(1)
+ assertThat(snapshots.value).isNull()
+ assertThat(names.value[0]).isEqualTo("blueSquare")
+ assertThat(views.value[0]).isEqualTo(startBlue)
+
+ val endBlue = findBlue()
+
+ verify(enterCallback).onSharedElementEnd(
+ names.capture(), views.capture(),
+ snapshots.capture()
+ )
+ assertThat(names.value.size).isEqualTo(1)
+ assertThat(views.value.size).isEqualTo(1)
+ assertThat(snapshots.value).isNull()
+ assertThat(names.value[0]).isEqualTo("blueSquare")
+ assertThat(views.value[0]).isEqualTo(endBlue)
+
+ // Now pop the back stack
+ reset(enterCallback)
+ verifyPopTransition(1, fragment2, fragment1)
+
+ verify(enterCallback).onSharedElementStart(
+ names.capture(), views.capture(),
+ snapshots.capture()
+ )
+ assertThat(names.value.size).isEqualTo(1)
+ assertThat(views.value.size).isEqualTo(1)
+ assertThat(snapshots.value).isNull()
+ assertThat(names.value[0]).isEqualTo("blueSquare")
+ assertThat(views.value[0]).isEqualTo(endBlue)
+
+ val reenterBlue = findBlue()
+
+ verify(enterCallback).onSharedElementEnd(
+ names.capture(), views.capture(),
+ snapshots.capture()
+ )
+ assertThat(names.value.size).isEqualTo(1)
+ assertThat(views.value.size).isEqualTo(1)
+ assertThat(snapshots.value).isNull()
+ assertThat(names.value[0]).isEqualTo("blueSquare")
+ assertThat(views.value[0]).isEqualTo(reenterBlue)
+ }
+
+ // Make sure that onMapSharedElement works to change the shared element going out
+ @Test
+ fun onMapSharedElementOut() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+
+ val startGreenBounds = getBoundsOnScreen(startGreen)
+
+ val mapOut = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: List<String>,
+ sharedElements: MutableMap<String, View>
+ ) {
+ assertThat(names.size).isEqualTo(1)
+ assertThat(names[0]).isEqualTo("blueSquare")
+ assertThat(sharedElements.size).isEqualTo(1)
+ assertThat(sharedElements["blueSquare"]).isEqualTo(startBlue)
+ sharedElements["blueSquare"] = startGreen
+ }
+ }
+ fragment1.setExitSharedElementCallback(mapOut)
+
+ fragmentManager.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .setReorderingAllowed(reorderingAllowed)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val endBlue = findBlue()
+ val endBlueBounds = getBoundsOnScreen(endBlue)
+
+ verifyAndClearTransition(
+ fragment2.sharedElementEnter, startGreenBounds, startGreen,
+ endBlue
+ )
+
+ val mapBack = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: List<String>,
+ sharedElements: MutableMap<String, View>
+ ) {
+ assertThat(names.size).isEqualTo(1)
+ assertThat(names[0]).isEqualTo("blueSquare")
+ assertThat(sharedElements.size).isEqualTo(1)
+ val expectedBlue = findViewById(fragment1, R.id.blueSquare)
+ assertThat(sharedElements["blueSquare"]).isEqualTo(expectedBlue)
+ val greenSquare = findViewById(fragment1, R.id.greenSquare)
+ sharedElements["blueSquare"] = greenSquare
+ }
+ }
+ fragment1.setExitSharedElementCallback(mapBack)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val reenterGreen = findGreen()
+ verifyAndClearTransition(
+ fragment2.sharedElementReturn, endBlueBounds, endBlue,
+ reenterGreen
+ )
+ }
+
+ // Make sure that onMapSharedElement works to change the shared element target
+ @Test
+ fun onMapSharedElementIn() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val startBlue = findBlue()
+ val startBlueBounds = getBoundsOnScreen(startBlue)
+
+ val mapIn = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: List<String>,
+ sharedElements: MutableMap<String, View>
+ ) {
+ assertThat(names.size).isEqualTo(1)
+ assertThat(names[0]).isEqualTo("blueSquare")
+ assertThat(sharedElements.size).isEqualTo(1)
+ val blueSquare = findViewById(fragment2, R.id.blueSquare)
+ assertThat(sharedElements["blueSquare"]).isEqualTo(blueSquare)
+ val greenSquare = findViewById(fragment2, R.id.greenSquare)
+ sharedElements["blueSquare"] = greenSquare
+ }
+ }
+ fragment2.setEnterSharedElementCallback(mapIn)
+
+ fragmentManager.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .setReorderingAllowed(reorderingAllowed)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val endGreen = findGreen()
+ val endBlue = findBlue()
+ val endGreenBounds = getBoundsOnScreen(endGreen)
+
+ verifyAndClearTransition(
+ fragment2.sharedElementEnter, startBlueBounds, startBlue,
+ endGreen
+ )
+
+ val mapBack = object : SharedElementCallback() {
+ override fun onMapSharedElements(
+ names: List<String>,
+ sharedElements: MutableMap<String, View>
+ ) {
+ assertThat(names.size).isEqualTo(1)
+ assertThat(names[0]).isEqualTo("blueSquare")
+ assertThat(sharedElements.size).isEqualTo(1)
+ assertThat(sharedElements["blueSquare"]).isEqualTo(endBlue)
+ sharedElements["blueSquare"] = endGreen
+ }
+ }
+ fragment2.setEnterSharedElementCallback(mapBack)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val reenterBlue = findBlue()
+ verifyAndClearTransition(
+ fragment2.sharedElementReturn, endGreenBounds, endGreen,
+ reenterBlue
+ )
+ }
+
+ // Ensure that shared element transitions that have targets properly target the views
+ @Test
+ fun complexSharedElementTransition() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = ComplexTransitionFragment()
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startBlueBounds = getBoundsOnScreen(startBlue)
+
+ fragmentManager.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .addSharedElement(startGreen, "greenSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(2)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val endBlue = findBlue()
+ val endGreen = findGreen()
+ val endBlueBounds = getBoundsOnScreen(endBlue)
+
+ verifyAndClearTransition(
+ fragment2.sharedElementEnterTransition1, startBlueBounds,
+ startBlue, endBlue
+ )
+ verifyAndClearTransition(
+ fragment2.sharedElementEnterTransition2, startBlueBounds,
+ startGreen, endGreen
+ )
+
+ // Now see if it works when popped
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(3)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val reenterBlue = findBlue()
+ val reenterGreen = findGreen()
+
+ verifyAndClearTransition(
+ fragment2.sharedElementReturnTransition1, endBlueBounds,
+ endBlue, reenterBlue
+ )
+ verifyAndClearTransition(
+ fragment2.sharedElementReturnTransition2, endBlueBounds,
+ endGreen, reenterGreen
+ )
+ }
+
+ // Ensure that after transitions have executed that they don't have any targets or other
+ // unfortunate modifications.
+ @Test
+ fun transitionsEndUnchanged() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ verifyTransition(fragment1, fragment2, "blueSquare")
+ assertThat(fragment1.exitTransition.getTargets().size).isEqualTo(0)
+ assertThat(fragment2.sharedElementEnter.getTargets().size).isEqualTo(0)
+ assertThat(fragment2.enterTransition.getTargets().size).isEqualTo(0)
+ assertThat(fragment1.exitTransition.epicenterCallback).isNull()
+ assertThat(fragment2.enterTransition.epicenterCallback).isNull()
+ assertThat(fragment2.sharedElementEnter.epicenterCallback).isNull()
+
+ // Now pop the back stack
+ verifyPopTransition(1, fragment2, fragment1)
+
+ assertThat(fragment2.returnTransition.getTargets().size).isEqualTo(0)
+ assertThat(fragment2.sharedElementReturn.getTargets().size).isEqualTo(0)
+ assertThat(fragment1.reenterTransition.getTargets().size).isEqualTo(0)
+ assertThat(fragment2.returnTransition.epicenterCallback).isNull()
+ assertThat(fragment2.sharedElementReturn.epicenterCallback).isNull()
+ assertThat(fragment2.reenterTransition.epicenterCallback).isNull()
+ }
+
+ // Ensure that transitions are done when a fragment is shown and hidden
+ @Test
+ fun showHideTransition() {
+ val fragment1 = setupInitialFragment()
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment2)
+ .hide(fragment1)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val endGreen = findViewById(fragment2, R.id.greenSquare)
+ val endBlue = findViewById(fragment2, R.id.blueSquare)
+
+ assertThat(fragment1.requireView().visibility).isEqualTo(View.GONE)
+ assertThat(startGreen.visibility).isEqualTo(View.VISIBLE)
+ assertThat(startBlue.visibility).isEqualTo(View.VISIBLE)
+
+ verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+ verifyNoOtherTransitions(fragment1)
+
+ verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+ verifyNoOtherTransitions(fragment2)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ verifyAndClearTransition(fragment1.reenterTransition, null, startGreen, startBlue)
+ verifyNoOtherTransitions(fragment1)
+
+ assertThat(fragment1.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(startGreen.visibility).isEqualTo(View.VISIBLE)
+ assertThat(startBlue.visibility).isEqualTo(View.VISIBLE)
+
+ verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue)
+ verifyNoOtherTransitions(fragment2)
+ }
+
+ // Ensure that transitions are done when a fragment is attached and detached
+ @Test
+ fun attachDetachTransition() {
+ val fragment1 = setupInitialFragment()
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment2)
+ .detach(fragment1)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val endGreen = findViewById(fragment2, R.id.greenSquare)
+ val endBlue = findViewById(fragment2, R.id.blueSquare)
+
+ verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+ verifyNoOtherTransitions(fragment1)
+
+ verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+ verifyNoOtherTransitions(fragment2)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val reenterBlue = findBlue()
+ val reenterGreen = findGreen()
+
+ verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue)
+ verifyNoOtherTransitions(fragment1)
+
+ verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue)
+ verifyNoOtherTransitions(fragment2)
+ }
+
+ // Ensure that shared element without matching transition name doesn't error out
+ @Test
+ fun sharedElementMismatch() {
+ val fragment1 = setupInitialFragment()
+
+ // Now do a transition to scene2
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startBlueBounds = getBoundsOnScreen(startBlue)
+
+ fragmentManager.beginTransaction()
+ .addSharedElement(startBlue, "fooSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .setReorderingAllowed(reorderingAllowed)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val endBlue = findBlue()
+ val endGreen = findGreen()
+
+ if (reorderingAllowed) {
+ verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue)
+ } else {
+ verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen)
+ verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue)
+ }
+ verifyNoOtherTransitions(fragment1)
+
+ verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue)
+ verifyNoOtherTransitions(fragment2)
+ }
+
+ // Ensure that using the same source or target shared element results in an exception.
+ @Test
+ fun sharedDuplicateTargetNames() {
+ setupInitialFragment()
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+
+ val ft = fragmentManager.beginTransaction()
+ ft.addSharedElement(startBlue, "blueSquare")
+ try {
+ ft.addSharedElement(startGreen, "blueSquare")
+ fail("Expected IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ assertThat(e)
+ .hasMessageThat().contains("A shared element with the target name 'blueSquare' " +
+ "has already been added to the transaction.")
+ }
+
+ try {
+ ft.addSharedElement(startBlue, "greenSquare")
+ fail("Expected IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ assertThat(e)
+ .hasMessageThat().contains("A shared element with the source name 'blueSquare' " +
+ "has already been added to the transaction.")
+ }
+ }
+
+ // Test that invisible fragment views don't participate in transitions
+ @Test
+ fun invisibleNoTransitions() {
+ if (!reorderingAllowed) {
+ return // only reordered transitions can avoid interaction
+ }
+ // enter transition
+ val fragment = InvisibleFragment()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment.waitForNoTransition()
+ verifyNoOtherTransitions(fragment)
+
+ // exit transition
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+
+ fragment.waitForNoTransition()
+ verifyNoOtherTransitions(fragment)
+
+ // reenter transition
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragment.waitForNoTransition()
+ verifyNoOtherTransitions(fragment)
+
+ // return transition
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragment.waitForNoTransition()
+ verifyNoOtherTransitions(fragment)
+ }
+
+ // No crash when transitioning a shared element and there is no shared element transition.
+ @Test
+ fun noSharedElementTransition() {
+ val fragment1 = setupInitialFragment()
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startBlueBounds = getBoundsOnScreen(startBlue)
+
+ val fragment2 = TransitionFragment(R.layout.scene2)
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+ val midGreen = findGreen()
+ val midBlue = findBlue()
+ val midBlueBounds = getBoundsOnScreen(midBlue)
+ verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen)
+ verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue)
+ verifyAndClearTransition(fragment2.enterTransition, midBlueBounds, midGreen)
+ verifyNoOtherTransitions(fragment1)
+ verifyNoOtherTransitions(fragment2)
+
+ val fragment3 = TransitionFragment(R.layout.scene3)
+
+ activityRule.runOnUiThread {
+ val fm = activityRule.activity.supportFragmentManager
+ fm.popBackStack()
+ fm.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack(null)
+ .commit()
+ }
+
+ // This shouldn't give an error.
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ fragment2.waitForTransition()
+ // It does not transition properly for ordered transactions, though.
+ if (reorderingAllowed) {
+ verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue)
+ val endGreen = findGreen()
+ val endBlue = findBlue()
+ val endRed = findRed()
+ verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed!!)
+ verifyNoOtherTransitions(fragment2)
+ verifyNoOtherTransitions(fragment3)
+ } else {
+ // fragment3 doesn't get a transition since it conflicts with the pop transition
+ verifyNoOtherTransitions(fragment3)
+ // Everything else is just doing its best. Ordered transactions can't handle
+ // multiple transitions acting together except for popping multiple together.
+ }
+ }
+
+ // When there is no matching shared element, the transition name should not be changed
+ @Test
+ fun noMatchingSharedElementRetainName() {
+ val fragment1 = setupInitialFragment()
+
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startGreenBounds = getBoundsOnScreen(startGreen)
+
+ val fragment2 = TransitionFragment(R.layout.scene3)
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(startGreen, "greenSquare")
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+
+ fragment2.waitForTransition()
+ val midGreen = findGreen()
+ val midBlue = findBlue()
+ val midRed = findRed()
+ val midGreenBounds = getBoundsOnScreen(midGreen)
+ if (reorderingAllowed) {
+ verifyAndClearTransition(
+ fragment2.sharedElementEnter, startGreenBounds, startGreen,
+ midGreen
+ )
+ } else {
+ verifyAndClearTransition(
+ fragment2.sharedElementEnter, startGreenBounds, startGreen,
+ midGreen, startBlue
+ )
+ }
+ verifyAndClearTransition(fragment2.enterTransition, midGreenBounds, midBlue, midRed!!)
+ verifyNoOtherTransitions(fragment2)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+ fragment2.waitForTransition()
+ fragment1.waitForTransition()
+
+ val endBlue = findBlue()
+ val endGreen = findGreen()
+
+ assertThat(endBlue.transitionName).isEqualTo("blueSquare")
+ assertThat(endGreen.transitionName).isEqualTo("greenSquare")
+ }
+
+ private fun setupInitialFragment(): TransitionFragment {
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(1)
+ fragment1.waitForTransition()
+ val blueSquare1 = findBlue()
+ val greenSquare1 = findGreen()
+ verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1)
+ verifyNoOtherTransitions(fragment1)
+ return fragment1
+ }
+
+ private fun findViewById(fragment: Fragment, id: Int): View {
+ return fragment.requireView().findViewById(id)
+ }
+
+ private fun findGreen(): View {
+ return activityRule.activity.findViewById(R.id.greenSquare)
+ }
+
+ private fun findBlue(): View {
+ return activityRule.activity.findViewById(R.id.blueSquare)
+ }
+
+ private fun findRed(): View? {
+ return activityRule.activity.findViewById(R.id.redSquare)
+ }
+
+ private fun verifyAndClearTransition(
+ transition: TargetTracking,
+ epicenter: Rect?,
+ vararg expected: View
+ ) {
+ if (epicenter == null) {
+ assertThat(transition.capturedEpicenter).isNull()
+ } else {
+ assertThat(transition.capturedEpicenter).isEqualTo(epicenter)
+ }
+ val targets = transition.trackedTargets
+ val sb = StringBuilder()
+ sb.append("Expected: [")
+ .append(expected.size)
+ .append("] {")
+ var isFirst = true
+ for (view in expected) {
+ if (isFirst) {
+ isFirst = false
+ } else {
+ sb.append(", ")
+ }
+ sb.append(view)
+ }
+ sb.append("}, but got: [")
+ .append(targets.size)
+ .append("] {")
+ isFirst = true
+ for (view in targets) {
+ if (isFirst) {
+ isFirst = false
+ } else {
+ sb.append(", ")
+ }
+ sb.append(view)
+ }
+ sb.append("}")
+ val errorMessage = sb.toString()
+
+ assertWithMessage(errorMessage).that(targets.size).isEqualTo(expected.size)
+ for (view in expected) {
+ assertWithMessage(errorMessage).that(targets.contains(view)).isTrue()
+ }
+ transition.clearTargets()
+ }
+
+ private fun verifyNoOtherTransitions(fragment: TransitionFragment) {
+ assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.exitTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
+ }
+
+ private fun verifyTransition(
+ from: TransitionFragment,
+ to: TransitionFragment,
+ sharedElementName: String
+ ) {
+ val startOnBackStackChanged = onBackStackChangedTimes
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startRed = findRed()
+
+ val startBlueRect = getBoundsOnScreen(startBlue)
+
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(startBlue, sharedElementName)
+ .replace(R.id.fragmentContainer, to)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo(startOnBackStackChanged + 1)
+
+ to.waitForTransition()
+ val endGreen = findGreen()
+ val endBlue = findBlue()
+ val endRed = findRed()
+ val endBlueRect = getBoundsOnScreen(endBlue)
+
+ if (startRed != null) {
+ verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen, startRed)
+ } else {
+ verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen)
+ }
+ verifyNoOtherTransitions(from)
+
+ if (endRed != null) {
+ verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen, endRed)
+ } else {
+ verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen)
+ }
+ verifyAndClearTransition(to.sharedElementEnter, startBlueRect, startBlue, endBlue)
+ verifyNoOtherTransitions(to)
+ }
+
+ private fun verifyCrossTransition(
+ swapSource: Boolean,
+ from1: TransitionFragment,
+ from2: TransitionFragment
+ ) {
+ val startNumOnBackStackChanged = onBackStackChangedTimes
+ val changesPerOperation = if (reorderingAllowed) 1 else 2
+
+ val to1 = TransitionFragment(R.layout.scene2)
+ val to2 = TransitionFragment(R.layout.scene2)
+
+ val fromExit1 = findViewById(from1, R.id.greenSquare)
+ val fromShared1 = findViewById(from1, R.id.blueSquare)
+ val fromSharedRect1 = getBoundsOnScreen(fromShared1)
+
+ val fromExitId2 = if (swapSource) R.id.blueSquare else R.id.greenSquare
+ val fromSharedId2 = if (swapSource) R.id.greenSquare else R.id.blueSquare
+ val fromExit2 = findViewById(from2, fromExitId2)
+ val fromShared2 = findViewById(from2, fromSharedId2)
+ val fromSharedRect2 = getBoundsOnScreen(fromShared2)
+
+ val sharedElementName = if (swapSource) "blueSquare" else "greenSquare"
+
+ activityRule.runOnUiThread {
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(fromShared1, "blueSquare")
+ .replace(R.id.fragmentContainer1, to1)
+ .addToBackStack(null)
+ .commit()
+ fragmentManager.beginTransaction()
+ .setReorderingAllowed(reorderingAllowed)
+ .addSharedElement(fromShared2, sharedElementName)
+ .replace(R.id.fragmentContainer2, to2)
+ .addToBackStack(null)
+ .commit()
+ }
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes)
+ .isEqualTo(startNumOnBackStackChanged + changesPerOperation)
+
+ from1.waitForTransition()
+ from2.waitForTransition()
+ to1.waitForTransition()
+ to2.waitForTransition()
+
+ val toEnter1 = findViewById(to1, R.id.greenSquare)
+ val toShared1 = findViewById(to1, R.id.blueSquare)
+ val toSharedRect1 = getBoundsOnScreen(toShared1)
+
+ val toEnter2 = findViewById(to2, fromSharedId2)
+ val toShared2 = findViewById(to2, fromExitId2)
+ val toSharedRect2 = getBoundsOnScreen(toShared2)
+
+ verifyAndClearTransition(from1.exitTransition, fromSharedRect1, fromExit1)
+ verifyAndClearTransition(from2.exitTransition, fromSharedRect2, fromExit2)
+ verifyNoOtherTransitions(from1)
+ verifyNoOtherTransitions(from2)
+
+ verifyAndClearTransition(to1.enterTransition, toSharedRect1, toEnter1)
+ verifyAndClearTransition(to2.enterTransition, toSharedRect2, toEnter2)
+ verifyAndClearTransition(to1.sharedElementEnter, fromSharedRect1, fromShared1, toShared1)
+ verifyAndClearTransition(to2.sharedElementEnter, fromSharedRect2, fromShared2, toShared2)
+ verifyNoOtherTransitions(to1)
+ verifyNoOtherTransitions(to2)
+
+ // Now pop it back
+ activityRule.runOnUiThread {
+ fragmentManager.popBackStack()
+ fragmentManager.popBackStack()
+ }
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes)
+ .isEqualTo(startNumOnBackStackChanged + changesPerOperation + 1)
+
+ from1.waitForTransition()
+ from2.waitForTransition()
+ to1.waitForTransition()
+ to2.waitForTransition()
+
+ val returnEnter1 = findViewById(from1, R.id.greenSquare)
+ val returnShared1 = findViewById(from1, R.id.blueSquare)
+
+ val returnEnter2 = findViewById(from2, fromExitId2)
+ val returnShared2 = findViewById(from2, fromSharedId2)
+
+ verifyAndClearTransition(to1.returnTransition, toSharedRect1, toEnter1)
+ verifyAndClearTransition(to2.returnTransition, toSharedRect2, toEnter2)
+ verifyAndClearTransition(to1.sharedElementReturn, toSharedRect1, toShared1, returnShared1)
+ verifyAndClearTransition(to2.sharedElementReturn, toSharedRect2, toShared2, returnShared2)
+ verifyNoOtherTransitions(to1)
+ verifyNoOtherTransitions(to2)
+
+ verifyAndClearTransition(from1.reenterTransition, fromSharedRect1, returnEnter1)
+ verifyAndClearTransition(from2.reenterTransition, fromSharedRect2, returnEnter2)
+ verifyNoOtherTransitions(from1)
+ verifyNoOtherTransitions(from2)
+ }
+
+ private fun verifyPopTransition(
+ numPops: Int,
+ from: TransitionFragment,
+ to: TransitionFragment,
+ vararg others: TransitionFragment
+ ) {
+ val startOnBackStackChanged = onBackStackChangedTimes
+ val startBlue = findBlue()
+ val startGreen = findGreen()
+ val startRed = findRed()
+ val startSharedRect = getBoundsOnScreen(startBlue)
+
+ instrumentation.runOnMainSync {
+ for (i in 0 until numPops) {
+ fragmentManager.popBackStack()
+ }
+ }
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertThat(onBackStackChangedTimes).isEqualTo((startOnBackStackChanged + 1))
+
+ to.waitForTransition()
+ val endGreen = findGreen()
+ val endBlue = findBlue()
+ val endRed = findRed()
+ val endSharedRect = getBoundsOnScreen(endBlue)
+
+ if (startRed != null) {
+ verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen, startRed)
+ } else {
+ verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen)
+ }
+ verifyAndClearTransition(from.sharedElementReturn, startSharedRect, startBlue, endBlue)
+ verifyNoOtherTransitions(from)
+
+ if (endRed != null) {
+ verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen, endRed)
+ } else {
+ verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen)
+ }
+ verifyNoOtherTransitions(to)
+
+ for (fragment in others) {
+ verifyNoOtherTransitions(fragment)
+ }
+ }
+
+ class ComplexTransitionFragment : TransitionFragment(R.layout.scene2) {
+ val sharedElementEnterTransition1 = TrackingTransition()
+ val sharedElementEnterTransition2 = TrackingTransition()
+ val sharedElementReturnTransition1 = TrackingTransition()
+ val sharedElementReturnTransition2 = TrackingTransition()
+
+ val sharedElementEnterTransition: TransitionSet = TransitionSet()
+ .addTransition(sharedElementEnterTransition1)
+ .addTransition(sharedElementEnterTransition2)
+ val sharedElementReturnTransition: TransitionSet = TransitionSet()
+ .addTransition(sharedElementReturnTransition1)
+ .addTransition(sharedElementReturnTransition2)
+
+ init {
+ sharedElementEnterTransition1.addTarget(R.id.blueSquare)
+ sharedElementEnterTransition2.addTarget(R.id.greenSquare)
+ sharedElementReturnTransition1.addTarget(R.id.blueSquare)
+ sharedElementReturnTransition2.addTarget(R.id.greenSquare)
+ setSharedElementEnterTransition(sharedElementEnterTransition)
+ setSharedElementReturnTransition(sharedElementReturnTransition)
+ }
+ }
+
+ class InvisibleFragment : TransitionFragment(R.layout.scene1) {
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ view.visibility = View.INVISIBLE
+ super.onViewCreated(view, savedInstanceState)
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Array<Boolean> {
+ return arrayOf(false, true)
+ }
+
+ private fun getBoundsOnScreen(view: View): Rect {
+ val loc = IntArray(2)
+ view.getLocationOnScreen(loc)
+ return Rect(loc[0], loc[1], loc[0] + view.width, loc[1] + view.height)
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.java
deleted file mode 100644
index 40c6c96..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.lifecycle.GenericLifecycleObserver;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.Observer;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class FragmentViewLifecycleTest {
-
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<>(FragmentTestActivity.class);
-
- @Test
- @UiThreadTest
- public void testFragmentViewLifecycle() {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final StrictViewFragment fragment = new StrictViewFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- fm.beginTransaction().add(R.id.content, fragment).commitNow();
- assertEquals(Lifecycle.State.RESUMED,
- fragment.getViewLifecycleOwner().getLifecycle().getCurrentState());
- }
-
- @Test
- @UiThreadTest
- public void testFragmentViewLifecycleNullView() {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final Fragment fragment = new Fragment();
- fm.beginTransaction().add(fragment, "fragment").commitNow();
- try {
- fragment.getViewLifecycleOwner();
- fail("getViewLifecycleOwner should be unavailable if onCreateView returned null");
- } catch (IllegalStateException expected) {
- // Expected
- }
- }
-
- @Test
- @UiThreadTest
- public void testObserveInOnCreateViewNullView() {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final Fragment fragment = new ObserveInOnCreateViewFragment();
- try {
- fm.beginTransaction().add(fragment, "fragment").commitNow();
- fail("Fragments accessing view lifecycle should fail if onCreateView returned null");
- } catch (IllegalStateException expected) {
- // We need to clean up the Fragment to avoid it still being around
- // when the instrumentation test Activity pauses. Real apps would have
- // just crashed right after onCreateView().
- fm.beginTransaction().remove(fragment).commitNow();
- }
- }
-
- @Test
- public void testFragmentViewLifecycleRunOnCommit() throws Throwable {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final StrictViewFragment fragment = new StrictViewFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- fm.beginTransaction().add(R.id.content, fragment).runOnCommit(new Runnable() {
- @Override
- public void run() {
- assertEquals(Lifecycle.State.RESUMED,
- fragment.getViewLifecycleOwner().getLifecycle().getCurrentState());
- countDownLatch.countDown();
-
- }
- }).commit();
- countDownLatch.await(1, TimeUnit.SECONDS);
- }
-
- @Test
- public void testFragmentViewLifecycleOwnerLiveData() throws Throwable {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final StrictViewFragment fragment = new StrictViewFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fragment.getViewLifecycleOwnerLiveData().observe(activity,
- new Observer<LifecycleOwner>() {
- @Override
- public void onChanged(LifecycleOwner lifecycleOwner) {
- if (lifecycleOwner != null) {
- assertTrue("Fragment View LifecycleOwner should be "
- + "only be set after onCreateView()",
- fragment.mOnCreateViewCalled);
- countDownLatch.countDown();
- } else {
- assertTrue("Fragment View LifecycleOwner should be "
- + "set to null after onDestroyView()",
- fragment.mOnDestroyViewCalled);
- countDownLatch.countDown();
- }
- }
- });
- fm.beginTransaction().add(R.id.content, fragment).commitNow();
- // Now remove the Fragment to trigger the destruction of the view
- fm.beginTransaction().remove(fragment).commitNow();
- }
- });
- countDownLatch.await(1, TimeUnit.SECONDS);
- }
-
- @Test
- public void testViewLifecycleInFragmentLifecycle() throws Throwable {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final StrictViewFragment fragment = new StrictViewFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- final GenericLifecycleObserver lifecycleObserver = mock(GenericLifecycleObserver.class);
- final LifecycleOwner[] viewLifecycleOwner = new LifecycleOwner[1];
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fragment.getViewLifecycleOwnerLiveData().observe(activity,
- new Observer<LifecycleOwner>() {
- @Override
- public void onChanged(LifecycleOwner lifecycleOwner) {
- if (lifecycleOwner != null) {
- viewLifecycleOwner[0] = lifecycleOwner;
- lifecycleOwner.getLifecycle().addObserver(lifecycleObserver);
- }
- }
- });
- fragment.getLifecycle().addObserver(lifecycleObserver);
- fm.beginTransaction().add(R.id.content, fragment).commitNow();
- // Now remove the Fragment to trigger the destruction of the view
- fm.beginTransaction().remove(fragment).commitNow();
- }
- });
-
- // The Fragment's lifecycle should change first, followed by the fragment's view lifecycle
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_CREATE);
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_CREATE);
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_START);
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_START);
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_RESUME);
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_RESUME);
- // Now the order reverses as things unwind
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_PAUSE);
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_PAUSE);
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_STOP);
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_STOP);
- verify(lifecycleObserver)
- .onStateChanged(viewLifecycleOwner[0], Lifecycle.Event.ON_DESTROY);
- verify(lifecycleObserver)
- .onStateChanged(fragment, Lifecycle.Event.ON_DESTROY);
- verifyNoMoreInteractions(lifecycleObserver);
- }
-
- @Test
- @UiThreadTest
- public void testFragmentViewLifecycleDetach() {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final ObservingFragment fragment = new ObservingFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- fm.beginTransaction().add(R.id.content, fragment).commitNow();
- LifecycleOwner viewLifecycleOwner = fragment.getViewLifecycleOwner();
- assertEquals(Lifecycle.State.RESUMED,
- viewLifecycleOwner.getLifecycle().getCurrentState());
- assertTrue("LiveData should have active observers when RESUMED",
- fragment.mLiveData.hasActiveObservers());
-
- fm.beginTransaction().detach(fragment).commitNow();
- assertEquals(Lifecycle.State.DESTROYED,
- viewLifecycleOwner.getLifecycle().getCurrentState());
- assertFalse("LiveData should not have active observers after detach()",
- fragment.mLiveData.hasActiveObservers());
- try {
- fragment.getViewLifecycleOwner();
- fail("getViewLifecycleOwner should be unavailable after onDestroyView");
- } catch (IllegalStateException expected) {
- // Expected
- }
- }
-
- @Test
- @UiThreadTest
- public void testFragmentViewLifecycleReattach() {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- final ObservingFragment fragment = new ObservingFragment();
- fragment.setLayoutId(R.layout.fragment_a);
- fm.beginTransaction().add(R.id.content, fragment).commitNow();
- LifecycleOwner viewLifecycleOwner = fragment.getViewLifecycleOwner();
- assertEquals(Lifecycle.State.RESUMED,
- viewLifecycleOwner.getLifecycle().getCurrentState());
- assertTrue("LiveData should have active observers when RESUMED",
- fragment.mLiveData.hasActiveObservers());
-
- fm.beginTransaction().detach(fragment).commitNow();
- // The existing view lifecycle should be destroyed
- assertEquals(Lifecycle.State.DESTROYED,
- viewLifecycleOwner.getLifecycle().getCurrentState());
- assertFalse("LiveData should not have active observers after detach()",
- fragment.mLiveData.hasActiveObservers());
-
- fm.beginTransaction().attach(fragment).commitNow();
- assertNotEquals("A new view LifecycleOwner should be returned after reattachment",
- viewLifecycleOwner, fragment.getViewLifecycleOwner());
- assertEquals(Lifecycle.State.RESUMED,
- fragment.getViewLifecycleOwner().getLifecycle().getCurrentState());
- assertTrue("LiveData should have active observers when RESUMED",
- fragment.mLiveData.hasActiveObservers());
- }
-
- public static class ObserveInOnCreateViewFragment extends Fragment {
- MutableLiveData<Boolean> mLiveData = new MutableLiveData<>();
- private Observer<Boolean> mOnCreateViewObserver = new Observer<Boolean>() {
- @Override
- public void onChanged(Boolean value) {
- }
- };
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mLiveData.observe(getViewLifecycleOwner(), mOnCreateViewObserver);
- assertTrue("LiveData should have observers after onCreateView observe",
- mLiveData.hasObservers());
- // Return null - oops!
- return null;
- }
-
- }
-
- public static class ObservingFragment extends StrictViewFragment {
- MutableLiveData<Boolean> mLiveData = new MutableLiveData<>();
- private Observer<Boolean> mOnCreateViewObserver = new Observer<Boolean>() {
- @Override
- public void onChanged(Boolean value) {
- }
- };
- private Observer<Boolean> mOnViewCreatedObserver = new Observer<Boolean>() {
- @Override
- public void onChanged(Boolean value) {
- }
- };
- private Observer<Boolean> mOnViewStateRestoredObserver = new Observer<Boolean>() {
- @Override
- public void onChanged(Boolean value) {
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mLiveData.observe(getViewLifecycleOwner(), mOnCreateViewObserver);
- assertTrue("LiveData should have observers after onCreateView observe",
- mLiveData.hasObservers());
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mLiveData.observe(getViewLifecycleOwner(), mOnViewCreatedObserver);
- assertTrue("LiveData should have observers after onViewCreated observe",
- mLiveData.hasObservers());
- }
-
- @Override
- public void onViewStateRestored(Bundle savedInstanceState) {
- super.onViewStateRestored(savedInstanceState);
- mLiveData.observe(getViewLifecycleOwner(), mOnViewStateRestoredObserver);
- assertTrue("LiveData should have observers after onViewStateRestored observe",
- mLiveData.hasObservers());
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
new file mode 100644
index 0000000..be3612c
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewLifecycleTest.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class FragmentViewLifecycleTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ @Test
+ @UiThreadTest
+ fun testFragmentViewLifecycle() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = StrictViewFragment(R.layout.fragment_a)
+ fm.beginTransaction().add(R.id.content, fragment).commitNow()
+ assertThat(fragment.viewLifecycleOwner.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testFragmentViewLifecycleNullView() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = Fragment()
+ fm.beginTransaction().add(fragment, "fragment").commitNow()
+ try {
+ fragment.viewLifecycleOwner
+ fail("getViewLifecycleOwner should be unavailable if onCreateView returned null")
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat().contains("Can't access the Fragment View's LifecycleOwner when" +
+ " getView() is null i.e., before onCreateView() or after onDestroyView()")
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testObserveInOnCreateViewNullView() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = ObserveInOnCreateViewFragment()
+ try {
+ fm.beginTransaction().add(fragment, "fragment").commitNow()
+ fail("Fragments accessing view lifecycle should fail if onCreateView returned null")
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat()
+ .contains("Called getViewLifecycleOwner() but onCreateView() returned null")
+ // We need to clean up the Fragment to avoid it still being around
+ // when the instrumentation test Activity pauses. Real apps would have
+ // just crashed right after onCreateView().
+ fm.beginTransaction().remove(fragment).commitNow()
+ }
+ }
+
+ @Test
+ fun testFragmentViewLifecycleRunOnCommit() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val countDownLatch = CountDownLatch(1)
+ val fragment = StrictViewFragment(R.layout.fragment_a)
+ fm.beginTransaction().add(R.id.content, fragment).runOnCommit {
+ assertThat(fragment.viewLifecycleOwner.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ countDownLatch.countDown()
+ }.commit()
+ countDownLatch.await(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ fun testFragmentViewLifecycleOwnerLiveData() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val countDownLatch = CountDownLatch(2)
+ val fragment = StrictViewFragment(R.layout.fragment_a)
+ activityRule.runOnUiThread {
+ fragment.viewLifecycleOwnerLiveData.observe(activity,
+ Observer { lifecycleOwner ->
+ if (lifecycleOwner != null) {
+ assertWithMessage("Fragment View LifecycleOwner should be only be set" +
+ "after onCreateView()")
+ .that(fragment.onCreateViewCalled)
+ .isTrue()
+ countDownLatch.countDown()
+ } else {
+ assertWithMessage("Fragment View LifecycleOwner should be set to null" +
+ " after onDestroyView()")
+ .that(fragment.onDestroyViewCalled)
+ .isTrue()
+ countDownLatch.countDown()
+ }
+ })
+ fm.beginTransaction().add(R.id.content, fragment).commitNow()
+ // Now remove the Fragment to trigger the destruction of the view
+ fm.beginTransaction().remove(fragment).commitNow()
+ }
+ countDownLatch.await(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ fun testViewLifecycleInFragmentLifecycle() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = StrictViewFragment(R.layout.fragment_a)
+ val lifecycleObserver = mock(LifecycleEventObserver::class.java)
+ lateinit var viewLifecycleOwner: LifecycleOwner
+ activityRule.runOnUiThread {
+ fragment.viewLifecycleOwnerLiveData.observe(activity,
+ Observer { lifecycleOwner ->
+ if (lifecycleOwner != null) {
+ viewLifecycleOwner = lifecycleOwner
+ lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
+ }
+ })
+ fragment.lifecycle.addObserver(lifecycleObserver)
+ fm.beginTransaction().add(R.id.content, fragment).commitNow()
+ // Now remove the Fragment to trigger the destruction of the view
+ fm.beginTransaction().remove(fragment).commitNow()
+ }
+
+ // The Fragment's lifecycle should change first, followed by the fragment's view lifecycle
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_CREATE)
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_CREATE)
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_START)
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_START)
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_RESUME)
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_RESUME)
+ // Now the order reverses as things unwind
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_PAUSE)
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_PAUSE)
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_STOP)
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_STOP)
+ verify(lifecycleObserver).onStateChanged(viewLifecycleOwner, Lifecycle.Event.ON_DESTROY)
+ verify(lifecycleObserver).onStateChanged(fragment, Lifecycle.Event.ON_DESTROY)
+ verifyNoMoreInteractions(lifecycleObserver)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testFragmentViewLifecycleDetach() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = ObservingFragment()
+ fm.beginTransaction().add(R.id.content, fragment).commitNow()
+ val viewLifecycleOwner = fragment.viewLifecycleOwner
+ assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ assertWithMessage("LiveData should have active observers when RESUMED")
+ .that(fragment.liveData.hasActiveObservers()).isTrue()
+
+ fm.beginTransaction().detach(fragment).commitNow()
+ assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+ assertWithMessage("LiveData should not have active observers after detach()")
+ .that(fragment.liveData.hasActiveObservers()).isFalse()
+ try {
+ fragment.viewLifecycleOwner
+ fail("getViewLifecycleOwner should be unavailable after onDestroyView")
+ } catch (expected: IllegalStateException) {
+ assertThat(expected)
+ .hasMessageThat().contains("Can't access the Fragment View's LifecycleOwner when" +
+ " getView() is null i.e., before onCreateView() or after onDestroyView()")
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ fun testFragmentViewLifecycleReattach() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val fragment = ObservingFragment()
+ fm.beginTransaction().add(R.id.content, fragment).commitNow()
+ val viewLifecycleOwner = fragment.viewLifecycleOwner
+ assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ assertWithMessage("LiveData should have active observers when RESUMED")
+ .that(fragment.liveData.hasActiveObservers()).isTrue()
+
+ fm.beginTransaction().detach(fragment).commitNow()
+ // The existing view lifecycle should be destroyed
+ assertThat(viewLifecycleOwner.lifecycle.currentState).isEqualTo(Lifecycle.State.DESTROYED)
+ assertWithMessage("LiveData should not have active observers after detach()")
+ .that(fragment.liveData.hasActiveObservers()).isFalse()
+
+ fm.beginTransaction().attach(fragment).commitNow()
+ assertWithMessage("A new view LifecycleOwner should be returned after reattachment")
+ .that(fragment.viewLifecycleOwner).isNotEqualTo(viewLifecycleOwner)
+ assertThat(fragment.viewLifecycleOwner.lifecycle.currentState)
+ .isEqualTo(Lifecycle.State.RESUMED)
+ assertWithMessage("LiveData should have active observers when RESUMED")
+ .that(fragment.liveData.hasActiveObservers()).isTrue()
+ }
+
+ class ObserveInOnCreateViewFragment : Fragment() {
+ private val liveData = MutableLiveData<Boolean>()
+ private val onCreateViewObserver = Observer<Boolean> { }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ liveData.observe(viewLifecycleOwner, onCreateViewObserver)
+ assertWithMessage("LiveData should have observers after onCreateView observe")
+ .that(liveData.hasObservers()).isTrue()
+ // Return null - oops!
+ return null
+ }
+ }
+
+ class ObservingFragment : StrictViewFragment(R.layout.fragment_a) {
+ val liveData = MutableLiveData<Boolean>()
+ private val onCreateViewObserver = Observer<Boolean> { }
+ private val onViewCreatedObserver = Observer<Boolean> { }
+ private val onViewStateRestoredObserver = Observer<Boolean> { }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = super.onCreateView(inflater, container, savedInstanceState).also {
+ liveData.observe(viewLifecycleOwner, onCreateViewObserver)
+ assertWithMessage("LiveData should have observers after onCreateView observe")
+ .that(liveData.hasObservers()).isTrue()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ liveData.observe(viewLifecycleOwner, onViewCreatedObserver)
+ assertWithMessage("LiveData should have observers after onViewCreated observe")
+ .that(liveData.hasObservers()).isTrue()
+ }
+
+ override fun onViewStateRestored(savedInstanceState: Bundle?) {
+ super.onViewStateRestored(savedInstanceState)
+ liveData.observe(viewLifecycleOwner, onViewStateRestoredObserver)
+ assertWithMessage("LiveData should have observers after onViewStateRestored observe")
+ .that(liveData.hasObservers()).isTrue()
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
new file mode 100644
index 0000000..4b2ff74
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTest.kt
@@ -0,0 +1,1040 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FragmentViewTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ // Test that adding a fragment adds the Views in the proper order. Popping the back stack
+ // should remove the correct Views.
+ @Test
+ fun addFragments() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ // One fragment with a view
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ // Add another on top
+ val fragment2 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment2).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+ // Now add two in one transaction:
+ val fragment3 = StrictViewFragment()
+ val fragment4 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment3)
+ .add(R.id.fragmentContainer, fragment4)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(container.childCount).isEqualTo(1)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+ }
+
+ // Add fragments to multiple containers in the same transaction. Make sure that
+ // they pop correctly, too.
+ @Test
+ fun addTwoContainers() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+ val container1 =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+ val container2 =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer2) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer1, fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container1, fragment1)
+
+ val fragment2 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer2, fragment2).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container2, fragment2)
+
+ val fragment3 = StrictViewFragment()
+ val fragment4 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment3)
+ .add(R.id.fragmentContainer2, fragment4)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container1, fragment1, fragment3)
+ FragmentTestUtil.assertChildren(container2, fragment2, fragment4)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container1, fragment1)
+ FragmentTestUtil.assertChildren(container2, fragment2)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container1, fragment1)
+ FragmentTestUtil.assertChildren(container2)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(container1.childCount).isEqualTo(0)
+ }
+
+ // When you add a fragment that's has already been added, it should throw.
+ @Test
+ fun doubleAdd() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ instrumentation.runOnMainSync {
+ try {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ fail("Adding a fragment that is already added should be an error")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("Fragment already added: $fragment1")
+ }
+ }
+ }
+
+ // Make sure that removed fragments remove the right Views. Popping the back stack should
+ // add the Views back properly
+ @Test
+ fun removeFragments() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+ val fragment3 = StrictViewFragment()
+ val fragment4 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "1")
+ .add(R.id.fragmentContainer, fragment2, "2")
+ .add(R.id.fragmentContainer, fragment3, "3")
+ .add(R.id.fragmentContainer, fragment4, "4")
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4)
+
+ // Remove a view
+ fm.beginTransaction().remove(fragment4).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ assertThat(container.childCount).isEqualTo(3)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3)
+
+ // remove another one
+ fm.beginTransaction().remove(fragment2).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1, fragment3)
+
+ // Now remove the remaining:
+ fm.beginTransaction()
+ .remove(fragment3)
+ .remove(fragment1)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val replacement1 = fm.findFragmentByTag("1")
+ val replacement3 = fm.findFragmentByTag("3")
+ FragmentTestUtil.assertChildren(container, replacement1, replacement3)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val replacement2 = fm.findFragmentByTag("2")
+ FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val replacement4 = fm.findFragmentByTag("4")
+ FragmentTestUtil.assertChildren(
+ container, replacement1, replacement3, replacement2,
+ replacement4
+ )
+ }
+
+ // Removing a hidden fragment should remove the View and popping should bring it back hidden
+ @Test
+ fun removeHiddenView() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").hide(fragment1).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+ assertThat(fragment1.isHidden).isTrue()
+
+ fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val replacement1 = fm.findFragmentByTag("1")!!
+ FragmentTestUtil.assertChildren(container, replacement1)
+ assertThat(replacement1.isHidden).isTrue()
+ assertThat(replacement1.requireView().visibility).isEqualTo(View.GONE)
+ }
+
+ // Removing a detached fragment should do nothing to the View and popping should bring
+ // the Fragment back detached
+ @Test
+ fun removeDetatchedView() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "1")
+ .detach(fragment1)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment1.isDetached).isTrue()
+
+ fm.beginTransaction().remove(fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ val replacement1 = fm.findFragmentByTag("1")!!
+ FragmentTestUtil.assertChildren(container)
+ assertThat(replacement1.isDetached).isTrue()
+ }
+
+ // Unlike adding the same fragment twice, you should be able to add and then remove and then
+ // add the same fragment in one transaction.
+ @Test
+ fun addRemoveAdd() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .remove(fragment)
+ .add(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container)
+ }
+
+ // Removing a fragment that isn't in should not throw
+ @Test
+ fun removeNotThere() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().remove(fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Hide a fragment and its View should be GONE. Then pop it and the View should be VISIBLE
+ @Test
+ fun hideFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.beginTransaction().hide(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ // Hiding a hidden fragment should not throw
+ @Test
+ fun doubleHide() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .hide(fragment)
+ .hide(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Hiding a non-existing fragment should not throw
+ @Test
+ fun hideUnAdded() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .hide(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Show a hidden fragment and its View should be VISIBLE. Then pop it and the View should be
+ // GONE.
+ @Test
+ fun showFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+ fm.beginTransaction().show(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+ }
+
+ // Showing a shown fragment should not throw
+ @Test
+ fun showShown() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .show(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Showing a non-existing fragment should not throw
+ @Test
+ fun showUnAdded() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .show(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Detaching a fragment should remove the View from the hierarchy. Then popping it should
+ // bring it back VISIBLE
+ @Test
+ fun detachFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.beginTransaction().detach(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isDetached).isTrue()
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ // Detaching a hidden fragment should remove the View from the hierarchy. Then popping it should
+ // bring it back hidden
+ @Test
+ fun detachHiddenFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+ fm.beginTransaction().detach(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.isDetached).isTrue()
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+ }
+
+ // Detaching a detached fragment should not throw
+ @Test
+ fun detachDetatched() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .detach(fragment)
+ .detach(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Detaching a non-existing fragment should not throw
+ @Test
+ fun detachUnAdded() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .detach(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Attaching a fragment should add the View back into the hierarchy. Then popping it should
+ // remove it again
+ @Test
+ fun attachFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment).detach(fragment).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isDetached).isTrue()
+
+ fm.beginTransaction().attach(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isDetached).isTrue()
+ }
+
+ // Attaching a hidden fragment should add the View as GONE the hierarchy. Then popping it should
+ // remove it again.
+ @Test
+ fun attachHiddenFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .hide(fragment)
+ .detach(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isDetached).isTrue()
+ assertThat(fragment.isHidden).isTrue()
+
+ fm.beginTransaction().attach(fragment).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.isHidden).isTrue()
+ assertThat(fragment.isDetached).isFalse()
+ assertThat(fragment.requireView().visibility).isEqualTo(View.GONE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ assertThat(fragment.isDetached).isTrue()
+ assertThat(fragment.isHidden).isTrue()
+ }
+
+ // Attaching an attached fragment should not throw
+ @Test
+ fun attachAttached() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment)
+ .attach(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Attaching a non-existing fragment should not throw
+ @Test
+ fun attachUnAdded() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .attach(fragment)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ }
+
+ // Simple replace of one fragment in a container. Popping should replace it back again
+ @Test
+ fun replaceOne() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ val fragment2 = StrictViewFragment()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment2)
+ assertThat(fragment2.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ val replacement1 = fm.findFragmentByTag("1")!!
+ assertThat(replacement1).isNotNull()
+ FragmentTestUtil.assertChildren(container, replacement1)
+ assertThat(replacement1.isHidden).isFalse()
+ assertThat(replacement1.isAdded).isTrue()
+ assertThat(replacement1.isDetached).isFalse()
+ assertThat(replacement1.requireView().visibility).isEqualTo(View.VISIBLE)
+ }
+
+ // Replace of multiple fragments in a container. Popping should replace it back again
+ @Test
+ fun replaceTwo() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "1")
+ .add(R.id.fragmentContainer, fragment2, "2")
+ .hide(fragment2)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+ val fragment3 = StrictViewFragment()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment3)
+ assertThat(fragment3.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ val replacement1 = fm.findFragmentByTag("1")!!
+ val replacement2 = fm.findFragmentByTag("2")!!
+ assertThat(replacement1).isNotNull()
+ assertThat(replacement2).isNotNull()
+ FragmentTestUtil.assertChildren(container, replacement1, replacement2)
+ assertThat(replacement1.isHidden).isFalse()
+ assertThat(replacement1.isAdded).isTrue()
+ assertThat(replacement1.isDetached).isFalse()
+ assertThat(replacement1.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ // fragment2 was hidden, so it should be returned hidden
+ assertThat(replacement2.isHidden).isTrue()
+ assertThat(replacement2.isAdded).isTrue()
+ assertThat(replacement2.isDetached).isFalse()
+ assertThat(replacement2.requireView().visibility).isEqualTo(View.GONE)
+ }
+
+ // Replace of empty container. Should act as add and popping should just remove the fragment
+ @Test
+ fun replaceZero() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment = StrictViewFragment()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment)
+ assertThat(fragment.requireView().visibility).isEqualTo(View.VISIBLE)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container)
+ }
+
+ // Replace a fragment that exists with itself
+ @Test
+ fun replaceExisting() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "1")
+ .add(R.id.fragmentContainer, fragment2, "2")
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment1, fragment2)
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ val replacement1 = fm.findFragmentByTag("1")
+ val replacement2 = fm.findFragmentByTag("2")
+
+ assertThat(replacement1).isSameAs(fragment1)
+ FragmentTestUtil.assertChildren(container, replacement1, replacement2)
+ }
+
+ // Have two replace operations in the same transaction to ensure that they
+ // don't interfere with each other
+ @Test
+ fun replaceReplace() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+ val container1 =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer1) as ViewGroup
+ val container2 =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer2) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = StrictViewFragment()
+ val fragment2 = StrictViewFragment()
+ val fragment3 = StrictViewFragment()
+ val fragment4 = StrictViewFragment()
+ val fragment5 = StrictViewFragment()
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment1)
+ .add(R.id.fragmentContainer2, fragment2)
+ .replace(R.id.fragmentContainer1, fragment3)
+ .replace(R.id.fragmentContainer2, fragment4)
+ .replace(R.id.fragmentContainer1, fragment5)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ assertChildren(container1, fragment5)
+ assertChildren(container2, fragment4)
+
+ fm.popBackStack()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ assertChildren(container1)
+ assertChildren(container2)
+ }
+
+ // Test to prevent regressions in FragmentManager fragment replace method. See b/24693644
+ @Test
+ fun testReplaceFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+ val fragmentA = StrictViewFragment(R.layout.text_a)
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragmentA)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ assertThat(findViewById(R.id.textA)).isNotNull()
+ assertThat(findViewById(R.id.textB)).isNull()
+ assertThat(findViewById(R.id.textC)).isNull()
+
+ val fragmentB = StrictViewFragment(R.layout.text_b)
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragmentB)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(findViewById(R.id.textA)).isNotNull()
+ assertThat(findViewById(R.id.textB)).isNotNull()
+ assertThat(findViewById(R.id.textC)).isNull()
+
+ val fragmentC = StrictViewFragment(R.layout.text_c)
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragmentC)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ assertThat(findViewById(R.id.textA)).isNull()
+ assertThat(findViewById(R.id.textB)).isNull()
+ assertThat(findViewById(R.id.textC)).isNotNull()
+ }
+
+ // Test that adding a fragment with invisible or gone views does not end up with the view
+ // being visible
+ @Test
+ fun addInvisibleAndGoneFragments() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = InvisibleFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ assertThat(fragment1.requireView().visibility).isEqualTo(View.INVISIBLE)
+
+ val fragment2 = InvisibleFragment()
+ fragment2.visibility = View.GONE
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment2)
+
+ assertThat(fragment2.requireView().visibility).isEqualTo(View.GONE)
+ }
+
+ // Test to ensure that popping and adding a fragment properly track the fragments added
+ // and removed.
+ @Test
+ fun popAdd() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ // One fragment with a view
+ val fragment1 = StrictViewFragment()
+ fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit()
+ FragmentTestUtil.executePendingTransactions(activityRule)
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ val fragment2 = StrictViewFragment()
+ val fragment3 = StrictViewFragment()
+ instrumentation.runOnMainSync {
+ fm.popBackStack()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ fm.popBackStack()
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment3)
+ }
+
+ // Ensure that ordered transactions are executed individually rather than together.
+ // This forces references from one fragment to another that should be executed earlier
+ // to work.
+ @Test
+ fun orderedOperationsTogether() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = StrictViewFragment(R.layout.scene1)
+ val fragment2 = StrictViewFragment(R.layout.fragment_a)
+
+ activityRule.runOnUiThread {
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .setReorderingAllowed(false)
+ .addToBackStack(null)
+ .commit()
+ fm.beginTransaction()
+ .add(R.id.squareContainer, fragment2)
+ .setReorderingAllowed(false)
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+ }
+ FragmentTestUtil.assertChildren(container, fragment1)
+ assertThat(findViewById(R.id.textA)).isNotNull()
+ }
+
+ // Ensure that there is no problem if the child fragment manager is used before
+ // the View has been added.
+ @Test
+ fun childFragmentManager() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = ParentFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+ val innerContainer =
+ fragment1.requireView().findViewById<ViewGroup>(R.id.fragmentContainer1)
+
+ val fragment2 = fragment1.childFragmentManager.findFragmentByTag("inner")
+ FragmentTestUtil.assertChildren(innerContainer, fragment2)
+ }
+
+ // Popping the backstack with ordered fragments should execute the operations together.
+ // When a non-backstack fragment will be raised, it should not be destroyed.
+ @Test
+ fun popToNonBackStackFragment() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+
+ val fragment1 = SimpleViewFragment()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ val fragment2 = SimpleViewFragment()
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack("two")
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ val fragment3 = SimpleViewFragment()
+
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack("three")
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule)
+
+ assertThat(fragment1.onCreateViewCount).isEqualTo(1)
+ assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+ assertThat(fragment3.onCreateViewCount).isEqualTo(1)
+
+ FragmentTestUtil.popBackStackImmediate(
+ activityRule, "two",
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+
+ val container =
+ activityRule.activity.findViewById<View>(R.id.fragmentContainer) as ViewGroup
+
+ FragmentTestUtil.assertChildren(container, fragment1)
+
+ assertThat(fragment1.onCreateViewCount).isEqualTo(2)
+ assertThat(fragment2.onCreateViewCount).isEqualTo(1)
+ assertThat(fragment3.onCreateViewCount).isEqualTo(1)
+ }
+
+ private fun findViewById(viewId: Int): View? {
+ return activityRule.activity.findViewById(viewId)
+ }
+
+ private fun assertChildren(container: ViewGroup, vararg fragments: Fragment) {
+ val numFragments = fragments.size
+ assertWithMessage("There aren't the correct number of fragment Views in its container")
+ .that(container.childCount)
+ .isEqualTo(numFragments)
+ for (i in 0 until numFragments) {
+ assertWithMessage("Wrong Fragment View order for [$i]")
+ .that(fragments[i].view)
+ .isEqualTo(container.getChildAt(i))
+ }
+ }
+
+ class InvisibleFragment : StrictViewFragment() {
+ var visibility = View.INVISIBLE
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ view.visibility = visibility
+ super.onViewCreated(view, savedInstanceState)
+ }
+ }
+
+ class ParentFragment : StrictViewFragment(R.layout.double_container) {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val fragment2 = StrictViewFragment(R.layout.fragment_a)
+
+ childFragmentManager.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment2, "inner")
+ .addToBackStack(null)
+ .commit()
+ childFragmentManager.executePendingTransactions()
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+ }
+
+ class SimpleViewFragment : Fragment(R.layout.fragment_a) {
+ var onCreateViewCount: Int = 0
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ onCreateViewCount++
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java b/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java
deleted file mode 100644
index 51a53c5..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/FragmentViewTests.java
+++ /dev/null
@@ -1,1069 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class FragmentViewTests {
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private Instrumentation mInstrumentation;
-
- @Before
- public void setupInstrumentation() {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- }
-
- // Test that adding a fragment adds the Views in the proper order. Popping the back stack
- // should remove the correct Views.
- @Test
- public void addFragments() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- // One fragment with a view
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1);
-
- // Add another on top
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment2).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
- // Now add two in one transaction:
- final StrictViewFragment fragment3 = new StrictViewFragment();
- final StrictViewFragment fragment4 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment3)
- .add(R.id.fragmentContainer, fragment4)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(1, container.getChildCount());
- FragmentTestUtil.assertChildren(container, fragment1);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
- }
-
- // Add fragments to multiple containers in the same transaction. Make sure that
- // they pop correctly, too.
- @Test
- public void addTwoContainers() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
- ViewGroup container1 = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
- ViewGroup container2 = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer2);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer1, fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container1, fragment1);
-
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer2, fragment2).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container2, fragment2);
-
- final StrictViewFragment fragment3 = new StrictViewFragment();
- final StrictViewFragment fragment4 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment3)
- .add(R.id.fragmentContainer2, fragment4)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container1, fragment1, fragment3);
- FragmentTestUtil.assertChildren(container2, fragment2, fragment4);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container1, fragment1);
- FragmentTestUtil.assertChildren(container2, fragment2);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container1, fragment1);
- FragmentTestUtil.assertChildren(container2);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertEquals(0, container1.getChildCount());
- }
-
- // When you add a fragment that's has already been added, it should throw.
- @Test
- public void doubleAdd() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- try {
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- fail("Adding a fragment that is already added should be an error");
- } catch (IllegalStateException e) {
- // expected
- }
- }
- });
- }
-
- // Make sure that removed fragments remove the right Views. Popping the back stack should
- // add the Views back properly
- @Test
- public void removeFragments() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- final StrictViewFragment fragment2 = new StrictViewFragment();
- final StrictViewFragment fragment3 = new StrictViewFragment();
- final StrictViewFragment fragment4 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1, "1")
- .add(R.id.fragmentContainer, fragment2, "2")
- .add(R.id.fragmentContainer, fragment3, "3")
- .add(R.id.fragmentContainer, fragment4, "4")
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3, fragment4);
-
- // Remove a view
- fm.beginTransaction().remove(fragment4).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- assertEquals(3, container.getChildCount());
- FragmentTestUtil.assertChildren(container, fragment1, fragment2, fragment3);
-
- // remove another one
- fm.beginTransaction().remove(fragment2).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1, fragment3);
-
- // Now remove the remaining:
- fm.beginTransaction()
- .remove(fragment3)
- .remove(fragment1)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final Fragment replacement1 = fm.findFragmentByTag("1");
- final Fragment replacement3 = fm.findFragmentByTag("3");
- FragmentTestUtil.assertChildren(container, replacement1, replacement3);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final Fragment replacement2 = fm.findFragmentByTag("2");
- FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final Fragment replacement4 = fm.findFragmentByTag("4");
- FragmentTestUtil.assertChildren(container, replacement1, replacement3, replacement2,
- replacement4);
- }
-
- // Removing a hidden fragment should remove the View and popping should bring it back hidden
- @Test
- public void removeHiddenView() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").hide(fragment1).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1);
- assertTrue(fragment1.isHidden());
-
- fm.beginTransaction().remove(fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final Fragment replacement1 = fm.findFragmentByTag("1");
- FragmentTestUtil.assertChildren(container, replacement1);
- assertTrue(replacement1.isHidden());
- assertEquals(View.GONE, replacement1.requireView().getVisibility());
- }
-
- // Removing a detached fragment should do nothing to the View and popping should bring
- // the Fragment back detached
- @Test
- public void removeDetatchedView() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1, "1")
- .detach(fragment1)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment1.isDetached());
-
- fm.beginTransaction().remove(fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- final Fragment replacement1 = fm.findFragmentByTag("1");
- FragmentTestUtil.assertChildren(container);
- assertTrue(replacement1.isDetached());
- }
-
- // Unlike adding the same fragment twice, you should be able to add and then remove and then
- // add the same fragment in one transaction.
- @Test
- public void addRemoveAdd() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .remove(fragment)
- .add(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container);
- }
-
- // Removing a fragment that isn't in should not throw
- @Test
- public void removeNothThere() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().remove(fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Hide a fragment and its View should be GONE. Then pop it and the View should be VISIBLE
- @Test
- public void hideFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
- fm.beginTransaction().hide(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertTrue(fragment.isHidden());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isHidden());
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
- }
-
- // Hiding a hidden fragment should not throw
- @Test
- public void doubleHide() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .hide(fragment)
- .hide(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Hiding a non-existing fragment should not throw
- @Test
- public void hideUnAdded() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .hide(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Show a hidden fragment and its View should be VISIBLE. Then pop it and the View should be
- // GONE.
- @Test
- public void showFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertTrue(fragment.isHidden());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
-
- fm.beginTransaction().show(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isHidden());
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertTrue(fragment.isHidden());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
- }
-
- // Showing a shown fragment should not throw
- @Test
- public void showShown() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .show(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Showing a non-existing fragment should not throw
- @Test
- public void showUnAdded() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .show(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Detaching a fragment should remove the View from the hierarchy. Then popping it should
- // bring it back VISIBLE
- @Test
- public void detachFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isDetached());
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
- fm.beginTransaction().detach(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isDetached());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isDetached());
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
- }
-
- // Detaching a hidden fragment should remove the View from the hierarchy. Then popping it should
- // bring it back hidden
- @Test
- public void detachHiddenFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment).hide(fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isDetached());
- assertTrue(fragment.isHidden());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
-
- fm.beginTransaction().detach(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isHidden());
- assertTrue(fragment.isDetached());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertTrue(fragment.isHidden());
- assertFalse(fragment.isDetached());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
- }
-
- // Detaching a detached fragment should not throw
- @Test
- public void detachDetatched() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .detach(fragment)
- .detach(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Detaching a non-existing fragment should not throw
- @Test
- public void detachUnAdded() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .detach(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Attaching a fragment should add the View back into the hierarchy. Then popping it should
- // remove it again
- @Test
- public void attachFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment).detach(fragment).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isDetached());
-
- fm.beginTransaction().attach(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertFalse(fragment.isDetached());
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isDetached());
- }
-
- // Attaching a hidden fragment should add the View as GONE the hierarchy. Then popping it should
- // remove it again.
- @Test
- public void attachHiddenFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .hide(fragment)
- .detach(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isDetached());
- assertTrue(fragment.isHidden());
-
- fm.beginTransaction().attach(fragment).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertTrue(fragment.isHidden());
- assertFalse(fragment.isDetached());
- assertEquals(View.GONE, fragment.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- assertTrue(fragment.isDetached());
- assertTrue(fragment.isHidden());
- }
-
- // Attaching an attached fragment should not throw
- @Test
- public void attachAttached() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment)
- .attach(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Attaching a non-existing fragment should not throw
- @Test
- public void attachUnAdded() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .attach(fragment)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- }
-
- // Simple replace of one fragment in a container. Popping should replace it back again
- @Test
- public void replaceOne() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1, "1").commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment1);
-
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment2);
- assertEquals(View.VISIBLE, fragment2.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- Fragment replacement1 = fm.findFragmentByTag("1");
- assertNotNull(replacement1);
- FragmentTestUtil.assertChildren(container, replacement1);
- assertFalse(replacement1.isHidden());
- assertTrue(replacement1.isAdded());
- assertFalse(replacement1.isDetached());
- assertEquals(View.VISIBLE, replacement1.requireView().getVisibility());
- }
-
- // Replace of multiple fragments in a container. Popping should replace it back again
- @Test
- public void replaceTwo() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1, "1")
- .add(R.id.fragmentContainer, fragment2, "2")
- .hide(fragment2)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
- final StrictViewFragment fragment3 = new StrictViewFragment();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment3);
- assertEquals(View.VISIBLE, fragment3.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- Fragment replacement1 = fm.findFragmentByTag("1");
- Fragment replacement2 = fm.findFragmentByTag("2");
- assertNotNull(replacement1);
- assertNotNull(replacement2);
- FragmentTestUtil.assertChildren(container, replacement1, replacement2);
- assertFalse(replacement1.isHidden());
- assertTrue(replacement1.isAdded());
- assertFalse(replacement1.isDetached());
- assertEquals(View.VISIBLE, replacement1.requireView().getVisibility());
-
- // fragment2 was hidden, so it should be returned hidden
- assertTrue(replacement2.isHidden());
- assertTrue(replacement2.isAdded());
- assertFalse(replacement2.isDetached());
- assertEquals(View.GONE, replacement2.requireView().getVisibility());
- }
-
- // Replace of empty container. Should act as add and popping should just remove the fragment
- @Test
- public void replaceZero() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment = new StrictViewFragment();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment);
- assertEquals(View.VISIBLE, fragment.requireView().getVisibility());
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container);
- }
-
- // Replace a fragment that exists with itself
- @Test
- public void replaceExisting() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictViewFragment fragment1 = new StrictViewFragment();
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1, "1")
- .add(R.id.fragmentContainer, fragment2, "2")
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment1, fragment2);
-
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment1);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- final Fragment replacement1 = fm.findFragmentByTag("1");
- final Fragment replacement2 = fm.findFragmentByTag("2");
-
- assertSame(fragment1, replacement1);
- FragmentTestUtil.assertChildren(container, replacement1, replacement2);
- }
-
- // Have two replace operations in the same transaction to ensure that they
- // don't interfere with each other
- @Test
- public void replaceReplace() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
- ViewGroup container1 = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
- ViewGroup container2 = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer2);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment1 = new StrictViewFragment();
- final StrictViewFragment fragment2 = new StrictViewFragment();
- final StrictViewFragment fragment3 = new StrictViewFragment();
- final StrictViewFragment fragment4 = new StrictViewFragment();
- final StrictViewFragment fragment5 = new StrictViewFragment();
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment1)
- .add(R.id.fragmentContainer2, fragment2)
- .replace(R.id.fragmentContainer1, fragment3)
- .replace(R.id.fragmentContainer2, fragment4)
- .replace(R.id.fragmentContainer1, fragment5)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- assertChildren(container1, fragment5);
- assertChildren(container2, fragment4);
-
- fm.popBackStack();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- assertChildren(container1);
- assertChildren(container2);
- }
-
- // Test to prevent regressions in FragmentManager fragment replace method. See b/24693644
- @Test
- public void testReplaceFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- StrictViewFragment fragmentA = new StrictViewFragment();
- fragmentA.setLayoutId(R.layout.text_a);
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragmentA)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- assertNotNull(findViewById(R.id.textA));
- assertNull(findViewById(R.id.textB));
- assertNull(findViewById(R.id.textC));
-
- StrictViewFragment fragmentB = new StrictViewFragment();
- fragmentB.setLayoutId(R.layout.text_b);
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragmentB)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertNotNull(findViewById(R.id.textA));
- assertNotNull(findViewById(R.id.textB));
- assertNull(findViewById(R.id.textC));
-
- StrictViewFragment fragmentC = new StrictViewFragment();
- fragmentC.setLayoutId(R.layout.text_c);
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragmentC)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- assertNull(findViewById(R.id.textA));
- assertNull(findViewById(R.id.textB));
- assertNotNull(findViewById(R.id.textC));
- }
-
- // Test that adding a fragment with invisible or gone views does not end up with the view
- // being visible
- @Test
- public void addInvisibleAndGoneFragments() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment1 = new InvisibleFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1);
-
- assertEquals(View.INVISIBLE, fragment1.requireView().getVisibility());
-
- final InvisibleFragment fragment2 = new InvisibleFragment();
- fragment2.visibility = View.GONE;
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment2);
-
- assertEquals(View.GONE, fragment2.requireView().getVisibility());
- }
-
- // Test to ensure that popping and adding a fragment properly track the fragments added
- // and removed.
- @Test
- public void popAdd() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- // One fragment with a view
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
- FragmentTestUtil.executePendingTransactions(mActivityRule);
- FragmentTestUtil.assertChildren(container, fragment1);
-
- final StrictViewFragment fragment2 = new StrictViewFragment();
- final StrictViewFragment fragment3 = new StrictViewFragment();
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- fm.popBackStack();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- fm.popBackStack();
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(container, fragment3);
- }
-
- // Ensure that ordered transactions are executed individually rather than together.
- // This forces references from one fragment to another that should be executed earlier
- // to work.
- @Test
- public void orderedOperationsTogether() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment1 = new StrictViewFragment();
- fragment1.setLayoutId(R.layout.scene1);
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fragment2.setLayoutId(R.layout.fragment_a);
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .setReorderingAllowed(false)
- .addToBackStack(null)
- .commit();
- fm.beginTransaction()
- .add(R.id.squareContainer, fragment2)
- .setReorderingAllowed(false)
- .addToBackStack(null)
- .commit();
- fm.executePendingTransactions();
- }
- });
- FragmentTestUtil.assertChildren(container, fragment1);
- assertNotNull(findViewById(R.id.textA));
- }
-
- // Ensure that there is no problem if the child fragment manager is used before
- // the View has been added.
- @Test
- public void childFragmentManager() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final StrictViewFragment fragment1 = new ParentFragment();
- fragment1.setLayoutId(R.layout.double_container);
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- FragmentTestUtil.assertChildren(container, fragment1);
- ViewGroup innerContainer = fragment1.requireView().findViewById(R.id.fragmentContainer1);
-
- Fragment fragment2 = fragment1.getChildFragmentManager().findFragmentByTag("inner");
- FragmentTestUtil.assertChildren(innerContainer, fragment2);
- }
-
- // Popping the backstack with ordered fragments should execute the operations together.
- // When a non-backstack fragment will be raised, it should not be destroyed.
- @Test
- public void popToNonBackStackFragment() throws Throwable {
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- final SimpleViewFragment fragment1 = new SimpleViewFragment();
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer, fragment1)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- final SimpleViewFragment fragment2 = new SimpleViewFragment();
-
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack("two")
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- final SimpleViewFragment fragment3 = new SimpleViewFragment();
-
- fm.beginTransaction()
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack("three")
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule);
-
- assertEquals(1, fragment1.onCreateViewCount);
- assertEquals(1, fragment2.onCreateViewCount);
- assertEquals(1, fragment3.onCreateViewCount);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, "two",
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
-
- ViewGroup container = (ViewGroup)
- mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
-
- FragmentTestUtil.assertChildren(container, fragment1);
-
- assertEquals(2, fragment1.onCreateViewCount);
- assertEquals(1, fragment2.onCreateViewCount);
- assertEquals(1, fragment3.onCreateViewCount);
- }
-
- private View findViewById(int viewId) {
- return mActivityRule.getActivity().findViewById(viewId);
- }
-
- private void assertChildren(ViewGroup container, Fragment... fragments) {
- final int numFragments = fragments == null ? 0 : fragments.length;
- assertEquals("There aren't the correct number of fragment Views in its container",
- numFragments, container.getChildCount());
- for (int i = 0; i < numFragments; i++) {
- assertEquals("Wrong Fragment View order for [" + i + "]", container.getChildAt(i),
- fragments[i].getView());
- }
- }
-
- public static class InvisibleFragment extends StrictViewFragment {
- public int visibility = View.INVISIBLE;
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- view.setVisibility(visibility);
- super.onViewCreated(view, savedInstanceState);
- }
- }
-
- public static class ParentFragment extends StrictViewFragment {
- public ParentFragment() {
- setLayoutId(R.layout.double_container);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = super.onCreateView(inflater, container, savedInstanceState);
- final StrictViewFragment fragment2 = new StrictViewFragment();
- fragment2.setLayoutId(R.layout.fragment_a);
-
- getChildFragmentManager().beginTransaction()
- .add(R.id.fragmentContainer1, fragment2, "inner")
- .addToBackStack(null)
- .commit();
- getChildFragmentManager().executePendingTransactions();
- return view;
- }
- }
-
- @ContentView(R.layout.fragment_a)
- public static class SimpleViewFragment extends Fragment {
- public int onCreateViewCount;
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- onCreateViewCount++;
- return super.onCreateView(inflater, container, savedInstanceState);
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.java b/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.java
deleted file mode 100644
index 4b681c1..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.LoaderActivity;
-import androidx.fragment.test.R;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.testutils.FragmentActivityUtils;
-import androidx.testutils.RecreatedActivity;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.lang.ref.WeakReference;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class LoaderTest {
- @Rule
- public ActivityTestRule<LoaderActivity> mActivityRule =
- new ActivityTestRule<>(LoaderActivity.class);
-
- /**
- * Test to ensure that there is no Activity leak due to Loader
- */
- @Test
- public void testLeak() throws Throwable {
- // Restart the activity because mActivityRule keeps a strong reference to the
- // old activity.
- LoaderActivity activity = FragmentActivityUtils.recreateActivity(mActivityRule,
- mActivityRule.getActivity());
-
- LoaderFragment fragment = new LoaderFragment();
- FragmentManager fm = activity.getSupportFragmentManager();
-
- fm.beginTransaction()
- .add(fragment, "1")
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
-
- fm.beginTransaction()
- .remove(fragment)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
- fm = null; // clear it so that it can be released
-
- WeakReference<RecreatedActivity> weakActivity =
- new WeakReference<>(LoaderActivity.sActivity);
-
- activity = FragmentActivityUtils.recreateActivity(mActivityRule, activity);
-
- // Wait for everything to settle. We have to make sure that the old Activity
- // is ready to be collected.
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // Force a garbage collection.
- FragmentTestUtil.forceGC();
- assertNull(weakActivity.get());
- }
-
- /**
- * When a LoaderManager is reused, it should notify in onResume
- */
- @Test
- public void startWhenReused() throws Throwable {
- LoaderActivity activity = mActivityRule.getActivity();
-
- assertEquals("Loaded!", activity.textView.getText().toString());
-
- activity = FragmentActivityUtils.recreateActivity(mActivityRule, activity);
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // After orientation change, the text should still be loaded properly
- assertEquals("Loaded!", activity.textView.getText().toString());
- }
-
- @Test
- public void testRedeliverWhenReattached() throws Throwable {
- LoaderActivity activity = mActivityRule.getActivity();
-
- FragmentManager fm = activity.getSupportFragmentManager();
-
- LoaderActivity.TextLoaderFragment fragment =
- (LoaderActivity.TextLoaderFragment) fm.findFragmentById(R.id.fragmentContainer);
-
- assertNotNull(fragment);
- assertEquals("Loaded!", fragment.textView.getText().toString());
-
- fm.beginTransaction()
- .detach(fragment)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
-
- fm.beginTransaction()
- .attach(fragment)
- .commit();
-
- FragmentTestUtil.executePendingTransactions(mActivityRule, fm);
-
- assertEquals("Loaded!", fragment.textView.getText().toString());
- }
-
- public static class LoaderFragment extends Fragment implements
- LoaderManager.LoaderCallbacks<Boolean> {
- private static final int LOADER_ID = 1;
-
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this);
- }
-
- @NonNull
- @Override
- public Loader<Boolean> onCreateLoader(int id, @Nullable Bundle args) {
- return new SimpleLoader(requireContext());
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<Boolean> loader, Boolean data) {
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<Boolean> loader) {
- }
-
- static class SimpleLoader extends Loader<Boolean> {
-
- SimpleLoader(@NonNull Context context) {
- super(context);
- }
-
- @Override
- protected void onStartLoading() {
- deliverResult(true);
- }
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt
new file mode 100644
index 0000000..4242a3b
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/LoaderTest.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import androidx.fragment.app.test.LoaderActivity
+import androidx.fragment.test.R
+import androidx.loader.app.LoaderManager
+import androidx.loader.content.Loader
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.testutils.FragmentActivityUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.ref.WeakReference
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class LoaderTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(LoaderActivity::class.java)
+
+ /**
+ * Test to ensure that there is no Activity leak due to Loader
+ */
+ @Test
+ fun testLeak() {
+ // Restart the activity because activityRule keeps a strong reference to the
+ // old activity.
+ val activity = FragmentActivityUtils.recreateActivity(activityRule, activityRule.activity)
+
+ val fragment = LoaderFragment()
+ val fm: FragmentManager = activity.supportFragmentManager
+
+ fm.beginTransaction()
+ .add(fragment, "1")
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule, fm)
+
+ fm.beginTransaction()
+ .remove(fragment)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule, fm)
+
+ val weakActivity = WeakReference(LoaderActivity.activity)
+
+ // Wait for everything to settle. We have to make sure that the old Activity
+ // is ready to be collected.
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // Force a garbage collection.
+ FragmentTestUtil.forceGC()
+ assertThat(weakActivity.get()).isNull()
+ }
+
+ /**
+ * When a LoaderManager is reused, it should notify in onResume
+ */
+ @Test
+ fun startWhenReused() {
+ var activity = activityRule.activity
+
+ assertThat(activity.textView.text).isEqualTo("Loaded!")
+
+ activity = FragmentActivityUtils.recreateActivity(activityRule, activity)
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // After orientation change, the text should still be loaded properly
+ assertThat(activity.textView.text).isEqualTo("Loaded!")
+ }
+
+ @Test
+ fun testRedeliverWhenReattached() {
+ val activity = activityRule.activity
+
+ val fm = activity.supportFragmentManager
+
+ val fragment =
+ fm.findFragmentById(R.id.fragmentContainer) as LoaderActivity.TextLoaderFragment
+
+ assertThat(fragment).isNotNull()
+ assertThat(fragment.textView.text).isEqualTo("Loaded!")
+
+ fm.beginTransaction()
+ .detach(fragment)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule, fm)
+
+ fm.beginTransaction()
+ .attach(fragment)
+ .commit()
+
+ FragmentTestUtil.executePendingTransactions(activityRule, fm)
+
+ assertThat(fragment.textView.text).isEqualTo("Loaded!")
+ }
+
+ class LoaderFragment : Fragment(), LoaderManager.LoaderCallbacks<Boolean> {
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+
+ LoaderManager.getInstance(this).initLoader(LOADER_ID, null, this)
+ }
+
+ override fun onCreateLoader(id: Int, args: Bundle?):
+ Loader<Boolean> = SimpleLoader(requireContext())
+
+ override fun onLoadFinished(loader: Loader<Boolean>, data: Boolean?) {}
+
+ override fun onLoaderReset(loader: Loader<Boolean>) {}
+
+ internal class SimpleLoader(context: Context) : Loader<Boolean>(context) {
+ override fun onStartLoading() {
+ deliverResult(true)
+ }
+ }
+
+ companion object {
+ private const val LOADER_ID = 1
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.java b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.java
deleted file mode 100644
index 05b3858..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.app.test.FragmentTestActivity.ChildFragment;
-import androidx.fragment.app.test.FragmentTestActivity.ChildFragment.OnAttachListener;
-import androidx.fragment.app.test.FragmentTestActivity.ParentFragment;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class NestedFragmentRestoreTest {
-
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule = new ActivityTestRule<>(
- FragmentTestActivity.class);
-
- public NestedFragmentRestoreTest() {
- }
-
- @Test
- @MediumTest
- public void recreateActivity() throws Throwable {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- FragmentTestActivity.ParentFragment parent = new ParentFragment();
- parent.setRetainChildInstance(true);
-
- activity.getSupportFragmentManager().beginTransaction()
- .add(parent, "parent")
- .commitNow();
- }
- });
-
- FragmentManager fm = activity.getSupportFragmentManager();
- ParentFragment parent = (ParentFragment) fm.findFragmentByTag("parent");
- ChildFragment child = parent.getChildFragment();
-
- final Context[] attachedTo = new Context[1];
- final CountDownLatch latch = new CountDownLatch(1);
- child.setOnAttachListener(new OnAttachListener() {
- @Override
- public void onAttach(Context activity, ChildFragment fragment) {
- attachedTo[0] = activity;
- latch.countDown();
- }
- });
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- activity.recreate();
- }
- });
-
- assertTrue("timeout waiting for recreate", latch.await(10, TimeUnit.SECONDS));
-
- assertNotNull("attached as part of recreate", attachedTo[0]);
- assertNotSame("attached to new context", activity, attachedTo[0]);
- assertNotSame("attached to new parent fragment", parent, child);
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt
new file mode 100644
index 0000000..ec335ed
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentRestoreTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.app.test.FragmentTestActivity.ParentFragment
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+class NestedFragmentRestoreTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ @Test
+ @MediumTest
+ fun recreateActivity() {
+ val activity = activityRule.activity
+ activityRule.runOnUiThread {
+ val parent = ParentFragment()
+ parent.retainChildInstance = true
+
+ activity.supportFragmentManager.beginTransaction()
+ .add(parent, "parent")
+ .commitNow()
+ }
+
+ val fm = activity.supportFragmentManager
+ val parent = fm.findFragmentByTag("parent") as ParentFragment
+ val child = parent.childFragment
+
+ var attachedTo: Context? = null
+ val latch = CountDownLatch(1)
+ child.onAttachListener = { context ->
+ attachedTo = context
+ latch.countDown()
+ }
+
+ activityRule.runOnUiThread { activity.recreate() }
+
+ assertWithMessage("timeout waiting for recreate")
+ .that(latch.await(10, TimeUnit.SECONDS))
+ .isTrue()
+
+ assertWithMessage("attached as part of recreate").that(attachedTo).isNotNull()
+ assertWithMessage("attached to new context")
+ .that(attachedTo)
+ .isNotSameAs(activity)
+ assertWithMessage("attached to new parent fragment")
+ .that(child)
+ .isNotSameAs(parent)
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.java b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.java
deleted file mode 100644
index cab44cc..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.content.IntentFilter;
-
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.app.test.FragmentTestActivity.ChildFragment;
-import androidx.fragment.app.test.FragmentTestActivity.ParentFragment;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class NestedFragmentTest {
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private Instrumentation mInstrumentation;
- private ParentFragment mParentFragment;
-
- @Before
- public void setup() throws Throwable {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- final FragmentManager fragmentManager =
- mActivityRule.getActivity().getSupportFragmentManager();
- mParentFragment = new ParentFragment();
- fragmentManager.beginTransaction().add(mParentFragment, "parent").commit();
- final CountDownLatch latch = new CountDownLatch(1);
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fragmentManager.executePendingTransactions();
- latch.countDown();
- }
- });
- assertTrue(latch.await(1, TimeUnit.SECONDS));
- }
-
- @UiThreadTest
- @Test(expected = IllegalArgumentException.class)
- public void testThrowsWhenUsingReservedRequestCode() {
- mParentFragment.getChildFragment().startActivityForResult(
- new Intent(Intent.ACTION_CALL), 16777216 /* requestCode */);
- }
-
- @Test
- public void testNestedFragmentStartActivityForResult() throws Throwable {
- Instrumentation.ActivityResult activityResult = new Instrumentation.ActivityResult(
- Activity.RESULT_OK, new Intent());
-
- Instrumentation.ActivityMonitor activityMonitor =
- mInstrumentation.addMonitor(
- new IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */);
-
- // Sanity check that onActivityResult hasn't been called yet.
- assertFalse(mParentFragment.getChildFragment().onActivityResultCalled);
-
- final CountDownLatch latch = new CountDownLatch(1);
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mParentFragment.getChildFragment().startActivityForResult(
- new Intent(Intent.ACTION_CALL),
- 5 /* requestCode */);
- latch.countDown();
- }
- });
- assertTrue(latch.await(1, TimeUnit.SECONDS));
-
- assertTrue(mInstrumentation.checkMonitorHit(activityMonitor, 1));
-
- final ChildFragment childFragment = mParentFragment.getChildFragment();
- assertTrue(childFragment.onActivityResultCalled);
- assertEquals(5, childFragment.onActivityResultRequestCode);
- assertEquals(Activity.RESULT_OK, childFragment.onActivityResultResultCode);
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.kt
new file mode 100644
index 0000000..dba95bc
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/NestedFragmentTest.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.app.test.FragmentTestActivity.ParentFragment
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class NestedFragmentTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private lateinit var instrumentation: Instrumentation
+ private lateinit var parentFragment: ParentFragment
+
+ @Before
+ fun setup() {
+ instrumentation = InstrumentationRegistry.getInstrumentation()
+ val fragmentManager = activityRule.activity.supportFragmentManager
+ parentFragment = ParentFragment()
+ fragmentManager.beginTransaction().add(parentFragment, "parent").commit()
+ val latch = CountDownLatch(1)
+ activityRule.runOnUiThread {
+ fragmentManager.executePendingTransactions()
+ latch.countDown()
+ }
+ assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue()
+ }
+
+ @UiThreadTest
+ @Test(expected = IllegalArgumentException::class)
+ fun testThrowsWhenUsingReservedRequestCode() {
+ parentFragment.childFragment.startActivityForResult(
+ Intent(Intent.ACTION_CALL), 16777216 /* requestCode */
+ )
+ }
+
+ @Test
+ fun testNestedFragmentStartActivityForResult() {
+ val activityResult = Instrumentation.ActivityResult(Activity.RESULT_OK, Intent())
+
+ val activityMonitor = instrumentation.addMonitor(
+ IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */
+ )
+
+ // Sanity check that onActivityResult hasn't been called yet.
+ assertThat(parentFragment.childFragment.onActivityResultCalled).isFalse()
+
+ val latch = CountDownLatch(1)
+ activityRule.runOnUiThread {
+ parentFragment.childFragment.startActivityForResult(
+ Intent(Intent.ACTION_CALL),
+ 5 /* requestCode */
+ )
+ latch.countDown()
+ }
+ assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue()
+
+ assertThat(instrumentation.checkMonitorHit(activityMonitor, 1)).isTrue()
+
+ val childFragment = parentFragment.childFragment
+ assertThat(childFragment.onActivityResultCalled).isTrue()
+ assertThat(childFragment.onActivityResultRequestCode).isEqualTo(5)
+ assertThat(childFragment.onActivityResultResultCode).isEqualTo(Activity.RESULT_OK)
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.java b/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.java
deleted file mode 100644
index 5c0d2e8..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class NestedInflatedFragmentTest {
- private static final String TAG = "NestedInflatedFragmentTest";
-
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<>(FragmentTestActivity.class);
-
- @Test
- @UiThreadTest
- public void inflatedChildFragment() throws Throwable {
- final FragmentTestActivity activity = mActivityRule.getActivity();
- final FragmentManager fm = activity.getSupportFragmentManager();
-
- ParentFragment parentFragment = new ParentFragment();
- fm.beginTransaction().add(android.R.id.content, parentFragment).commitNow();
-
- fm.beginTransaction().replace(android.R.id.content, new SimpleFragment())
- .addToBackStack(null).commit();
- fm.executePendingTransactions();
-
- fm.popBackStackImmediate();
- }
-
- /**
- * This mimics the behavior of FragmentStatePagerAdapter jumping between pages
- */
- @Test
- @UiThreadTest
- public void nestedSetUserVisibleHint() throws Throwable {
- FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-
- // Add a UserVisibleHintParentFragment
- UserVisibleHintParentFragment fragment = new UserVisibleHintParentFragment();
- fm.beginTransaction().add(android.R.id.content, fragment).commit();
- fm.executePendingTransactions();
-
- fragment.setUserVisibleHint(false);
-
- Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
- fm.beginTransaction().remove(fragment).commit();
- fm.executePendingTransactions();
-
- fragment = new UserVisibleHintParentFragment();
- fragment.setInitialSavedState(state);
- fragment.setUserVisibleHint(true);
-
- fm.beginTransaction().add(android.R.id.content, fragment).commit();
- fm.executePendingTransactions();
- }
-
- @ContentView(R.layout.nested_inflated_fragment_parent)
- public static class ParentFragment extends Fragment {
- }
-
- public static class UserVisibleHintParentFragment extends ParentFragment {
- @Override
- public void setUserVisibleHint(boolean isVisibleToUser) {
- super.setUserVisibleHint(isVisibleToUser);
- if (getHost() != null) {
- for (Fragment fragment : getChildFragmentManager().getFragments()) {
- fragment.setUserVisibleHint(isVisibleToUser);
- }
- }
- }
-
- @Override
- public void onAttachFragment(Fragment childFragment) {
- super.onAttachFragment(childFragment);
- childFragment.setUserVisibleHint(getUserVisibleHint());
- }
- }
-
- @ContentView(R.layout.nested_inflated_fragment_child)
- public static class InflatedChildFragment extends Fragment {
- }
-
- public static class SimpleFragment extends Fragment {
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- TextView textView = new TextView(inflater.getContext());
- textView.setText("Simple fragment");
- return textView;
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt
new file mode 100644
index 0000000..59c3d43
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/NestedInflatedFragmentTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class NestedInflatedFragmentTest {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ @Test
+ @UiThreadTest
+ fun inflatedChildFragment() {
+ val activity = activityRule.activity
+ val fm = activity.supportFragmentManager
+
+ val parentFragment = ParentFragment()
+ fm.beginTransaction().add(android.R.id.content, parentFragment).commitNow()
+
+ fm.beginTransaction()
+ .replace(android.R.id.content, SimpleFragment())
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+
+ fm.popBackStackImmediate()
+ }
+
+ /**
+ * This mimics the behavior of FragmentStatePagerAdapter jumping between pages
+ */
+ @Test
+ @UiThreadTest
+ fun nestedSetUserVisibleHint() {
+ val fm = activityRule.activity.supportFragmentManager
+
+ // Add a UserVisibleHintParentFragment
+ var fragment = UserVisibleHintParentFragment()
+ fm.beginTransaction().add(android.R.id.content, fragment).commit()
+ fm.executePendingTransactions()
+
+ fragment.userVisibleHint = false
+
+ val state = fm.saveFragmentInstanceState(fragment)
+ fm.beginTransaction().remove(fragment).commit()
+ fm.executePendingTransactions()
+
+ fragment = UserVisibleHintParentFragment()
+ fragment.setInitialSavedState(state)
+ fragment.userVisibleHint = true
+
+ fm.beginTransaction().add(android.R.id.content, fragment).commit()
+ fm.executePendingTransactions()
+ }
+
+ open class ParentFragment : Fragment(R.layout.nested_inflated_fragment_parent)
+
+ class UserVisibleHintParentFragment : ParentFragment() {
+ override fun setUserVisibleHint(isVisibleToUser: Boolean) {
+ super.setUserVisibleHint(isVisibleToUser)
+ if (host != null) {
+ for (fragment in childFragmentManager.fragments) {
+ fragment.userVisibleHint = isVisibleToUser
+ }
+ }
+ }
+
+ override fun onAttachFragment(childFragment: Fragment) {
+ super.onAttachFragment(childFragment)
+ childFragment.userVisibleHint = userVisibleHint
+ }
+ }
+
+ class InflatedChildFragment : Fragment(R.layout.nested_inflated_fragment_child)
+
+ class SimpleFragment : Fragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = TextView(inflater.context).apply {
+ text = "Simple fragment"
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.java b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.java
deleted file mode 100644
index 114461e..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.java
+++ /dev/null
@@ -1,987 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.FragmentTestActivity;
-import androidx.fragment.test.R;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
-public class PostponedTransitionTest {
- @Rule
- public ActivityTestRule<FragmentTestActivity> mActivityRule =
- new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
-
- private Instrumentation mInstrumentation;
- private PostponedFragment1 mBeginningFragment;
-
- @Before
- public void setupContainer() throws Throwable {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- mBeginningFragment = new PostponedFragment1();
-
- final CountDownLatch backStackLatch = new CountDownLatch(1);
- FragmentManager.OnBackStackChangedListener backstackListener =
- new FragmentManager.OnBackStackChangedListener() {
-
- @Override
- public void onBackStackChanged() {
- backStackLatch.countDown();
- fm.removeOnBackStackChangedListener(this);
- }
- };
- fm.addOnBackStackChangedListener(backstackListener);
- fm.beginTransaction()
- .add(R.id.fragmentContainer, mBeginningFragment)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
-
- backStackLatch.await();
-
- mBeginningFragment.startPostponedEnterTransition();
- mBeginningFragment.waitForTransition();
- clearTargets(mBeginningFragment);
- }
-
- // Ensure that replacing with a fragment that has a postponed transition
- // will properly postpone it, both adding and popping.
- @Test
- public void replaceTransition() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final PostponedFragment2 fragment = new PostponedFragment2();
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // should be postponed now
- assertPostponedTransition(mBeginningFragment, fragment, null);
-
- // start the postponed transition
- fragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(mBeginningFragment, fragment);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- // should be postponed going back, too
- assertPostponedTransition(fragment, mBeginningFragment, null);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment, mBeginningFragment);
- }
-
- // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
- @Test
- public void backStackNestingLevel() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment1 = new TransitionFragment2();
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment1)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- // make sure transition ran
- assertForwardTransition(mBeginningFragment, fragment1);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- // should be postponed going back
- assertPostponedTransition(fragment1, mBeginningFragment, null);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment1, mBeginningFragment);
-
- startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment2 = new TransitionFragment2();
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- // make sure transition ran
- assertForwardTransition(mBeginningFragment, fragment2);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- // should be postponed going back
- assertPostponedTransition(fragment2, mBeginningFragment, null);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment2, mBeginningFragment);
- }
-
- // Ensure that postponed transition is forced after another has been committed.
- // This tests when the transactions are executed together
- @Test
- public void forcedTransition1() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final PostponedFragment2 fragment2 = new PostponedFragment2();
- final PostponedFragment1 fragment3 = new PostponedFragment1();
-
- final int[] commit = new int[1];
- // Need to run this on the UI thread so that the transaction doesn't start
- // between the two
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- commit[0] = fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- }
- });
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // transition to fragment2 should be started
- assertForwardTransition(mBeginningFragment, fragment2);
-
- // fragment3 should be postponed, but fragment2 should be executed with no transition.
- assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(fragment2, fragment3);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule, commit[0],
- FragmentManager.POP_BACK_STACK_INCLUSIVE);
-
- assertBackTransition(fragment3, fragment2);
-
- assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment2, mBeginningFragment);
- }
-
- // Ensure that postponed transition is forced after another has been committed.
- // This tests when the transactions are processed separately.
- @Test
- public void forcedTransition2() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final PostponedFragment2 fragment2 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(mBeginningFragment, fragment2, null);
-
- final PostponedFragment1 fragment3 = new PostponedFragment1();
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment3)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- // This should cancel the mBeginningFragment -> fragment2 transition
- // and start fragment2 -> fragment3 transition postponed
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // fragment3 should be postponed, but fragment2 should be executed with no transition.
- assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(fragment2, fragment3);
-
- // Pop back to fragment2, but it should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment3, fragment2, null);
-
- // Pop to mBeginningFragment -- should cancel the fragment2 transition and
- // start the mBeginningFragment transaction postponed
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment2, mBeginningFragment);
- }
-
- // Do a bunch of things to one fragment in a transaction and see if it can screw things up.
- @Test
- public void crazyTransition() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final PostponedFragment2 fragment2 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .hide(mBeginningFragment)
- .replace(R.id.fragmentContainer, fragment2)
- .hide(fragment2)
- .detach(fragment2)
- .attach(fragment2)
- .show(fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(mBeginningFragment, fragment2, null);
-
- // start the postponed transition
- fragment2.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(mBeginningFragment, fragment2);
-
- // Pop back to fragment2, but it should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment2, mBeginningFragment, null);
-
- // start the postponed transition
- mBeginningFragment.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment2, mBeginningFragment);
- }
-
- // Execute transactions on different containers and ensure that they don't conflict
- @Test
- public void differentContainers() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction()
- .remove(mBeginningFragment)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
- TransitionFragment fragment1 = new PostponedFragment1();
- TransitionFragment fragment2 = new PostponedFragment1();
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment1)
- .add(R.id.fragmentContainer2, fragment2)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.startPostponedEnterTransition();
- fragment2.startPostponedEnterTransition();
- fragment1.waitForTransition();
- fragment2.waitForTransition();
- clearTargets(fragment1);
- clearTargets(fragment2);
-
- final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
- final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment3 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue1, "blueSquare")
- .replace(R.id.fragmentContainer1, fragment3)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
-
- final TransitionFragment fragment4 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue2, "blueSquare")
- .replace(R.id.fragmentContainer2, fragment4)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
- assertPostponedTransition(fragment2, fragment4, null);
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition();
-
- // make sure only one ran
- assertForwardTransition(fragment1, fragment3);
- assertPostponedTransition(fragment2, fragment4, null);
-
- // start the postponed transition
- fragment4.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(fragment2, fragment4);
-
- // Pop back to fragment2 -- should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment4, fragment2, null);
-
- // Pop back to fragment1 -- also should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment4, fragment2, null);
- assertPostponedTransition(fragment3, fragment1, null);
-
- // start the postponed transition
- fragment2.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment4, fragment2);
-
- // but not the postponed one
- assertPostponedTransition(fragment3, fragment1, null);
-
- // start the postponed transition
- fragment1.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment3, fragment1);
- }
-
- // Execute transactions on different containers and ensure that they don't conflict.
- // The postponement can be started out-of-order
- @Test
- public void outOfOrderContainers() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction()
- .remove(mBeginningFragment)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
- TransitionFragment fragment1 = new PostponedFragment1();
- TransitionFragment fragment2 = new PostponedFragment1();
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment1)
- .add(R.id.fragmentContainer2, fragment2)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.startPostponedEnterTransition();
- fragment2.startPostponedEnterTransition();
- fragment1.waitForTransition();
- fragment2.waitForTransition();
- clearTargets(fragment1);
- clearTargets(fragment2);
-
- final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
- final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment3 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue1, "blueSquare")
- .replace(R.id.fragmentContainer1, fragment3)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
-
- final TransitionFragment fragment4 = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue2, "blueSquare")
- .replace(R.id.fragmentContainer2, fragment4)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
- assertPostponedTransition(fragment2, fragment4, null);
-
- // start the postponed transition
- fragment4.startPostponedEnterTransition();
-
- // make sure only one ran
- assertForwardTransition(fragment2, fragment4);
- assertPostponedTransition(fragment1, fragment3, null);
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(fragment1, fragment3);
-
- // Pop back to fragment2 -- should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment4, fragment2, null);
-
- // Pop back to fragment1 -- also should be postponed
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- assertPostponedTransition(fragment4, fragment2, null);
- assertPostponedTransition(fragment3, fragment1, null);
-
- // start the postponed transition
- fragment1.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment3, fragment1);
-
- // but not the postponed one
- assertPostponedTransition(fragment4, fragment2, null);
-
- // start the postponed transition
- fragment2.startPostponedEnterTransition();
-
- // make sure it ran
- assertBackTransition(fragment4, fragment2);
- }
-
- // Make sure that commitNow for a transaction on a different fragment container doesn't
- // affect the postponed transaction
- @Test
- public void commitNowNoEffect() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction()
- .remove(mBeginningFragment)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
- final TransitionFragment fragment1 = new PostponedFragment1();
- final TransitionFragment fragment2 = new PostponedFragment1();
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment1)
- .add(R.id.fragmentContainer2, fragment2)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.startPostponedEnterTransition();
- fragment2.startPostponedEnterTransition();
- fragment1.waitForTransition();
- fragment2.waitForTransition();
- clearTargets(fragment1);
- clearTargets(fragment2);
-
- final View startBlue1 = fragment1.requireView().findViewById(R.id.blueSquare);
- final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment3 = new PostponedFragment2();
- final StrictFragment strictFragment1 = new StrictFragment();
-
- fm.beginTransaction()
- .addSharedElement(startBlue1, "blueSquare")
- .replace(R.id.fragmentContainer1, fragment3)
- .add(strictFragment1, "1")
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
-
- final TransitionFragment fragment4 = new PostponedFragment2();
- final StrictFragment strictFragment2 = new StrictFragment();
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- fm.beginTransaction()
- .addSharedElement(startBlue2, "blueSquare")
- .replace(R.id.fragmentContainer2, fragment4)
- .remove(strictFragment1)
- .add(strictFragment2, "2")
- .setReorderingAllowed(true)
- .commitNow();
- }
- });
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment3, null);
- assertPostponedTransition(fragment2, fragment4, null);
-
- // start the postponed transition
- fragment4.startPostponedEnterTransition();
-
- // make sure only one ran
- assertForwardTransition(fragment2, fragment4);
- assertPostponedTransition(fragment1, fragment3, null);
-
- // start the postponed transition
- fragment3.startPostponedEnterTransition();
-
- // make sure it ran
- assertForwardTransition(fragment1, fragment3);
- }
-
- // Make sure that commitNow for a transaction affecting a postponed fragment in the same
- // container forces the postponed transition to start.
- @Test
- public void commitNowStartsPostponed() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue1 = mBeginningFragment.requireView().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment2 = new PostponedFragment2();
- final TransitionFragment fragment1 = new PostponedFragment1();
-
- fm.beginTransaction()
- .addSharedElement(startBlue1, "blueSquare")
- .replace(R.id.fragmentContainer, fragment2)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- final View startBlue2 = fragment2.requireView().findViewById(R.id.blueSquare);
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- fm.beginTransaction()
- .addSharedElement(startBlue2, "blueSquare")
- .replace(R.id.fragmentContainer, fragment1)
- .setReorderingAllowed(true)
- .commitNow();
- }
- });
-
- assertPostponedTransition(fragment2, fragment1, mBeginningFragment);
-
- // start the postponed transition
- fragment1.startPostponedEnterTransition();
-
- assertForwardTransition(fragment2, fragment1);
- }
-
- // Make sure that when a transaction that removes a view is postponed that
- // another transaction doesn't accidentally remove the view early.
- @Test
- public void noAccidentalRemoval() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- fm.beginTransaction()
- .remove(mBeginningFragment)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
-
- TransitionFragment fragment1 = new PostponedFragment1();
-
- fm.beginTransaction()
- .add(R.id.fragmentContainer1, fragment1)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- fragment1.startPostponedEnterTransition();
- fragment1.waitForTransition();
- clearTargets(fragment1);
-
- TransitionFragment fragment2 = new PostponedFragment2();
- // Create a postponed transaction that removes a view
- fm.beginTransaction()
- .replace(R.id.fragmentContainer1, fragment2)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
- assertPostponedTransition(fragment1, fragment2, null);
-
- TransitionFragment fragment3 = new PostponedFragment1();
- // Create a transaction that doesn't interfere with the previously postponed one
- fm.beginTransaction()
- .replace(R.id.fragmentContainer2, fragment3)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(fragment1, fragment2, null);
-
- fragment3.startPostponedEnterTransition();
- fragment3.waitForTransition();
- clearTargets(fragment3);
-
- assertPostponedTransition(fragment1, fragment2, null);
- }
-
- // Ensure that a postponed transaction that is popped runs immediately and that
- // the transaction results in the original state with no transition.
- @Test
- public void popPostponedTransaction() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mBeginningFragment.requireView().findViewById(R.id.blueSquare);
-
- final TransitionFragment fragment = new PostponedFragment2();
-
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment)
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- assertPostponedTransition(mBeginningFragment, fragment, null);
-
- FragmentTestUtil.popBackStackImmediate(mActivityRule);
-
- fragment.waitForNoTransition();
- mBeginningFragment.waitForNoTransition();
-
- assureNoTransition(fragment);
- assureNoTransition(mBeginningFragment);
-
- assertFalse(fragment.isAdded());
- assertNull(fragment.getView());
- assertNotNull(mBeginningFragment.getView());
- assertEquals(View.VISIBLE, mBeginningFragment.getView().getVisibility());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- assertEquals(1f, mBeginningFragment.getView().getAlpha(), 0f);
- }
- assertTrue(mBeginningFragment.getView().isAttachedToWindow());
- }
-
- // Make sure that when saving the state during a postponed transaction that it saves
- // the state as if it wasn't postponed.
- @Test
- public void saveWhilePostponed() throws Throwable {
- final FragmentController fc1 = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc1, null);
-
- final FragmentManager fm1 = fc1.getSupportFragmentManager();
-
- PostponedFragment1 fragment1 = new PostponedFragment1();
- fm1.beginTransaction()
- .add(R.id.fragmentContainer, fragment1, "1")
- .addToBackStack(null)
- .setReorderingAllowed(true)
- .commit();
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- Pair<Parcelable, FragmentManagerNonConfig> state =
- FragmentTestUtil.destroy(mActivityRule, fc1);
-
- final FragmentController fc2 = FragmentTestUtil.createController(mActivityRule);
- FragmentTestUtil.resume(mActivityRule, fc2, state);
-
- final FragmentManager fm2 = fc2.getSupportFragmentManager();
- Fragment fragment2 = fm2.findFragmentByTag("1");
- assertNotNull(fragment2);
- assertNotNull(fragment2.getView());
- assertEquals(View.VISIBLE, fragment2.getView().getVisibility());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- assertEquals(1f, fragment2.getView().getAlpha(), 0f);
- }
- assertTrue(fragment2.isResumed());
- assertTrue(fragment2.isAdded());
- assertTrue(fragment2.getView().isAttachedToWindow());
-
- mInstrumentation.runOnMainSync(new Runnable() {
- @Override
- public void run() {
- assertTrue(fm2.popBackStackImmediate());
-
- }
- });
-
- assertFalse(fragment2.isResumed());
- assertFalse(fragment2.isAdded());
- assertNull(fragment2.getView());
- }
-
- // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
- @Test
- public void postponeDoesNotAllowReentrancy() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
-
- final CommitNowFragment fragment = new CommitNowFragment();
- fm.beginTransaction()
- .addSharedElement(startBlue, "blueSquare")
- .replace(R.id.fragmentContainer, fragment)
- .setReorderingAllowed(true)
- .addToBackStack(null)
- .commit();
-
- FragmentTestUtil.waitForExecution(mActivityRule);
-
- // should be postponed now
- assertPostponedTransition(mBeginningFragment, fragment, null);
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- // start the postponed transition
- fragment.startPostponedEnterTransition();
-
- try {
- // This should trigger an IllegalStateException
- fm.executePendingTransactions();
- fail("commitNow() while executing a transaction should cause an "
- + "IllegalStateException");
- } catch (IllegalStateException e) {
- // expected
- }
- }
- });
- }
-
- private void assertPostponedTransition(TransitionFragment fromFragment,
- TransitionFragment toFragment, TransitionFragment removedFragment)
- throws InterruptedException {
- if (removedFragment != null) {
- assertNull(removedFragment.getView());
- assureNoTransition(removedFragment);
- }
-
- toFragment.waitForNoTransition();
- assertNotNull(fromFragment.getView());
- assertNotNull(toFragment.getView());
- assertTrue(fromFragment.getView().isAttachedToWindow());
- assertTrue(toFragment.getView().isAttachedToWindow());
- assertEquals(View.VISIBLE, fromFragment.getView().getVisibility());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- assertEquals(View.VISIBLE, toFragment.getView().getVisibility());
- assertEquals(0f, toFragment.getView().getAlpha(), 0f);
- } else {
- assertEquals(View.INVISIBLE, toFragment.getView().getVisibility());
- }
- assureNoTransition(fromFragment);
- assureNoTransition(toFragment);
- assertTrue(fromFragment.isResumed());
- assertFalse(toFragment.isResumed());
- }
-
- private void clearTargets(TransitionFragment fragment) {
- fragment.enterTransition.targets.clear();
- fragment.reenterTransition.targets.clear();
- fragment.exitTransition.targets.clear();
- fragment.returnTransition.targets.clear();
- fragment.sharedElementEnter.targets.clear();
- fragment.sharedElementReturn.targets.clear();
- }
-
- private void assureNoTransition(TransitionFragment fragment) {
- assertEquals(0, fragment.enterTransition.targets.size());
- assertEquals(0, fragment.reenterTransition.targets.size());
- assertEquals(0, fragment.enterTransition.targets.size());
- assertEquals(0, fragment.returnTransition.targets.size());
- assertEquals(0, fragment.sharedElementEnter.targets.size());
- assertEquals(0, fragment.sharedElementReturn.targets.size());
- }
-
- private void assertForwardTransition(TransitionFragment start, TransitionFragment end)
- throws InterruptedException {
- start.waitForTransition();
- end.waitForTransition();
- assertEquals(0, start.enterTransition.targets.size());
- assertEquals(1, end.enterTransition.targets.size());
-
- assertEquals(0, start.reenterTransition.targets.size());
- assertEquals(0, end.reenterTransition.targets.size());
-
- assertEquals(0, start.returnTransition.targets.size());
- assertEquals(0, end.returnTransition.targets.size());
-
- assertEquals(1, start.exitTransition.targets.size());
- assertEquals(0, end.exitTransition.targets.size());
-
- assertEquals(0, start.sharedElementEnter.targets.size());
- assertEquals(2, end.sharedElementEnter.targets.size());
-
- assertEquals(0, start.sharedElementReturn.targets.size());
- assertEquals(0, end.sharedElementReturn.targets.size());
-
- final View blue = end.requireView().findViewById(R.id.blueSquare);
- assertTrue(end.sharedElementEnter.targets.contains(blue));
- assertEquals("blueSquare", end.sharedElementEnter.targets.get(0).getTransitionName());
- assertEquals("blueSquare", end.sharedElementEnter.targets.get(1).getTransitionName());
-
- assertNoTargets(start);
- assertNoTargets(end);
-
- clearTargets(start);
- clearTargets(end);
- }
-
- private void assertBackTransition(TransitionFragment start, TransitionFragment end)
- throws InterruptedException {
- start.waitForTransition();
- end.waitForTransition();
- assertEquals(1, end.reenterTransition.targets.size());
- assertEquals(0, start.reenterTransition.targets.size());
-
- assertEquals(0, end.returnTransition.targets.size());
- assertEquals(1, start.returnTransition.targets.size());
-
- assertEquals(0, start.enterTransition.targets.size());
- assertEquals(0, end.enterTransition.targets.size());
-
- assertEquals(0, start.exitTransition.targets.size());
- assertEquals(0, end.exitTransition.targets.size());
-
- assertEquals(0, start.sharedElementEnter.targets.size());
- assertEquals(0, end.sharedElementEnter.targets.size());
-
- assertEquals(2, start.sharedElementReturn.targets.size());
- assertEquals(0, end.sharedElementReturn.targets.size());
-
- final View blue = end.requireView().findViewById(R.id.blueSquare);
- assertTrue(start.sharedElementReturn.targets.contains(blue));
- assertEquals("blueSquare", start.sharedElementReturn.targets.get(0).getTransitionName());
- assertEquals("blueSquare", start.sharedElementReturn.targets.get(1).getTransitionName());
-
- assertNoTargets(end);
- assertNoTargets(start);
-
- clearTargets(start);
- clearTargets(end);
- }
-
- private static void assertNoTargets(TransitionFragment fragment) {
- assertTrue(fragment.enterTransition.getTargets().isEmpty());
- assertTrue(fragment.reenterTransition.getTargets().isEmpty());
- assertTrue(fragment.exitTransition.getTargets().isEmpty());
- assertTrue(fragment.returnTransition.getTargets().isEmpty());
- assertTrue(fragment.sharedElementEnter.getTargets().isEmpty());
- assertTrue(fragment.sharedElementReturn.getTargets().isEmpty());
- }
-
- @ContentView(R.layout.scene1)
- public static class PostponedFragment1 extends TransitionFragment {
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- postponeEnterTransition();
- return super.onCreateView(inflater, container, savedInstanceState);
- }
- }
-
- @ContentView(R.layout.scene2)
- public static class PostponedFragment2 extends TransitionFragment {
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- postponeEnterTransition();
- return super.onCreateView(inflater, container, savedInstanceState);
- }
- }
-
- public static class CommitNowFragment extends PostponedFragment1 {
- @Override
- public void onResume() {
- super.onResume();
- // This should throw because this happens during the execution
- getFragmentManager().beginTransaction()
- .add(R.id.fragmentContainer, new PostponedFragment1())
- .commitNow();
- }
- }
-
- @ContentView(R.layout.scene2)
- public static class TransitionFragment2 extends TransitionFragment {
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
new file mode 100644
index 0000000..5432969
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/PostponedTransitionTest.kt
@@ -0,0 +1,937 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
+class PostponedTransitionTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(FragmentTestActivity::class.java)
+
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val beginningFragment = PostponedFragment1()
+
+ @Before
+ fun setupContainer() {
+ FragmentTestUtil.setContentView(activityRule, R.layout.simple_container)
+ val fm = activityRule.activity.supportFragmentManager
+
+ val backStackLatch = CountDownLatch(1)
+ val backStackListener = object : FragmentManager.OnBackStackChangedListener {
+ override fun onBackStackChanged() {
+ backStackLatch.countDown()
+ fm.removeOnBackStackChangedListener(this)
+ }
+ }
+ fm.addOnBackStackChangedListener(backStackListener)
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer, beginningFragment)
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+
+ backStackLatch.await()
+
+ beginningFragment.startPostponedEnterTransition()
+ beginningFragment.waitForTransition()
+ clearTargets(beginningFragment)
+ }
+
+ // Ensure that replacing with a fragment that has a postponed transition
+ // will properly postpone it, both adding and popping.
+ @Test
+ fun replaceTransition() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment = PostponedFragment2()
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // should be postponed now
+ assertPostponedTransition(beginningFragment, fragment)
+
+ // start the postponed transition
+ fragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(beginningFragment, fragment)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ // should be postponed going back, too
+ assertPostponedTransition(fragment, beginningFragment)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment, beginningFragment)
+ }
+
+ // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
+ @Test
+ fun backStackNestingLevel() {
+ val fm = activityRule.activity.supportFragmentManager
+ var startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment1 = TransitionFragment(R.layout.scene2)
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment1)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ // make sure transition ran
+ assertForwardTransition(beginningFragment, fragment1)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ // should be postponed going back
+ assertPostponedTransition(fragment1, beginningFragment)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment1, beginningFragment)
+
+ startBlue = activityRule.activity.findViewById(R.id.blueSquare)
+
+ val fragment2 = TransitionFragment(R.layout.scene2)
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ // make sure transition ran
+ assertForwardTransition(beginningFragment, fragment2)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ // should be postponed going back
+ assertPostponedTransition(fragment2, beginningFragment)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment2, beginningFragment)
+ }
+
+ // Ensure that postponed transition is forced after another has been committed.
+ // This tests when the transactions are executed together
+ @Test
+ fun forcedTransition1() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment2 = PostponedFragment2()
+ val fragment3 = PostponedFragment1()
+
+ var commit = 0
+ // Need to run this on the UI thread so that the transaction doesn't start
+ // between the two
+ instrumentation.runOnMainSync {
+ commit = fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ }
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // transition to fragment2 should be started
+ assertForwardTransition(beginningFragment, fragment2)
+
+ // fragment3 should be postponed, but fragment2 should be executed with no transition.
+ assertPostponedTransition(fragment2, fragment3, beginningFragment)
+
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(fragment2, fragment3)
+
+ FragmentTestUtil.popBackStackImmediate(
+ activityRule, commit,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+
+ assertBackTransition(fragment3, fragment2)
+
+ assertPostponedTransition(fragment2, beginningFragment, fragment3)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment2, beginningFragment)
+ }
+
+ // Ensure that postponed transition is forced after another has been committed.
+ // This tests when the transactions are processed separately.
+ @Test
+ fun forcedTransition2() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment2 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(beginningFragment, fragment2)
+
+ val fragment3 = PostponedFragment1()
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment3)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ // This should cancel the beginningFragment -> fragment2 transition
+ // and start fragment2 -> fragment3 transition postponed
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // fragment3 should be postponed, but fragment2 should be executed with no transition.
+ assertPostponedTransition(fragment2, fragment3, beginningFragment)
+
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(fragment2, fragment3)
+
+ // Pop back to fragment2, but it should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment3, fragment2)
+
+ // Pop to beginningFragment -- should cancel the fragment2 transition and
+ // start the beginningFragment transaction postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment2, beginningFragment, fragment3)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment2, beginningFragment)
+ }
+
+ // Do a bunch of things to one fragment in a transaction and see if it can screw things up.
+ @Test
+ fun crazyTransition() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment2 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .hide(beginningFragment)
+ .replace(R.id.fragmentContainer, fragment2)
+ .hide(fragment2)
+ .detach(fragment2)
+ .attach(fragment2)
+ .show(fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(beginningFragment, fragment2)
+
+ // start the postponed transition
+ fragment2.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(beginningFragment, fragment2)
+
+ // Pop back to fragment2, but it should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment2, beginningFragment)
+
+ // start the postponed transition
+ beginningFragment.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment2, beginningFragment)
+ }
+
+ // Execute transactions on different containers and ensure that they don't conflict
+ @Test
+ fun differentContainers() {
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .remove(beginningFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+ val fragment1 = PostponedFragment1()
+ val fragment2 = PostponedFragment1()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment1)
+ .add(R.id.fragmentContainer2, fragment2)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.startPostponedEnterTransition()
+ fragment2.startPostponedEnterTransition()
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+ clearTargets(fragment1)
+ clearTargets(fragment2)
+
+ val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+ val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+ val fragment3 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue1, "blueSquare")
+ .replace(R.id.fragmentContainer1, fragment3)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+
+ val fragment4 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue2, "blueSquare")
+ .replace(R.id.fragmentContainer2, fragment4)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+ assertPostponedTransition(fragment2, fragment4)
+
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
+
+ // make sure only one ran
+ assertForwardTransition(fragment1, fragment3)
+ assertPostponedTransition(fragment2, fragment4)
+
+ // start the postponed transition
+ fragment4.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(fragment2, fragment4)
+
+ // Pop back to fragment2 -- should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment4, fragment2)
+
+ // Pop back to fragment1 -- also should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment4, fragment2)
+ assertPostponedTransition(fragment3, fragment1)
+
+ // start the postponed transition
+ fragment2.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment4, fragment2)
+
+ // but not the postponed one
+ assertPostponedTransition(fragment3, fragment1)
+
+ // start the postponed transition
+ fragment1.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment3, fragment1)
+ }
+
+ // Execute transactions on different containers and ensure that they don't conflict.
+ // The postponement can be started out-of-order
+ @Test
+ fun outOfOrderContainers() {
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .remove(beginningFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+ val fragment1 = PostponedFragment1()
+ val fragment2 = PostponedFragment1()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment1)
+ .add(R.id.fragmentContainer2, fragment2)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.startPostponedEnterTransition()
+ fragment2.startPostponedEnterTransition()
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+ clearTargets(fragment1)
+ clearTargets(fragment2)
+
+ val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+ val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+ val fragment3 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue1, "blueSquare")
+ .replace(R.id.fragmentContainer1, fragment3)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+
+ val fragment4 = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue2, "blueSquare")
+ .replace(R.id.fragmentContainer2, fragment4)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+ assertPostponedTransition(fragment2, fragment4)
+
+ // start the postponed transition
+ fragment4.startPostponedEnterTransition()
+
+ // make sure only one ran
+ assertForwardTransition(fragment2, fragment4)
+ assertPostponedTransition(fragment1, fragment3)
+
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(fragment1, fragment3)
+
+ // Pop back to fragment2 -- should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment4, fragment2)
+
+ // Pop back to fragment1 -- also should be postponed
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ assertPostponedTransition(fragment4, fragment2)
+ assertPostponedTransition(fragment3, fragment1)
+
+ // start the postponed transition
+ fragment1.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment3, fragment1)
+
+ // but not the postponed one
+ assertPostponedTransition(fragment4, fragment2)
+
+ // start the postponed transition
+ fragment2.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertBackTransition(fragment4, fragment2)
+ }
+
+ // Make sure that commitNow for a transaction on a different fragment container doesn't
+ // affect the postponed transaction
+ @Test
+ fun commitNowNoEffect() {
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .remove(beginningFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+ val fragment1 = PostponedFragment1()
+ val fragment2 = PostponedFragment1()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment1)
+ .add(R.id.fragmentContainer2, fragment2)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.startPostponedEnterTransition()
+ fragment2.startPostponedEnterTransition()
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+ clearTargets(fragment1)
+ clearTargets(fragment2)
+
+ val startBlue1 = fragment1.requireView().findViewById<View>(R.id.blueSquare)
+ val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+ val fragment3 = PostponedFragment2()
+ val strictFragment1 = StrictFragment()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue1, "blueSquare")
+ .replace(R.id.fragmentContainer1, fragment3)
+ .add(strictFragment1, "1")
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+
+ val fragment4 = PostponedFragment2()
+ val strictFragment2 = StrictFragment()
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .addSharedElement(startBlue2, "blueSquare")
+ .replace(R.id.fragmentContainer2, fragment4)
+ .remove(strictFragment1)
+ .add(strictFragment2, "2")
+ .setReorderingAllowed(true)
+ .commitNow()
+ }
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment3)
+ assertPostponedTransition(fragment2, fragment4)
+
+ // start the postponed transition
+ fragment4.startPostponedEnterTransition()
+
+ // make sure only one ran
+ assertForwardTransition(fragment2, fragment4)
+ assertPostponedTransition(fragment1, fragment3)
+
+ // start the postponed transition
+ fragment3.startPostponedEnterTransition()
+
+ // make sure it ran
+ assertForwardTransition(fragment1, fragment3)
+ }
+
+ // Make sure that commitNow for a transaction affecting a postponed fragment in the same
+ // container forces the postponed transition to start.
+ @Test
+ fun commitNowStartsPostponed() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue1 = beginningFragment.requireView().findViewById<View>(R.id.blueSquare)
+
+ val fragment2 = PostponedFragment2()
+ val fragment1 = PostponedFragment1()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue1, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment2)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val startBlue2 = fragment2.requireView().findViewById<View>(R.id.blueSquare)
+
+ instrumentation.runOnMainSync {
+ fm.beginTransaction()
+ .addSharedElement(startBlue2, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment1)
+ .setReorderingAllowed(true)
+ .commitNow()
+ }
+
+ assertPostponedTransition(fragment2, fragment1, beginningFragment)
+
+ // start the postponed transition
+ fragment1.startPostponedEnterTransition()
+
+ assertForwardTransition(fragment2, fragment1)
+ }
+
+ // Make sure that when a transaction that removes a view is postponed that
+ // another transaction doesn't accidentally remove the view early.
+ @Test
+ fun noAccidentalRemoval() {
+ val fm = activityRule.activity.supportFragmentManager
+ fm.beginTransaction()
+ .remove(beginningFragment)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ FragmentTestUtil.setContentView(activityRule, R.layout.double_container)
+
+ val fragment1 = PostponedFragment1()
+
+ fm.beginTransaction()
+ .add(R.id.fragmentContainer1, fragment1)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ fragment1.startPostponedEnterTransition()
+ fragment1.waitForTransition()
+ clearTargets(fragment1)
+
+ val fragment2 = PostponedFragment2()
+ // Create a postponed transaction that removes a view
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer1, fragment2)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+ assertPostponedTransition(fragment1, fragment2)
+
+ val fragment3 = PostponedFragment1()
+ // Create a transaction that doesn't interfere with the previously postponed one
+ fm.beginTransaction()
+ .replace(R.id.fragmentContainer2, fragment3)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(fragment1, fragment2)
+
+ fragment3.startPostponedEnterTransition()
+ fragment3.waitForTransition()
+ clearTargets(fragment3)
+
+ assertPostponedTransition(fragment1, fragment2)
+ }
+
+ // Ensure that a postponed transaction that is popped runs immediately and that
+ // the transaction results in the original state with no transition.
+ @Test
+ fun popPostponedTransaction() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = beginningFragment.requireView().findViewById<View>(R.id.blueSquare)
+
+ val fragment = PostponedFragment2()
+
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment)
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ assertPostponedTransition(beginningFragment, fragment)
+
+ FragmentTestUtil.popBackStackImmediate(activityRule)
+
+ fragment.waitForNoTransition()
+ beginningFragment.waitForNoTransition()
+
+ assureNoTransition(fragment)
+ assureNoTransition(beginningFragment)
+
+ assertThat(fragment.isAdded).isFalse()
+ assertThat(fragment.view).isNull()
+ assertThat(beginningFragment.view).isNotNull()
+ assertThat(beginningFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(beginningFragment.requireView().alpha).isWithin(0f).of(1f)
+ assertThat(beginningFragment.requireView().isAttachedToWindow).isTrue()
+ }
+
+ // Make sure that when saving the state during a postponed transaction that it saves
+ // the state as if it wasn't postponed.
+ @Test
+ fun saveWhilePostponed() {
+ val fc1 = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc1, null)
+
+ val fm1 = fc1.supportFragmentManager
+
+ val fragment1 = PostponedFragment1()
+ fm1.beginTransaction()
+ .add(R.id.fragmentContainer, fragment1, "1")
+ .addToBackStack(null)
+ .setReorderingAllowed(true)
+ .commit()
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ val state = FragmentTestUtil.destroy(activityRule, fc1)
+
+ val fc2 = FragmentTestUtil.createController(activityRule)
+ FragmentTestUtil.resume(activityRule, fc2, state)
+
+ val fm2 = fc2.supportFragmentManager
+ val fragment2 = fm2.findFragmentByTag("1")!!
+ assertThat(fragment2).isNotNull()
+ assertThat(fragment2.requireView()).isNotNull()
+ assertThat(fragment2.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(fragment2.requireView().alpha).isWithin(0f).of(1f)
+ assertThat(fragment2.isResumed).isTrue()
+ assertThat(fragment2.isAdded).isTrue()
+ assertThat(fragment2.requireView().isAttachedToWindow).isTrue()
+
+ instrumentation.runOnMainSync { assertThat(fm2.popBackStackImmediate()).isTrue() }
+
+ assertThat(fragment2.isResumed).isFalse()
+ assertThat(fragment2.isAdded).isFalse()
+ assertThat(fragment2.view).isNull()
+ }
+
+ // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
+ @Test
+ fun postponeDoesNotAllowReentrancy() {
+ val fm = activityRule.activity.supportFragmentManager
+ val startBlue = activityRule.activity.findViewById<View>(R.id.blueSquare)
+
+ val fragment = CommitNowFragment()
+ fm.beginTransaction()
+ .addSharedElement(startBlue, "blueSquare")
+ .replace(R.id.fragmentContainer, fragment)
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+
+ FragmentTestUtil.waitForExecution(activityRule)
+
+ // should be postponed now
+ assertPostponedTransition(beginningFragment, fragment)
+
+ activityRule.runOnUiThread {
+ // start the postponed transition
+ fragment.startPostponedEnterTransition()
+
+ try {
+ // This should trigger an IllegalStateException
+ fm.executePendingTransactions()
+ fail("commitNow() while executing a transaction should cause an" +
+ " IllegalStateException")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat().contains("FragmentManager is already executing transactions")
+ }
+ }
+ }
+
+ private fun assertPostponedTransition(
+ fromFragment: TransitionFragment,
+ toFragment: TransitionFragment,
+ removedFragment: TransitionFragment? = null
+ ) {
+ if (removedFragment != null) {
+ assertThat(removedFragment.view).isNull()
+ assureNoTransition(removedFragment)
+ }
+
+ toFragment.waitForNoTransition()
+ assertThat(fromFragment.view).isNotNull()
+ assertThat(toFragment.view).isNotNull()
+ assertThat(fromFragment.requireView().isAttachedToWindow).isTrue()
+ assertThat(toFragment.requireView().isAttachedToWindow).isTrue()
+ assertThat(fromFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(toFragment.requireView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(toFragment.requireView().alpha).isWithin(0f).of(0f)
+ assureNoTransition(fromFragment)
+ assureNoTransition(toFragment)
+ assertThat(fromFragment.isResumed).isTrue()
+ assertThat(toFragment.isResumed).isFalse()
+ }
+
+ private fun clearTargets(fragment: TransitionFragment) {
+ fragment.enterTransition.targets.clear()
+ fragment.reenterTransition.targets.clear()
+ fragment.exitTransition.targets.clear()
+ fragment.returnTransition.targets.clear()
+ fragment.sharedElementEnter.targets.clear()
+ fragment.sharedElementReturn.targets.clear()
+ }
+
+ private fun assureNoTransition(fragment: TransitionFragment) {
+ assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.reenterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.enterTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.returnTransition.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementEnter.targets.size).isEqualTo(0)
+ assertThat(fragment.sharedElementReturn.targets.size).isEqualTo(0)
+ }
+
+ private fun assertForwardTransition(start: TransitionFragment, end: TransitionFragment) {
+ start.waitForTransition()
+ end.waitForTransition()
+ assertThat(start.enterTransition.targets.size).isEqualTo(0)
+ assertThat(end.enterTransition.targets.size).isEqualTo(1)
+
+ assertThat(start.reenterTransition.targets.size).isEqualTo(0)
+ assertThat(end.reenterTransition.targets.size).isEqualTo(0)
+
+ assertThat(start.returnTransition.targets.size).isEqualTo(0)
+ assertThat(end.returnTransition.targets.size).isEqualTo(0)
+
+ assertThat(start.exitTransition.targets.size).isEqualTo(1)
+ assertThat(end.exitTransition.targets.size).isEqualTo(0)
+
+ assertThat(start.sharedElementEnter.targets.size).isEqualTo(0)
+ assertThat(end.sharedElementEnter.targets.size).isEqualTo(2)
+
+ assertThat(start.sharedElementReturn.targets.size).isEqualTo(0)
+ assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
+
+ val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+ assertThat(end.sharedElementEnter.targets.contains(blue)).isTrue()
+ assertThat(end.sharedElementEnter.targets[0].transitionName).isEqualTo("blueSquare")
+ assertThat(end.sharedElementEnter.targets[1].transitionName).isEqualTo("blueSquare")
+
+ assertNoTargets(start)
+ assertNoTargets(end)
+
+ clearTargets(start)
+ clearTargets(end)
+ }
+
+ private fun assertBackTransition(start: TransitionFragment, end: TransitionFragment) {
+ start.waitForTransition()
+ end.waitForTransition()
+ assertThat(end.reenterTransition.targets.size).isEqualTo(1)
+ assertThat(start.reenterTransition.targets.size).isEqualTo(0)
+
+ assertThat(end.returnTransition.targets.size).isEqualTo(0)
+ assertThat(start.returnTransition.targets.size).isEqualTo(1)
+
+ assertThat(start.enterTransition.targets.size).isEqualTo(0)
+ assertThat(end.enterTransition.targets.size).isEqualTo(0)
+
+ assertThat(start.exitTransition.targets.size).isEqualTo(0)
+ assertThat(end.exitTransition.targets.size).isEqualTo(0)
+
+ assertThat(start.sharedElementEnter.targets.size).isEqualTo(0)
+ assertThat(end.sharedElementEnter.targets.size).isEqualTo(0)
+
+ assertThat(start.sharedElementReturn.targets.size).isEqualTo(2)
+ assertThat(end.sharedElementReturn.targets.size).isEqualTo(0)
+
+ val blue = end.requireView().findViewById<View>(R.id.blueSquare)
+ assertThat(start.sharedElementReturn.targets.contains(blue)).isTrue()
+ assertThat(start.sharedElementReturn.targets[0].transitionName).isEqualTo("blueSquare")
+ assertThat(start.sharedElementReturn.targets[1].transitionName).isEqualTo("blueSquare")
+
+ assertNoTargets(end)
+ assertNoTargets(start)
+
+ clearTargets(start)
+ clearTargets(end)
+ }
+
+ private fun assertNoTargets(fragment: TransitionFragment) {
+ assertThat(fragment.enterTransition.getTargets().isEmpty()).isTrue()
+ assertThat(fragment.reenterTransition.getTargets().isEmpty()).isTrue()
+ assertThat(fragment.exitTransition.getTargets().isEmpty()).isTrue()
+ assertThat(fragment.returnTransition.getTargets().isEmpty()).isTrue()
+ assertThat(fragment.sharedElementEnter.getTargets().isEmpty()).isTrue()
+ assertThat(fragment.sharedElementReturn.getTargets().isEmpty()).isTrue()
+ }
+
+ open class PostponedFragment1 : TransitionFragment(R.layout.scene1) {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = super.onCreateView(inflater, container, savedInstanceState).also {
+ postponeEnterTransition()
+ }
+ }
+
+ class PostponedFragment2 : TransitionFragment(R.layout.scene2) {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ) = super.onCreateView(inflater, container, savedInstanceState).also {
+ postponeEnterTransition()
+ }
+ }
+
+ class CommitNowFragment : PostponedFragment1() {
+ override fun onResume() {
+ super.onResume()
+ // This should throw because this happens during the execution
+ fragmentManager!!.beginTransaction()
+ .add(R.id.fragmentContainer, PostponedFragment1())
+ .commitNow()
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.java b/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.java
deleted file mode 100644
index 6b53622..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import androidx.fragment.app.test.EmptyFragmentTestActivity;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class PrimaryNavFragmentTest {
- @Rule
- public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
- new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
-
- @Test
- public void delegateBackToPrimaryNav() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment strictFragment = new StrictFragment();
-
- fm.beginTransaction().add(strictFragment, null).setPrimaryNavigationFragment(strictFragment)
- .commit();
- executePendingTransactions(fm);
-
- assertSame("new fragment is not primary nav fragment", strictFragment,
- fm.getPrimaryNavigationFragment());
-
- final StrictFragment child = new StrictFragment();
- FragmentManager cfm = strictFragment.getChildFragmentManager();
- cfm.beginTransaction().add(child, null).addToBackStack(null).commit();
- executePendingTransactions(cfm);
-
- assertEquals("child transaction not on back stack", 1, cfm.getBackStackEntryCount());
-
- // Should execute the pop for the child fragmentmanager
- assertTrue("popBackStackImmediate returned no action performed",
- popBackStackImmediate(fm));
-
- assertEquals("child transaction still on back stack", 0, cfm.getBackStackEntryCount());
- }
-
- @Test
- public void popPrimaryNav() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment strictFragment1 = new StrictFragment();
-
- fm.beginTransaction().add(strictFragment1, null)
- .setPrimaryNavigationFragment(strictFragment1)
- .commit();
- executePendingTransactions(fm);
-
- assertSame("new fragment is not primary nav fragment", strictFragment1,
- fm.getPrimaryNavigationFragment());
-
- fm.beginTransaction().remove(strictFragment1).addToBackStack(null).commit();
- executePendingTransactions(fm);
-
- assertNull("primary nav fragment is not null after remove",
- fm.getPrimaryNavigationFragment());
-
- popBackStackImmediate(fm);
-
- assertSame("primary nav fragment was not restored on pop", strictFragment1,
- fm.getPrimaryNavigationFragment());
-
- final StrictFragment strictFragment2 = new StrictFragment();
- fm.beginTransaction().remove(strictFragment1).add(strictFragment2, null)
- .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
- executePendingTransactions(fm);
-
- assertSame("primary nav fragment not updated to new fragment", strictFragment2,
- fm.getPrimaryNavigationFragment());
-
- popBackStackImmediate(fm);
-
- assertSame("primary nav fragment not restored on pop", strictFragment1,
- fm.getPrimaryNavigationFragment());
-
- fm.beginTransaction().setPrimaryNavigationFragment(strictFragment1)
- .addToBackStack(null).commit();
- executePendingTransactions(fm);
-
- assertSame("primary nav fragment not retained when set again in new transaction",
- strictFragment1, fm.getPrimaryNavigationFragment());
- popBackStackImmediate(fm);
-
- assertSame("same primary nav fragment not retained when set primary nav transaction popped",
- strictFragment1, fm.getPrimaryNavigationFragment());
- }
-
- @Test
- public void replacePrimaryNav() throws Throwable {
- final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
- final StrictFragment strictFragment1 = new StrictFragment();
-
- fm.beginTransaction().add(android.R.id.content, strictFragment1)
- .setPrimaryNavigationFragment(strictFragment1).commit();
- executePendingTransactions(fm);
-
- assertSame("new fragment is not primary nav fragment", strictFragment1,
- fm.getPrimaryNavigationFragment());
-
- final StrictFragment strictFragment2 = new StrictFragment();
- fm.beginTransaction().replace(android.R.id.content, strictFragment2)
- .addToBackStack(null).commit();
-
- executePendingTransactions(fm);
-
- assertNull("primary nav fragment not null after replace",
- fm.getPrimaryNavigationFragment());
-
- popBackStackImmediate(fm);
-
- assertSame("primary nav fragment not restored after popping replace", strictFragment1,
- fm.getPrimaryNavigationFragment());
-
- fm.beginTransaction().setPrimaryNavigationFragment(null).commit();
- executePendingTransactions(fm);
-
- assertNull("primary nav fragment not null after explicit set to null",
- fm.getPrimaryNavigationFragment());
-
- fm.beginTransaction().replace(android.R.id.content, strictFragment2)
- .setPrimaryNavigationFragment(strictFragment2).addToBackStack(null).commit();
- executePendingTransactions(fm);
-
- assertSame("primary nav fragment not set correctly after replace", strictFragment2,
- fm.getPrimaryNavigationFragment());
-
- popBackStackImmediate(fm);
-
- assertNull("primary nav fragment not null after popping replace",
- fm.getPrimaryNavigationFragment());
- }
-
- private void executePendingTransactions(final FragmentManager fm) throws Throwable {
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- fm.executePendingTransactions();
- }
- });
- }
-
- private boolean popBackStackImmediate(final FragmentManager fm) throws Throwable {
- final boolean[] result = new boolean[1];
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- result[0] = fm.popBackStackImmediate();
- }
- });
- return result[0];
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
new file mode 100644
index 0000000..2b06bf5
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/PrimaryNavFragmentTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PrimaryNavFragmentTest {
+ @get:Rule
+ val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ @Test
+ fun delegateBackToPrimaryNav() {
+ val fm = activityRule.activity.supportFragmentManager
+ val strictFragment = StrictFragment()
+
+ fm.beginTransaction()
+ .add(strictFragment, null)
+ .setPrimaryNavigationFragment(strictFragment)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("new fragment is not primary nav fragment")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment)
+
+ val child = StrictFragment()
+ val cfm = strictFragment.childFragmentManager
+ cfm.beginTransaction()
+ .add(child, null)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions(cfm)
+
+ assertWithMessage("child transaction not on back stack")
+ .that(cfm.backStackEntryCount)
+ .isEqualTo(1)
+
+ // Should execute the pop for the child fragmentmanager
+ assertWithMessage("popBackStackImmediate returned no action performed")
+ .that(popBackStackImmediate(fm))
+ .isTrue()
+
+ assertWithMessage("child transaction still on back stack")
+ .that(cfm.backStackEntryCount)
+ .isEqualTo(0)
+ }
+
+ @Test
+ fun popPrimaryNav() {
+ val fm = activityRule.activity.supportFragmentManager
+ val strictFragment1 = StrictFragment()
+
+ fm.beginTransaction()
+ .add(strictFragment1, null)
+ .setPrimaryNavigationFragment(strictFragment1)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("new fragment is not primary nav fragment")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+
+ fm.beginTransaction()
+ .remove(strictFragment1)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment is not null after remove")
+ .that(fm.primaryNavigationFragment)
+ .isNull()
+
+ popBackStackImmediate(fm)
+
+ assertWithMessage("primary nav fragment was not restored on pop")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+
+ val strictFragment2 = StrictFragment()
+ fm.beginTransaction()
+ .remove(strictFragment1)
+ .add(strictFragment2, null)
+ .setPrimaryNavigationFragment(strictFragment2)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment not updated to new fragment")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment2)
+
+ popBackStackImmediate(fm)
+
+ assertWithMessage("primary nav fragment not restored on pop")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+
+ fm.beginTransaction()
+ .setPrimaryNavigationFragment(strictFragment1)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment not retained when set again in new transaction")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+ popBackStackImmediate(fm)
+
+ assertWithMessage(
+ "same primary nav fragment not retained when set primary nav transaction popped")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+ }
+
+ @Test
+ fun replacePrimaryNav() {
+ val fm = activityRule.activity.supportFragmentManager
+ val strictFragment1 = StrictFragment()
+
+ fm.beginTransaction()
+ .add(android.R.id.content, strictFragment1)
+ .setPrimaryNavigationFragment(strictFragment1)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("new fragment is not primary nav fragment")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+
+ val strictFragment2 = StrictFragment()
+ fm.beginTransaction()
+ .replace(android.R.id.content, strictFragment2)
+ .addToBackStack(null)
+ .commit()
+
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment not null after replace")
+ .that(fm.primaryNavigationFragment)
+ .isNull()
+
+ popBackStackImmediate(fm)
+
+ assertWithMessage("primary nav fragment not restored after popping replace")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment1)
+
+ fm.beginTransaction()
+ .setPrimaryNavigationFragment(null)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment not null after explicit set to null")
+ .that(fm.primaryNavigationFragment)
+ .isNull()
+
+ fm.beginTransaction()
+ .replace(android.R.id.content, strictFragment2)
+ .setPrimaryNavigationFragment(strictFragment2)
+ .addToBackStack(null)
+ .commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("primary nav fragment not set correctly after replace")
+ .that(fm.primaryNavigationFragment)
+ .isSameAs(strictFragment2)
+
+ popBackStackImmediate(fm)
+
+ assertWithMessage("primary nav fragment not null after popping replace")
+ .that(fm.primaryNavigationFragment)
+ .isNull()
+ }
+
+ private fun executePendingTransactions(fm: FragmentManager) {
+ activityRule.runOnUiThread { fm.executePendingTransactions() }
+ }
+
+ private fun popBackStackImmediate(fm: FragmentManager): Boolean {
+ var popped = false
+ activityRule.runOnUiThread { popped = fm.popBackStackImmediate() }
+ return popped
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java
deleted file mode 100644
index ae70d7f..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragment.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import android.os.Bundle;
-
-public class ReentrantFragment extends StrictFragment {
- private static final String FROM_STATE = "fromState";
- private static final String TO_STATE = "toState";
- int mFromState = 0;
- int mToState = 0;
- boolean mIsRestored;
-
- public static ReentrantFragment create(int fromState, int toState) {
- ReentrantFragment fragment = new ReentrantFragment();
- fragment.mFromState = fromState;
- fragment.mToState = toState;
- fragment.mIsRestored = false;
- return fragment;
- }
-
- @Override
- public void onStateChanged(int fromState) {
- super.onStateChanged(fromState);
- // We execute the transaction when shutting down or after restoring
- if (fromState == mFromState && mState == mToState
- && (mToState < mFromState || mIsRestored)) {
- executeTransaction();
- }
- }
-
- private void executeTransaction() {
- getFragmentManager().beginTransaction()
- .add(new StrictFragment(), "should throw")
- .commitNow();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(FROM_STATE, mFromState);
- outState.putInt(TO_STATE, mToState);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- if (savedInstanceState != null) {
- mFromState = savedInstanceState.getInt(FROM_STATE);
- mToState = savedInstanceState.getInt(TO_STATE);
- mIsRestored = true;
- }
- super.onCreate(savedInstanceState);
- }
-}
-
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragmentTest.kt
new file mode 100644
index 0000000..1aab674
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ReentrantFragmentTest.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.lifecycle.ViewModelStore
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+class ReentrantFragmentTest(
+ private val fromState: StrictFragment.State,
+ private val toState: StrictFragment.State
+) {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "fromState={0}, toState={1}")
+ fun data() = mutableListOf<Array<Any?>>().apply {
+ add(arrayOf(StrictFragment.State.ATTACHED, StrictFragment.State.CREATED))
+ add(arrayOf(StrictFragment.State.CREATED, StrictFragment.State.ACTIVITY_CREATED))
+ add(arrayOf(StrictFragment.State.ACTIVITY_CREATED, StrictFragment.State.STARTED))
+ add(arrayOf(StrictFragment.State.STARTED, StrictFragment.State.RESUMED))
+ add(arrayOf(StrictFragment.State.RESUMED, StrictFragment.State.STARTED))
+ add(arrayOf(StrictFragment.State.STARTED, StrictFragment.State.CREATED))
+ add(arrayOf(StrictFragment.State.CREATED, StrictFragment.State.ATTACHED))
+ add(arrayOf(StrictFragment.State.ATTACHED, StrictFragment.State.DETACHED))
+ }
+ }
+
+ @get:Rule
+ val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ // Make sure that executing transactions during activity lifecycle events
+ // is properly prevented.
+ @Test
+ fun preventReentrantCalls() {
+ activityRule.runOnUiThread(Runnable {
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentTestUtil.startupFragmentController(
+ activityRule.activity,
+ null,
+ viewModelStore
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ val reentrantFragment = ReentrantFragment.create(fromState, toState)
+
+ fm1.beginTransaction().add(reentrantFragment, "reentrant").commit()
+ try {
+ fm1.executePendingTransactions()
+ } catch (e: IllegalStateException) {
+ fail("An exception shouldn't happen when initially adding the fragment")
+ }
+
+ // Now shut down the fragment controller. When fromState > toState, this should
+ // result in an exception
+ val savedState: Parcelable?
+ try {
+ fc1.dispatchPause()
+ savedState = fc1.saveAllState()
+ fc1.dispatchStop()
+ fc1.dispatchDestroy()
+ if (fromState > toState) {
+ fail("Expected IllegalStateException when moving from " +
+ "$fromState to $toState")
+ }
+ } catch (e: IllegalStateException) {
+ if (fromState < toState) {
+ fail("Unexpected IllegalStateException when moving from " +
+ "$fromState to $toState")
+ }
+ assertThat(e)
+ .hasMessageThat().contains("FragmentManager is already executing transactions")
+ return@Runnable // test passed!
+ }
+
+ // now restore from saved state. This will be reached when
+ // fromState < toState. We want to catch the fragment while it
+ // is being restored as the fragment controller state is being brought up.
+
+ try {
+ FragmentTestUtil.startupFragmentController(
+ activityRule.activity,
+ savedState,
+ viewModelStore
+ )
+ fail("Expected IllegalStateException when moving from " +
+ "$fromState to $toState")
+ } catch (e: IllegalStateException) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("FragmentManager is already executing transactions")
+ }
+ })
+ }
+}
+
+class ReentrantFragment : StrictFragment() {
+ companion object {
+ private const val FROM_STATE = "fromState"
+ private const val TO_STATE = "toState"
+
+ fun create(fromState: State, toState: State): ReentrantFragment {
+ val fragment = ReentrantFragment()
+ fragment.fromState = fromState
+ fragment.toState = toState
+ fragment.isRestored = false
+ return fragment
+ }
+ }
+
+ private var fromState = State.DETACHED
+ private var toState = State.DETACHED
+ private var isRestored: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ if (savedInstanceState != null) {
+ fromState = savedInstanceState.getSerializable(FROM_STATE) as State
+ toState = savedInstanceState.getSerializable(TO_STATE) as State
+ isRestored = true
+ }
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putSerializable(FROM_STATE, fromState)
+ outState.putSerializable(TO_STATE, toState)
+ }
+
+ override fun onStateChanged(fromState: State) {
+ super.onStateChanged(fromState)
+ // We execute the transaction when shutting down or after restoring
+ if (fromState == this.fromState && currentState == toState &&
+ (toState < this.fromState || isRestored)
+ ) {
+ executeTransaction()
+ }
+ }
+
+ private fun executeTransaction() {
+ requireFragmentManager().beginTransaction()
+ .add(StrictFragment(), "should throw")
+ .commitNow()
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt
new file mode 100644
index 0000000..304d0bf
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/SaveStateFragmentTest.kt
@@ -0,0 +1,718 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentTestUtil.HostCallbacks
+import androidx.fragment.app.FragmentTestUtil.createController
+import androidx.fragment.app.FragmentTestUtil.destroy
+import androidx.fragment.app.FragmentTestUtil.restartFragmentController
+import androidx.fragment.app.FragmentTestUtil.resume
+import androidx.fragment.app.FragmentTestUtil.shutdownFragmentController
+import androidx.fragment.app.FragmentTestUtil.startupFragmentController
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.fragment.test.R
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class SaveStateFragmentTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ @Test
+ @UiThreadTest
+ fun setInitialSavedState() {
+ val fm = activityRule.activity.supportFragmentManager
+
+ // Add a StateSaveFragment
+ var fragment = StateSaveFragment("Saved", "")
+ fm.beginTransaction().add(fragment, "tag").commit()
+ executePendingTransactions(fm)
+
+ // Change the user visible hint before we save state
+ fragment.userVisibleHint = false
+
+ // Save its state and remove it
+ val state = fm.saveFragmentInstanceState(fragment)
+ fm.beginTransaction().remove(fragment).commit()
+ executePendingTransactions(fm)
+
+ // Create a new instance, calling setInitialSavedState
+ fragment = StateSaveFragment("", "")
+ fragment.setInitialSavedState(state)
+
+ // Add the new instance
+ fm.beginTransaction().add(fragment, "tag").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("setInitialSavedState did not restore saved state")
+ .that(fragment.savedState).isEqualTo("Saved")
+ assertWithMessage("setInitialSavedState did not restore user visible hint")
+ .that(fragment.userVisibleHint).isEqualTo(false)
+ }
+
+ @Test
+ @UiThreadTest
+ fun setInitialSavedStateWithSetUserVisibleHint() {
+ val fm = activityRule.activity.supportFragmentManager
+
+ // Add a StateSaveFragment
+ var fragment = StateSaveFragment("Saved", "")
+ fm.beginTransaction().add(fragment, "tag").commit()
+ executePendingTransactions(fm)
+
+ // Save its state and remove it
+ val state = fm.saveFragmentInstanceState(fragment)
+ fm.beginTransaction().remove(fragment).commit()
+ executePendingTransactions(fm)
+
+ // Create a new instance, calling setInitialSavedState
+ fragment = StateSaveFragment("", "")
+ fragment.setInitialSavedState(state)
+
+ // Change the user visible hint after we call setInitialSavedState
+ fragment.userVisibleHint = false
+
+ // Add the new instance
+ fm.beginTransaction().add(fragment, "tag").commit()
+ executePendingTransactions(fm)
+
+ assertWithMessage("setInitialSavedState did not restore saved state")
+ .that(fragment.savedState).isEqualTo("Saved")
+ assertWithMessage("setUserVisibleHint should override setInitialSavedState")
+ .that(fragment.userVisibleHint).isEqualTo(false)
+ }
+
+ @Test
+ @UiThreadTest
+ fun restoreRetainedInstanceFragments() {
+ // Create a new FragmentManager in isolation, nest some assorted fragments
+ // and then restore them to a second new FragmentManager.
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ fc1.attachHost(null)
+ fc1.dispatchCreate()
+
+ // Configure fragments.
+
+ // This retained fragment will be added, then removed. After being removed, it
+ // should no longer be retained by the FragmentManager
+ val removedFragment = StateSaveFragment("Removed", "UnsavedRemoved")
+ removedFragment.retainInstance = true
+ fm1.beginTransaction().add(removedFragment, "tag:removed").commitNow()
+ fm1.beginTransaction().remove(removedFragment).commitNow()
+
+ // This retained fragment will be added, then detached. After being detached, it
+ // should continue to be retained by the FragmentManager
+ val detachedFragment = StateSaveFragment("Detached", "UnsavedDetached")
+ removedFragment.retainInstance = true
+ fm1.beginTransaction().add(detachedFragment, "tag:detached").commitNow()
+ fm1.beginTransaction().detach(detachedFragment).commitNow()
+
+ // Grandparent fragment will not retain instance
+ val grandparentFragment = StateSaveFragment("Grandparent", "UnsavedGrandparent")
+ assertWithMessage("grandparent fragment saved state not initialized")
+ .that(grandparentFragment.savedState).isNotNull()
+ assertWithMessage("grandparent fragment unsaved state not initialized")
+ .that(grandparentFragment.unsavedState).isNotNull()
+ fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow()
+
+ // Parent fragment will retain instance
+ val parentFragment = StateSaveFragment("Parent", "UnsavedParent")
+ assertWithMessage("parent fragment saved state not initialized")
+ .that(parentFragment.savedState).isNotNull()
+ assertWithMessage("parent fragment unsaved state not initialized")
+ .that(parentFragment.unsavedState).isNotNull()
+ parentFragment.retainInstance = true
+ grandparentFragment.childFragmentManager.beginTransaction()
+ .add(parentFragment, "tag:parent").commitNow()
+ assertWithMessage("parent fragment is not a child of grandparent")
+ .that(parentFragment.parentFragment).isSameAs(grandparentFragment)
+
+ // Child fragment will not retain instance
+ val childFragment = StateSaveFragment("Child", "UnsavedChild")
+ assertWithMessage("child fragment saved state not initialized")
+ .that(childFragment.savedState).isNotNull()
+ assertWithMessage("child fragment unsaved state not initialized")
+ .that(childFragment.unsavedState).isNotNull()
+ parentFragment.childFragmentManager.beginTransaction()
+ .add(childFragment, "tag:child").commitNow()
+ assertWithMessage("child fragment is not a child of grandparent")
+ .that(childFragment.parentFragment).isSameAs(parentFragment)
+
+ // Saved for comparison later
+ val parentChildFragmentManager = parentFragment.childFragmentManager
+
+ fc1.dispatchActivityCreated()
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+ fc1.dispatchResume()
+ fc1.execPendingActions()
+
+ // Bring the state back down to destroyed, simulating an activity restart
+ fc1.dispatchPause()
+ val savedState = fc1.saveAllState()
+ fc1.dispatchStop()
+ fc1.dispatchDestroy()
+
+ // Create the new controller and restore state
+ val fc2 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm2 = fc2.supportFragmentManager
+
+ fc2.attachHost(null)
+ fc2.restoreSaveState(savedState)
+ fc2.dispatchCreate()
+
+ // Confirm that the restored fragments are available and in the expected states
+ val restoredRemovedFragment = fm2.findFragmentByTag("tag:removed") as StateSaveFragment?
+ assertThat(restoredRemovedFragment).isNull()
+ assertWithMessage("Removed Fragment should be destroyed")
+ .that(removedFragment.calledOnDestroy).isTrue()
+
+ val restoredDetachedFragment = fm2.findFragmentByTag("tag:detached") as StateSaveFragment
+ assertThat(restoredDetachedFragment).isNotNull()
+
+ val restoredGrandparent = fm2.findFragmentByTag("tag:grandparent") as StateSaveFragment
+ assertWithMessage("grandparent fragment not restored").that(restoredGrandparent).isNotNull()
+
+ assertWithMessage("grandparent fragment instance was saved")
+ .that(restoredGrandparent).isNotSameAs(grandparentFragment)
+ assertWithMessage("grandparent fragment saved state was not equal")
+ .that(restoredGrandparent.savedState).isEqualTo(grandparentFragment.savedState)
+ assertWithMessage("grandparent fragment unsaved state was unexpectedly preserved")
+ .that(restoredGrandparent.unsavedState).isNotEqualTo(grandparentFragment.unsavedState)
+
+ val restoredParent = restoredGrandparent
+ .childFragmentManager.findFragmentByTag("tag:parent") as StateSaveFragment
+ assertWithMessage("parent fragment not restored").that(restoredParent).isNotNull()
+
+ assertWithMessage("parent fragment instance was not saved")
+ .that(restoredParent).isSameAs(parentFragment)
+ assertWithMessage("parent fragment saved state was not equal")
+ .that(restoredParent.savedState).isEqualTo(parentFragment.savedState)
+ assertWithMessage("parent fragment unsaved state was not equal")
+ .that(restoredParent.unsavedState).isEqualTo(parentFragment.unsavedState)
+ assertWithMessage("parent fragment has the same child FragmentManager")
+ .that(restoredParent.childFragmentManager).isNotSameAs(parentChildFragmentManager)
+
+ val restoredChild = restoredParent
+ .childFragmentManager.findFragmentByTag("tag:child") as StateSaveFragment
+ assertWithMessage("child fragment not restored").that(restoredChild).isNotNull()
+
+ assertWithMessage("child fragment instance state was saved")
+ .that(restoredChild).isNotSameAs(childFragment)
+ assertWithMessage("child fragment saved state was not equal")
+ .that(restoredChild.savedState).isEqualTo(childFragment.savedState)
+ assertWithMessage("child fragment saved state was unexpectedly equal")
+ .that(restoredChild.unsavedState).isNotEqualTo(childFragment.unsavedState)
+
+ fc2.dispatchActivityCreated()
+ fc2.noteStateNotSaved()
+ fc2.execPendingActions()
+ fc2.dispatchStart()
+ fc2.dispatchResume()
+ fc2.execPendingActions()
+
+ // Test that the fragments are in the configuration we expect
+
+ // Bring the state back down to destroyed before we finish the test
+ shutdownFragmentController(fc2, viewModelStore)
+
+ assertWithMessage("grandparent not destroyed")
+ .that(restoredGrandparent.calledOnDestroy).isTrue()
+ assertWithMessage("parent not destroyed").that(restoredParent.calledOnDestroy).isTrue()
+ assertWithMessage("child not destroyed").that(restoredChild.calledOnDestroy).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun restoreRetainedInstanceFragmentWithTransparentActivityConfigChange() {
+ // Create a new FragmentManager in isolation, add a retained instance Fragment,
+ // then mimic the following scenario:
+ // 1. Activity A adds retained Fragment F
+ // 2. Activity A starts translucent Activity B
+ // 3. Activity B start opaque Activity C
+ // 4. Rotate phone
+ // 5. Finish Activity C
+ // 6. Finish Activity B
+
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ fc1.attachHost(null)
+ fc1.dispatchCreate()
+
+ // Add the retained Fragment
+ val retainedFragment = StateSaveFragment("Retained", "UnsavedRetained")
+ retainedFragment.retainInstance = true
+ fm1.beginTransaction().add(retainedFragment, "tag:retained").commitNow()
+
+ // Move the activity to resumed
+ fc1.dispatchActivityCreated()
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+ fc1.dispatchResume()
+ fc1.execPendingActions()
+
+ // Launch the transparent activity on top
+ fc1.dispatchPause()
+
+ // Launch the opaque activity on top
+ val savedState = fc1.saveAllState()
+ fc1.dispatchStop()
+
+ // Finish the opaque activity, making our Activity visible i.e., started
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+
+ // Finish the transparent activity, causing a config change
+ fc1.dispatchStop()
+ fc1.dispatchDestroy()
+
+ // Create the new controller and restore state
+ val fc2 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm2 = fc2.supportFragmentManager
+
+ fc2.attachHost(null)
+ fc2.restoreSaveState(savedState)
+ fc2.dispatchCreate()
+
+ val restoredFragment = fm2.findFragmentByTag("tag:retained") as StateSaveFragment
+ assertWithMessage("retained fragment not restored").that(restoredFragment).isNotNull()
+ assertWithMessage("The retained Fragment shouldn't be recreated")
+ .that(restoredFragment).isEqualTo(retainedFragment)
+
+ fc2.dispatchActivityCreated()
+ fc2.noteStateNotSaved()
+ fc2.execPendingActions()
+ fc2.dispatchStart()
+ fc2.dispatchResume()
+ fc2.execPendingActions()
+
+ // Bring the state back down to destroyed before we finish the test
+ shutdownFragmentController(fc2, viewModelStore)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testSavedInstanceStateAfterRestore() {
+
+ val viewModelStore = ViewModelStore()
+ val fc1 =
+ startupFragmentController(activityRule.activity, null, viewModelStore)
+ val fm1 = fc1.supportFragmentManager
+
+ // Add the initial state
+ val parentFragment = StrictFragment()
+ parentFragment.retainInstance = true
+ val childFragment = StrictFragment()
+ fm1.beginTransaction().add(parentFragment, "parent").commitNow()
+ val childFragmentManager = parentFragment.childFragmentManager
+ childFragmentManager.beginTransaction().add(childFragment, "child").commitNow()
+
+ // Confirm the initial state
+ assertWithMessage("Initial parent saved instance state should be null")
+ .that(parentFragment.lastSavedInstanceState).isNull()
+ assertWithMessage("Initial child saved instance state should be null")
+ .that(childFragment.lastSavedInstanceState).isNull()
+
+ // Bring the state back down to destroyed, simulating an activity restart
+ fc1.dispatchPause()
+ val savedState = fc1.saveAllState()
+ fc1.dispatchStop()
+ fc1.dispatchDestroy()
+
+ // Create the new controller and restore state
+ val fc2 = startupFragmentController(
+ activityRule.activity,
+ savedState,
+ viewModelStore
+ )
+ val fm2 = fc2.supportFragmentManager
+
+ val restoredParentFragment = fm2.findFragmentByTag("parent") as StrictFragment
+ assertWithMessage("Parent fragment was not restored")
+ .that(restoredParentFragment).isNotNull()
+ val restoredChildFragment = restoredParentFragment
+ .childFragmentManager.findFragmentByTag("child") as StrictFragment
+ assertWithMessage("Child fragment was not restored").that(restoredChildFragment).isNotNull()
+
+ assertWithMessage("Parent fragment saved instance state should still be null since it is " +
+ "a retained Fragment").that(restoredParentFragment.lastSavedInstanceState).isNull()
+ assertWithMessage("Child fragment saved instance state should be non-null")
+ .that(restoredChildFragment.lastSavedInstanceState).isNotNull()
+
+ // Bring the state back down to destroyed before we finish the test
+ shutdownFragmentController(fc2, viewModelStore)
+ }
+
+ @Test
+ @UiThreadTest
+ fun restoreNestedFragmentsOnBackStack() {
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ fc1.attachHost(null)
+ fc1.dispatchCreate()
+
+ // Add the initial state
+ val parentFragment = StrictFragment()
+ val childFragment = StrictFragment()
+ fm1.beginTransaction().add(parentFragment, "parent").commitNow()
+ val childFragmentManager = parentFragment.childFragmentManager
+ childFragmentManager.beginTransaction().add(childFragment, "child").commitNow()
+
+ // Now add a Fragment to the back stack
+ val replacementChildFragment = StrictFragment()
+ childFragmentManager.beginTransaction()
+ .remove(childFragment)
+ .add(replacementChildFragment, "child")
+ .addToBackStack("back_stack").commit()
+ childFragmentManager.executePendingTransactions()
+
+ // Move the activity to resumed
+ fc1.dispatchActivityCreated()
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+ fc1.dispatchResume()
+ fc1.execPendingActions()
+
+ // Now bring the state back down
+ fc1.dispatchPause()
+ val savedState = fc1.saveAllState()
+ fc1.dispatchStop()
+ fc1.dispatchDestroy()
+
+ // Create the new controller and restore state
+ val fc2 = FragmentController.createController(
+ HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm2 = fc2.supportFragmentManager
+
+ fc2.attachHost(null)
+ fc2.restoreSaveState(savedState)
+ fc2.dispatchCreate()
+
+ val restoredParentFragment = fm2.findFragmentByTag("parent") as StrictFragment
+ assertWithMessage("Parent fragment was not restored")
+ .that(restoredParentFragment).isNotNull()
+ val restoredChildFragment = restoredParentFragment
+ .childFragmentManager.findFragmentByTag("child") as StrictFragment
+ assertWithMessage("Child fragment was not restored").that(restoredChildFragment).isNotNull()
+
+ fc2.dispatchActivityCreated()
+ fc2.noteStateNotSaved()
+ fc2.execPendingActions()
+ fc2.dispatchStart()
+ fc2.dispatchResume()
+ fc2.execPendingActions()
+
+ // Bring the state back down to destroyed before we finish the test
+ shutdownFragmentController(fc2, viewModelStore)
+ }
+
+ /**
+ * When a fragment has been optimized out, it state should still be saved during
+ * save and restore instance state.
+ */
+ @Test
+ @UiThreadTest
+ fun saveRemovedFragment() {
+ var fc = createController(activityRule)
+ resume(activityRule, fc, null)
+ var fm = fc.supportFragmentManager
+
+ var fragment1 = SaveStateFragment.create(1)
+ fm.beginTransaction()
+ .add(android.R.id.content, fragment1, "1")
+ .addToBackStack(null)
+ .commit()
+ var fragment2 = SaveStateFragment.create(2)
+ fm.beginTransaction()
+ .replace(android.R.id.content, fragment2, "2")
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+
+ val savedState = destroy(activityRule, fc)
+
+ fc = createController(activityRule)
+ resume(activityRule, fc, savedState)
+ fm = fc.supportFragmentManager
+ fragment2 = fm.findFragmentByTag("2") as SaveStateFragment
+ assertThat(fragment2).isNotNull()
+ assertThat(fragment2.value).isEqualTo(2)
+ fm.popBackStackImmediate()
+ fragment1 = fm.findFragmentByTag("1") as SaveStateFragment
+ assertThat(fragment1).isNotNull()
+ assertThat(fragment1.value).isEqualTo(1)
+ }
+
+ /**
+ * Test to ensure that when dispatch* is called that the fragment manager
+ * doesn't cause the contained fragment states to change even if no state changes.
+ */
+ @Test
+ @UiThreadTest
+ fun noPrematureStateChange() {
+ val viewModelStore = ViewModelStore()
+ var fc =
+ startupFragmentController(activityRule.activity, null, viewModelStore)
+ var fm = fc.supportFragmentManager
+
+ fm.beginTransaction().add(StrictFragment(), "1").commitNow()
+
+ fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+
+ fm = fc.supportFragmentManager
+
+ val fragment1 = fm.findFragmentByTag("1") as StrictFragment
+ assertWithMessage("Fragment should be resumed after restart")
+ .that(fragment1.calledOnResume).isTrue()
+ fragment1.calledOnResume = false
+ fc.dispatchResume()
+
+ assertWithMessage("Fragment should not get onResume() after second dispatchResume()")
+ .that(fragment1.calledOnResume).isFalse()
+ }
+
+ @Test
+ @UiThreadTest
+ fun testIsStateSaved() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ startupFragmentController(activityRule.activity, null, viewModelStore)
+ val fm = fc.supportFragmentManager
+
+ val f = StrictFragment()
+ fm.beginTransaction().add(f, "1").commitNow()
+
+ assertWithMessage("fragment reported state saved while resumed")
+ .that(f.isStateSaved).isFalse()
+
+ fc.dispatchPause()
+ fc.saveAllState()
+
+ assertWithMessage("fragment reported state not saved after saveAllState")
+ .that(f.isStateSaved).isTrue()
+
+ fc.dispatchStop()
+
+ assertWithMessage("fragment reported state not saved after stop")
+ .that(f.isStateSaved).isTrue()
+
+ viewModelStore.clear()
+ fc.dispatchDestroy()
+
+ assertWithMessage("fragment reported state saved after destroy")
+ .that(f.isStateSaved).isFalse()
+ }
+
+ @Test
+ @UiThreadTest
+ fun saveAnimationState() {
+ val viewModelStore = ViewModelStore()
+ var fc = startupFragmentController(
+ activityRule.activity, null,
+ viewModelStore
+ )
+ var fm = fc.supportFragmentManager
+
+ fm.beginTransaction()
+ .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+ .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+ // Causes save and restore of fragments and back stack
+ fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+ fm = fc.supportFragmentManager
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+ fm.beginTransaction()
+ .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
+ .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
+ .addToBackStack(null)
+ .commit()
+ fm.executePendingTransactions()
+
+ assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0)
+
+ // Causes save and restore of fragments and back stack
+ fc = restartFragmentController(activityRule.activity, fc, viewModelStore)
+ fm = fc.supportFragmentManager
+
+ assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0)
+
+ fm.popBackStackImmediate()
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+
+ shutdownFragmentController(fc, viewModelStore)
+ }
+
+ private fun executePendingTransactions(fm: FragmentManager) {
+ activityRule.runOnUiThread { fm.executePendingTransactions() }
+ }
+
+ private fun assertAnimationsMatch(
+ fm: FragmentManager,
+ enter: Int,
+ exit: Int,
+ popEnter: Int,
+ popExit: Int
+ ) {
+ val fmImpl = fm as FragmentManagerImpl
+ val record = fmImpl.mBackStack[fmImpl.mBackStack.size - 1]
+
+ assertThat(record.mEnterAnim).isEqualTo(enter)
+ assertThat(record.mExitAnim).isEqualTo(exit)
+ assertThat(record.mPopEnterAnim).isEqualTo(popEnter)
+ assertThat(record.mPopExitAnim).isEqualTo(popExit)
+ }
+
+ class SimpleFragment : Fragment() {
+ private var layoutId: Int = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState != null) {
+ layoutId = savedInstanceState.getInt(LAYOUT_ID, layoutId)
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putInt(LAYOUT_ID, layoutId)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = inflater.inflate(layoutId, container, false)
+
+ companion object {
+ private const val LAYOUT_ID = "layoutId"
+
+ fun create(layoutId: Int): SimpleFragment {
+ val fragment = SimpleFragment()
+ fragment.layoutId = layoutId
+ return fragment
+ }
+ }
+ }
+
+ class SaveStateFragment : Fragment() {
+ var value: Int = 0
+ private set
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putInt(VALUE_KEY, value)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState != null) {
+ value = savedInstanceState.getInt(VALUE_KEY, value)
+ }
+ }
+
+ companion object {
+ private const val VALUE_KEY = "SaveStateFragment.mValue"
+
+ fun create(value: Int): SaveStateFragment {
+ val saveStateFragment = SaveStateFragment()
+ saveStateFragment.value = value
+ return saveStateFragment
+ }
+ }
+ }
+
+ class StateSaveFragment : StrictFragment {
+
+ var savedState: String? = null
+ private set
+ var unsavedState: String? = null
+
+ constructor()
+
+ constructor(savedState: String, unsavedState: String) {
+ this.savedState = savedState
+ this.unsavedState = unsavedState
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState != null) {
+ savedState = savedInstanceState.getString(STATE_KEY)
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putString(STATE_KEY, savedState)
+ }
+
+ companion object {
+ private const val STATE_KEY = "state"
+ }
+ }
+}
\ No newline at end of file
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java
deleted file mode 100644
index 2acbeda..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import android.content.Context;
-import android.os.Bundle;
-
-/**
- * This fragment watches its primary lifecycle events and throws IllegalStateException
- * if any of them are called out of order or from a bad/unexpected state.
- */
-public class StrictFragment extends Fragment {
- public static final int DETACHED = 0;
- public static final int ATTACHED = 1;
- public static final int CREATED = 2;
- public static final int ACTIVITY_CREATED = 3;
- public static final int STARTED = 4;
- public static final int RESUMED = 5;
-
- int mState;
-
- boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated,
- mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState,
- mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach,
- mCalledOnAttachFragment;
- Bundle mSavedInstanceState;
-
- static String stateToString(int state) {
- switch (state) {
- case DETACHED: return "DETACHED";
- case ATTACHED: return "ATTACHED";
- case CREATED: return "CREATED";
- case ACTIVITY_CREATED: return "ACTIVITY_CREATED";
- case STARTED: return "STARTED";
- case RESUMED: return "RESUMED";
- }
- return "(unknown " + state + ")";
- }
-
- public void onStateChanged(int fromState) {
- checkGetActivity();
- }
-
- public void checkGetActivity() {
- if (getActivity() == null) {
- throw new IllegalStateException("getActivity() returned null at unexpected time");
- }
- }
-
- public void checkState(String caller, int... expected) {
- if (expected == null || expected.length == 0) {
- throw new IllegalArgumentException("must supply at least one expected state");
- }
- for (int expect : expected) {
- if (mState == expect) {
- return;
- }
- }
- final StringBuilder expectString = new StringBuilder(stateToString(expected[0]));
- for (int i = 1; i < expected.length; i++) {
- expectString.append(" or ").append(stateToString(expected[i]));
- }
- throw new IllegalStateException(caller + " called while fragment was "
- + stateToString(mState) + "; expected " + expectString.toString());
- }
-
- public void checkStateAtLeast(String caller, int minState) {
- if (mState < minState) {
- throw new IllegalStateException(caller + " called while fragment was "
- + stateToString(mState) + "; expected at least " + stateToString(minState));
- }
- }
-
- @Override
- public void onAttachFragment(Fragment childFragment) {
- mCalledOnAttachFragment = true;
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mCalledOnAttach = true;
- checkState("onAttach", DETACHED);
- mState = ATTACHED;
- onStateChanged(DETACHED);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (mCalledOnCreate && !mCalledOnDestroy) {
- throw new IllegalStateException("onCreate called more than once with no onDestroy");
- }
- mCalledOnCreate = true;
- mSavedInstanceState = savedInstanceState;
- checkState("onCreate", ATTACHED);
- mState = CREATED;
- onStateChanged(ATTACHED);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mCalledOnActivityCreated = true;
- checkState("onActivityCreated", ATTACHED, CREATED);
- int fromState = mState;
- mState = ACTIVITY_CREATED;
- onStateChanged(fromState);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- mCalledOnStart = true;
- checkState("onStart", CREATED, ACTIVITY_CREATED);
- mState = STARTED;
- onStateChanged(ACTIVITY_CREATED);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mCalledOnResume = true;
- checkState("onResume", STARTED);
- mState = RESUMED;
- onStateChanged(STARTED);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mCalledOnSaveInstanceState = true;
- checkGetActivity();
- // FIXME: We should not allow onSaveInstanceState except when STARTED or greater.
- // But FragmentManager currently does it in saveAllState for fragments on the
- // back stack, so fragments may be in the CREATED state.
- checkStateAtLeast("onSaveInstanceState", CREATED);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mCalledOnPause = true;
- checkState("onPause", RESUMED);
- mState = STARTED;
- onStateChanged(RESUMED);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mCalledOnStop = true;
- checkState("onStop", STARTED);
- mState = CREATED;
- onStateChanged(STARTED);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mCalledOnDestroy = true;
- checkState("onDestroy", CREATED);
- mState = ATTACHED;
- onStateChanged(CREATED);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mCalledOnDetach = true;
- checkState("onDestroy", CREATED, ATTACHED);
- int fromState = mState;
- mState = DETACHED;
- onStateChanged(fromState);
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt
new file mode 100644
index 0000000..4fd5f18
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictFragment.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.content.Context
+import android.os.Bundle
+import androidx.annotation.LayoutRes
+import com.google.common.truth.Truth.assertWithMessage
+
+/**
+ * This fragment watches its primary lifecycle events and throws IllegalStateException
+ * if any of them are called out of order or from a bad/unexpected state.
+ */
+open class StrictFragment(@LayoutRes contentLayoutId: Int = 0) : Fragment(contentLayoutId) {
+ var currentState: State = State.DETACHED
+
+ var calledOnAttach: Boolean = false
+ var calledOnCreate: Boolean = false
+ var calledOnActivityCreated: Boolean = false
+ var calledOnStart: Boolean = false
+ var calledOnResume: Boolean = false
+ var calledOnSaveInstanceState: Boolean = false
+ var calledOnPause: Boolean = false
+ var calledOnStop: Boolean = false
+ var calledOnDestroy: Boolean = false
+ var calledOnDetach: Boolean = false
+ var calledOnAttachFragment: Boolean = false
+ var lastSavedInstanceState: Bundle? = null
+
+ open fun onStateChanged(fromState: State) {
+ checkGetActivity()
+ }
+
+ fun checkGetActivity() {
+ assertWithMessage("getActivity() returned null at unexpected time")
+ .that(activity)
+ .isNotNull()
+ }
+
+ fun checkState(caller: String, vararg expected: State) {
+ if (expected.isEmpty()) {
+ throw IllegalArgumentException("must supply at least one expected state")
+ }
+ for (expect in expected) {
+ if (currentState == expect) {
+ return
+ }
+ }
+ val expectString = StringBuilder(expected[0].toString())
+ for (i in 1 until expected.size) {
+ expectString.append(" or ").append(expected[i])
+ }
+ throw IllegalStateException(
+ "$caller called while fragment was $currentState; " +
+ "expected $expectString"
+ )
+ }
+
+ fun checkStateAtLeast(caller: String, minState: State) {
+ if (currentState < minState) {
+ throw IllegalStateException(
+ "$caller called while fragment was $currentState; " +
+ "expected at least $minState"
+ )
+ }
+ }
+
+ override fun onAttachFragment(childFragment: Fragment) {
+ calledOnAttachFragment = true
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ calledOnAttach = true
+ checkState("onAttach", State.DETACHED)
+ currentState = State.ATTACHED
+ onStateChanged(State.DETACHED)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (calledOnCreate && !calledOnDestroy) {
+ throw IllegalStateException("onCreate called more than once with no onDestroy")
+ }
+ calledOnCreate = true
+ lastSavedInstanceState = savedInstanceState
+ checkState("onCreate", State.ATTACHED)
+ currentState = State.CREATED
+ onStateChanged(State.ATTACHED)
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ calledOnActivityCreated = true
+ checkState("onActivityCreated", State.ATTACHED, State.CREATED)
+ val fromState = currentState
+ currentState = State.ACTIVITY_CREATED
+ onStateChanged(fromState)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ calledOnStart = true
+ checkState("onStart", State.CREATED, State.ACTIVITY_CREATED)
+ currentState = State.STARTED
+ onStateChanged(State.ACTIVITY_CREATED)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ calledOnResume = true
+ checkState("onResume", State.STARTED)
+ currentState = State.RESUMED
+ onStateChanged(State.STARTED)
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ calledOnSaveInstanceState = true
+ checkGetActivity()
+ // FIXME: We should not allow onSaveInstanceState except when STARTED or greater.
+ // But FragmentManager currently does it in saveAllState for fragments on the
+ // back stack, so fragments may be in the CREATED state.
+ checkStateAtLeast("onSaveInstanceState", State.CREATED)
+ }
+
+ override fun onPause() {
+ super.onPause()
+ calledOnPause = true
+ checkState("onPause", State.RESUMED)
+ currentState = State.STARTED
+ onStateChanged(State.RESUMED)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ calledOnStop = true
+ checkState("onStop", State.STARTED)
+ currentState = State.CREATED
+ onStateChanged(State.STARTED)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ calledOnDestroy = true
+ checkState("onDestroy", State.CREATED)
+ currentState = State.ATTACHED
+ onStateChanged(State.CREATED)
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ calledOnDetach = true
+ checkState("onDestroy", State.CREATED, State.ATTACHED)
+ val fromState = currentState
+ currentState = State.DETACHED
+ onStateChanged(fromState)
+ }
+
+ enum class State {
+ DETACHED,
+ ATTACHED,
+ CREATED,
+ ACTIVITY_CREATED,
+ STARTED,
+ RESUMED
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java
deleted file mode 100644
index ec1a553..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package androidx.fragment.app;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.test.R;
-
-public class StrictViewFragment extends StrictFragment {
- boolean mOnCreateViewCalled, mOnViewCreatedCalled, mOnDestroyViewCalled;
- int mLayoutId = R.layout.strict_view_fragment;
-
- public void setLayoutId(int layoutId) {
- mLayoutId = layoutId;
- }
-
- public static StrictViewFragment create(int layoutId) {
- StrictViewFragment fragment = new StrictViewFragment();
- fragment.mLayoutId = layoutId;
- return fragment;
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- checkGetActivity();
- checkState("onCreateView", CREATED);
- View result = super.onCreateView(inflater, container, savedInstanceState);
- if (result == null) {
- result = inflater.inflate(mLayoutId, container, false);
- }
- mOnCreateViewCalled = true;
- return result;
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- if (view == null) {
- throw new IllegalArgumentException("onViewCreated view argument should not be null");
- }
- checkGetActivity();
- checkState("onViewCreated", CREATED);
- mOnViewCreatedCalled = true;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- if (getView() == null) {
- throw new IllegalStateException("getView returned null in onDestroyView");
- }
- checkGetActivity();
- checkState("onDestroyView", CREATED);
- mOnDestroyViewCalled = true;
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
new file mode 100644
index 0000000..cc7deadd
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/StrictViewFragment.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+import com.google.common.truth.Truth.assertWithMessage
+
+open class StrictViewFragment(
+ @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictFragment(contentLayoutId) {
+
+ internal var onCreateViewCalled: Boolean = false
+ internal var onViewCreatedCalled: Boolean = false
+ internal var onDestroyViewCalled: Boolean = false
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ checkGetActivity()
+ checkState("onCreateView", State.CREATED)
+ return super.onCreateView(inflater, container, savedInstanceState).also {
+ onCreateViewCalled = true
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ checkGetActivity()
+ checkState("onViewCreated", State.CREATED)
+ onViewCreatedCalled = true
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ assertWithMessage("getView returned null in onDestroyView")
+ .that(view)
+ .isNotNull()
+ checkGetActivity()
+ checkState("onDestroyView", State.CREATED)
+ onDestroyViewCalled = true
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt
new file mode 100644
index 0000000..0a62f5e
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TargetFragmentLifeCycleTest.kt
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.lifecycle.ViewModelStore
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TargetFragmentLifeCycleTest {
+
+ @get:Rule
+ val activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ @Test
+ fun targetFragmentNoCycles() {
+ val one = Fragment()
+ val two = Fragment()
+ val three = Fragment()
+
+ try {
+ one.setTargetFragment(two, 0)
+ two.setTargetFragment(three, 0)
+ three.setTargetFragment(one, 0)
+ Assert.fail("creating a fragment target cycle did not throw IllegalArgumentException")
+ } catch (e: IllegalArgumentException) {
+ assertThat(e).hasMessageThat().contains("Setting $one as the target of $three would" +
+ " create a target cycle")
+ }
+ }
+
+ @Test
+ fun targetFragmentSetClear() {
+ val one = Fragment()
+ val two = Fragment()
+
+ one.setTargetFragment(two, 0)
+ one.setTargetFragment(null, 0)
+ }
+
+ /**
+ * Test that target fragments are in a useful state when we restore them, even if they're
+ * on the back stack.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentRestoreLifecycleStateBackStack() {
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentController.createController(
+ FragmentTestUtil.HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ fc1.attachHost(null)
+ fc1.dispatchCreate()
+
+ val target = TargetFragment()
+ fm1.beginTransaction().add(target, "target").commitNow()
+
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+
+ fm1.beginTransaction()
+ .remove(target)
+ .add(referrer, "referrer")
+ .addToBackStack(null)
+ .commit()
+
+ fc1.dispatchActivityCreated()
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+ fc1.dispatchResume()
+ fc1.execPendingActions()
+
+ // Simulate an activity restart
+ val fc2 =
+ FragmentTestUtil.restartFragmentController(activityRule.activity, fc1, viewModelStore)
+
+ // Bring the state back down to destroyed before we finish the test
+ FragmentTestUtil.shutdownFragmentController(fc2, viewModelStore)
+ }
+
+ @Test
+ @UiThreadTest
+ fun targetFragmentRestoreLifecycleStateManagerOrder() {
+ val viewModelStore = ViewModelStore()
+ val fc1 = FragmentController.createController(
+ FragmentTestUtil.HostCallbacks(activityRule.activity, viewModelStore)
+ )
+
+ val fm1 = fc1.supportFragmentManager
+
+ fc1.attachHost(null)
+ fc1.dispatchCreate()
+
+ val target1 = TargetFragment()
+ val referrer1 = ReferrerFragment()
+ referrer1.setTargetFragment(target1, 0)
+
+ fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow()
+
+ val target2 = TargetFragment()
+ val referrer2 = ReferrerFragment()
+ referrer2.setTargetFragment(target2, 0)
+
+ // Order shouldn't matter.
+ fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow()
+
+ fc1.dispatchActivityCreated()
+ fc1.noteStateNotSaved()
+ fc1.execPendingActions()
+ fc1.dispatchStart()
+ fc1.dispatchResume()
+ fc1.execPendingActions()
+
+ // Simulate an activity restart
+ val fc2 =
+ FragmentTestUtil.restartFragmentController(activityRule.activity, fc1, viewModelStore)
+
+ // Bring the state back down to destroyed before we finish the test
+ FragmentTestUtil.shutdownFragmentController(fc2, viewModelStore)
+ }
+
+ @Test
+ @UiThreadTest
+ fun targetFragmentClearedWhenSetToNull() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ referrer.setTargetFragment(null, 0)
+
+ assertWithMessage("Target Fragment should cleared after setTargetFragment with null")
+ .that(referrer.targetFragment).isNull()
+
+ fm.beginTransaction().remove(referrer).commitNow()
+
+ assertWithMessage("Target Fragment should still be cleared after being removed")
+ .that(referrer.targetFragment).isNull()
+
+ FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+ }
+
+ @Test
+ @UiThreadTest
+ fun targetFragment_replacement() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val referrer = ReferrerFragment()
+ val target = TargetFragment()
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(referrer, "referrer").add(target, "target").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ val newTarget = TargetFragment()
+ referrer.setTargetFragment(newTarget, 0)
+
+ assertWithMessage("New Target Fragment should returned despite not being added")
+ .that(referrer.targetFragment).isSameAs(newTarget)
+
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Replacement Target Fragment should override previous target")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target Fragment is already
+ * attached to a FragmentManager, but the referrer Fragment is not attached.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentOnlyTargetAdded() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ // Add just the target Fragment to the FragmentManager
+ fm.beginTransaction().add(target, "target").commitNow()
+
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().remove(referrer).commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being removed")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * not retained and the referrer fragment is not retained.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentNonRetainedNonRetained() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().remove(referrer).commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being removed")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+
+ assertWithMessage("Target Fragment should be accessible after destruction")
+ .that(referrer.targetFragment).isSameAs(target)
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * retained and the referrer fragment is not retained.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentRetainedNonRetained() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ target.retainInstance = true
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().remove(referrer).commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being removed")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ FragmentTestUtil.shutdownFragmentController(fc, viewModelStore)
+
+ assertWithMessage("Target Fragment should be accessible after destruction")
+ .that(referrer.targetFragment).isSameAs(target)
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * not retained and the referrer fragment is retained.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentNonRetainedRetained() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ val referrer = ReferrerFragment()
+ referrer.setTargetFragment(target, 0)
+ referrer.retainInstance = true
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ // Save the state
+ fc.dispatchPause()
+ fc.saveAllState()
+ fc.dispatchStop()
+ fc.dispatchDestroy()
+
+ assertWithMessage("Target Fragment should be accessible after target Fragment destruction")
+ .that(referrer.targetFragment).isSameAs(target)
+ }
+
+ /**
+ * Test the availability of getTargetFragment() when the target fragment is
+ * retained and the referrer fragment is also retained.
+ */
+ @Test
+ @UiThreadTest
+ fun targetFragmentRetainedRetained() {
+ val viewModelStore = ViewModelStore()
+ val fc =
+ FragmentTestUtil.startupFragmentController(activityRule.activity, null, viewModelStore)
+
+ val fm = fc.supportFragmentManager
+
+ val target = TargetFragment()
+ target.retainInstance = true
+ val referrer = ReferrerFragment()
+ referrer.retainInstance = true
+ referrer.setTargetFragment(target, 0)
+
+ assertWithMessage("Target Fragment should be accessible before being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ fm.beginTransaction().add(target, "target").add(referrer, "referrer").commitNow()
+
+ assertWithMessage("Target Fragment should be accessible after being added")
+ .that(referrer.targetFragment).isSameAs(target)
+
+ // Save the state
+ fc.dispatchPause()
+ fc.saveAllState()
+ fc.dispatchStop()
+ fc.dispatchDestroy()
+
+ assertWithMessage("Target Fragment should be accessible after FragmentManager destruction")
+ .that(referrer.targetFragment).isSameAs(target)
+ }
+
+ class TargetFragment : Fragment() {
+ var calledCreate: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ calledCreate = true
+ }
+ }
+
+ class ReferrerFragment : Fragment() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val target = targetFragment
+ assertWithMessage("target fragment was null during referrer onCreate")
+ .that(target).isNotNull()
+
+ if (target !is TargetFragment) {
+ throw IllegalStateException("target fragment was not a TargetFragment")
+ }
+
+ assertWithMessage("target fragment has not yet been created")
+ .that(target.calledCreate).isTrue()
+ }
+ }
+}
\ No newline at end of file
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.java b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.java
deleted file mode 100644
index 7b2a494..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import android.animation.Animator;
-import android.graphics.Rect;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * A transition that tracks which targets are applied to it.
- * It will assume any target that it applies to will have differences
- * between the start and end state, regardless of the differences
- * that actually exist. In other words, it doesn't actually check
- * any size or position differences or any other property of the view.
- * It just records the difference.
- * <p>
- * Both start and end value Views are recorded, but no actual animation
- * is created.
- */
-class TrackingTransition extends Transition implements TargetTracking {
- public final ArrayList<View> targets = new ArrayList<>();
- private final Rect[] mEpicenter = new Rect[1];
- private static final String PROP = "tracking:prop";
- private static final String[] PROPS = { PROP };
-
- @Override
- public String[] getTransitionProperties() {
- return PROPS;
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- transitionValues.values.put(PROP, 0);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- transitionValues.values.put(PROP, 1);
- }
-
- @Override
- public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
- TransitionValues endValues) {
- if (startValues != null) {
- targets.add(startValues.view);
- }
- if (endValues != null) {
- targets.add(endValues.view);
- }
- Rect epicenter = getEpicenter();
- if (epicenter != null) {
- mEpicenter[0] = new Rect(epicenter);
- } else {
- mEpicenter[0] = null;
- }
- return null;
- }
-
- @Override
- public ArrayList<View> getTrackedTargets() {
- return targets;
- }
-
- @Override
- public void clearTargets() {
- targets.clear();
- }
-
- @Override
- public Rect getCapturedEpicenter() {
- return mEpicenter[0];
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt
new file mode 100644
index 0000000..e45a428
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TrackingTransition.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.graphics.Rect
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.view.View
+import android.view.ViewGroup
+
+import java.util.ArrayList
+
+/**
+ * A transition that tracks which targets are applied to it.
+ * It will assume any target that it applies to will have differences
+ * between the start and end state, regardless of the differences
+ * that actually exist. In other words, it doesn't actually check
+ * any size or position differences or any other property of the view.
+ * It just records the difference.
+ *
+ *
+ * Both start and end value Views are recorded, but no actual animation
+ * is created.
+ */
+class TrackingTransition : Transition(), TargetTracking {
+ val targets = ArrayList<View>()
+ private val baseEpicenter = Rect()
+
+ override fun getTransitionProperties(): Array<String> {
+ return PROPS
+ }
+
+ override fun captureStartValues(transitionValues: TransitionValues) {
+ transitionValues.values[PROP] = 0
+ }
+
+ override fun captureEndValues(transitionValues: TransitionValues) {
+ transitionValues.values[PROP] = 1
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ) = null.also {
+ if (startValues != null) {
+ targets.add(startValues.view)
+ }
+ if (endValues != null) {
+ targets.add(endValues.view)
+ }
+ if (epicenter != null) {
+ baseEpicenter.set(Rect(epicenter))
+ }
+ }
+
+ override fun getTrackedTargets(): ArrayList<View> {
+ return targets
+ }
+
+ override fun clearTargets() {
+ targets.clear()
+ }
+
+ override fun getCapturedEpicenter(): Rect? {
+ return baseEpicenter
+ }
+
+ companion object {
+ private val PROP = "tracking:prop"
+ private val PROPS = arrayOf(PROP)
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java b/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
index 3b0dbb1a..2c54f2d 100644
--- a/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TrackingVisibility.java
@@ -28,7 +28,7 @@
* Visibility transition that tracks which targets are applied to it.
* This transition does no animation.
*/
-class TrackingVisibility extends Visibility implements TargetTracking {
+public class TrackingVisibility extends Visibility implements TargetTracking {
public final ArrayList<View> targets = new ArrayList<>();
private final Rect[] mEpicenter = new Rect[1];
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java
deleted file mode 100644
index ad8b45b1..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.transition.Transition;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * A fragment that has transitions that can be tracked.
- */
-public class TransitionFragment extends StrictViewFragment {
- public final TrackingVisibility enterTransition = new TrackingVisibility();
- public final TrackingVisibility reenterTransition = new TrackingVisibility();
- public final TrackingVisibility exitTransition = new TrackingVisibility();
- public final TrackingVisibility returnTransition = new TrackingVisibility();
- public final TrackingTransition sharedElementEnter = new TrackingTransition();
- public final TrackingTransition sharedElementReturn = new TrackingTransition();
-
- private Transition.TransitionListener mListener = mock(Transition.TransitionListener.class);
-
- public TransitionFragment() {
- setEnterTransition(enterTransition);
- setReenterTransition(reenterTransition);
- setExitTransition(exitTransition);
- setReturnTransition(returnTransition);
- setSharedElementEnterTransition(sharedElementEnter);
- setSharedElementReturnTransition(sharedElementReturn);
- enterTransition.addListener(mListener);
- sharedElementEnter.addListener(mListener);
- reenterTransition.addListener(mListener);
- exitTransition.addListener(mListener);
- returnTransition.addListener(mListener);
- sharedElementReturn.addListener(mListener);
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- checkGetActivity();
- checkState("onCreateView", CREATED);
- mOnCreateViewCalled = true;
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- void waitForTransition() throws InterruptedException {
- verify(mListener, CtsMockitoUtils.within(1000)).onTransitionEnd((Transition) any());
- reset(mListener);
- }
-
- void waitForNoTransition() throws InterruptedException {
- SystemClock.sleep(250);
- verify(mListener, never()).onTransitionStart((Transition) any());
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
new file mode 100644
index 0000000..bd20bd9
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/TransitionFragment.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app
+
+import android.os.SystemClock
+import android.transition.Transition
+import androidx.annotation.LayoutRes
+import androidx.fragment.test.R
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+/**
+ * A fragment that has transitions that can be tracked.
+ */
+open class TransitionFragment(
+ @LayoutRes contentLayoutId: Int = R.layout.strict_view_fragment
+) : StrictViewFragment(contentLayoutId) {
+ val enterTransition = TrackingVisibility()
+ val reenterTransition = TrackingVisibility()
+ val exitTransition = TrackingVisibility()
+ val returnTransition = TrackingVisibility()
+ val sharedElementEnter = TrackingTransition()
+ val sharedElementReturn = TrackingTransition()
+
+ private val listener = mock(Transition.TransitionListener::class.java)
+
+ init {
+ @Suppress("LeakingThis")
+ setEnterTransition(enterTransition)
+ @Suppress("LeakingThis")
+ setReenterTransition(reenterTransition)
+ @Suppress("LeakingThis")
+ setExitTransition(exitTransition)
+ @Suppress("LeakingThis")
+ setReturnTransition(returnTransition)
+ sharedElementEnterTransition = sharedElementEnter
+ sharedElementReturnTransition = sharedElementReturn
+ enterTransition.addListener(listener)
+ sharedElementEnter.addListener(listener)
+ reenterTransition.addListener(listener)
+ exitTransition.addListener(listener)
+ returnTransition.addListener(listener)
+ sharedElementReturn.addListener(listener)
+ }
+
+ internal fun waitForTransition() {
+ verify(
+ listener,
+ CtsMockitoUtils.within(1000)
+ ).onTransitionEnd(ArgumentMatchers.any())
+ reset(listener)
+ }
+
+ internal fun waitForNoTransition() {
+ SystemClock.sleep(250)
+ verify(
+ listener,
+ never()
+ ).onTransitionStart(ArgumentMatchers.any())
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java
deleted file mode 100644
index 09807d0..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static androidx.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Instrumentation;
-
-import androidx.fragment.app.test.TestViewModel;
-import androidx.fragment.app.test.ViewModelActivity;
-import androidx.fragment.app.test.ViewModelActivity.ViewModelFragment;
-import androidx.lifecycle.LifecycleObserver;
-import androidx.lifecycle.OnLifecycleEvent;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class ViewModelTest {
- private static final int TIMEOUT = 2; // secs
-
- @Rule
- public ActivityTestRule<ViewModelActivity> mActivityRule =
- new ActivityTestRule<>(ViewModelActivity.class);
-
- @Test(expected = IllegalStateException.class)
- @UiThreadTest
- public void testNotAttachedFragment() {
- // This is similar to calling getViewModelStore in Fragment's constructor
- new Fragment().getViewModelStore();
- }
-
- @Test
- public void testSameActivityViewModels() throws Throwable {
- final TestViewModel[] activityModel = new TestViewModel[1];
- final TestViewModel[] defaultActivityModel = new TestViewModel[1];
- final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
- viewModelActivity[0] = mActivityRule.getActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- activityModel[0] = viewModelActivity[0].activityModel;
- defaultActivityModel[0] = viewModelActivity[0].defaultActivityModel;
- assertThat(defaultActivityModel[0], not(is(activityModel[0])));
-
- ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_1);
- ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_2);
- assertThat(fragment1, notNullValue());
- assertThat(fragment2, notNullValue());
-
- assertThat(fragment1.activityModel, is(activityModel[0]));
- assertThat(fragment2.activityModel, is(activityModel[0]));
-
- assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
- assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
- }
- });
- viewModelActivity[0] = recreateActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- assertThat(viewModelActivity[0].activityModel, is(activityModel[0]));
- assertThat(viewModelActivity[0].defaultActivityModel,
- is(defaultActivityModel[0]));
-
- ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_1);
- ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_2);
- assertThat(fragment1, notNullValue());
- assertThat(fragment2, notNullValue());
-
- assertThat(fragment1.activityModel, is(activityModel[0]));
- assertThat(fragment2.activityModel, is(activityModel[0]));
-
- assertThat(fragment1.defaultActivityModel, is(defaultActivityModel[0]));
- assertThat(fragment2.defaultActivityModel, is(defaultActivityModel[0]));
- }
- });
- }
-
- @Test
- public void testSameFragmentViewModels() throws Throwable {
- final TestViewModel[] fragment1Model = new TestViewModel[1];
- final TestViewModel[] fragment2Model = new TestViewModel[1];
- final ViewModelActivity[] viewModelActivity = new ViewModelActivity[1];
- viewModelActivity[0] = mActivityRule.getActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_1);
- ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_2);
- assertThat(fragment1, notNullValue());
- assertThat(fragment2, notNullValue());
-
- assertThat(fragment1.fragmentModel, not(is(fragment2.fragmentModel)));
- fragment1Model[0] = fragment1.fragmentModel;
- fragment2Model[0] = fragment2.fragmentModel;
- }
- });
- viewModelActivity[0] = recreateActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ViewModelFragment fragment1 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_1);
- ViewModelFragment fragment2 = getFragment(viewModelActivity[0],
- ViewModelActivity.FRAGMENT_TAG_2);
- assertThat(fragment1, notNullValue());
- assertThat(fragment2, notNullValue());
-
- assertThat(fragment1.fragmentModel, is(fragment1Model[0]));
- assertThat(fragment2.fragmentModel, is(fragment2Model[0]));
- }
- });
- }
-
- @Test
- public void testFragmentOnClearedWhenFinished() throws Throwable {
- final ViewModelActivity activity = mActivityRule.getActivity();
- final ViewModelFragment fragment = getFragment(activity,
- ViewModelActivity.FRAGMENT_TAG_1);
- final CountDownLatch latch = new CountDownLatch(1);
- final LifecycleObserver observer = new LifecycleObserver() {
- @SuppressWarnings("unused")
- @OnLifecycleEvent(ON_DESTROY)
- void onDestroy() {
- activity.getWindow().getDecorView().post(new Runnable() {
- @Override
- public void run() {
- try {
- assertThat(fragment.fragmentModel.mCleared, is(true));
- } finally {
- latch.countDown();
- }
- }
- });
- }
- };
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- activity.getLifecycle().addObserver(observer);
- }
- });
- activity.finish();
- assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
- }
-
- @Test
- public void testFragmentOnCleared() throws Throwable {
- final ViewModelActivity activity = mActivityRule.getActivity();
- final CountDownLatch latch = new CountDownLatch(1);
- final LifecycleObserver observer = new LifecycleObserver() {
- @SuppressWarnings("unused")
- @OnLifecycleEvent(ON_RESUME)
- void onResume() {
- try {
- final FragmentManager manager = activity.getSupportFragmentManager();
- Fragment fragment = new Fragment();
- manager.beginTransaction().add(fragment, "temp").commitNow();
- ViewModelProvider viewModelProvider = new ViewModelProvider(fragment,
- new ViewModelProvider.NewInstanceFactory());
- TestViewModel vm = viewModelProvider.get(TestViewModel.class);
- assertThat(vm.mCleared, is(false));
- manager.beginTransaction().remove(fragment).commitNow();
- assertThat(vm.mCleared, is(true));
- } finally {
- latch.countDown();
- }
- }
- };
-
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- activity.getLifecycle().addObserver(observer);
- }
- });
- assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS), is(true));
- }
-
- private ViewModelFragment getFragment(FragmentActivity activity, String tag) {
- return (ViewModelFragment) activity.getSupportFragmentManager()
- .findFragmentByTag(tag);
- }
-
- private ViewModelActivity recreateActivity() throws Throwable {
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- ViewModelActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
- final ViewModelActivity previous = mActivityRule.getActivity();
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- previous.recreate();
- }
- });
- ViewModelActivity result;
-
- // this guarantee that we will reinstall monitor between notifications about onDestroy
- // and onCreate
- //noinspection SynchronizationOnLocalVariableOrMethodParameter
- synchronized (monitor) {
- do {
- // the documentation says "Block until an Activity is created
- // that matches this monitor." This statement is true, but there are some other
- // true statements like: "Block until an Activity is destroyed" or
- // "Block until an Activity is resumed"...
-
- // this call will release synchronization monitor's monitor
- result = (ViewModelActivity) monitor.waitForActivityWithTimeout(4000);
- if (result == null) {
- throw new RuntimeException("Timeout. Failed to recreate an activity");
- }
- } while (result == previous);
- }
- return result;
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
new file mode 100644
index 0000000..e0205d42
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.app.Instrumentation
+import androidx.fragment.app.test.TestViewModel
+import androidx.fragment.app.test.ViewModelActivity
+import androidx.fragment.app.test.ViewModelActivity.ViewModelFragment
+import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
+import androidx.lifecycle.Lifecycle.Event.ON_RESUME
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import androidx.lifecycle.ViewModelProvider
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class ViewModelTest {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(ViewModelActivity::class.java)
+
+ @Test(expected = IllegalStateException::class)
+ @UiThreadTest
+ fun testNotAttachedFragment() {
+ // This is similar to calling getViewModelStore in Fragment's constructor
+ Fragment().viewModelStore
+ }
+
+ @Test
+ fun testSameActivityViewModels() {
+ var viewModelActivity = activityRule.activity
+ val activityModel = viewModelActivity.activityModel
+ val defaultActivityModel = viewModelActivity.defaultActivityModel
+ activityRule.runOnUiThread {
+ assertThat(defaultActivityModel).isNotSameAs(activityModel)
+
+ val fragment1 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_1)
+ val fragment2 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_2)
+ assertThat(fragment1).isNotNull()
+ assertThat(fragment2).isNotNull()
+
+ assertThat(fragment1.activityModel).isSameAs(activityModel)
+ assertThat(fragment2.activityModel).isSameAs(activityModel)
+
+ assertThat(fragment1.defaultActivityModel).isSameAs(defaultActivityModel)
+ assertThat(fragment2.defaultActivityModel).isSameAs(defaultActivityModel)
+ }
+ viewModelActivity = recreateActivity()
+ activityRule.runOnUiThread {
+ assertThat(viewModelActivity.activityModel).isSameAs(activityModel)
+ assertThat(viewModelActivity.defaultActivityModel).isSameAs(defaultActivityModel)
+
+ val fragment1 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_1)
+ val fragment2 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_2)
+ assertThat(fragment1).isNotNull()
+ assertThat(fragment2).isNotNull()
+
+ assertThat(fragment1.activityModel).isSameAs(activityModel)
+ assertThat(fragment2.activityModel).isSameAs(activityModel)
+
+ assertThat(fragment1.defaultActivityModel).isSameAs(defaultActivityModel)
+ assertThat(fragment2.defaultActivityModel).isSameAs(defaultActivityModel)
+ }
+ }
+
+ @Test
+ fun testSameFragmentViewModels() {
+ var viewModelActivity = activityRule.activity
+ lateinit var fragment1Model: TestViewModel
+ lateinit var fragment2Model: TestViewModel
+ activityRule.runOnUiThread {
+ val fragment1 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_1)
+ val fragment2 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_2)
+ assertThat(fragment1).isNotNull()
+ assertThat(fragment2).isNotNull()
+
+ assertThat(fragment1.fragmentModel).isNotSameAs(fragment2.fragmentModel)
+ fragment1Model = fragment1.fragmentModel
+ fragment2Model = fragment2.fragmentModel
+ }
+ viewModelActivity = recreateActivity()
+ activityRule.runOnUiThread {
+ val fragment1 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_1)
+ val fragment2 = getFragment(viewModelActivity, ViewModelActivity.FRAGMENT_TAG_2)
+ assertThat(fragment1).isNotNull()
+ assertThat(fragment2).isNotNull()
+
+ assertThat(fragment1.fragmentModel).isSameAs(fragment1Model)
+ assertThat(fragment2.fragmentModel).isSameAs(fragment2Model)
+ }
+ }
+
+ @Test
+ fun testFragmentOnClearedWhenFinished() {
+ val activity = activityRule.activity
+ val fragment = getFragment(activity, ViewModelActivity.FRAGMENT_TAG_1)
+ val latch = CountDownLatch(1)
+ val observer = object : LifecycleObserver {
+ @OnLifecycleEvent(ON_DESTROY)
+ fun onDestroy() {
+ activity.window.decorView.post {
+ try {
+ assertThat(fragment.fragmentModel.cleared).isTrue()
+ } finally {
+ latch.countDown()
+ }
+ }
+ }
+ }
+
+ activityRule.runOnUiThread { activity.lifecycle.addObserver(observer) }
+ activity.finish()
+ assertThat(latch.await(TIMEOUT.toLong(), TimeUnit.SECONDS)).isTrue()
+ }
+
+ @Test
+ fun testFragmentOnCleared() {
+ val activity = activityRule.activity
+ val latch = CountDownLatch(1)
+ val observer = object : LifecycleObserver {
+ @OnLifecycleEvent(ON_RESUME)
+ fun onResume() {
+ try {
+ val manager = activity.supportFragmentManager
+ val fragment = Fragment()
+ manager.beginTransaction().add(fragment, "temp").commitNow()
+ val viewModelProvider = ViewModelProvider(
+ fragment,
+ ViewModelProvider.NewInstanceFactory()
+ )
+ val vm = viewModelProvider.get(TestViewModel::class.java)
+ assertThat(vm.cleared).isFalse()
+ manager.beginTransaction().remove(fragment).commitNow()
+ assertThat(vm.cleared).isTrue()
+ } finally {
+ latch.countDown()
+ }
+ }
+ }
+
+ activityRule.runOnUiThread { activity.lifecycle.addObserver(observer) }
+ assertThat(latch.await(TIMEOUT.toLong(), TimeUnit.SECONDS)).isTrue()
+ }
+
+ private fun getFragment(activity: FragmentActivity, tag: String) =
+ activity.supportFragmentManager.findFragmentByTag(tag) as ViewModelFragment
+
+ private fun recreateActivity(): ViewModelActivity {
+ val monitor = Instrumentation.ActivityMonitor(
+ ViewModelActivity::class.java.canonicalName, null, false
+ )
+ val instrumentation = InstrumentationRegistry.getInstrumentation()
+ instrumentation.addMonitor(monitor)
+ val previous = activityRule.activity
+ activityRule.runOnUiThread { previous.recreate() }
+ var result: ViewModelActivity
+
+ // this guarantee that we will reinstall monitor between notifications about onDestroy
+ // and onCreate
+
+ synchronized(monitor) {
+ do {
+ // the documentation says "Block until an Activity is created
+ // that matches this monitor." This statement is true, but there are some other
+ // true statements like: "Block until an Activity is destroyed" or
+ // "Block until an Activity is resumed"...
+
+ // this call will release synchronization monitor's monitor
+ result = monitor.waitForActivityWithTimeout(4000) as ViewModelActivity
+ } while (result === previous)
+ }
+ return result
+ }
+
+ companion object {
+ private const val TIMEOUT = 2 // secs
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java
deleted file mode 100644
index 378dd87..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.test.EmptyFragmentTestActivity;
-import androidx.fragment.app.test.TestViewModel;
-import androidx.lifecycle.ViewModelProvider;
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ViewModelTestInTransaction {
-
- @Rule
- public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
- new ActivityTestRule<>(EmptyFragmentTestActivity.class);
-
- @Test
- @UiThreadTest
- public void testViewModelInTransactionActivity() {
- EmptyFragmentTestActivity activity = mActivityRule.getActivity();
- TestFragment fragment = new TestFragment();
- activity.getSupportFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
- ViewModelProvider viewModelProvider = new ViewModelProvider(activity,
- new ViewModelProvider.NewInstanceFactory());
- TestViewModel viewModel = viewModelProvider.get(TestViewModel.class);
- assertThat(viewModel, is(fragment.mViewModel));
- }
-
- @Test
- @UiThreadTest
- public void testViewModelInTransactionFragment() {
- EmptyFragmentTestActivity activity = mActivityRule.getActivity();
- ParentFragment parent = new ParentFragment();
- activity.getSupportFragmentManager().beginTransaction().add(parent, "parent").commitNow();
- assertThat(parent.mExecuted, is(true));
- }
-
-
- public static class ParentFragment extends Fragment {
-
- private boolean mExecuted;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- TestFragment fragment = new TestFragment();
- getChildFragmentManager().beginTransaction().add(fragment, "tag").commitNow();
- ViewModelProvider viewModelProvider = new ViewModelProvider(this,
- new ViewModelProvider.NewInstanceFactory());
- TestViewModel viewModel = viewModelProvider.get(TestViewModel.class);
- assertThat(viewModel, is(fragment.mViewModel));
- mExecuted = true;
- }
- }
-
- public static class TestFragment extends Fragment {
-
- TestViewModel mViewModel;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Fragment parentFragment = getParentFragment();
- ViewModelProvider provider = new ViewModelProvider(
- parentFragment != null ? parentFragment : requireActivity(),
- new ViewModelProvider.NewInstanceFactory());
- mViewModel = provider.get(TestViewModel.class);
- assertThat(mViewModel, notNullValue());
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt
new file mode 100644
index 0000000..7bf3aea
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTestInTransaction.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app
+
+import android.os.Bundle
+import androidx.fragment.app.test.EmptyFragmentTestActivity
+import androidx.fragment.app.test.TestViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ViewModelTestInTransaction {
+
+ @get:Rule
+ var activityRule = ActivityTestRule(EmptyFragmentTestActivity::class.java)
+
+ @Test
+ @UiThreadTest
+ fun testViewModelInTransactionActivity() {
+ val activity = activityRule.activity
+ val fragment = TestFragment()
+ activity.supportFragmentManager.beginTransaction().add(fragment, "tag").commitNow()
+ val viewModelProvider = ViewModelProvider(activity, ViewModelProvider.NewInstanceFactory())
+ val viewModel = viewModelProvider.get(TestViewModel::class.java)
+ assertThat(viewModel).isSameAs(fragment.viewModel)
+ }
+
+ @Test
+ @UiThreadTest
+ fun testViewModelInTransactionFragment() {
+ val activity = activityRule.activity
+ val parent = ParentFragment()
+ activity.supportFragmentManager.beginTransaction().add(parent, "parent").commitNow()
+ assertThat(parent.executed).isTrue()
+ }
+
+ class ParentFragment : Fragment() {
+
+ var executed: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val fragment = TestFragment()
+ childFragmentManager.beginTransaction().add(fragment, "tag").commitNow()
+ val viewModelProvider = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
+ val viewModel = viewModelProvider.get(TestViewModel::class.java)
+ assertThat(viewModel).isSameAs(fragment.viewModel)
+ executed = true
+ }
+ }
+
+ class TestFragment : Fragment() {
+
+ lateinit var viewModel: TestViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val parentFragment = parentFragment
+ val provider = ViewModelProvider(
+ (parentFragment ?: requireActivity()) as ViewModelStoreOwner,
+ ViewModelProvider.NewInstanceFactory()
+ )
+ viewModel = provider.get(TestViewModel::class.java)
+ assertThat(viewModel).isNotNull()
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java
deleted file mode 100644
index 0d7443d..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package androidx.fragment.app.test;
-
-import static org.junit.Assert.assertFalse;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
-import android.transition.Transition;
-import android.transition.Transition.TransitionListener;
-import android.transition.TransitionInflater;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.test.R;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple activity used for Fragment Transitions and lifecycle event ordering
- */
-@ContentView(R.layout.activity_content)
-public class FragmentTestActivity extends FragmentActivity {
- public final CountDownLatch onDestroyLatch = new CountDownLatch(1);
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- Intent intent = getIntent();
- if (intent != null && intent.getBooleanExtra("finishEarly", false)) {
- finish();
- getSupportFragmentManager().beginTransaction()
- .add(new AssertNotDestroyed(), "not destroyed")
- .commit();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- onDestroyLatch.countDown();
- }
-
- public static class TestFragment extends Fragment {
- public static final int ENTER = 0;
- public static final int RETURN = 1;
- public static final int EXIT = 2;
- public static final int REENTER = 3;
- public static final int SHARED_ELEMENT_ENTER = 4;
- public static final int SHARED_ELEMENT_RETURN = 5;
- private static final int TRANSITION_COUNT = 6;
-
- private static final String LAYOUT_ID = "layoutId";
- private static final String TRANSITION_KEY = "transition_";
- private int mLayoutId = R.layout.fragment_start;
- private final int[] mTransitionIds = new int[] {
- R.transition.fade,
- R.transition.fade,
- R.transition.fade,
- R.transition.fade,
- R.transition.change_bounds,
- R.transition.change_bounds,
- };
- private final Object[] mListeners = new Object[TRANSITION_COUNT];
-
- public TestFragment() {
- if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
- for (int i = 0; i < TRANSITION_COUNT; i++) {
- mListeners[i] = new TransitionCalledListener();
- }
- }
- }
-
- public static TestFragment create(int layoutId) {
- TestFragment testFragment = new TestFragment();
- testFragment.mLayoutId = layoutId;
- return testFragment;
- }
-
- public void clearTransitions() {
- for (int i = 0; i < TRANSITION_COUNT; i++) {
- mTransitionIds[i] = 0;
- }
- }
-
- public void clearNotifications() {
- for (int i = 0; i < TRANSITION_COUNT; i++) {
- ((TransitionCalledListener)mListeners[i]).startLatch = new CountDownLatch(1);
- ((TransitionCalledListener)mListeners[i]).endLatch = new CountDownLatch(1);
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null) {
- mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
- for (int i = 0; i < TRANSITION_COUNT; i++) {
- String key = TRANSITION_KEY + i;
- mTransitionIds[i] = savedInstanceState.getInt(key, mTransitionIds[i]);
- }
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(LAYOUT_ID, mLayoutId);
- for (int i = 0; i < TRANSITION_COUNT; i++) {
- String key = TRANSITION_KEY + i;
- outState.putInt(key, mTransitionIds[i]);
- }
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(mLayoutId, container, false);
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- if (VERSION.SDK_INT > VERSION_CODES.KITKAT) {
- setEnterTransition(loadTransition(ENTER));
- setReenterTransition(loadTransition(REENTER));
- setExitTransition(loadTransition(EXIT));
- setReturnTransition(loadTransition(RETURN));
- setSharedElementEnterTransition(loadTransition(SHARED_ELEMENT_ENTER));
- setSharedElementReturnTransition(loadTransition(SHARED_ELEMENT_RETURN));
- }
- }
-
- public boolean wasStartCalled(int transitionKey) {
- return ((TransitionCalledListener)mListeners[transitionKey]).startLatch.getCount() == 0;
- }
-
- public boolean wasEndCalled(int transitionKey) {
- return ((TransitionCalledListener)mListeners[transitionKey]).endLatch.getCount() == 0;
- }
-
- public boolean waitForStart(int transitionKey)
- throws InterruptedException {
- TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
- return l.startLatch.await(500,TimeUnit.MILLISECONDS);
- }
-
- public boolean waitForEnd(int transitionKey)
- throws InterruptedException {
- TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
- return l.endLatch.await(500,TimeUnit.MILLISECONDS);
- }
-
- private Transition loadTransition(int key) {
- final int id = mTransitionIds[key];
- if (id == 0) {
- return null;
- }
- Transition transition = TransitionInflater.from(getActivity()).inflateTransition(id);
- transition.addListener(((TransitionCalledListener)mListeners[key]));
- return transition;
- }
-
- private class TransitionCalledListener implements TransitionListener {
- public CountDownLatch startLatch = new CountDownLatch(1);
- public CountDownLatch endLatch = new CountDownLatch(1);
-
- public TransitionCalledListener() {
- }
-
- @Override
- public void onTransitionStart(Transition transition) {
- startLatch.countDown();
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- endLatch.countDown();
- }
-
- @Override
- public void onTransitionCancel(Transition transition) {
- }
-
- @Override
- public void onTransitionPause(Transition transition) {
- }
-
- @Override
- public void onTransitionResume(Transition transition) {
- }
- }
- }
-
- public static class ParentFragment extends Fragment {
- static final String CHILD_FRAGMENT_TAG = "childFragment";
- public boolean wasAttachedInTime;
-
- private boolean mRetainChild;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- ChildFragment f = getChildFragment();
- if (f == null) {
- f = new ChildFragment();
- if (mRetainChild) {
- f.setRetainInstance(true);
- }
- getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
- }
- wasAttachedInTime = f.attached;
- }
-
- public ChildFragment getChildFragment() {
- return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
- }
-
- public void setRetainChildInstance(boolean retainChild) {
- mRetainChild = retainChild;
- }
- }
-
- public static class ChildFragment extends Fragment {
- private OnAttachListener mOnAttachListener;
-
- public boolean attached;
- public boolean onActivityResultCalled;
- public int onActivityResultRequestCode;
- public int onActivityResultResultCode;
-
- @Override
- public void onAttach(Context activity) {
- super.onAttach(activity);
- attached = true;
- if (mOnAttachListener != null) {
- mOnAttachListener.onAttach(activity, this);
- }
- }
-
- public void setOnAttachListener(OnAttachListener listener) {
- mOnAttachListener = listener;
- }
-
- public interface OnAttachListener {
- void onAttach(Context activity, ChildFragment fragment);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- onActivityResultCalled = true;
- onActivityResultRequestCode = requestCode;
- onActivityResultResultCode = resultCode;
- }
- }
-
- public static class AssertNotDestroyed extends Fragment {
- @Override
- public void onActivityCreated(@Nullable Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
- assertFalse(getActivity().isDestroyed());
- }
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt
new file mode 100644
index 0000000..64dbc8d
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/FragmentTestActivity.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.fragment.app.test
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.test.R
+
+/**
+ * A simple activity used for Fragment Transitions and lifecycle event ordering
+ */
+class FragmentTestActivity : FragmentActivity(R.layout.activity_content) {
+
+ class ParentFragment : Fragment() {
+ var wasAttachedInTime: Boolean = false
+
+ var retainChildInstance: Boolean = false
+
+ val childFragment: ChildFragment
+ get() = childFragmentManager.findFragmentByTag(CHILD_FRAGMENT_TAG) as ChildFragment
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (childFragmentManager.findFragmentByTag(CHILD_FRAGMENT_TAG) == null) {
+ childFragmentManager.beginTransaction()
+ .add(ChildFragment().apply {
+ if (retainChildInstance) {
+ retainInstance = true
+ }
+ }, CHILD_FRAGMENT_TAG)
+ .commitNow()
+ }
+ wasAttachedInTime = childFragment.attached
+ }
+
+ companion object {
+ internal const val CHILD_FRAGMENT_TAG = "childFragment"
+ }
+ }
+
+ class ChildFragment : Fragment() {
+ var onAttachListener: (context: Context) -> Unit = {}
+
+ var attached: Boolean = false
+ var onActivityResultCalled: Boolean = false
+ var onActivityResultRequestCode: Int = 0
+ var onActivityResultResultCode: Int = 0
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ attached = true
+ onAttachListener.invoke(context)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ onActivityResultCalled = true
+ onActivityResultRequestCode = requestCode
+ onActivityResultResultCode = resultCode
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java
deleted file mode 100644
index 971314b..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.test.R;
-import androidx.testutils.RecreatedActivity;
-
-public class HangingFragmentActivity extends RecreatedActivity {
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(savedInstanceState == null ? R.layout.activity_inflated_fragment
- : R.layout.activity_content);
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt
new file mode 100644
index 0000000..39a4aff
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/HangingFragmentActivity.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import android.os.Bundle
+import androidx.fragment.test.R
+import androidx.testutils.RecreatedActivity
+
+class HangingFragmentActivity : RecreatedActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(
+ if (savedInstanceState == null)
+ R.layout.activity_inflated_fragment
+ else
+ R.layout.activity_content
+ )
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java
deleted file mode 100644
index 77f4145..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.ContentView;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.test.R;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.AsyncTaskLoader;
-import androidx.loader.content.Loader;
-import androidx.testutils.RecreatedActivity;
-
-@ContentView(R.layout.activity_loader)
-public class LoaderActivity extends RecreatedActivity
- implements LoaderManager.LoaderCallbacks<String> {
- private static final int TEXT_LOADER_ID = 14;
-
- public TextView textView;
- public TextView textViewB;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- textView = findViewById(R.id.textA);
- textViewB = findViewById(R.id.textB);
-
- if (savedInstanceState == null) {
- getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.fragmentContainer, new TextLoaderFragment())
- .commit();
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this);
- }
-
- @NonNull
- @Override
- public Loader<String> onCreateLoader(int id, @Nullable Bundle args) {
- return new TextLoader(this);
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<String> loader, String data) {
- textView.setText(data);
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<String> loader) {
- }
-
- static class TextLoader extends AsyncTaskLoader<String> {
- TextLoader(Context context) {
- super(context);
- }
-
- @Override
- protected void onStartLoading() {
- forceLoad();
- }
-
- @Override
- public String loadInBackground() {
- return "Loaded!";
- }
- }
-
- @ContentView(R.layout.fragment_c)
- public static class TextLoaderFragment extends Fragment
- implements LoaderManager.LoaderCallbacks<String> {
- public TextView textView;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this);
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- textView = view.findViewById(R.id.textC);
- }
-
- @NonNull
- @Override
- public Loader<String> onCreateLoader(int id, @Nullable Bundle args) {
- return new TextLoader(getContext());
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<String> loader, String data) {
- textView.setText(data);
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<String> loader) {
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt
new file mode 100644
index 0000000..9baf280
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/LoaderActivity.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.fragment.test.R
+import androidx.loader.app.LoaderManager
+import androidx.loader.content.AsyncTaskLoader
+import androidx.loader.content.Loader
+import androidx.testutils.RecreatedActivity
+
+class LoaderActivity : RecreatedActivity(R.layout.activity_loader),
+ LoaderManager.LoaderCallbacks<String> {
+
+ lateinit var textView: TextView
+ lateinit var textViewB: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ textView = findViewById(R.id.textA)
+ textViewB = findViewById(R.id.textB)
+
+ if (savedInstanceState == null) {
+ supportFragmentManager
+ .beginTransaction()
+ .add(R.id.fragmentContainer, TextLoaderFragment())
+ .commit()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this)
+ }
+
+ override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
+ return TextLoader(this)
+ }
+
+ override fun onLoadFinished(loader: Loader<String>, data: String) {
+ textView.text = data
+ }
+
+ override fun onLoaderReset(loader: Loader<String>) {
+ }
+
+ internal class TextLoader(context: Context) : AsyncTaskLoader<String>(context) {
+
+ override fun onStartLoading() {
+ forceLoad()
+ }
+
+ override fun loadInBackground(): String? {
+ return "Loaded!"
+ }
+ }
+
+ class TextLoaderFragment : Fragment(R.layout.fragment_c),
+ LoaderManager.LoaderCallbacks<String> {
+ lateinit var textView: TextView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ LoaderManager.getInstance(this).initLoader(TEXT_LOADER_ID, null, this)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ textView = view.findViewById(R.id.textC)
+ }
+
+ override fun onCreateLoader(id: Int, args: Bundle?): Loader<String> {
+ return TextLoader(requireContext())
+ }
+
+ override fun onLoadFinished(loader: Loader<String>, data: String) {
+ textView.text = data
+ }
+
+ override fun onLoaderReset(loader: Loader<String>) {}
+ }
+
+ companion object {
+ private const val TEXT_LOADER_ID = 14
+
+ val activity get() = RecreatedActivity.activity
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java
deleted file mode 100644
index 7cba3a7..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import androidx.fragment.app.Fragment;
-import androidx.testutils.RecreatedActivity;
-
-public class NonConfigOnStopActivity extends RecreatedActivity {
- @Override
- protected void onStop() {
- super.onStop();
-
- getSupportFragmentManager()
- .beginTransaction()
- .add(new RetainedFragment(), "1")
- .commitNowAllowingStateLoss();
- }
-
- public static class RetainedFragment extends Fragment {
- public RetainedFragment() {
- setRetainInstance(true);
- }
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt
new file mode 100644
index 0000000..2c011c6
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/NonConfigOnStopActivity.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import androidx.fragment.app.Fragment
+import androidx.testutils.RecreatedActivity
+
+class NonConfigOnStopActivity : RecreatedActivity() {
+ override fun onStop() {
+ super.onStop()
+
+ supportFragmentManager
+ .beginTransaction()
+ .add(RetainedFragment(), "1")
+ .commitNowAllowingStateLoss()
+ }
+
+ class RetainedFragment : Fragment() {
+ init {
+ retainInstance = true
+ }
+ }
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java b/fragment/src/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java
new file mode 100644
index 0000000..63092bc
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/OuterPackagePrivateFragment.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test;
+
+import androidx.fragment.app.Fragment;
+
+/**
+ * A class with a static PackagePrivate Fragment.
+ * Used for testing FragmentTransactionTest.
+ *
+ * Must be java, the concept of a static PackagePrivate class does not exist in Kotlin.
+ */
+public class OuterPackagePrivateFragment {
+ static class PackagePrivateFragment extends Fragment {}
+}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java b/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java
deleted file mode 100644
index 81433c7..0000000
--- a/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.fragment.app.test;
-
-import androidx.lifecycle.ViewModel;
-
-public class TestViewModel extends ViewModel {
- public boolean mCleared = false;
-
- @Override
- protected void onCleared() {
- mCleared = true;
- }
-}
diff --git a/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.kt b/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.kt
new file mode 100644
index 0000000..cbc4269
--- /dev/null
+++ b/fragment/src/androidTest/java/androidx/fragment/app/test/TestViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.test
+
+import androidx.lifecycle.ViewModel
+
+class TestViewModel : ViewModel() {
+ var cleared = false
+
+ override fun onCleared() {
+ cleared = true
+ }
+}
diff --git a/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index cccbffb..618c387 100644
--- a/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -422,7 +422,7 @@
+ name + "' has already been added to the transaction.");
} else if (mSharedElementSourceNames.contains(transitionName)) {
throw new IllegalArgumentException("A shared element with the source name '"
- + transitionName + " has already been added to the transaction.");
+ + transitionName + "' has already been added to the transaction.");
}
mSharedElementSourceNames.add(transitionName);
@@ -872,7 +872,7 @@
* @return the new oldPrimaryNav fragment after this record's ops would be popped
*/
Fragment trackAddedFragmentsInPop(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
- for (int opNum = 0; opNum < mOps.size(); opNum++) {
+ for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
switch (op.cmd) {
case OP_ADD:
diff --git a/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 22659ec..89cf832 100644
--- a/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -48,6 +48,7 @@
import androidx.annotation.CallSuper;
import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -72,7 +73,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
import java.util.UUID;
/**
@@ -250,8 +250,8 @@
SavedStateRegistryController mSavedStateRegistryController;
- // Cache the ContentView layoutIds for Fragments.
- private static final HashMap<Class, Integer> sAnnotationIds = new HashMap<>();
+ @LayoutRes
+ private int mContentLayoutId;
/**
* {@inheritDoc}
@@ -403,7 +403,7 @@
}
/**
- * Thrown by {@link FragmentFactory#instantiate(ClassLoader, String, Bundle)} when
+ * Thrown by {@link FragmentFactory#instantiate(ClassLoader, String)} when
* there is an instantiation failure.
*/
@SuppressWarnings("JavaLangClash")
@@ -414,13 +414,14 @@
}
/**
- * Default constructor. <strong>Every</strong> fragment must have an
- * empty constructor, so it can be instantiated when restoring its
- * activity's state. It is strongly recommended that subclasses do not
- * have other constructors with parameters, since these constructors
- * will not be called when the fragment is re-instantiated; instead,
- * arguments can be supplied by the caller with {@link #setArguments}
- * and later retrieved by the Fragment with {@link #getArguments}.
+ * Constructor used by the default {@link FragmentFactory}. You must
+ * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory}
+ * if you want to use a non-default constructor to ensure that your constructor
+ * is called when the fragment is re-instantiated.
+ *
+ * <p>It is strongly recommended to supply arguments with {@link #setArguments}
+ * and later retrieved by the Fragment with {@link #getArguments}. These arguments
+ * are automatically saved and restored alongside the Fragment.
*
* <p>Applications should generally not implement a constructor. Prefer
* {@link #onAttach(Context)} instead. It is the first place application code can run where
@@ -432,6 +433,19 @@
initLifecycle();
}
+ /**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ *
+ * @see #Fragment()
+ * @see #onCreateView(LayoutInflater, ViewGroup, Bundle)
+ */
+ @ContentView
+ public Fragment(@LayoutRes int contentLayoutId) {
+ this();
+ mContentLayoutId = contentLayoutId;
+ }
+
private void initLifecycle() {
mLifecycleRegistry = new LifecycleRegistry(this);
mSavedStateRegistryController = SavedStateRegistryController.create(this);
@@ -453,7 +467,7 @@
* Like {@link #instantiate(Context, String, Bundle)} but with a null
* argument Bundle.
* @deprecated Use {@link FragmentManager#getFragmentFactory()} and
- * {@link FragmentFactory#instantiate(ClassLoader, String, Bundle)}
+ * {@link FragmentFactory#instantiate(ClassLoader, String)}
*/
@SuppressWarnings("deprecation")
@Deprecated
@@ -477,7 +491,7 @@
* the given fragment class. This is a runtime exception; it is not
* normally expected to happen.
* @deprecated Use {@link FragmentManager#getFragmentFactory()} and
- * {@link FragmentFactory#instantiate(ClassLoader, String, Bundle)}, manually calling
+ * {@link FragmentFactory#instantiate(ClassLoader, String)}, manually calling
* {@link #setArguments(Bundle)} on the returned Fragment.
*/
@Deprecated
@@ -686,8 +700,10 @@
} else if (mFragmentManager != null && fragment.mFragmentManager != null) {
// Just save the reference to the Fragment
mTargetWho = fragment.mWho;
+ mTarget = null;
} else {
// Save the Fragment itself, waiting until we're attached
+ mTargetWho = null;
mTarget = fragment;
}
mTargetRequestCode = requestCode;
@@ -1614,9 +1630,8 @@
* Called to have the fragment instantiate its user interface view.
* This is optional, and non-graphical fragments can return null. This will be called between
* {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
- * <p>The default implementation looks for an {@link ContentView} annotation, inflating
- * and returning that layout. If the annotation is not found or has an invalid layout resource
- * id, this method returns null.
+ * <p>A default View can be returned by calling {@link #Fragment(int)} in your
+ * constructor. Otherwise, this method returns null.
*
* <p>It is recommended to <strong>only</strong> inflate the layout in this method and move
* logic that operates on the returned View to {@link #onViewCreated(View, Bundle)}.
@@ -1637,18 +1652,8 @@
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- Class<? extends Fragment> clazz = getClass();
- if (!sAnnotationIds.containsKey(clazz)) {
- ContentView annotation = clazz.getAnnotation(ContentView.class);
- if (annotation != null) {
- sAnnotationIds.put(clazz, annotation.value());
- } else {
- sAnnotationIds.put(clazz, null);
- }
- }
- Integer layoutId = sAnnotationIds.get(clazz);
- if (layoutId != null && layoutId != 0) {
- return inflater.inflate(layoutId, container, false);
+ if (mContentLayoutId != 0) {
+ return inflater.inflate(mContentLayoutId, container, false);
}
return null;
}
@@ -2088,7 +2093,7 @@
* @param transition The Transition to use to move Views out of the Scene when the Fragment
* is preparing to close. <code>transition</code> must be an
* {@link android.transition.Transition android.transition.Transition} or
- * {@link androidx.transition.Transition androidx.transition.Transition}.
+ * {@link androidx.transition.Transition}.
*/
public void setReturnTransition(@Nullable Object transition) {
ensureAnimationInfo().mReturnTransition = transition;
@@ -2493,7 +2498,7 @@
void instantiateChildFragmentManager() {
if (mHost == null) {
- throw new IllegalStateException("Fragment has not been attached yet.");
+ throw new IllegalStateException("Fragment " + this + " has not been attached yet.");
}
mChildFragmentManager = new FragmentManagerImpl();
mChildFragmentManager.attachController(mHost, new FragmentContainer() {
@@ -2501,7 +2506,7 @@
@Nullable
public View onFindViewById(int id) {
if (mView == null) {
- throw new IllegalStateException("Fragment does not have a view");
+ throw new IllegalStateException("Fragment " + this + " does not have a view");
}
return mView.findViewById(id);
}
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index 376c8d4..ace2868 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -37,6 +37,8 @@
import androidx.activity.ComponentActivity;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.CallSuper;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
@@ -107,17 +109,38 @@
// for startActivityForResult calls where a result has not yet been delivered.
SparseArrayCompat<String> mPendingFragmentActivityResults;
+ /**
+ * Default constructor for FragmentActivity. All Activities must have a default constructor
+ * for API 27 and lower devices or when using the default
+ * {@link android.app.AppComponentFactory}.
+ */
public FragmentActivity() {
super();
+ init();
+ }
+
+ /**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated as part of <code>super.onCreate(savedInstanceState)</code>.
+ *
+ * <p>This should generally be called from your constructor that takes no parameters,
+ * as is required for API 27 and lower or when using the default
+ * {@link android.app.AppComponentFactory}.
+ *
+ * @see #FragmentActivity()
+ */
+ @ContentView
+ public FragmentActivity(@LayoutRes int contentLayoutId) {
+ super(contentLayoutId);
+ init();
+ }
+
+ private void init() {
// Route onBackPressed() callbacks to the FragmentManager
- addOnBackPressedCallback(new OnBackPressedCallback() {
+ getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback() {
@Override
public boolean handleOnBackPressed() {
FragmentManager fragmentManager = mFragments.getSupportFragmentManager();
- if (fragmentManager.isStateSaved()) {
- // Cannot pop after state is saved
- return false;
- }
return fragmentManager.popBackStackImmediate();
}
});
@@ -131,6 +154,7 @@
* Dispatch incoming result to the correct fragment.
*/
@Override
+ @CallSuper
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
mFragments.noteStateNotSaved();
int requestIndex = requestCode>>16;
@@ -256,14 +280,6 @@
}
/**
- * Returns the context to be used for inflating any fragment view hierarchies.
- */
- @NonNull
- public Context getThemedContext() {
- return this;
- }
-
- /**
* Perform initialization of all fragments.
*/
@SuppressWarnings("deprecation")
@@ -271,8 +287,6 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
- super.onCreate(savedInstanceState);
-
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreSaveState(p);
@@ -300,6 +314,8 @@
mNextCandidateRequestIndex = 0;
}
+ super.onCreate(savedInstanceState);
+
mFragments.dispatchCreate();
}
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentController.java b/fragment/src/main/java/androidx/fragment/app/FragmentController.java
index 2086392..5b5e07f 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentController.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentController.java
@@ -312,13 +312,13 @@
* Moves Fragments managed by the controller's FragmentManager
* into the destroy state.
* <p>
- * If the {@link FragmentHostCallback} is an instance of {@link ViewModelStoreOwner},
+ * If the {@link androidx.fragment.app.FragmentHostCallback} is an instance of {@link ViewModelStoreOwner},
* then retained Fragments and any other non configuration state such as any
* {@link androidx.lifecycle.ViewModel} attached to Fragments will only be destroyed if
* {@link androidx.lifecycle.ViewModelStore#clear()} is called prior to this method.
* <p>
* Otherwise, the FragmentManager will look to see if the
- * {@link FragmentHostCallback#getContext() host's Context} is an {@link Activity}
+ * {@link FragmentHostCallback host's} Context is an {@link Activity}
* and if {@link Activity#isChangingConfigurations()} returns true. In only that case
* will non configuration state be retained.
* <p>Call when Fragments should be destroyed.
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentFactory.java b/fragment/src/main/java/androidx/fragment/app/FragmentFactory.java
index e3044cd..d92f403 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentFactory.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentFactory.java
@@ -115,10 +115,29 @@
* @throws InstantiationException If there is a failure in instantiating
* the given fragment class. This is a runtime exception; it is not
* normally expected to happen.
+ * @deprecated Use {@link #instantiate(ClassLoader, String)}.
*/
+ @Deprecated
@NonNull
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className,
- @SuppressWarnings("unused") @Nullable Bundle args) {
+ @SuppressWarnings("unused") @Nullable Bundle args) {
+ return instantiate(classLoader, className);
+ }
+
+ /**
+ * Create a new instance of a Fragment with the given class name. This uses
+ * {@link #loadFragmentClass(ClassLoader, String)} and the empty
+ * constructor of the resulting Class by default.
+ *
+ * @param classLoader The default classloader to use for instantiation
+ * @param className The class name of the fragment to instantiate.
+ * @return Returns a new fragment instance.
+ * @throws Fragment.InstantiationException If there is a failure in instantiating
+ * the given fragment class. This is a runtime exception; it is not
+ * normally expected to happen.
+ */
+ @NonNull
+ public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
try {
Class<? extends Fragment> cls = loadFragmentClass(classLoader, className);
return cls.getConstructor().newInstance();
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
index b4b8ea1..e5edd2c 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
@@ -2776,8 +2776,8 @@
@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader,
- @NonNull String className, @Nullable Bundle args) {
- return mHost.instantiate(mHost.getContext(), className, args);
+ @NonNull String className) {
+ return mHost.instantiate(mHost.getContext(), className, null);
}
});
}
@@ -3103,7 +3103,7 @@
+ Integer.toHexString(id) + " fname=" + fname
+ " existing=" + fragment);
if (fragment == null) {
- fragment = getFragmentFactory().instantiate(context.getClassLoader(), fname, null);
+ fragment = getFragmentFactory().instantiate(context.getClassLoader(), fname);
fragment.mFromLayout = true;
fragment.mFragmentId = id != 0 ? id : containerId;
fragment.mContainerId = containerId;
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentState.java b/fragment/src/main/java/androidx/fragment/app/FragmentState.java
index e63a4de..8644d3e 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentState.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentState.java
@@ -75,7 +75,7 @@
mArguments.setClassLoader(classLoader);
}
- mInstance = factory.instantiate(classLoader, mClassName, mArguments);
+ mInstance = factory.instantiate(classLoader, mClassName);
mInstance.setArguments(mArguments);
if (mSavedFragmentState != null) {
diff --git a/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java b/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
index 72b7a20..f006b70 100644
--- a/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
+++ b/fragment/src/main/java/androidx/fragment/app/FragmentTabHost.java
@@ -41,16 +41,10 @@
* the hierarchy you must call {@link #setup(Context, FragmentManager, int)}
* to complete the initialization of the tab host.
*
- * <p>Here is a simple example of using a FragmentTabHost in an Activity:
- *
- * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
- * complete}
- *
- * <p>This can also be used inside of a fragment through fragment nesting:
- *
- * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
- * complete}
+ * @deprecated Use <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
+@Deprecated
public class FragmentTabHost extends TabHost
implements TabHost.OnTabChangeListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<>();
@@ -131,6 +125,12 @@
};
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
public FragmentTabHost(@NonNull Context context) {
// Note that we call through to the version that takes an AttributeSet,
// because the simple Context construct can result in a broken object!
@@ -138,6 +138,12 @@
initFragmentTabHost(context, null);
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
public FragmentTabHost(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initFragmentTabHost(context, attrs);
@@ -181,9 +187,9 @@
}
/**
- * @deprecated Don't call the original TabHost setup, you must instead
- * call {@link #setup(Context, FragmentManager)} or
- * {@link #setup(Context, FragmentManager, int)}.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Override @Deprecated
public void setup() {
@@ -193,7 +199,12 @@
/**
* Set up the FragmentTabHost to use the given FragmentManager
+ *
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
+ @Deprecated
public void setup(@NonNull Context context, @NonNull FragmentManager manager) {
ensureHierarchy(context); // Ensure views required by super.setup()
super.setup();
@@ -204,7 +215,12 @@
/**
* Set up the FragmentTabHost to use the given FragmentManager
+ *
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
+ @Deprecated
public void setup(@NonNull Context context, @NonNull FragmentManager manager,
int containerId) {
ensureHierarchy(context); // Ensure views required by super.setup()
@@ -232,11 +248,23 @@
}
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
public void setOnTabChangedListener(@Nullable OnTabChangeListener l) {
mOnTabChangeListener = l;
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class<?> clss,
@Nullable Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
@@ -260,6 +288,12 @@
addTab(tabSpec);
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -299,12 +333,24 @@
}
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
@NonNull
protected Parcelable onSaveInstanceState() {
@@ -314,6 +360,12 @@
return ss;
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
protected void onRestoreInstanceState(@SuppressLint("UnknownNullness") Parcelable state) {
if (!(state instanceof SavedState)) {
@@ -325,6 +377,12 @@
setCurrentTabByTag(ss.curTab);
}
+ /**
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
+ */
+ @Deprecated
@Override
public void onTabChanged(@Nullable String tabId) {
if (mAttached) {
@@ -356,7 +414,7 @@
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = mFragmentManager.getFragmentFactory().instantiate(
- mContext.getClassLoader(), newTab.clss.getName(), newTab.args);
+ mContext.getClassLoader(), newTab.clss.getName());
newTab.fragment.setArguments(newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
diff --git a/fragment/testing/api/1.1.0-alpha06.txt b/fragment/testing/api/1.1.0-alpha06.txt
new file mode 100644
index 0000000..0a2c6be
--- /dev/null
+++ b/fragment/testing/api/1.1.0-alpha06.txt
@@ -0,0 +1,31 @@
+// Signature format: 3.0
+package androidx.fragment.app.testing {
+
+ public final class FragmentScenario<F extends androidx.fragment.app.Fragment> {
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, @StyleRes int, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, @StyleRes int, androidx.fragment.app.FragmentFactory?);
+ method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State);
+ method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F>);
+ method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
+ }
+
+ public static interface FragmentScenario.FragmentAction<F extends androidx.fragment.app.Fragment> {
+ method public void perform(F);
+ }
+
+ public final class FragmentScenarioKt {
+ ctor public FragmentScenarioKt();
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function0<? extends F>! instantiate);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function0<? extends F>! instantiate);
+ }
+
+}
+
diff --git a/fragment/testing/api/current.txt b/fragment/testing/api/current.txt
index 6374c9f..0a2c6be 100644
--- a/fragment/testing/api/current.txt
+++ b/fragment/testing/api/current.txt
@@ -5,11 +5,11 @@
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>);
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?);
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, int, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launch(Class<F>, android.os.Bundle?, @StyleRes int, androidx.fragment.app.FragmentFactory?);
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>);
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?);
method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, androidx.fragment.app.FragmentFactory?);
- method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, int, androidx.fragment.app.FragmentFactory?);
+ method public static <F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F> launchInContainer(Class<F>, android.os.Bundle?, @StyleRes int, androidx.fragment.app.FragmentFactory?);
method public androidx.fragment.app.testing.FragmentScenario<F> moveToState(androidx.lifecycle.Lifecycle.State);
method public androidx.fragment.app.testing.FragmentScenario<F> onFragment(androidx.fragment.app.testing.FragmentScenario.FragmentAction<F>);
method public androidx.fragment.app.testing.FragmentScenario<F> recreate();
@@ -21,10 +21,10 @@
public final class FragmentScenarioKt {
ctor public FragmentScenarioKt();
- method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
- method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function1<? super android.os.Bundle,? extends F>! instantiate);
- method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
- method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function1<? super android.os.Bundle,? extends F>! instantiate);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragment(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function0<? extends F>! instantiate);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, androidx.fragment.app.FragmentFactory! factory = null);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.testing.FragmentScenario<F>! launchFragmentInContainer(android.os.Bundle! fragmentArgs = null, @StyleRes int themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, kotlin.jvm.functions.Function0<? extends F>! instantiate);
}
}
diff --git a/fragment/testing/api/res-1.1.0-alpha06.txt b/fragment/testing/api/res-1.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fragment/testing/api/res-1.1.0-alpha06.txt
diff --git a/fragment/testing/api/restricted_1.1.0-alpha06.txt b/fragment/testing/api/restricted_1.1.0-alpha06.txt
new file mode 100644
index 0000000..6415760
--- /dev/null
+++ b/fragment/testing/api/restricted_1.1.0-alpha06.txt
@@ -0,0 +1,7 @@
+// Signature format: 3.0
+package androidx.fragment.app.testing {
+
+
+
+}
+
diff --git a/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioDialogFragmentTest.kt b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioDialogFragmentTest.kt
new file mode 100644
index 0000000..148cbd0
--- /dev/null
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioDialogFragmentTest.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.fragment.app.testing
+
+import androidx.core.os.BuildCompat.isAtLeastQ
+import androidx.lifecycle.Lifecycle.State
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for FragmentScenario's implementation against DialogFragment.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class FragmentScenarioDialogFragmentTest {
+ @Test
+ fun launchFragment() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ onView(withText("my button")).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun launchFragmentInContainer() {
+ with(launchFragmentInContainer<SimpleDialogFragment>()) {
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ // We show SimpleDialogFragment in container so dialog is not created.
+ assertThat(fragment.dialog).isNull()
+ }
+ onView(withText("my button")).check(matches(isDisplayed()))
+ }
+ }
+
+ @Test
+ fun fromResumedToCreated() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.CREATED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.CREATED)
+ assertThat(fragment.dialog).isNotNull()
+ if (isAtLeastQ()) {
+ assertThat(fragment.requireDialog().isShowing).isFalse()
+ } else {
+ // Dialog#isShowing should've returned false but it returns true due to a
+ // bug which was fixed by aosp/637304.
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun fromResumedToStarted() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.STARTED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.STARTED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromResumedToResumed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.RESUMED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromResumedToDestroyed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.DESTROYED)
+ }
+ }
+
+ @Test
+ fun fromCreatedToCreated() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.CREATED)
+ moveToState(State.CREATED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.CREATED)
+ assertThat(fragment.dialog).isNotNull()
+ if (isAtLeastQ()) {
+ assertThat(fragment.requireDialog().isShowing).isFalse()
+ } else {
+ // Dialog#isShowing should've returned false but it returns true due to a
+ // bug which was fixed by aosp/637304.
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun fromCreatedToStarted() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.CREATED)
+ moveToState(State.STARTED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.STARTED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromCreatedToResumed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.CREATED)
+ moveToState(State.RESUMED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromCreatedToDestroyed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.CREATED)
+ moveToState(State.DESTROYED)
+ }
+ }
+
+ @Test
+ fun fromStartedToCreated() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.STARTED)
+ moveToState(State.CREATED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.CREATED)
+ assertThat(fragment.dialog).isNotNull()
+ if (isAtLeastQ()) {
+ assertThat(fragment.requireDialog().isShowing).isFalse()
+ } else {
+ // Dialog#isShowing should've returned false but it returns true due to a
+ // bug which was fixed by aosp/637304.
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun fromStartedToStarted() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.STARTED)
+ moveToState(State.STARTED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.STARTED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromStartedToResumed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.STARTED)
+ moveToState(State.RESUMED)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun fromStartedToDestroyed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.STARTED)
+ moveToState(State.DESTROYED)
+ }
+ }
+
+ @Test
+ fun fromDestroyedToDestroyed() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ moveToState(State.DESTROYED)
+ moveToState(State.DESTROYED)
+ }
+ }
+
+ @Test
+ fun recreateCreatedFragment() {
+ var numOfInstantiation = 0
+ with(launchFragment {
+ ++numOfInstantiation
+ SimpleDialogFragment()
+ }) {
+ assertThat(numOfInstantiation).isEqualTo(1)
+ moveToState(State.CREATED)
+ recreate()
+ assertThat(numOfInstantiation).isEqualTo(2)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.CREATED)
+ assertThat(fragment.dialog).isNotNull()
+ if (isAtLeastQ()) {
+ assertThat(fragment.requireDialog().isShowing).isFalse()
+ } else {
+ // Dialog#isShowing should've returned false but it returns true due to a
+ // bug which was fixed by aosp/637304.
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+ }
+
+ @Test
+ fun recreateStartedFragment() {
+ var numOfInstantiation = 0
+ with(launchFragment {
+ ++numOfInstantiation
+ SimpleDialogFragment()
+ }) {
+ assertThat(numOfInstantiation).isEqualTo(1)
+ moveToState(State.STARTED)
+ recreate()
+ assertThat(numOfInstantiation).isEqualTo(2)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.STARTED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun recreateResumedFragment() {
+ var numOfInstantiation = 0
+ with(launchFragment {
+ ++numOfInstantiation
+ SimpleDialogFragment()
+ }) {
+ assertThat(numOfInstantiation).isEqualTo(1)
+ recreate()
+ assertThat(numOfInstantiation).isEqualTo(2)
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun dismissDialog() {
+ with(launchFragment<SimpleDialogFragment>()) {
+ onFragment { fragment ->
+ assertThat(fragment.lifecycle.currentState).isEqualTo(State.RESUMED)
+ assertThat(fragment.dialog).isNotNull()
+ assertThat(fragment.requireDialog().isShowing).isTrue()
+ fragment.dismiss()
+ fragment.requireFragmentManager().executePendingTransactions()
+ assertThat(fragment.dialog).isNull()
+ }
+ }
+ onView(withText("my button")).check(doesNotExist())
+ }
+}
\ No newline at end of file
diff --git a/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.kt b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.kt
index b04ca7f..b6fff9a 100644
--- a/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.kt
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/FragmentScenarioTest.kt
@@ -41,11 +41,10 @@
private class NoDefaultConstructorFragmentFactory(val arg: String) : FragmentFactory() {
override fun instantiate(
classLoader: ClassLoader,
- className: String,
- args: Bundle?
+ className: String
) = when (className) {
NoDefaultConstructorFragment::class.java.name -> NoDefaultConstructorFragment(arg)
- else -> super.instantiate(classLoader, className, args)
+ else -> super.instantiate(classLoader, className)
}
}
diff --git a/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/SimpleDialogFragment.kt b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/SimpleDialogFragment.kt
new file mode 100644
index 0000000..f5bd569
--- /dev/null
+++ b/fragment/testing/src/androidTest/java/androidx/fragment/app/testing/SimpleDialogFragment.kt
@@ -0,0 +1,19 @@
+package androidx.fragment.app.testing
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.testing.test.R
+
+/**
+ * A dialog fragment with a button to demonstrate unit testing.
+ */
+class SimpleDialogFragment : DialogFragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? = inflater.inflate(R.layout.fragment_simple_dialog, container, false)
+}
\ No newline at end of file
diff --git a/fragment/testing/src/androidTest/res/layout/fragment_simple_dialog.xml b/fragment/testing/src/androidTest/res/layout/fragment_simple_dialog.xml
new file mode 100644
index 0000000..32118ab
--- /dev/null
+++ b/fragment/testing/src/androidTest/res/layout/fragment_simple_dialog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="my button"/>
+</FrameLayout>
diff --git a/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
index 473fa4a..74b5da5 100644
--- a/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
+++ b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.java
@@ -26,9 +26,11 @@
import android.content.Intent;
import android.os.Bundle;
+import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
+import androidx.annotation.StyleRes;
import androidx.core.util.Preconditions;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
@@ -192,7 +194,7 @@
@NonNull
public static <F extends Fragment> FragmentScenario<F> launch(
@NonNull Class<F> fragmentClass, @Nullable Bundle fragmentArgs,
- int themeResId, @Nullable FragmentFactory factory) {
+ @StyleRes int themeResId, @Nullable FragmentFactory factory) {
return internalLaunch(fragmentClass, fragmentArgs, themeResId, factory,
/*containerViewId=*/ 0);
}
@@ -242,9 +244,8 @@
public static <F extends Fragment> FragmentScenario<F> launchInContainer(
@NonNull Class<F> fragmentClass, @Nullable Bundle fragmentArgs,
@Nullable FragmentFactory factory) {
- return launchInContainer(
- fragmentClass, fragmentArgs, R.style.FragmentScenarioEmptyFragmentActivityTheme,
- factory);
+ return launchInContainer(fragmentClass, fragmentArgs,
+ R.style.FragmentScenarioEmptyFragmentActivityTheme, factory);
}
/**
@@ -262,18 +263,17 @@
@NonNull
public static <F extends Fragment> FragmentScenario<F> launchInContainer(
@NonNull Class<F> fragmentClass, @Nullable Bundle fragmentArgs,
- int themeResId, @Nullable FragmentFactory factory) {
+ @StyleRes int themeResId, @Nullable FragmentFactory factory) {
return internalLaunch(
- fragmentClass, fragmentArgs, themeResId, factory,
- /*containerViewId=*/ android.R.id.content);
+ fragmentClass, fragmentArgs, themeResId, factory, android.R.id.content);
}
@NonNull
@SuppressLint("RestrictedApi")
private static <F extends Fragment> FragmentScenario<F> internalLaunch(
@NonNull final Class<F> fragmentClass, final @Nullable Bundle fragmentArgs,
- final int themeResId, @Nullable final FragmentFactory factory,
- final int containerViewId) {
+ @StyleRes int themeResId, @Nullable final FragmentFactory factory,
+ @IdRes final int containerViewId) {
Intent startActivityIntent =
Intent.makeMainActivity(
new ComponentName(getApplicationContext(),
@@ -299,8 +299,7 @@
Fragment fragment = activity.getSupportFragmentManager()
.getFragmentFactory().instantiate(
Preconditions.checkNotNull(fragmentClass.getClassLoader()),
- fragmentClass.getName(),
- fragmentArgs);
+ fragmentClass.getName());
fragment.setArguments(fragmentArgs);
activity.getSupportFragmentManager()
.beginTransaction()
diff --git a/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
index 48da729..a4dd150 100644
--- a/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
+++ b/fragment/testing/src/main/java/androidx/fragment/app/testing/FragmentScenario.kt
@@ -17,6 +17,7 @@
package androidx.fragment.app.testing
import android.os.Bundle
+import androidx.annotation.StyleRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentFactory
@@ -34,7 +35,7 @@
*/
inline fun <reified F : Fragment> launchFragment(
fragmentArgs: Bundle? = null,
- themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
+ @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
factory: FragmentFactory? = null
) = FragmentScenario.launch(F::class.java, fragmentArgs, themeResId, factory)
@@ -50,16 +51,15 @@
*/
inline fun <reified F : Fragment> launchFragment(
fragmentArgs: Bundle? = null,
- themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
- crossinline instantiate: (args: Bundle?) -> F
+ @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
+ crossinline instantiate: () -> F
) = FragmentScenario.launch(F::class.java, fragmentArgs, themeResId, object : FragmentFactory() {
override fun instantiate(
classLoader: ClassLoader,
- className: String,
- args: Bundle?
+ className: String
) = when (className) {
- F::class.java.name -> instantiate(args)
- else -> super.instantiate(classLoader, className, args)
+ F::class.java.name -> instantiate()
+ else -> super.instantiate(classLoader, className)
}
})
@@ -76,7 +76,7 @@
*/
inline fun <reified F : Fragment> launchFragmentInContainer(
fragmentArgs: Bundle? = null,
- themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
+ @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
factory: FragmentFactory? = null
) = FragmentScenario.launchInContainer(F::class.java, fragmentArgs, themeResId, factory)
@@ -96,16 +96,15 @@
*/
inline fun <reified F : Fragment> launchFragmentInContainer(
fragmentArgs: Bundle? = null,
- themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
- crossinline instantiate: (args: Bundle?) -> F
+ @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
+ crossinline instantiate: () -> F
) = FragmentScenario.launchInContainer(F::class.java, fragmentArgs, themeResId,
object : FragmentFactory() {
override fun instantiate(
classLoader: ClassLoader,
- className: String,
- args: Bundle?
+ className: String
) = when (className) {
- F::class.java.name -> instantiate(args)
- else -> super.instantiate(classLoader, className, args)
+ F::class.java.name -> instantiate()
+ else -> super.instantiate(classLoader, className)
}
})
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index 4ce8b2a..fae5204 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -8,7 +8,7 @@
dependencies {
api(project(":vectordrawable"))
- implementation(project(":interpolator"))
+ implementation("androidx.interpolator:interpolator:1.0.0")
implementation(project(":collection"))
androidTestImplementation(TEST_EXT_JUNIT)
diff --git a/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java b/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
index daf6db4..428f088 100644
--- a/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
+++ b/heifwriter/src/androidTest/java/androidx/heifwriter/HeifWriterTest.java
@@ -30,6 +30,8 @@
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Rect;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
@@ -69,6 +71,8 @@
public class HeifWriterTest {
private static final String TAG = HeifWriterTest.class.getSimpleName();
+ private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+
@Rule
public GrantPermissionRule mRuntimePermissionRule1 =
GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE);
@@ -154,6 +158,8 @@
@Test
@LargeTest
public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, false);
doTestForVariousNumberImages(builder);
}
@@ -161,6 +167,8 @@
@Test
@LargeTest
public void testInputBuffer_Grid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, false);
doTestForVariousNumberImages(builder);
}
@@ -168,6 +176,8 @@
@Test
@LargeTest
public void testInputBuffer_NoGrid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, true);
doTestForVariousNumberImages(builder);
}
@@ -175,6 +185,8 @@
@Test
@LargeTest
public void testInputBuffer_Grid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, true);
doTestForVariousNumberImages(builder);
}
@@ -182,6 +194,8 @@
@Test
@LargeTest
public void testInputSurface_NoGrid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, false);
doTestForVariousNumberImages(builder);
}
@@ -189,6 +203,8 @@
@Test
@LargeTest
public void testInputSurface_Grid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, false);
doTestForVariousNumberImages(builder);
}
@@ -196,6 +212,8 @@
@Test
@LargeTest
public void testInputSurface_NoGrid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, true);
doTestForVariousNumberImages(builder);
}
@@ -203,6 +221,8 @@
@Test
@LargeTest
public void testInputSurface_Grid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, true);
doTestForVariousNumberImages(builder);
}
@@ -210,6 +230,8 @@
@Test
@LargeTest
public void testInputBitmap_NoGrid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, false);
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String inputPath = new File(Environment.getExternalStorageDirectory(),
@@ -221,6 +243,8 @@
@Test
@LargeTest
public void testInputBitmap_Grid_NoHandler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, false);
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String inputPath = new File(Environment.getExternalStorageDirectory(),
@@ -232,6 +256,8 @@
@Test
@LargeTest
public void testInputBitmap_NoGrid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, true);
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String inputPath = new File(Environment.getExternalStorageDirectory(),
@@ -243,6 +269,8 @@
@Test
@LargeTest
public void testInputBitmap_Grid_Handler() throws Throwable {
+ if (shouldSkip()) return;
+
TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, true);
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String inputPath = new File(Environment.getExternalStorageDirectory(),
@@ -283,6 +311,25 @@
return total;
}
+ private boolean shouldSkip() {
+ return !hasEncoderForMime(MediaFormat.MIMETYPE_VIDEO_HEVC)
+ && !hasEncoderForMime(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
+ }
+
+ private boolean hasEncoderForMime(String mime) {
+ for (MediaCodecInfo info : sMCL.getCodecInfos()) {
+ if (info.isEncoder()) {
+ for (String type : info.getSupportedTypes()) {
+ if (type.equalsIgnoreCase(mime)) {
+ Log.i(TAG, "found codec " + info.getName() + " for mime " + mime);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
private static class TestConfig {
final int mInputMode;
final boolean mUseGrid;
diff --git a/heifwriter/src/main/java/androidx/heifwriter/HeifWriter.java b/heifwriter/src/main/java/androidx/heifwriter/HeifWriter.java
index fe4d801..75f6854 100644
--- a/heifwriter/src/main/java/androidx/heifwriter/HeifWriter.java
+++ b/heifwriter/src/main/java/androidx/heifwriter/HeifWriter.java
@@ -117,6 +117,11 @@
* The input mode where the client renders the images to an input Surface
* created by the writer.
*
+ * The input surface operates in single buffer mode. As a result, for use case
+ * where camera directly outputs to the input surface, this mode will not work
+ * because camera framework requires multiple buffers to operate in a pipeline
+ * fashion.
+ *
* @see #getInputSurface()
*/
public static final int INPUT_MODE_SURFACE = 1;
diff --git a/heifwriter/src/main/java/androidx/heifwriter/Texture2dProgram.java b/heifwriter/src/main/java/androidx/heifwriter/Texture2dProgram.java
index 07b8e6a..6ba6f92 100644
--- a/heifwriter/src/main/java/androidx/heifwriter/Texture2dProgram.java
+++ b/heifwriter/src/main/java/androidx/heifwriter/Texture2dProgram.java
@@ -346,7 +346,10 @@
*/
public static void checkGlError(String op) {
int error = GLES20.glGetError();
- if (error != GLES20.GL_NO_ERROR) {
+ if (error == GLES20.GL_OUT_OF_MEMORY) {
+ Log.i(TAG, op + " GL_OUT_OF_MEMORY");
+ }
+ if (error != GLES20.GL_NO_ERROR && error != GLES20.GL_OUT_OF_MEMORY) {
String msg = op + ": glError 0x" + Integer.toHexString(error);
Log.e(TAG, msg);
throw new RuntimeException(msg);
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index c39f546..4a18f8d 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -1002,303 +1002,303 @@
],
"packageMap": [
{
- "from" = "android/support/exifinterface",
- "to" = "androidx/exifinterface"
+ "from" : "android/support/exifinterface",
+ "to" : "androidx/exifinterface"
},
{
- "from" = "android/support/heifwriter",
- "to" = "androidx/heifwriter"
+ "from" : "android/support/heifwriter",
+ "to" : "androidx/heifwriter"
},
{
- "from" = "android/support/graphics/drawable",
- "to" = "androidx/vectordrawable"
+ "from" : "android/support/graphics/drawable",
+ "to" : "androidx/vectordrawable"
},
{
- "from" = "android/support/graphics/drawable/animated",
- "to" = "androidx/vectordrawable"
+ "from" : "android/support/graphics/drawable/animated",
+ "to" : "androidx/vectordrawable"
},
{
- "from" = "android/support/media/tv",
- "to" = "androidx/tvprovider"
+ "from" : "android/support/media/tv",
+ "to" : "androidx/tvprovider"
},
{
- "from" = "android/support/textclassifier",
- "to" = "androidx/textclassifier"
+ "from" : "android/support/textclassifier",
+ "to" : "androidx/textclassifier"
},
{
- "from" = "androidx/recyclerview/selection",
- "to" = "androidx/recyclerview/selection"},
+ "from" : "androidx/recyclerview/selection",
+ "to" : "androidx/recyclerview/selection"},
{
- "from" = "android/support/v4",
- "to" = "androidx/legacy/v4"
+ "from" : "android/support/v4",
+ "to" : "androidx/legacy/v4"
},
{
- "from" = "android/support/print",
- "to" = "androidx/print"
+ "from" : "android/support/print",
+ "to" : "androidx/print"
},
{
- "from" = "android/support/documentfile",
- "to" = "androidx/documentfile"
+ "from" : "android/support/documentfile",
+ "to" : "androidx/documentfile"
},
{
- "from" = "android/support/coordinatorlayout",
- "to" = "androidx/coordinatorlayout"
+ "from" : "android/support/coordinatorlayout",
+ "to" : "androidx/coordinatorlayout"
},
{
- "from" = "android/support/swiperefreshlayout",
- "to" = "androidx/swiperefreshlayout"
+ "from" : "android/support/swiperefreshlayout",
+ "to" : "androidx/swiperefreshlayout"
},
{
- "from" = "android/support/slidingpanelayout",
- "to" = "androidx/slidingpanelayout"
+ "from" : "android/support/slidingpanelayout",
+ "to" : "androidx/slidingpanelayout"
},
{
- "from" = "android/support/asynclayoutinflater",
- "to" = "androidx/asynclayoutinflater"
+ "from" : "android/support/asynclayoutinflater",
+ "to" : "androidx/asynclayoutinflater"
},
{
- "from" = "android/support/interpolator",
- "to" = "androidx/interpolator"
+ "from" : "android/support/interpolator",
+ "to" : "androidx/interpolator"
},
{
- "from" = "android/support/v7/palette",
- "to" = "androidx/palette"
+ "from" : "android/support/v7/palette",
+ "to" : "androidx/palette"
},
{
- "from" = "android/support/v7/cardview",
- "to" = "androidx/cardview"
+ "from" : "android/support/v7/cardview",
+ "to" : "androidx/cardview"
},
{
- "from" = "android/support/customview",
- "to" = "androidx/customview"
+ "from" : "android/support/customview",
+ "to" : "androidx/customview"
},
{
- "from" = "android/support/loader",
- "to" = "androidx/loader"
+ "from" : "android/support/loader",
+ "to" : "androidx/loader"
},
{
- "from" = "android/support/cursoradapter",
- "to" = "androidx/cursoradapter"
+ "from" : "android/support/cursoradapter",
+ "to" : "androidx/cursoradapter"
},
{
- "from" = "android/support/v7/mediarouter",
- "to" = "androidx/mediarouter"
+ "from" : "android/support/v7/mediarouter",
+ "to" : "androidx/mediarouter"
},
{
- "from" = "android/support/v7/appcompat",
- "to" = "androidx/appcompat"
+ "from" : "android/support/v7/appcompat",
+ "to" : "androidx/appcompat"
},
{
- "from" = "android/support/v7/recyclerview",
- "to" = "androidx/recyclerview"
+ "from" : "android/support/v7/recyclerview",
+ "to" : "androidx/recyclerview"
},
{
- "from" = "android/support/v7/viewpager",
- "to" = "androidx/viewpager"
+ "from" : "android/support/v7/viewpager",
+ "to" : "androidx/viewpager"
},
{
- "from" = "android/support/percent",
- "to" = "androidx/percentlayout"
+ "from" : "android/support/percent",
+ "to" : "androidx/percentlayout"
},
{
- "from" = "android/support/v7/gridlayout",
- "to" = "androidx/gridlayout"
+ "from" : "android/support/v7/gridlayout",
+ "to" : "androidx/gridlayout"
},
{
- "from" = "android/support/v13",
- "to" = "androidx/legacy/v13"
+ "from" : "android/support/v13",
+ "to" : "androidx/legacy/v13"
},
{
- "from" = "android/support/v7/preference",
- "to" = "androidx/preference"
+ "from" : "android/support/v7/preference",
+ "to" : "androidx/preference"
},
{
- "from" = "android/support/v14/preference",
- "to" = "androidx/legacy/preference"
+ "from" : "android/support/v14/preference",
+ "to" : "androidx/legacy/preference"
},
{
- "from" = "android/support/v17/leanback",
- "to" = "androidx/leanback"
+ "from" : "android/support/v17/leanback",
+ "to" : "androidx/leanback"
},
{
- "from" = "android/support/v17/preference",
- "to" = "androidx/leanback/preference"
+ "from" : "android/support/v17/preference",
+ "to" : "androidx/leanback/preference"
},
{
- "from" = "android/support/compat",
- "to" = "androidx/core"
+ "from" : "android/support/compat",
+ "to" : "androidx/core"
},
{
- "from" = "android/support/mediacompat",
- "to" = "androidx/media"
+ "from" : "android/support/mediacompat",
+ "to" : "androidx/media"
},
{
- "from" = "android/support/media2",
- "to" = "androidx/media2"
+ "from" : "android/support/media2",
+ "to" : "androidx/media2"
},
{
- "from" = "androidx/media2/exoplayer/external",
- "to" = "androidx/media2/exoplayer/external"
+ "from" : "androidx/media2/exoplayer/external",
+ "to" : "androidx/media2/exoplayer/external"
},
{
- "from" = "android/support/fragment",
- "to" = "androidx/fragment"
+ "from" : "android/support/fragment",
+ "to" : "androidx/fragment"
},
{
- "from" = "android/support/coreutils",
- "to" = "androidx/legacy/coreutils"
+ "from" : "android/support/coreutils",
+ "to" : "androidx/legacy/coreutils"
},
{
- "from" = "android/support/dynamicanimation",
- "to" = "androidx/dynamicanimation"
+ "from" : "android/support/dynamicanimation",
+ "to" : "androidx/dynamicanimation"
},
{
- "from" = "android/support/customtabs",
- "to" = "androidx/browser"
+ "from" : "android/support/customtabs",
+ "to" : "androidx/browser"
},
{
- "from" = "android/support/coreui",
- "to" = "androidx/legacy/coreui"
+ "from" : "android/support/coreui",
+ "to" : "androidx/legacy/coreui"
},
{
- "from" = "android/support/content",
- "to" = "androidx/contentpager"
+ "from" : "android/support/content",
+ "to" : "androidx/contentpager"
},
{
- "from" = "android/support/transition",
- "to" = "androidx/transition"
+ "from" : "android/support/transition",
+ "to" : "androidx/transition"
},
{
- "from" = "android/support/recommendation",
- "to" = "androidx/recommendation"
+ "from" : "android/support/recommendation",
+ "to" : "androidx/recommendation"
},
{
- "from" = "android/support/drawerlayout",
- "to" = "androidx/drawerlayout"
+ "from" : "android/support/drawerlayout",
+ "to" : "androidx/drawerlayout"
},
{
- "from" = "android/support/wear",
- "to" = "androidx/wear"
+ "from" : "android/support/wear",
+ "to" : "androidx/wear"
},
{
- "from" = "android/support/design",
- "to" = "com/google/android/material"
+ "from" : "android/support/design",
+ "to" : "com/google/android/material"
},
{
- "from" = "android/support/text/emoji/appcompat",
- "to" = "androidx/emoji/appcompat"
+ "from" : "android/support/text/emoji/appcompat",
+ "to" : "androidx/emoji/appcompat"
},
{
- "from" = "android/support/text/emoji/bundled",
- "to" = "androidx/emoji/bundled"
+ "from" : "android/support/text/emoji/bundled",
+ "to" : "androidx/emoji/bundled"
},
{
- "from" = "android/support/text/emoji",
- "to" = "androidx/emoji"
+ "from" : "android/support/text/emoji",
+ "to" : "androidx/emoji"
},
{
- "from" = "androidx/text/emoji/bundled",
- "to" = "androidx/text/emoji/bundled"
+ "from" : "androidx/text/emoji/bundled",
+ "to" : "androidx/text/emoji/bundled"
},
{
- "from" = "android/support/localbroadcastmanager",
- "to" = "androidx/localbroadcastmanager"
+ "from" : "android/support/localbroadcastmanager",
+ "to" : "androidx/localbroadcastmanager"
},
{
- "from" = "androidx/text/emoji/bundled",
- "to" = "androidx/text/emoji/bundled"
+ "from" : "androidx/text/emoji/bundled",
+ "to" : "androidx/text/emoji/bundled"
},
{
- "from" = "androidx/webkit",
- "to" = "androidx/webkit"
+ "from" : "androidx/webkit",
+ "to" : "androidx/webkit"
},
{
- "from" = "androidx/versionedparcelable",
- "to" = "androidx/versionedparcelable"
+ "from" : "androidx/versionedparcelable",
+ "to" : "androidx/versionedparcelable"
},
{
- "from" = "androidx/slice/view",
- "to" = "androidx/slice/view"
+ "from" : "androidx/slice/view",
+ "to" : "androidx/slice/view"
},
{
- "from" = "androidx/slice/core",
- "to" = "androidx/slice/core"
+ "from" : "androidx/slice/core",
+ "to" : "androidx/slice/core"
},
{
- "from" = "androidx/slice/builders",
- "to" = "androidx/slice/builders"
+ "from" : "androidx/slice/builders",
+ "to" : "androidx/slice/builders"
},
{
- "from" = "android/arch/paging/runtime",
- "to" = "androidx/paging/runtime"
+ "from" : "android/arch/paging/runtime",
+ "to" : "androidx/paging/runtime"
},
{
- "from" = "android/arch/core/testing",
- "to" = "androidx/arch/core/testing"
+ "from" : "android/arch/core/testing",
+ "to" : "androidx/arch/core/testing"
},
{
- "from" = "android/arch/core",
- "to" = "androidx/arch/core"
+ "from" : "android/arch/core",
+ "to" : "androidx/arch/core"
},
{
- "from" = "android/arch/persistence/db/framework",
- "to" = "androidx/sqlite/db/framework"
+ "from" : "android/arch/persistence/db/framework",
+ "to" : "androidx/sqlite/db/framework"
},
{
- "from" = "android/arch/persistence/db",
- "to" = "androidx/sqlite/db"
+ "from" : "android/arch/persistence/db",
+ "to" : "androidx/sqlite/db"
},
{
- "from" = "android/arch/persistence/room/rxjava2",
- "to" = "androidx/room/rxjava2"
+ "from" : "android/arch/persistence/room/rxjava2",
+ "to" : "androidx/room/rxjava2"
},
{
- "from" = "android/arch/persistence/room/guava",
- "to" = "androidx/room/guava"
+ "from" : "android/arch/persistence/room/guava",
+ "to" : "androidx/room/guava"
},
{
- "from" = "android/arch/persistence/room/testing",
- "to" = "androidx/room/testing"
+ "from" : "android/arch/persistence/room/testing",
+ "to" : "androidx/room/testing"
},
{
- "from" = "android/arch/persistence/room",
- "to" = "androidx/room"
+ "from" : "android/arch/persistence/room",
+ "to" : "androidx/room"
},
{
- "from" = "android/arch/lifecycle/extensions",
- "to" = "androidx/lifecycle/extensions"
+ "from" : "android/arch/lifecycle/extensions",
+ "to" : "androidx/lifecycle/extensions"
},
{
- "from" = "android/arch/lifecycle/livedata/core",
- "to" = "androidx/lifecycle/livedata/core"
+ "from" : "android/arch/lifecycle/livedata/core",
+ "to" : "androidx/lifecycle/livedata/core"
},
{
- "from" = "android/arch/lifecycle",
- "to" = "androidx/lifecycle"
+ "from" : "android/arch/lifecycle",
+ "to" : "androidx/lifecycle"
},
{
- "from" = "android/arch/lifecycle/viewmodel",
- "to" = "androidx/lifecycle/viewmodel"
+ "from" : "android/arch/lifecycle/viewmodel",
+ "to" : "androidx/lifecycle/viewmodel"
},
{
- "from" = "android/arch/lifecycle/livedata",
- "to" = "androidx/lifecycle/livedata"
+ "from" : "android/arch/lifecycle/livedata",
+ "to" : "androidx/lifecycle/livedata"
},
{
- "from" = "android/arch/lifecycle/reactivestreams",
- "to" = "androidx/lifecycle/reactivestreams"
+ "from" : "android/arch/lifecycle/reactivestreams",
+ "to" : "androidx/lifecycle/reactivestreams"
},
{
- "from" = "android/support/multidex/instrumentation",
- "to" = "androidx/multidex/instrumentation"
+ "from" : "android/support/multidex/instrumentation",
+ "to" : "androidx/multidex/instrumentation"
},
{
- "from" = "android/support/multidex",
- "to" = "androidx/multidex"
+ "from" : "android/support/multidex",
+ "to" : "androidx/multidex"
},
{
- "from" = "android/support/biometric",
- "to" = "androidx/biometric"
+ "from" : "android/support/biometric",
+ "to" : "androidx/biometric"
}
],
"pomRules": [
@@ -1534,14 +1534,38 @@
# "from": { "groupId": "android.arch.background.workmanager", "artifactId": "workmanager-firebase", "version": "{newArchVersion}" },
# "to": { "groupId": "androidx.work", "artifactId": "runtime-firebase", "version": "{newArchVersion}" }
#},
- #{
- # "from": { "groupId": "android.arch.navigation", "artifactId": "runtime", "version": "{newArchVersion}" },
- # "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime", "version": "{newArchVersion}" }
- #},
- #{
- # "from": { "groupId": "android.arch.navigation", "artifactId": "fragment", "version": "{newArchVersion}" },
- # "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment", "version": "{newArchVersion}" }
- #},
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "common", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-common", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "common-ktx", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-common-ktx", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "fragment", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "fragment-ktx", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-fragment-ktx", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "runtime", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "runtime-ktx", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-runtime-ktx", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "ui", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-ui", "version": "{newNavigationVersion}" }
+ },
+ {
+ "from": { "groupId": "android.arch.navigation", "artifactId": "ui-ktx", "version": "{oldNavigationVersion}" },
+ "to": { "groupId": "androidx.navigation", "artifactId": "navigation-ui-ktx", "version": "{newNavigationVersion}" }
+ },
{
"from": { "groupId": "android.arch.core", "artifactId": "common", "version": "1.1.1" },
"to": { "groupId": "androidx.arch.core", "artifactId": "core-common", "version": "{newArchCoreVersion}" }
@@ -1770,6 +1794,7 @@
"oldMedia2Version": "28.0.0-alpha03",
"oldExoplayerVersion": "28.0.0-alpha01",
"oldBiometricVersion": "28.0.0-alpha03",
+ "oldNavigationVersion": "1.0.0",
"newSlVersion": "1.0.0",
"newMaterialVersion": "1.0.0",
"newArchCoreVersion": "2.0.0",
@@ -1785,7 +1810,8 @@
"newMedia2Version": "1.0.0-alpha03",
"newExoplayerVersion": "1.0.0-alpha01",
"newBiometricVersion": "1.0.0-alpha03",
- "newDataBindingVersion": "undefined"
+ "newDataBindingVersion": "undefined",
+ "newNavigationVersion": "2.0.0"
}
},
# Manual fallback types map
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index 7c257f0..fd6d282 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -1928,6 +1928,102 @@
},
{
"from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "common",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-common",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "common-ktx",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-common-ktx",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "fragment",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-fragment",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "fragment-ktx",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-fragment-ktx",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "runtime",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-runtime",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "runtime-ktx",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-runtime-ktx",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "ui",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-ui",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
+ "groupId": "android.arch.navigation",
+ "artifactId": "ui-ktx",
+ "version": "{oldNavigationVersion}"
+ },
+ "to": {
+ "groupId": "androidx.navigation",
+ "artifactId": "navigation-ui-ktx",
+ "version": "{newNavigationVersion}"
+ }
+ },
+ {
+ "from": {
"groupId": "android.arch.core",
"artifactId": "common",
"version": "1.1.1"
@@ -2561,6 +2657,7 @@
"oldMedia2Version": "28.0.0-alpha03",
"oldExoplayerVersion": "28.0.0-alpha01",
"oldBiometricVersion": "28.0.0-alpha03",
+ "oldNavigationVersion": "1.0.0",
"newSlVersion": "1.0.0",
"newMaterialVersion": "1.0.0",
"newArchCoreVersion": "2.0.0",
@@ -2576,7 +2673,8 @@
"newMedia2Version": "1.0.0-alpha03",
"newExoplayerVersion": "1.0.0-alpha01",
"newBiometricVersion": "1.0.0-alpha03",
- "newDataBindingVersion": "undefined"
+ "newDataBindingVersion": "undefined",
+ "newNavigationVersion": "2.0.0"
}
},
"map": {
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 78126a0..4f785a3 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -52,10 +52,6 @@
"to": "androidx/media/R{0}"
},
{
- "from": "android/support/v7/cardview/R(.*)",
- "to": "androidx/cardview/R{0}"
- },
- {
"from": "android/support/coordinatorlayout/R(.*)",
"to": "androidx/coordinatorlayout/R{0}"
},
@@ -64,10 +60,6 @@
"to": "androidx/recyclerview/R{0}"
},
{
- "from": "android/support/v7/gridlayout/R(.*)",
- "to": "androidx/gridlayout/R{0}"
- },
- {
"from": "android/support/v7/appcompat/R(.*)",
"to": "androidx/appcompat/R{0}"
},
@@ -76,14 +68,6 @@
"to": "androidx/mediarouter/R{0}"
},
{
- "from": "android/support/v4/app/ActionBarDrawerToggle(.*)",
- "to": "androidx/legacy/app/ActionBarDrawerToggle{0}"
- },
- {
- "from": "android/support/v4/widget/Space(.*)",
- "to": "androidx/legacy/widget/Space{0}"
- },
- {
"from": "android/support/v4/content/WakefulBroadcastReceiver(.*)",
"to": "androidx/legacy/content/WakefulBroadcastReceiver{0}"
},
@@ -232,22 +216,6 @@
"to": "androidx/mediarouter/media/{0}"
},
{
- "from": "android/support/v7/widget/CardView(.*)",
- "to": "androidx/cardview/widget/CardView{0}"
- },
- {
- "from": "android/support/v7/widget/RoundRectDrawable(.*)",
- "to": "androidx/cardview/widget/RoundRectDrawable{0}"
- },
- {
- "from": "android/support/v7/widget/RoundRectDrawableWithShadow(.*)",
- "to": "androidx/cardview/widget/RoundRectDrawableWithShadow{0}"
- },
- {
- "from": "android/support/v7/widget/GridLayout(.*)",
- "to": "androidx/gridlayout/widget/GridLayout{0}"
- },
- {
"from": "android/support/v4/content/res/GrowingArrayUtils(.*)",
"to": "androidx/core/content/res/GrowingArrayUtils{0}"
},
@@ -272,26 +240,10 @@
"to": "androidx/vectordrawable/graphics/drawable/{0}"
},
{
- "from": "android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout(.*)",
- "to": "androidx/leanback/preference/internal/OutlineOnlyWithChildrenFrameLayout{0}"
- },
- {
- "from": "android/support/v17/preference/(.*)",
- "to": "androidx/leanback/preference/{0}"
- },
- {
- "from": "android/support/v17/leanback/(.*)",
- "to": "androidx/leanback/{0}"
- },
- {
"from": "android/support/annotation/(.*)",
"to": "androidx/annotation/{0}"
},
{
- "from": "android/support/transition/(.*)",
- "to": "androidx/transition/{0}"
- },
- {
"from": "android/support/v4/view/animation/PathInterpolatorApi14(.*)",
"to": "androidx/core/view/animation/PathInterpolatorApi14{0}"
},
@@ -452,10 +404,6 @@
"to": "androidx/customview/widget/ViewDragHelper{0}"
},
{
- "from": "android/support/v4/view/animation/(.*)",
- "to": "androidx/interpolator/view/animation/{0}"
- },
- {
"from": "android/support/v4/widget/DrawerLayout(.*)",
"to": "androidx/drawerlayout/widget/DrawerLayout{0}"
},
@@ -504,10 +452,6 @@
"to": "androidx/collection/SparseArray{0}"
},
{
- "from": "android/support/v4/content/LocalBroadcastManager(.*)",
- "to": "androidx/localbroadcastmanager/content/LocalBroadcastManager{0}"
- },
- {
"from": "android/support/v4/(.*)",
"to": "androidx/core/{0}"
},
@@ -706,10 +650,38 @@
],
"slRules": [
{
+ "from": "androidx/core/view/inputmethod/(.*)",
+ "to": "android/support/v13/view/inputmethod/{0}"
+ },
+ {
"from": "androidx/viewpager2/(.*)",
"to": "ignore"
},
{
+ "from": "androidx/legacy/app/ActionBarDrawerToggle(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/legacy/widget/Space(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/gridlayout/widget/GridLayout(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/gridlayout/R(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/localbroadcastmanager/content/LocalBroadcastManager(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/interpolator/view/animation/(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/sharetarget/(.*)",
"to": "ignore"
},
@@ -738,6 +710,10 @@
"to": "ignore"
},
{
+ "from": "androidx/transition/(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/emoji/R(.*)",
"to": "ignore"
},
@@ -758,6 +734,30 @@
"to": "ignore"
},
{
+ "from": "android/support/customtabs/ICustomTabsCallback(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "android/support/customtabs/ICustomTabsService(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "android/support/customtabs/IPostMessageService(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/browser/customtabs/(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/browser/R(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/localbroadcastmanager/content/LocalBroadcastManager(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/legacy/view/ViewCompat(.*)",
"to": "ignore"
},
@@ -766,6 +766,22 @@
"to": "ignore"
},
{
+ "from": "androidx/cardview/R(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/cardview/widget/CardView(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/cardview/widget/RoundRectDrawable(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/cardview/widget/RoundRectDrawableWithShadow(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/legacy/app/FragmentCompat(.*)",
"to": "ignore"
},
@@ -790,10 +806,6 @@
"to": "ignore"
},
{
- "from": "androidx/core/view/inputmethod/(.*)",
- "to": "ignore"
- },
- {
"from": "androidx/exifinterface/(.*)",
"to": "ignore"
},
@@ -810,6 +822,18 @@
"to": "ignore"
},
{
+ "from": "androidx/leanback/preference/internal/OutlineOnlyWithChildrenFrameLayout(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/leanback/preference/(.*)",
+ "to": "ignore"
+ },
+ {
+ "from": "androidx/leanback/(.*)",
+ "to": "ignore"
+ },
+ {
"from": "androidx/print/(.*)",
"to": "ignore"
},
@@ -826,7 +850,7 @@
"to": "ignore"
},
{
- "from": "androidx/browser/(.*)",
+ "from": "androidx/browser/browseractions/(.*)",
"to": "ignore"
},
{
@@ -976,7 +1000,7 @@
"to": "androidx/asynclayoutinflater"
},
{
- "from": "android/support/interpolator",
+ "from": "androidx/interpolator",
"to": "androidx/interpolator"
},
{
@@ -984,7 +1008,7 @@
"to": "androidx/palette"
},
{
- "from": "android/support/v7/cardview",
+ "from": "androidx/cardview",
"to": "androidx/cardview"
},
{
@@ -1024,7 +1048,7 @@
"to": "androidx/percentlayout"
},
{
- "from": "android/support/v7/gridlayout",
+ "from": "androidx/gridlayout",
"to": "androidx/gridlayout"
},
{
@@ -1040,11 +1064,11 @@
"to": "androidx/legacy/preference"
},
{
- "from": "android/support/v17/leanback",
+ "from": "androidx/leanback",
"to": "androidx/leanback"
},
{
- "from": "android/support/v17/preference",
+ "from": "androidx/leanback/preference",
"to": "androidx/leanback/preference"
},
{
@@ -1080,7 +1104,7 @@
"to": "androidx/browser"
},
{
- "from": "android/support/coreui",
+ "from": "androidx/legacy/coreui",
"to": "androidx/legacy/coreui"
},
{
@@ -1088,7 +1112,7 @@
"to": "androidx/contentpager"
},
{
- "from": "android/support/transition",
+ "from": "androidx/transition",
"to": "androidx/transition"
},
{
@@ -1124,7 +1148,7 @@
"to": "androidx/text/emoji/bundled"
},
{
- "from": "android/support/localbroadcastmanager",
+ "from": "androidx/localbroadcastmanager",
"to": "androidx/localbroadcastmanager"
},
{
@@ -1299,9 +1323,9 @@
},
{
"from": {
- "groupId": "com.android.support",
- "artifactId": "cardview-v7",
- "version": "{oldSlVersion}"
+ "groupId": "androidx.cardview",
+ "artifactId": "cardview",
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.cardview",
@@ -1347,9 +1371,9 @@
},
{
"from": {
- "groupId": "com.android.support",
- "artifactId": "gridlayout-v7",
- "version": "{oldSlVersion}"
+ "groupId": "androidx.gridlayout",
+ "artifactId": "gridlayout",
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.gridlayout",
@@ -1359,9 +1383,9 @@
},
{
"from": {
- "groupId": "com.android.support",
- "artifactId": "leanback-v17",
- "version": "{oldSlVersion}"
+ "groupId": "androidx.leanback",
+ "artifactId": "leanback",
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.leanback",
@@ -1431,9 +1455,9 @@
},
{
"from": {
- "groupId": "com.android.support",
- "artifactId": "preference-leanback-v17",
- "version": "{oldSlVersion}"
+ "groupId": "androidx.leanback",
+ "artifactId": "leanback-preference",
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.leanback",
@@ -1527,9 +1551,9 @@
},
{
"from": {
- "groupId": "com.android.support",
- "artifactId": "support-core-ui",
- "version": "{oldSlVersion}"
+ "groupId": "androidx.legacy",
+ "artifactId": "legacy-support-core-ui",
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.legacy",
@@ -1683,9 +1707,9 @@
},
{
"from": {
- "groupId": "com.android.support",
+ "groupId": "androidx.transition",
"artifactId": "transition",
- "version": "{oldSlVersion}"
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.transition",
@@ -1791,9 +1815,9 @@
},
{
"from": {
- "groupId": "com.android.support",
+ "groupId": "androidx.interpolator",
"artifactId": "interpolator",
- "version": "{oldSlVersion}"
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.interpolator",
@@ -1815,9 +1839,9 @@
},
{
"from": {
- "groupId": "com.android.support",
+ "groupId": "androidx.localbroadcastmanager",
"artifactId": "localbroadcastmanager",
- "version": "{oldSlVersion}"
+ "version": "{newSlVersion}"
},
"to": {
"groupId": "androidx.localbroadcastmanager",
@@ -3609,65 +3633,6 @@
"android/support/test/uiautomator/UiWatcher": "androidx/test/uiautomator/UiWatcher",
"android/support/test/uiautomator/Until": "androidx/test/uiautomator/Until",
"android/support/test/uiautomator/WaitMixin": "androidx/test/uiautomator/WaitMixin",
- "android/support/transition/AnimatorUtils": "androidx/transition/AnimatorUtils",
- "android/support/transition/ArcMotion": "androidx/transition/ArcMotion",
- "android/support/transition/AutoTransition": "androidx/transition/AutoTransition",
- "android/support/transition/ChangeBounds": "androidx/transition/ChangeBounds",
- "android/support/transition/ChangeClipBounds": "androidx/transition/ChangeClipBounds",
- "android/support/transition/ChangeImageTransform": "androidx/transition/ChangeImageTransform",
- "android/support/transition/ChangeScroll": "androidx/transition/ChangeScroll",
- "android/support/transition/ChangeTransform": "androidx/transition/ChangeTransform",
- "android/support/transition/CircularPropagation": "androidx/transition/CircularPropagation",
- "android/support/transition/Explode": "androidx/transition/Explode",
- "android/support/transition/Fade": "androidx/transition/Fade",
- "android/support/transition/FloatArrayEvaluator": "androidx/transition/FloatArrayEvaluator",
- "android/support/transition/FragmentTransitionSupport": "androidx/transition/FragmentTransitionSupport",
- "android/support/transition/GhostViewApi14": "androidx/transition/GhostViewApi14",
- "android/support/transition/GhostViewApi21": "androidx/transition/GhostViewApi21",
- "android/support/transition/GhostViewImpl": "androidx/transition/GhostViewImpl",
- "android/support/transition/GhostViewUtils": "androidx/transition/GhostViewUtils",
- "android/support/transition/ImageViewUtils": "androidx/transition/ImageViewUtils",
- "android/support/transition/MatrixUtils": "androidx/transition/MatrixUtils",
- "android/support/transition/ObjectAnimatorUtils": "androidx/transition/ObjectAnimatorUtils",
- "android/support/transition/PathMotion": "androidx/transition/PathMotion",
- "android/support/transition/PathProperty": "androidx/transition/PathProperty",
- "android/support/transition/PatternPathMotion": "androidx/transition/PatternPathMotion",
- "android/support/transition/PropertyValuesHolderUtils": "androidx/transition/PropertyValuesHolderUtils",
- "android/support/transition/R": "androidx/transition/R",
- "android/support/transition/RectEvaluator": "androidx/transition/RectEvaluator",
- "android/support/transition/Scene": "androidx/transition/Scene",
- "android/support/transition/SidePropagation": "androidx/transition/SidePropagation",
- "android/support/transition/Slide": "androidx/transition/Slide",
- "android/support/transition/Styleable": "androidx/transition/Styleable",
- "android/support/transition/Transition": "androidx/transition/Transition",
- "android/support/transition/TransitionInflater": "androidx/transition/TransitionInflater",
- "android/support/transition/TransitionListenerAdapter": "androidx/transition/TransitionListenerAdapter",
- "android/support/transition/TransitionManager": "androidx/transition/TransitionManager",
- "android/support/transition/TransitionPropagation": "androidx/transition/TransitionPropagation",
- "android/support/transition/TransitionSet": "androidx/transition/TransitionSet",
- "android/support/transition/TransitionUtils": "androidx/transition/TransitionUtils",
- "android/support/transition/TransitionValues": "androidx/transition/TransitionValues",
- "android/support/transition/TransitionValuesMaps": "androidx/transition/TransitionValuesMaps",
- "android/support/transition/TranslationAnimationCreator": "androidx/transition/TranslationAnimationCreator",
- "android/support/transition/ViewGroupOverlayApi14": "androidx/transition/ViewGroupOverlayApi14",
- "android/support/transition/ViewGroupOverlayApi18": "androidx/transition/ViewGroupOverlayApi18",
- "android/support/transition/ViewGroupOverlayImpl": "androidx/transition/ViewGroupOverlayImpl",
- "android/support/transition/ViewGroupUtils": "androidx/transition/ViewGroupUtils",
- "android/support/transition/ViewGroupUtilsApi14": "androidx/transition/ViewGroupUtilsApi14",
- "android/support/transition/ViewGroupUtilsApi18": "androidx/transition/ViewGroupUtilsApi18",
- "android/support/transition/ViewOverlayApi14": "androidx/transition/ViewOverlayApi14",
- "android/support/transition/ViewOverlayApi18": "androidx/transition/ViewOverlayApi18",
- "android/support/transition/ViewOverlayImpl": "androidx/transition/ViewOverlayImpl",
- "android/support/transition/ViewUtils": "androidx/transition/ViewUtils",
- "android/support/transition/ViewUtilsApi19": "androidx/transition/ViewUtilsApi19",
- "android/support/transition/ViewUtilsApi21": "androidx/transition/ViewUtilsApi21",
- "android/support/transition/ViewUtilsApi22": "androidx/transition/ViewUtilsApi22",
- "android/support/transition/ViewUtilsBase": "androidx/transition/ViewUtilsBase",
- "android/support/transition/Visibility": "androidx/transition/Visibility",
- "android/support/transition/VisibilityPropagation": "androidx/transition/VisibilityPropagation",
- "android/support/transition/WindowIdApi14": "androidx/transition/WindowIdApi14",
- "android/support/transition/WindowIdApi18": "androidx/transition/WindowIdApi18",
- "android/support/transition/WindowIdImpl": "androidx/transition/WindowIdImpl",
"android/support/v14/preference/EditTextPreferenceDialogFragment": "androidx/preference/EditTextPreferenceDialogFragment",
"android/support/v14/preference/ListPreferenceDialogFragment": "androidx/preference/ListPreferenceDialogFragment",
"android/support/v14/preference/MultiSelectListPreference": "androidx/preference/MultiSelectListPreference",
@@ -3675,240 +3640,7 @@
"android/support/v14/preference/PreferenceDialogFragment": "androidx/preference/PreferenceDialogFragment",
"android/support/v14/preference/PreferenceFragment": "androidx/preference/PreferenceFragment",
"android/support/v14/preference/SwitchPreference": "androidx/preference/SwitchPreference",
- "android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout": "androidx/leanback/preference/internal/OutlineOnlyWithChildrenFrameLayout",
- "android/support/v17/leanback/R": "androidx/leanback/R",
- "android/support/v17/leanback/animation/LogAccelerateInterpolator": "androidx/leanback/animation/LogAccelerateInterpolator",
- "android/support/v17/leanback/animation/LogDecelerateInterpolator": "androidx/leanback/animation/LogDecelerateInterpolator",
- "android/support/v17/leanback/app/BackgroundFragment": "androidx/leanback/app/BackgroundFragment",
- "android/support/v17/leanback/app/BackgroundManager": "androidx/leanback/app/BackgroundManager",
- "android/support/v17/leanback/app/BaseFragment": "androidx/leanback/app/BaseFragment",
- "android/support/v17/leanback/app/BaseRowFragment": "androidx/leanback/app/BaseRowFragment",
- "android/support/v17/leanback/app/BaseRowSupportFragment": "androidx/leanback/app/BaseRowSupportFragment",
- "android/support/v17/leanback/app/BaseSupportFragment": "androidx/leanback/app/BaseSupportFragment",
- "android/support/v17/leanback/app/BrandedFragment": "androidx/leanback/app/BrandedFragment",
- "android/support/v17/leanback/app/BrandedSupportFragment": "androidx/leanback/app/BrandedSupportFragment",
- "android/support/v17/leanback/app/BrowseFragment": "androidx/leanback/app/BrowseFragment",
- "android/support/v17/leanback/app/BrowseSupportFragment": "androidx/leanback/app/BrowseSupportFragment",
- "android/support/v17/leanback/app/DetailsBackgroundVideoHelper": "androidx/leanback/app/DetailsBackgroundVideoHelper",
- "android/support/v17/leanback/app/DetailsFragment": "androidx/leanback/app/DetailsFragment",
- "android/support/v17/leanback/app/DetailsFragmentBackgroundController": "androidx/leanback/app/DetailsFragmentBackgroundController",
- "android/support/v17/leanback/app/DetailsSupportFragment": "androidx/leanback/app/DetailsSupportFragment",
- "android/support/v17/leanback/app/DetailsSupportFragmentBackgroundController": "androidx/leanback/app/DetailsSupportFragmentBackgroundController",
- "android/support/v17/leanback/app/ErrorFragment": "androidx/leanback/app/ErrorFragment",
- "android/support/v17/leanback/app/ErrorSupportFragment": "androidx/leanback/app/ErrorSupportFragment",
- "android/support/v17/leanback/app/FragmentUtil": "androidx/leanback/app/FragmentUtil",
- "android/support/v17/leanback/app/GuidedStepFragment": "androidx/leanback/app/GuidedStepFragment",
- "android/support/v17/leanback/app/GuidedStepRootLayout": "androidx/leanback/app/GuidedStepRootLayout",
- "android/support/v17/leanback/app/GuidedStepSupportFragment": "androidx/leanback/app/GuidedStepSupportFragment",
- "android/support/v17/leanback/app/HeadersFragment": "androidx/leanback/app/HeadersFragment",
- "android/support/v17/leanback/app/HeadersSupportFragment": "androidx/leanback/app/HeadersSupportFragment",
- "android/support/v17/leanback/app/ListRowDataAdapter": "androidx/leanback/app/ListRowDataAdapter",
- "android/support/v17/leanback/app/OnboardingFragment": "androidx/leanback/app/OnboardingFragment",
- "android/support/v17/leanback/app/OnboardingSupportFragment": "androidx/leanback/app/OnboardingSupportFragment",
- "android/support/v17/leanback/app/PermissionHelper": "androidx/leanback/app/PermissionHelper",
- "android/support/v17/leanback/app/PlaybackFragment": "androidx/leanback/app/PlaybackFragment",
- "android/support/v17/leanback/app/PlaybackFragmentGlueHost": "androidx/leanback/app/PlaybackFragmentGlueHost",
- "android/support/v17/leanback/app/PlaybackSupportFragment": "androidx/leanback/app/PlaybackSupportFragment",
- "android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost": "androidx/leanback/app/PlaybackSupportFragmentGlueHost",
- "android/support/v17/leanback/app/ProgressBarManager": "androidx/leanback/app/ProgressBarManager",
- "android/support/v17/leanback/app/RowsFragment": "androidx/leanback/app/RowsFragment",
- "android/support/v17/leanback/app/RowsSupportFragment": "androidx/leanback/app/RowsSupportFragment",
- "android/support/v17/leanback/app/SearchFragment": "androidx/leanback/app/SearchFragment",
- "android/support/v17/leanback/app/SearchSupportFragment": "androidx/leanback/app/SearchSupportFragment",
- "android/support/v17/leanback/app/VerticalGridFragment": "androidx/leanback/app/VerticalGridFragment",
- "android/support/v17/leanback/app/VerticalGridSupportFragment": "androidx/leanback/app/VerticalGridSupportFragment",
- "android/support/v17/leanback/app/VideoFragment": "androidx/leanback/app/VideoFragment",
- "android/support/v17/leanback/app/VideoFragmentGlueHost": "androidx/leanback/app/VideoFragmentGlueHost",
- "android/support/v17/leanback/app/VideoSupportFragment": "androidx/leanback/app/VideoSupportFragment",
- "android/support/v17/leanback/app/VideoSupportFragmentGlueHost": "androidx/leanback/app/VideoSupportFragmentGlueHost",
- "android/support/v17/leanback/database/CursorMapper": "androidx/leanback/database/CursorMapper",
- "android/support/v17/leanback/graphics/BoundsRule": "androidx/leanback/graphics/BoundsRule",
- "android/support/v17/leanback/graphics/ColorFilterCache": "androidx/leanback/graphics/ColorFilterCache",
- "android/support/v17/leanback/graphics/ColorFilterDimmer": "androidx/leanback/graphics/ColorFilterDimmer",
- "android/support/v17/leanback/graphics/ColorOverlayDimmer": "androidx/leanback/graphics/ColorOverlayDimmer",
- "android/support/v17/leanback/graphics/CompositeDrawable": "androidx/leanback/graphics/CompositeDrawable",
- "android/support/v17/leanback/graphics/FitWidthBitmapDrawable": "androidx/leanback/graphics/FitWidthBitmapDrawable",
- "android/support/v17/leanback/media/MediaControllerAdapter": "androidx/leanback/media/MediaControllerAdapter",
- "android/support/v17/leanback/media/MediaControllerGlue": "androidx/leanback/media/MediaControllerGlue",
- "android/support/v17/leanback/media/MediaPlayerAdapter": "androidx/leanback/media/MediaPlayerAdapter",
- "android/support/v17/leanback/media/MediaPlayerGlue": "androidx/leanback/media/MediaPlayerGlue",
- "android/support/v17/leanback/media/PlaybackBannerControlGlue": "androidx/leanback/media/PlaybackBannerControlGlue",
- "android/support/v17/leanback/media/PlaybackBaseControlGlue": "androidx/leanback/media/PlaybackBaseControlGlue",
- "android/support/v17/leanback/media/PlaybackControlGlue": "androidx/leanback/media/PlaybackControlGlue",
- "android/support/v17/leanback/media/PlaybackGlue": "androidx/leanback/media/PlaybackGlue",
- "android/support/v17/leanback/media/PlaybackGlueHost": "androidx/leanback/media/PlaybackGlueHost",
- "android/support/v17/leanback/media/PlaybackTransportControlGlue": "androidx/leanback/media/PlaybackTransportControlGlue",
- "android/support/v17/leanback/media/PlayerAdapter": "androidx/leanback/media/PlayerAdapter",
- "android/support/v17/leanback/media/SurfaceHolderGlueHost": "androidx/leanback/media/SurfaceHolderGlueHost",
- "android/support/v17/leanback/system/Settings": "androidx/leanback/system/Settings",
- "android/support/v17/leanback/transition/CustomChangeBounds": "androidx/leanback/transition/CustomChangeBounds",
- "android/support/v17/leanback/transition/FadeAndShortSlide": "androidx/leanback/transition/FadeAndShortSlide",
- "android/support/v17/leanback/transition/LeanbackTransitionHelper": "androidx/leanback/transition/LeanbackTransitionHelper",
- "android/support/v17/leanback/transition/ParallaxTransition": "androidx/leanback/transition/ParallaxTransition",
- "android/support/v17/leanback/transition/Scale": "androidx/leanback/transition/Scale",
- "android/support/v17/leanback/transition/SlideKitkat": "androidx/leanback/transition/SlideKitkat",
- "android/support/v17/leanback/transition/SlideNoPropagation": "androidx/leanback/transition/SlideNoPropagation",
- "android/support/v17/leanback/transition/TransitionEpicenterCallback": "androidx/leanback/transition/TransitionEpicenterCallback",
- "android/support/v17/leanback/transition/TransitionHelper": "androidx/leanback/transition/TransitionHelper",
- "android/support/v17/leanback/transition/TransitionListener": "androidx/leanback/transition/TransitionListener",
- "android/support/v17/leanback/transition/TranslationAnimationCreator": "androidx/leanback/transition/TranslationAnimationCreator",
- "android/support/v17/leanback/util/MathUtil": "androidx/leanback/util/MathUtil",
- "android/support/v17/leanback/util/StateMachine": "androidx/leanback/util/StateMachine",
- "android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter": "androidx/leanback/widget/AbstractDetailsDescriptionPresenter",
- "android/support/v17/leanback/widget/AbstractMediaItemPresenter": "androidx/leanback/widget/AbstractMediaItemPresenter",
- "android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter": "androidx/leanback/widget/AbstractMediaListHeaderPresenter",
- "android/support/v17/leanback/widget/Action": "androidx/leanback/widget/Action",
- "android/support/v17/leanback/widget/ActionPresenterSelector": "androidx/leanback/widget/ActionPresenterSelector",
- "android/support/v17/leanback/widget/ArrayObjectAdapter": "androidx/leanback/widget/ArrayObjectAdapter",
- "android/support/v17/leanback/widget/BackgroundHelper": "androidx/leanback/widget/BackgroundHelper",
- "android/support/v17/leanback/widget/BaseCardView": "androidx/leanback/widget/BaseCardView",
- "android/support/v17/leanback/widget/BaseGridView": "androidx/leanback/widget/BaseGridView",
- "android/support/v17/leanback/widget/BaseOnItemViewClickedListener": "androidx/leanback/widget/BaseOnItemViewClickedListener",
- "android/support/v17/leanback/widget/BaseOnItemViewSelectedListener": "androidx/leanback/widget/BaseOnItemViewSelectedListener",
- "android/support/v17/leanback/widget/BrowseFrameLayout": "androidx/leanback/widget/BrowseFrameLayout",
- "android/support/v17/leanback/widget/BrowseRowsFrameLayout": "androidx/leanback/widget/BrowseRowsFrameLayout",
- "android/support/v17/leanback/widget/CheckableImageView": "androidx/leanback/widget/CheckableImageView",
- "android/support/v17/leanback/widget/ClassPresenterSelector": "androidx/leanback/widget/ClassPresenterSelector",
- "android/support/v17/leanback/widget/ControlBar": "androidx/leanback/widget/ControlBar",
- "android/support/v17/leanback/widget/ControlBarPresenter": "androidx/leanback/widget/ControlBarPresenter",
- "android/support/v17/leanback/widget/ControlButtonPresenterSelector": "androidx/leanback/widget/ControlButtonPresenterSelector",
- "android/support/v17/leanback/widget/CursorObjectAdapter": "androidx/leanback/widget/CursorObjectAdapter",
- "android/support/v17/leanback/widget/DetailsOverviewLogoPresenter": "androidx/leanback/widget/DetailsOverviewLogoPresenter",
- "android/support/v17/leanback/widget/DetailsOverviewRow": "androidx/leanback/widget/DetailsOverviewRow",
- "android/support/v17/leanback/widget/DetailsOverviewRowPresenter": "androidx/leanback/widget/DetailsOverviewRowPresenter",
- "android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper": "androidx/leanback/widget/DetailsOverviewSharedElementHelper",
- "android/support/v17/leanback/widget/DetailsParallax": "androidx/leanback/widget/DetailsParallax",
- "android/support/v17/leanback/widget/DetailsParallaxDrawable": "androidx/leanback/widget/DetailsParallaxDrawable",
- "android/support/v17/leanback/widget/DiffCallback": "androidx/leanback/widget/DiffCallback",
- "android/support/v17/leanback/widget/DividerPresenter": "androidx/leanback/widget/DividerPresenter",
- "android/support/v17/leanback/widget/DividerRow": "androidx/leanback/widget/DividerRow",
- "android/support/v17/leanback/widget/FacetProvider": "androidx/leanback/widget/FacetProvider",
- "android/support/v17/leanback/widget/FacetProviderAdapter": "androidx/leanback/widget/FacetProviderAdapter",
- "android/support/v17/leanback/widget/FocusHighlight": "androidx/leanback/widget/FocusHighlight",
- "android/support/v17/leanback/widget/FocusHighlightHandler": "androidx/leanback/widget/FocusHighlightHandler",
- "android/support/v17/leanback/widget/FocusHighlightHelper": "androidx/leanback/widget/FocusHighlightHelper",
- "android/support/v17/leanback/widget/ForegroundHelper": "androidx/leanback/widget/ForegroundHelper",
- "android/support/v17/leanback/widget/FragmentAnimationProvider": "androidx/leanback/widget/FragmentAnimationProvider",
- "android/support/v17/leanback/widget/FullWidthDetailsOverviewRowPresenter": "androidx/leanback/widget/FullWidthDetailsOverviewRowPresenter",
- "android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper": "androidx/leanback/widget/FullWidthDetailsOverviewSharedElementHelper",
- "android/support/v17/leanback/widget/Grid": "androidx/leanback/widget/Grid",
- "android/support/v17/leanback/widget/GridLayoutManager": "androidx/leanback/widget/GridLayoutManager",
- "android/support/v17/leanback/widget/GuidanceStylingRelativeLayout": "androidx/leanback/widget/GuidanceStylingRelativeLayout",
- "android/support/v17/leanback/widget/GuidanceStylist": "androidx/leanback/widget/GuidanceStylist",
- "android/support/v17/leanback/widget/GuidedAction": "androidx/leanback/widget/GuidedAction",
- "android/support/v17/leanback/widget/GuidedActionAdapter": "androidx/leanback/widget/GuidedActionAdapter",
- "android/support/v17/leanback/widget/GuidedActionAdapterGroup": "androidx/leanback/widget/GuidedActionAdapterGroup",
- "android/support/v17/leanback/widget/GuidedActionAutofillSupport": "androidx/leanback/widget/GuidedActionAutofillSupport",
- "android/support/v17/leanback/widget/GuidedActionDiffCallback": "androidx/leanback/widget/GuidedActionDiffCallback",
- "android/support/v17/leanback/widget/GuidedActionEditText": "androidx/leanback/widget/GuidedActionEditText",
- "android/support/v17/leanback/widget/GuidedActionItemContainer": "androidx/leanback/widget/GuidedActionItemContainer",
- "android/support/v17/leanback/widget/GuidedActionsRelativeLayout": "androidx/leanback/widget/GuidedActionsRelativeLayout",
- "android/support/v17/leanback/widget/GuidedActionsStylist": "androidx/leanback/widget/GuidedActionsStylist",
- "android/support/v17/leanback/widget/GuidedDatePickerAction": "androidx/leanback/widget/GuidedDatePickerAction",
- "android/support/v17/leanback/widget/HeaderItem": "androidx/leanback/widget/HeaderItem",
- "android/support/v17/leanback/widget/HorizontalGridView": "androidx/leanback/widget/HorizontalGridView",
- "android/support/v17/leanback/widget/HorizontalHoverCardSwitcher": "androidx/leanback/widget/HorizontalHoverCardSwitcher",
- "android/support/v17/leanback/widget/ImageCardView": "androidx/leanback/widget/ImageCardView",
- "android/support/v17/leanback/widget/ImeKeyMonitor": "androidx/leanback/widget/ImeKeyMonitor",
- "android/support/v17/leanback/widget/InvisibleRowPresenter": "androidx/leanback/widget/InvisibleRowPresenter",
- "android/support/v17/leanback/widget/ItemAlignment": "androidx/leanback/widget/ItemAlignment",
- "android/support/v17/leanback/widget/ItemAlignmentFacet": "androidx/leanback/widget/ItemAlignmentFacet",
- "android/support/v17/leanback/widget/ItemAlignmentFacetHelper": "androidx/leanback/widget/ItemAlignmentFacetHelper",
- "android/support/v17/leanback/widget/ItemBridgeAdapter": "androidx/leanback/widget/ItemBridgeAdapter",
- "android/support/v17/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper": "androidx/leanback/widget/ItemBridgeAdapterShadowOverlayWrapper",
- "android/support/v17/leanback/widget/ListRow": "androidx/leanback/widget/ListRow",
- "android/support/v17/leanback/widget/ListRowHoverCardView": "androidx/leanback/widget/ListRowHoverCardView",
- "android/support/v17/leanback/widget/ListRowPresenter": "androidx/leanback/widget/ListRowPresenter",
- "android/support/v17/leanback/widget/ListRowView": "androidx/leanback/widget/ListRowView",
- "android/support/v17/leanback/widget/MediaItemActionPresenter": "androidx/leanback/widget/MediaItemActionPresenter",
- "android/support/v17/leanback/widget/MediaNowPlayingView": "androidx/leanback/widget/MediaNowPlayingView",
- "android/support/v17/leanback/widget/MediaRowFocusView": "androidx/leanback/widget/MediaRowFocusView",
- "android/support/v17/leanback/widget/MultiActionsProvider": "androidx/leanback/widget/MultiActionsProvider",
- "android/support/v17/leanback/widget/NonOverlappingFrameLayout": "androidx/leanback/widget/NonOverlappingFrameLayout",
- "android/support/v17/leanback/widget/NonOverlappingLinearLayout": "androidx/leanback/widget/NonOverlappingLinearLayout",
- "android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground": "androidx/leanback/widget/NonOverlappingLinearLayoutWithForeground",
- "android/support/v17/leanback/widget/NonOverlappingRelativeLayout": "androidx/leanback/widget/NonOverlappingRelativeLayout",
- "android/support/v17/leanback/widget/NonOverlappingView": "androidx/leanback/widget/NonOverlappingView",
- "android/support/v17/leanback/widget/ObjectAdapter": "androidx/leanback/widget/ObjectAdapter",
- "android/support/v17/leanback/widget/OnActionClickedListener": "androidx/leanback/widget/OnActionClickedListener",
- "android/support/v17/leanback/widget/OnChildLaidOutListener": "androidx/leanback/widget/OnChildLaidOutListener",
- "android/support/v17/leanback/widget/OnChildSelectedListener": "androidx/leanback/widget/OnChildSelectedListener",
- "android/support/v17/leanback/widget/OnChildViewHolderSelectedListener": "androidx/leanback/widget/OnChildViewHolderSelectedListener",
- "android/support/v17/leanback/widget/OnItemViewClickedListener": "androidx/leanback/widget/OnItemViewClickedListener",
- "android/support/v17/leanback/widget/OnItemViewSelectedListener": "androidx/leanback/widget/OnItemViewSelectedListener",
- "android/support/v17/leanback/widget/PageRow": "androidx/leanback/widget/PageRow",
- "android/support/v17/leanback/widget/PagingIndicator": "androidx/leanback/widget/PagingIndicator",
- "android/support/v17/leanback/widget/Parallax": "androidx/leanback/widget/Parallax",
- "android/support/v17/leanback/widget/ParallaxEffect": "androidx/leanback/widget/ParallaxEffect",
- "android/support/v17/leanback/widget/ParallaxTarget": "androidx/leanback/widget/ParallaxTarget",
- "android/support/v17/leanback/widget/PersistentFocusWrapper": "androidx/leanback/widget/PersistentFocusWrapper",
- "android/support/v17/leanback/widget/PlaybackControlsPresenter": "androidx/leanback/widget/PlaybackControlsPresenter",
- "android/support/v17/leanback/widget/PlaybackControlsRow": "androidx/leanback/widget/PlaybackControlsRow",
- "android/support/v17/leanback/widget/PlaybackControlsRowPresenter": "androidx/leanback/widget/PlaybackControlsRowPresenter",
- "android/support/v17/leanback/widget/PlaybackControlsRowView": "androidx/leanback/widget/PlaybackControlsRowView",
- "android/support/v17/leanback/widget/PlaybackRowPresenter": "androidx/leanback/widget/PlaybackRowPresenter",
- "android/support/v17/leanback/widget/PlaybackSeekDataProvider": "androidx/leanback/widget/PlaybackSeekDataProvider",
- "android/support/v17/leanback/widget/PlaybackSeekUi": "androidx/leanback/widget/PlaybackSeekUi",
- "android/support/v17/leanback/widget/PlaybackTransportRowPresenter": "androidx/leanback/widget/PlaybackTransportRowPresenter",
- "android/support/v17/leanback/widget/PlaybackTransportRowView": "androidx/leanback/widget/PlaybackTransportRowView",
- "android/support/v17/leanback/widget/Presenter": "androidx/leanback/widget/Presenter",
- "android/support/v17/leanback/widget/PresenterSelector": "androidx/leanback/widget/PresenterSelector",
- "android/support/v17/leanback/widget/PresenterSwitcher": "androidx/leanback/widget/PresenterSwitcher",
- "android/support/v17/leanback/widget/RecyclerViewParallax": "androidx/leanback/widget/RecyclerViewParallax",
- "android/support/v17/leanback/widget/ResizingTextView": "androidx/leanback/widget/ResizingTextView",
- "android/support/v17/leanback/widget/RoundedRectHelper": "androidx/leanback/widget/RoundedRectHelper",
- "android/support/v17/leanback/widget/RoundedRectHelperApi21": "androidx/leanback/widget/RoundedRectHelperApi21",
- "android/support/v17/leanback/widget/Row": "androidx/leanback/widget/Row",
- "android/support/v17/leanback/widget/RowContainerView": "androidx/leanback/widget/RowContainerView",
- "android/support/v17/leanback/widget/RowHeaderPresenter": "androidx/leanback/widget/RowHeaderPresenter",
- "android/support/v17/leanback/widget/RowHeaderView": "androidx/leanback/widget/RowHeaderView",
- "android/support/v17/leanback/widget/RowPresenter": "androidx/leanback/widget/RowPresenter",
- "android/support/v17/leanback/widget/ScaleFrameLayout": "androidx/leanback/widget/ScaleFrameLayout",
- "android/support/v17/leanback/widget/SearchBar": "androidx/leanback/widget/SearchBar",
- "android/support/v17/leanback/widget/SearchEditText": "androidx/leanback/widget/SearchEditText",
- "android/support/v17/leanback/widget/SearchOrbView": "androidx/leanback/widget/SearchOrbView",
- "android/support/v17/leanback/widget/SectionRow": "androidx/leanback/widget/SectionRow",
- "android/support/v17/leanback/widget/SeekBar": "androidx/leanback/widget/SeekBar",
- "android/support/v17/leanback/widget/ShadowHelper": "androidx/leanback/widget/ShadowHelper",
- "android/support/v17/leanback/widget/ShadowHelperApi21": "androidx/leanback/widget/ShadowHelperApi21",
- "android/support/v17/leanback/widget/ShadowOverlayContainer": "androidx/leanback/widget/ShadowOverlayContainer",
- "android/support/v17/leanback/widget/ShadowOverlayHelper": "androidx/leanback/widget/ShadowOverlayHelper",
- "android/support/v17/leanback/widget/SinglePresenterSelector": "androidx/leanback/widget/SinglePresenterSelector",
- "android/support/v17/leanback/widget/SingleRow": "androidx/leanback/widget/SingleRow",
- "android/support/v17/leanback/widget/SparseArrayObjectAdapter": "androidx/leanback/widget/SparseArrayObjectAdapter",
- "android/support/v17/leanback/widget/SpeechOrbView": "androidx/leanback/widget/SpeechOrbView",
- "android/support/v17/leanback/widget/SpeechRecognitionCallback": "androidx/leanback/widget/SpeechRecognitionCallback",
- "android/support/v17/leanback/widget/StaggeredGrid": "androidx/leanback/widget/StaggeredGrid",
- "android/support/v17/leanback/widget/StaggeredGridDefault": "androidx/leanback/widget/StaggeredGridDefault",
- "android/support/v17/leanback/widget/StaticShadowHelper": "androidx/leanback/widget/StaticShadowHelper",
- "android/support/v17/leanback/widget/StreamingTextView": "androidx/leanback/widget/StreamingTextView",
- "android/support/v17/leanback/widget/ThumbsBar": "androidx/leanback/widget/ThumbsBar",
- "android/support/v17/leanback/widget/TitleHelper": "androidx/leanback/widget/TitleHelper",
- "android/support/v17/leanback/widget/TitleView": "androidx/leanback/widget/TitleView",
- "android/support/v17/leanback/widget/TitleViewAdapter": "androidx/leanback/widget/TitleViewAdapter",
- "android/support/v17/leanback/widget/Util": "androidx/leanback/widget/Util",
- "android/support/v17/leanback/widget/VerticalGridPresenter": "androidx/leanback/widget/VerticalGridPresenter",
- "android/support/v17/leanback/widget/VerticalGridView": "androidx/leanback/widget/VerticalGridView",
- "android/support/v17/leanback/widget/VideoSurfaceView": "androidx/leanback/widget/VideoSurfaceView",
- "android/support/v17/leanback/widget/ViewHolderTask": "androidx/leanback/widget/ViewHolderTask",
- "android/support/v17/leanback/widget/ViewsStateBundle": "androidx/leanback/widget/ViewsStateBundle",
- "android/support/v17/leanback/widget/Visibility": "androidx/leanback/widget/Visibility",
- "android/support/v17/leanback/widget/WindowAlignment": "androidx/leanback/widget/WindowAlignment",
- "android/support/v17/leanback/widget/picker/DatePicker": "androidx/leanback/widget/picker/DatePicker",
- "android/support/v17/leanback/widget/picker/Picker": "androidx/leanback/widget/picker/Picker",
- "android/support/v17/leanback/widget/picker/PickerColumn": "androidx/leanback/widget/picker/PickerColumn",
- "android/support/v17/leanback/widget/picker/PickerUtility": "androidx/leanback/widget/picker/PickerUtility",
- "android/support/v17/leanback/widget/picker/TimePicker": "androidx/leanback/widget/picker/TimePicker",
- "android/support/v17/preference/BaseLeanbackPreferenceFragment": "androidx/leanback/preference/BaseLeanbackPreferenceFragment",
- "android/support/v17/preference/LeanbackListPreferenceDialogFragment": "androidx/leanback/preference/LeanbackListPreferenceDialogFragment",
- "android/support/v17/preference/LeanbackPreferenceDialogFragment": "androidx/leanback/preference/LeanbackPreferenceDialogFragment",
- "android/support/v17/preference/LeanbackPreferenceFragment": "androidx/leanback/preference/LeanbackPreferenceFragment",
- "android/support/v17/preference/LeanbackPreferenceFragmentTransitionHelperApi21": "androidx/leanback/preference/LeanbackPreferenceFragmentTransitionHelperApi21",
- "android/support/v17/preference/LeanbackSettingsFragment": "androidx/leanback/preference/LeanbackSettingsFragment",
- "android/support/v17/preference/LeanbackSettingsRootView": "androidx/leanback/preference/LeanbackSettingsRootView",
- "android/support/v17/preference/R": "androidx/leanback/preference/R",
"android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat": "androidx/core/accessibilityservice/AccessibilityServiceInfoCompat",
- "android/support/v4/app/ActionBarDrawerToggle": "androidx/legacy/app/ActionBarDrawerToggle",
"android/support/v4/app/ActivityCompat": "androidx/core/app/ActivityCompat",
"android/support/v4/app/ActivityManagerCompat": "androidx/core/app/ActivityManagerCompat",
"android/support/v4/app/ActivityOptionsCompat": "androidx/core/app/ActivityOptionsCompat",
@@ -3968,7 +3700,6 @@
"android/support/v4/content/FileProvider": "androidx/core/content/FileProvider",
"android/support/v4/content/IntentCompat": "androidx/core/content/IntentCompat",
"android/support/v4/content/Loader": "androidx/loader/content/Loader",
- "android/support/v4/content/LocalBroadcastManager": "androidx/localbroadcastmanager/content/LocalBroadcastManager",
"android/support/v4/content/MimeTypeFilter": "androidx/core/content/MimeTypeFilter",
"android/support/v4/content/ModernAsyncTask": "androidx/loader/content/ModernAsyncTask",
"android/support/v4/content/PermissionChecker": "androidx/core/content/PermissionChecker",
@@ -4158,10 +3889,6 @@
"android/support/v4/view/accessibility/AccessibilityNodeProviderCompat": "androidx/core/view/accessibility/AccessibilityNodeProviderCompat",
"android/support/v4/view/accessibility/AccessibilityRecordCompat": "androidx/core/view/accessibility/AccessibilityRecordCompat",
"android/support/v4/view/accessibility/AccessibilityWindowInfoCompat": "androidx/core/view/accessibility/AccessibilityWindowInfoCompat",
- "android/support/v4/view/animation/FastOutLinearInInterpolator": "androidx/interpolator/view/animation/FastOutLinearInInterpolator",
- "android/support/v4/view/animation/FastOutSlowInInterpolator": "androidx/interpolator/view/animation/FastOutSlowInInterpolator",
- "android/support/v4/view/animation/LinearOutSlowInInterpolator": "androidx/interpolator/view/animation/LinearOutSlowInInterpolator",
- "android/support/v4/view/animation/LookupTableInterpolator": "androidx/interpolator/view/animation/LookupTableInterpolator",
"android/support/v4/view/animation/PathInterpolatorApi14": "androidx/core/view/animation/PathInterpolatorApi14",
"android/support/v4/view/animation/PathInterpolatorCompat": "androidx/core/view/animation/PathInterpolatorCompat",
"android/support/v4/widget/AutoScrollHelper": "androidx/core/widget/AutoScrollHelper",
@@ -4185,7 +3912,6 @@
"android/support/v4/widget/ResourceCursorAdapter": "androidx/cursoradapter/widget/ResourceCursorAdapter",
"android/support/v4/widget/ScrollerCompat": "androidx/core/widget/ScrollerCompat",
"android/support/v4/widget/SimpleCursorAdapter": "androidx/cursoradapter/widget/SimpleCursorAdapter",
- "android/support/v4/widget/Space": "androidx/legacy/widget/Space",
"android/support/v4/widget/TextViewCompat": "androidx/core/widget/TextViewCompat",
"android/support/v4/widget/TintableCompoundButton": "androidx/core/widget/TintableCompoundButton",
"android/support/v4/widget/TintableImageSourceView": "androidx/core/widget/TintableImageSourceView",
@@ -4225,7 +3951,6 @@
"android/support/v7/app/TwilightManager": "androidx/appcompat/app/TwilightManager",
"android/support/v7/app/WindowDecorActionBar": "androidx/appcompat/app/WindowDecorActionBar",
"android/support/v7/appcompat/R": "androidx/appcompat/R",
- "android/support/v7/cardview/R": "androidx/cardview/R",
"android/support/v7/content/res/AppCompatResources": "androidx/appcompat/content/res/AppCompatResources",
"android/support/v7/graphics/ColorCutQuantizer": "androidx/palette/graphics/ColorCutQuantizer",
"android/support/v7/graphics/Palette": "androidx/palette/graphics/Palette",
@@ -4235,7 +3960,6 @@
"android/support/v7/graphics/drawable/DrawableWrapper": "androidx/appcompat/graphics/drawable/DrawableWrapper",
"android/support/v7/graphics/drawable/DrawerArrowDrawable": "androidx/appcompat/graphics/drawable/DrawerArrowDrawable",
"android/support/v7/graphics/drawable/StateListDrawable": "androidx/appcompat/graphics/drawable/StateListDrawable",
- "android/support/v7/gridlayout/R": "androidx/gridlayout/R",
"android/support/v7/internal/widget/PreferenceImageView": "androidx/preference/internal/PreferenceImageView",
"android/support/v7/media/MediaControlIntent": "androidx/mediarouter/media/MediaControlIntent",
"android/support/v7/media/MediaItemMetadata": "androidx/mediarouter/media/MediaItemMetadata",
@@ -4372,12 +4096,6 @@
"android/support/v7/widget/AppCompatTextView": "androidx/appcompat/widget/AppCompatTextView",
"android/support/v7/widget/AppCompatTextViewAutoSizeHelper": "androidx/appcompat/widget/AppCompatTextViewAutoSizeHelper",
"android/support/v7/widget/ButtonBarLayout": "androidx/appcompat/widget/ButtonBarLayout",
- "android/support/v7/widget/CardView": "androidx/cardview/widget/CardView",
- "android/support/v7/widget/CardViewApi17Impl": "androidx/cardview/widget/CardViewApi17Impl",
- "android/support/v7/widget/CardViewApi21Impl": "androidx/cardview/widget/CardViewApi21Impl",
- "android/support/v7/widget/CardViewBaseImpl": "androidx/cardview/widget/CardViewBaseImpl",
- "android/support/v7/widget/CardViewDelegate": "androidx/cardview/widget/CardViewDelegate",
- "android/support/v7/widget/CardViewImpl": "androidx/cardview/widget/CardViewImpl",
"android/support/v7/widget/ChildHelper": "androidx/recyclerview/widget/ChildHelper",
"android/support/v7/widget/ContentFrameLayout": "androidx/appcompat/widget/ContentFrameLayout",
"android/support/v7/widget/DecorContentParent": "androidx/appcompat/widget/DecorContentParent",
@@ -4393,7 +4111,6 @@
"android/support/v7/widget/FitWindowsViewGroup": "androidx/appcompat/widget/FitWindowsViewGroup",
"android/support/v7/widget/ForwardingListener": "androidx/appcompat/widget/ForwardingListener",
"android/support/v7/widget/GapWorker": "androidx/recyclerview/widget/GapWorker",
- "android/support/v7/widget/GridLayout": "androidx/gridlayout/widget/GridLayout",
"android/support/v7/widget/GridLayoutManager": "androidx/recyclerview/widget/GridLayoutManager",
"android/support/v7/widget/LayoutState": "androidx/recyclerview/widget/LayoutState",
"android/support/v7/widget/LinearLayoutCompat": "androidx/appcompat/widget/LinearLayoutCompat",
@@ -4410,8 +4127,6 @@
"android/support/v7/widget/RecyclerView": "androidx/recyclerview/widget/RecyclerView",
"android/support/v7/widget/RecyclerViewAccessibilityDelegate": "androidx/recyclerview/widget/RecyclerViewAccessibilityDelegate",
"android/support/v7/widget/ResourcesWrapper": "androidx/appcompat/widget/ResourcesWrapper",
- "android/support/v7/widget/RoundRectDrawable": "androidx/cardview/widget/RoundRectDrawable",
- "android/support/v7/widget/RoundRectDrawableWithShadow": "androidx/cardview/widget/RoundRectDrawableWithShadow",
"android/support/v7/widget/RtlSpacingHelper": "androidx/appcompat/widget/RtlSpacingHelper",
"android/support/v7/widget/ScrollbarHelper": "androidx/recyclerview/widget/ScrollbarHelper",
"android/support/v7/widget/ScrollingTabContainerView": "androidx/appcompat/widget/ScrollingTabContainerView",
@@ -4442,7 +4157,10 @@
"android/support/v7/widget/helper/ItemTouchHelper": "androidx/recyclerview/widget/ItemTouchHelper",
"android/support/v7/widget/helper/ItemTouchUIUtil": "androidx/recyclerview/widget/ItemTouchUIUtil",
"android/support/v7/widget/helper/ItemTouchUIUtilImpl": "androidx/recyclerview/widget/ItemTouchUIUtilImpl",
- "android/support/v7/widget/util/SortedListAdapterCallback": "androidx/recyclerview/widget/SortedListAdapterCallback"
+ "android/support/v7/widget/util/SortedListAdapterCallback": "androidx/recyclerview/widget/SortedListAdapterCallback",
+ "android/support/v13/view/inputmethod/EditorInfoCompat": "androidx/core/view/inputmethod/EditorInfoCompat",
+ "android/support/v13/view/inputmethod/InputConnectionCompat": "androidx/core/view/inputmethod/InputConnectionCompat",
+ "android/support/v13/view/inputmethod/InputContentInfoCompat": "androidx/core/view/inputmethod/InputContentInfoCompat"
}
},
"proGuardMap": {
@@ -4470,8 +4188,7 @@
"androidx/customview/view/{any}",
"androidx/core/view/{any}",
"androidx/asynclayoutinflater/{any}",
- "androidx/viewpager/{any}",
- "androidx/interpolator/{any}"
+ "androidx/viewpager/{any}"
],
"android/support/v4/media/{any}": [
"androidx/media/{any}",
@@ -4480,15 +4197,12 @@
"android/support/v7/{any}": [
"androidx/appcompat/{any}",
"androidx/mediarouter/{any}",
- "androidx/cardview/{any}",
"androidx/palette/{any}",
- "androidx/gridlayout/{any}",
"androidx/preference/{any}"
],
"android/support/v7/widget/{any}": [
"androidx/appcompat/widget/{any}",
- "androidx/recyclerview/widget/{any}",
- "androidx/cardview/widget/{any}"
+ "androidx/recyclerview/widget/{any}"
],
"android/support/v7/preference/{any}": [
"androidx/preference/{any}"
@@ -4496,12 +4210,6 @@
"android/support/v14/preference/{any}": [
"androidx/preference/{any}"
],
- "android/support/v17/preference/{any}": [
- "androidx/leanback/preference/{any}"
- ],
- "android/support/v17/leanback/{any}": [
- "androidx/leanback/{any}"
- ],
"android/support/design/widget/{any}": [
"androidx/coordinatorlayout/widget/{any}",
"com/google/android/material/**"
diff --git a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/archive/Archive.kt b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/archive/Archive.kt
index 3fd5390..2117b5c 100644
--- a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/archive/Archive.kt
+++ b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/archive/Archive.kt
@@ -103,7 +103,7 @@
}
// Create directories if they don't exist yet
- if (outputPath.parent != null) {
+ if (outputPath.parent == null) {
Files.createDirectories(outputPath.parent)
}
diff --git a/leanback/src/main/java/androidx/leanback/widget/SearchEditText.java b/leanback/src/main/java/androidx/leanback/widget/SearchEditText.java
index 06367fb..f87e7c4 100644
--- a/leanback/src/main/java/androidx/leanback/widget/SearchEditText.java
+++ b/leanback/src/main/java/androidx/leanback/widget/SearchEditText.java
@@ -37,7 +37,7 @@
public void onKeyboardDismiss();
}
- private OnKeyboardDismissListener mKeyboardDismissListener;
+ OnKeyboardDismissListener mKeyboardDismissListener;
public SearchEditText(Context context) {
this(context, null);
@@ -55,9 +55,17 @@
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (DEBUG) Log.v(TAG, "Keyboard being dismissed");
+ // Delay focus on result because focus to result in EditText.onKeyPreIme(KEYCODE_BACK).
+ // If set focus too early, the activity will be closed.
if (mKeyboardDismissListener != null) {
- mKeyboardDismissListener.onKeyboardDismiss();
- return true;
+ post(new Runnable() {
+ @Override
+ public void run() {
+ if (mKeyboardDismissListener != null) {
+ mKeyboardDismissListener.onKeyboardDismiss();
+ }
+ }
+ });
}
}
return super.onKeyPreIme(keyCode, event);
diff --git a/leanback/src/main/java/androidx/leanback/widget/StreamingTextView.java b/leanback/src/main/java/androidx/leanback/widget/StreamingTextView.java
index b5805eb..edbbc8f 100644
--- a/leanback/src/main/java/androidx/leanback/widget/StreamingTextView.java
+++ b/leanback/src/main/java/androidx/leanback/widget/StreamingTextView.java
@@ -301,7 +301,7 @@
/**
* See
- * {@link TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
+ * {@link androidx.core.widget.TextViewCompat#setCustomSelectionActionModeCallback(TextView, ActionMode.Callback)}
*/
@Override
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
diff --git a/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java b/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
index 4719107..48850f1 100644
--- a/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
+++ b/leanback/src/main/java/androidx/leanback/widget/picker/DatePicker.java
@@ -39,8 +39,8 @@
* can be customized. The columns can be customized by attribute "datePickerFormat" or
* {@link #setDatePickerFormat(String)}.
*
- * {@link R.attr#android_maxDate}
- * {@link R.attr#android_minDate}
+ * {@link android.R.attr#maxDate}
+ * {@link android.R.attr#minDate}
* {@link R.attr#datePickerFormat}
*/
public class DatePicker extends Picker {
@@ -522,4 +522,4 @@
}
});
}
-}
\ No newline at end of file
+}
diff --git a/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java b/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
index 98de1bb..09f23eb 100644
--- a/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
+++ b/legacy/v13/src/main/java/androidx/legacy/app/FragmentTabHost.java
@@ -39,7 +39,8 @@
* used with the platform {@link android.app.Fragment} APIs. You will not
* normally use this, instead using action bar tabs.
*
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public class FragmentTabHost extends TabHost implements TabHost.OnTabChangeListener {
@@ -121,7 +122,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public FragmentTabHost(Context context) {
@@ -132,7 +135,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public FragmentTabHost(Context context, AttributeSet attrs) {
@@ -178,7 +183,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Override
@Deprecated
@@ -188,7 +195,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public void setup(Context context, FragmentManager manager) {
@@ -200,7 +209,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public void setup(Context context, FragmentManager manager, int containerId) {
@@ -230,7 +241,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@@ -239,7 +252,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
@@ -265,7 +280,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@@ -308,7 +325,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@@ -318,7 +337,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@@ -330,7 +351,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@@ -345,7 +368,9 @@
}
/**
- * @deprecated Use {@link androidx.fragment.app.FragmentTabHost} instead.
+ * @deprecated Use
+ * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
+ * TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
diff --git a/lifecycle/common-java8/api/2.1.0-alpha04.txt b/lifecycle/common-java8/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..498d3a9
--- /dev/null
+++ b/lifecycle/common-java8/api/2.1.0-alpha04.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public interface DefaultLifecycleObserver extends androidx.lifecycle.LifecycleObserver androidx.lifecycle.LifecycleObserver {
+ method public default void onCreate(androidx.lifecycle.LifecycleOwner);
+ method public default void onDestroy(androidx.lifecycle.LifecycleOwner);
+ method public default void onPause(androidx.lifecycle.LifecycleOwner);
+ method public default void onResume(androidx.lifecycle.LifecycleOwner);
+ method public default void onStart(androidx.lifecycle.LifecycleOwner);
+ method public default void onStop(androidx.lifecycle.LifecycleOwner);
+ }
+
+}
+
diff --git a/lifecycle/common-java8/api/restricted_2.1.0-alpha04.txt b/lifecycle/common-java8/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/common-java8/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/common/api/2.1.0-alpha04.txt b/lifecycle/common/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..f23b5eb
--- /dev/null
+++ b/lifecycle/common/api/2.1.0-alpha04.txt
@@ -0,0 +1,46 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public abstract class Lifecycle {
+ ctor public Lifecycle();
+ method @MainThread public abstract void addObserver(androidx.lifecycle.LifecycleObserver);
+ method @MainThread public abstract androidx.lifecycle.Lifecycle.State getCurrentState();
+ method @MainThread public abstract void removeObserver(androidx.lifecycle.LifecycleObserver);
+ }
+
+ public enum Lifecycle.Event {
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_ANY;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_CREATE;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_DESTROY;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_PAUSE;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_RESUME;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_START;
+ enum_constant public static final androidx.lifecycle.Lifecycle.Event ON_STOP;
+ }
+
+ public enum Lifecycle.State {
+ method public boolean isAtLeast(androidx.lifecycle.Lifecycle.State);
+ enum_constant public static final androidx.lifecycle.Lifecycle.State CREATED;
+ enum_constant public static final androidx.lifecycle.Lifecycle.State DESTROYED;
+ enum_constant public static final androidx.lifecycle.Lifecycle.State INITIALIZED;
+ enum_constant public static final androidx.lifecycle.Lifecycle.State RESUMED;
+ enum_constant public static final androidx.lifecycle.Lifecycle.State STARTED;
+ }
+
+ public interface LifecycleEventObserver extends androidx.lifecycle.LifecycleObserver {
+ method public void onStateChanged(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.Event);
+ }
+
+ public interface LifecycleObserver {
+ }
+
+ public interface LifecycleOwner {
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface OnLifecycleEvent {
+ method public abstract androidx.lifecycle.Lifecycle.Event value();
+ }
+
+}
+
diff --git a/lifecycle/common/api/restricted_2.1.0-alpha04.ignore b/lifecycle/common/api/restricted_2.1.0-alpha04.ignore
new file mode 100644
index 0000000..0a4fad1
--- /dev/null
+++ b/lifecycle/common/api/restricted_2.1.0-alpha04.ignore
@@ -0,0 +1,11 @@
+// Baseline format: 1.0
+ChangedSuperclass: androidx.lifecycle.GenericLifecycleObserver:
+ Class androidx.lifecycle.GenericLifecycleObserver superclass changed from androidx.lifecycle.LifecycleObserver to androidx.lifecycle.LifecycleEventObserver
+
+
+RemovedClass: androidx.lifecycle.CompositeGeneratedAdaptersObserver:
+ Removed class androidx.lifecycle.CompositeGeneratedAdaptersObserver
+RemovedClass: androidx.lifecycle.SingleGeneratedAdapterObserver:
+ Removed class androidx.lifecycle.SingleGeneratedAdapterObserver
+
+
diff --git a/lifecycle/common/api/restricted_2.1.0-alpha04.txt b/lifecycle/common/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..7aa1c38
--- /dev/null
+++ b/lifecycle/common/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface GeneratedAdapter {
+ method public void callMethods(androidx.lifecycle.LifecycleOwner!, androidx.lifecycle.Lifecycle.Event!, boolean, androidx.lifecycle.MethodCallsLogger!);
+ }
+
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface GenericLifecycleObserver extends androidx.lifecycle.LifecycleEventObserver {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class Lifecycling {
+ method public static String! getAdapterName(String!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MethodCallsLogger {
+ ctor public MethodCallsLogger();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean approveCall(String!, int);
+ }
+
+}
+
diff --git a/lifecycle/extensions/api/2.1.0-alpha04.txt b/lifecycle/extensions/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..c85f1ab
--- /dev/null
+++ b/lifecycle/extensions/api/2.1.0-alpha04.txt
@@ -0,0 +1,22 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class ViewModelProviders {
+ ctor @Deprecated public ViewModelProviders();
+ method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment);
+ method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity);
+ method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.Fragment, androidx.lifecycle.ViewModelProvider.Factory?);
+ method @MainThread public static androidx.lifecycle.ViewModelProvider of(androidx.fragment.app.FragmentActivity, androidx.lifecycle.ViewModelProvider.Factory?);
+ }
+
+ @Deprecated public static class ViewModelProviders.DefaultFactory extends androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory {
+ ctor @Deprecated public ViewModelProviders.DefaultFactory(android.app.Application);
+ }
+
+ @Deprecated public class ViewModelStores {
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelStore of(androidx.fragment.app.FragmentActivity);
+ method @Deprecated @MainThread public static androidx.lifecycle.ViewModelStore of(androidx.fragment.app.Fragment);
+ }
+
+}
+
diff --git a/lifecycle/extensions/api/res-2.1.0-alpha04.txt b/lifecycle/extensions/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/extensions/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/extensions/api/restricted_2.1.0-alpha04.txt b/lifecycle/extensions/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/extensions/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/livedata-core/api/2.1.0-alpha04.txt b/lifecycle/livedata-core/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..f0db7db
--- /dev/null
+++ b/lifecycle/livedata-core/api/2.1.0-alpha04.txt
@@ -0,0 +1,32 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public abstract class LiveData<T> {
+ ctor public LiveData(T!);
+ ctor public LiveData();
+ method public T? getValue();
+ method public boolean hasActiveObservers();
+ method public boolean hasObservers();
+ method @MainThread public void observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer<? super T>);
+ method @MainThread public void observeForever(androidx.lifecycle.Observer<? super T>);
+ method protected void onActive();
+ method protected void onInactive();
+ method protected void postValue(T!);
+ method @MainThread public void removeObserver(androidx.lifecycle.Observer<? super T>);
+ method @MainThread public void removeObservers(androidx.lifecycle.LifecycleOwner);
+ method @MainThread protected void setValue(T!);
+ }
+
+ public class MutableLiveData<T> extends androidx.lifecycle.LiveData<T> {
+ ctor public MutableLiveData(T!);
+ ctor public MutableLiveData();
+ method public void postValue(T!);
+ method public void setValue(T!);
+ }
+
+ public interface Observer<T> {
+ method public void onChanged(T!);
+ }
+
+}
+
diff --git a/lifecycle/livedata-core/api/res-2.1.0-alpha04.txt b/lifecycle/livedata-core/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/livedata-core/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/livedata-core/api/restricted_2.1.0-alpha04.txt b/lifecycle/livedata-core/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/livedata-core/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/livedata-core/ktx/api/2.1.0-alpha04.txt b/lifecycle/livedata-core/ktx/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..861a341
--- /dev/null
+++ b/lifecycle/livedata-core/ktx/api/2.1.0-alpha04.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class LiveDataKt {
+ ctor public LiveDataKt();
+ method @MainThread public static inline <T> androidx.lifecycle.Observer<T> observe(androidx.lifecycle.LiveData<T>, androidx.lifecycle.LifecycleOwner owner, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onChanged);
+ }
+
+}
+
diff --git a/lifecycle/livedata-core/ktx/api/res-2.1.0-alpha04.txt b/lifecycle/livedata-core/ktx/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/livedata-core/ktx/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/livedata-core/ktx/api/restricted_2.1.0-alpha04.txt b/lifecycle/livedata-core/ktx/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/livedata-core/ktx/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/livedata/api/2.1.0-alpha04.txt b/lifecycle/livedata/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..2f5616b
--- /dev/null
+++ b/lifecycle/livedata/api/2.1.0-alpha04.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class MediatorLiveData<T> extends androidx.lifecycle.MutableLiveData<T> {
+ ctor public MediatorLiveData();
+ method @MainThread public <S> void addSource(androidx.lifecycle.LiveData<S>, androidx.lifecycle.Observer<? super S>);
+ method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S>);
+ }
+
+ public class Transformations {
+ method @MainThread public static <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
+ method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y>);
+ method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>>);
+ }
+
+}
+
diff --git a/lifecycle/livedata/api/res-2.1.0-alpha04.txt b/lifecycle/livedata/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/livedata/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/livedata/api/restricted_2.1.0-alpha04.txt b/lifecycle/livedata/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..c4aa36c
--- /dev/null
+++ b/lifecycle/livedata/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1,13 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class ComputableLiveData<T> {
+ ctor public ComputableLiveData();
+ ctor public ComputableLiveData(java.util.concurrent.Executor);
+ method @WorkerThread protected abstract T! compute();
+ method public androidx.lifecycle.LiveData<T> getLiveData();
+ method public void invalidate();
+ }
+
+}
+
diff --git a/lifecycle/livedata/eap/api/1.0.0-alpha01.txt b/lifecycle/livedata/eap/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..89968c5
--- /dev/null
+++ b/lifecycle/livedata/eap/api/1.0.0-alpha01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class CoroutineLiveDataKt {
+ ctor public CoroutineLiveDataKt();
+ method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>,?> block);
+ }
+
+ public interface LiveDataScope<T> {
+ method public T? getInitialValue();
+ method public suspend Object? yield(T! value, kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+ method public suspend Object? yieldSource(androidx.lifecycle.LiveData<T> source, kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+ property public abstract T? initialValue;
+ }
+
+}
+
diff --git a/lifecycle/livedata/eap/api/current.txt b/lifecycle/livedata/eap/api/current.txt
new file mode 100644
index 0000000..89968c5
--- /dev/null
+++ b/lifecycle/livedata/eap/api/current.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class CoroutineLiveDataKt {
+ ctor public CoroutineLiveDataKt();
+ method public static <T> androidx.lifecycle.LiveData<T> liveData(kotlin.coroutines.CoroutineContext context = EmptyCoroutineContext, long timeoutInMs = 5000L, kotlin.jvm.functions.Function2<? super androidx.lifecycle.LiveDataScope<T>,? super kotlin.coroutines.experimental.Continuation<? super kotlin.Unit>,?> block);
+ }
+
+ public interface LiveDataScope<T> {
+ method public T? getInitialValue();
+ method public suspend Object? yield(T! value, kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+ method public suspend Object? yieldSource(androidx.lifecycle.LiveData<T> source, kotlin.coroutines.experimental.Continuation<? super kotlin.Unit> p);
+ property public abstract T? initialValue;
+ }
+
+}
+
diff --git a/lifecycle/livedata/eap/api/res-1.0.0-alpha01.txt b/lifecycle/livedata/eap/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/livedata/eap/api/res-1.0.0-alpha01.txt
diff --git a/lifecycle/livedata/eap/api/restricted_1.0.0-alpha01.txt b/lifecycle/livedata/eap/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/livedata/eap/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/livedata/eap/api/restricted_current.txt b/lifecycle/livedata/eap/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/livedata/eap/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/livedata/eap/build.gradle b/lifecycle/livedata/eap/build.gradle
new file mode 100644
index 0000000..e00aabe
--- /dev/null
+++ b/lifecycle/livedata/eap/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":lifecycle:lifecycle-livedata"))
+ api(project(":lifecycle:lifecycle-livedata-core-ktx"))
+ api(KOTLIN_STDLIB)
+ api(KOTLIN_COROUTINES_CORE)
+ testImplementation(project(":lifecycle:lifecycle-runtime"))
+ testImplementation(project(":arch:core-testing"))
+ testImplementation(project(":lifecycle:lifecycle-livedata-ktx"))
+ testImplementation(JUNIT)
+ testImplementation(TRUTH)
+ testImplementation(TEST_EXT_JUNIT)
+ testImplementation(TEST_CORE)
+ testImplementation(TEST_RUNNER)
+ testImplementation(TEST_RULES)
+ testImplementation(KOTLIN_COROUTINES_TEST)
+}
+
+supportLibrary {
+ name = "LiveData Coroutine Extensions EAP"
+ publish = false
+ mavenVersion = LibraryVersions.LIFECYCLES_COROUTINES
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2018"
+ description = "Coroutines extensions for 'livedata' artifact"
+ useMetalava = true
+}
diff --git a/lifecycle/livedata/eap/src/main/AndroidManifest.xml b/lifecycle/livedata/eap/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9443d70
--- /dev/null
+++ b/lifecycle/livedata/eap/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.lifecycle.livedata.eap"/>
diff --git a/lifecycle/livedata/eap/src/main/java/androidx/lifecycle/CoroutineLiveData.kt b/lifecycle/livedata/eap/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
new file mode 100644
index 0000000..1eab6ec
--- /dev/null
+++ b/lifecycle/livedata/eap/src/main/java/androidx/lifecycle/CoroutineLiveData.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.experimental.ExperimentalTypeInference
+
+internal const val DEFAULT_TIMEOUT = 5000L
+/**
+ * Interface that allows controlling a [LiveData] from a coroutine block.
+ *
+ * @see liveData
+ */
+interface LiveDataScope<T> {
+ /**
+ * Set's the [LiveData]'s value to the given [value]. If you've called [yieldSource] previously,
+ * calling [yield] will remove that source.
+ *
+ * Note that this function suspends until the value is set on the [LiveData].
+ *
+ * @param value The new value for the [LiveData]
+ *
+ * @see yieldSource
+ */
+ suspend fun yield(value: T)
+
+ /**
+ * Add the given [LiveData] as a source, similar to [MediatorLiveData.addSource]. Calling this
+ * method will remove any source that was yielded before via [yieldSource].
+ *
+ * @param source The [LiveData] instance whose values will be dispatched from the current
+ * [LiveData].
+ *
+ * @see yield
+ * @see MediatorLiveData.addSource
+ * @see MediatorLiveData.removeSource
+ */
+ suspend fun yieldSource(source: LiveData<T>)
+
+ /**
+ * Denotes the value of the [LiveData] when this block is started.
+ *
+ * If it is the first time block is running, [initialValue] will be `null`. You can use this
+ * value to check what was then latest value `yield`ed by your `block` before it got cancelled.
+ *
+ * Note that if the block called [yieldSource], then `initialValue` will be last value
+ * dispatched by the `source` [LiveData].
+ */
+ val initialValue: T?
+}
+
+internal class LiveDataScopeImpl<T>(
+ private var target: CoroutineLiveData<T>,
+ context: CoroutineContext,
+ override val initialValue: T? = target.value
+) : LiveDataScope<T> {
+ // use `liveData` provided context + main dispatcher to communicate with the target
+ // LiveData. This gives us main thread safety as well as cancellation cooperation
+ private val coroutineContext = context + Dispatchers.Main
+
+ override suspend fun yieldSource(source: LiveData<T>) = withContext(coroutineContext) {
+ target.yieldSource(source)
+ }
+
+ override suspend fun yield(value: T) = withContext(coroutineContext) {
+ target.clearYieldedSource()
+ target.value = value
+ }
+}
+
+internal typealias Block<T> = suspend LiveDataScope<T>.() -> Unit
+
+/**
+ * Handles running a block at most once to completion.
+ */
+internal class BlockRunner<T>(
+ private val liveData: CoroutineLiveData<T>,
+ private val block: Block<T>,
+ private val timeoutInMs: Long,
+ private val scope: CoroutineScope,
+ private val onDone: () -> Unit
+) {
+ // currently running block job.
+ private var runningJob: Job? = null
+
+ // cancelation job created in cancel.
+ private var cancellationJob: Job? = null
+
+ @MainThread
+ fun maybeRun() {
+ cancellationJob?.cancel()
+ cancellationJob = null
+ if (runningJob != null) {
+ return
+ }
+ runningJob = scope.launch {
+ val liveDataScope = LiveDataScopeImpl(liveData, coroutineContext)
+ block(liveDataScope)
+ onDone()
+ }
+ }
+
+ @MainThread
+ fun cancel() {
+ if (cancellationJob != null) {
+ error("Cancel call cannot happen without a maybeRun")
+ }
+ cancellationJob = scope.launch(Dispatchers.Main) {
+ delay(timeoutInMs)
+ if (!liveData.hasActiveObservers()) {
+ // one last check on active observers to avoid any race condition between starting
+ // a running coroutine and cancelation
+ runningJob?.cancel()
+ runningJob = null
+ }
+ }
+ }
+}
+
+internal class CoroutineLiveData<T>(
+ context: CoroutineContext = EmptyCoroutineContext,
+ timeoutInMs: Long = 5000,
+ block: Block<T>
+) : MediatorLiveData<T>() {
+ private var blockRunner: BlockRunner<T>?
+
+ init {
+ // use an intermediate supervisor job so that if we cancel individual block runs due to losing
+ // observers, it won't cancel the given context as we only cancel w/ the intention of possibly
+ // relaunching using the same parent context.
+ val supervisorJob = SupervisorJob(context[Job])
+
+ // The scope for this LiveData where we launch every block Job.
+ // We default to Main dispatcher but developer can override it.
+ // The supervisor job is added last to isolate block runs.
+ val scope = CoroutineScope(Dispatchers.Main + context + supervisorJob)
+ blockRunner = BlockRunner(
+ liveData = this,
+ block = block,
+ timeoutInMs = timeoutInMs,
+ scope = scope
+ ) {
+ blockRunner = null
+ }
+ }
+
+ // The source we are delegated to, sent from LiveDataScope#yieldSource
+ // TODO We track this specifically since [MediatorLiveData] does not provide access to it.
+ // We should eventually get rid of this and provide the internal API from MediatorLiveData after
+ // the EAP.
+ private var yieldedSource: LiveData<T>? = null
+
+ @MainThread
+ internal fun yieldSource(source: LiveData<T>) {
+ clearYieldedSource()
+ yieldedSource = source
+ addSource(source) {
+ value = it
+ }
+ }
+
+ @MainThread
+ internal fun clearYieldedSource() {
+ yieldedSource?.let {
+ removeSource(it)
+ yieldedSource = null
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+ blockRunner?.maybeRun()
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ blockRunner?.cancel()
+ }
+}
+
+/**
+ * Builds a LiveData that has values yielded from the given [block] that executes on a
+ * [LiveDataScope].
+ *
+ * The [block] starts executing when the returned [LiveData] becomes active ([LiveData.onActive]).
+ * If the [LiveData] becomes inactive ([LiveData.onInactive]) while the [block] is executing, it
+ * will be cancelled after [timeoutInMs] milliseconds unless the [LiveData] becomes active again
+ * before that timeout (to gracefully handle cases like Activity rotation). Any value
+ * [LiveDataScope.yield]ed from a cancelled [block] will be ignored.
+ *
+ * After a cancellation, if the [LiveData] becomes active again, the [block] will be re-executed
+ * from the beginning. If you would like to continue the operations based on where it was stopped
+ * last, you can use the [LiveDataScope.initialValue] function to get the last
+ * [LiveDataScope.yield]ed value.
+
+ * If the [block] completes successfully *or* is cancelled due to reasons other than [LiveData]
+ * becoming inactive, it *will not* be re-executed even after [LiveData] goes through active
+ * inactive cycle.
+ *
+ * As a best practice, it is important for the [block] to cooperate in cancellation. See kotlin
+ * coroutines documentation for details
+ * https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html.
+ *
+ * ```
+ * // a simple LiveData that receives value 3, 3 seconds after being observed for the first time.
+ * val data : LiveData<Int> = liveData {
+ * delay(3000)
+ * yield(3)
+ * }
+ *
+ *
+ * // a LiveData that fetches a `User` object based on a `userId` and refreshes it every 30 seconds
+ * // as long as it is observed
+ * val userId : LiveData<String> = ...
+ * val user = userId.switchMap { id ->
+ * liveData {
+ * while(true) {
+ * // note that `while(true)` is fine because the `delay(30_000)` below will cooperate in
+ * // cancellation if LiveData is not actively observed anymore
+ * val data = api.fetch(id) // errors are ignored for brevity
+ * yield(data)
+ * delay(30_000)
+ * }
+ * }
+ * }
+ *
+ * // A retrying data fetcher with doubling back-off
+ * val user = liveData {
+ * var backOffTime = 1_000
+ * var succeeded = false
+ * while(!succeeded) {
+ * try {
+ * yield(api.fetch(id))
+ * succeeded = true
+ * } catch(ioError : IOException) {
+ * delay(backOffTime)
+ * backOffTime *= minOf(backOffTime * 2, 60_000)
+ * }
+ * }
+ * }
+ *
+ * // a LiveData that tries to load the `User` from local cache first and then tries to fetch
+ * // from the server and also yields the updated value
+ * val user = liveData {
+ * // dispatch loading first
+ * yield(LOADING(id))
+ * // check local storage
+ * val cached = cache.loadUser(id)
+ * if (cached != null) {
+ * yield(cached)
+ * }
+ * if (cached == null || cached.isStale()) {
+ * val fresh = api.fetch(id) // errors are ignored for brevity
+ * cache.save(fresh)
+ * yield(fresh)
+ * }
+ * }
+ *
+ * // a LiveData that immediately receives a LiveData<User> from the database and yields it as a
+ * // source but also tries to back-fill the database from the server
+ * val user = liveData {
+ * val fromDb: LiveData<User> = roomDatabase.loadUser(id)
+ * yieldSource(fromDb)
+ * val updated = api.fetch(id) // errors are ignored for brevity
+ * // Since we are using Room here, updating the database will update the `fromDb` LiveData
+ * // that was obtained above. See Room's documentation for more details.
+ * // https://developer.android.com/training/data-storage/room/accessing-data#query-observable
+ * roomDatabase.insert(updated)
+ * }
+ * ```
+ *
+ * @param context The CoroutineContext to run the given block in. Defaults to
+ * [EmptyCoroutineContext] combined with [Dispatchers.Main]
+ * @param timeoutInMs The timeout in ms before cancelling the block if there are no active observers
+ * ([LiveData.hasActiveObservers]. Defaults to [DEFAULT_TIMEOUT].
+ * @param block The block to run when the [LiveData] has active observers.
+ */
+@UseExperimental(ExperimentalTypeInference::class)
+fun <T> liveData(
+ context: CoroutineContext = EmptyCoroutineContext,
+ timeoutInMs: Long = DEFAULT_TIMEOUT,
+ @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
+): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)
\ No newline at end of file
diff --git a/lifecycle/livedata/eap/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt b/lifecycle/livedata/eap/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt
new file mode 100644
index 0000000..15bcff4
--- /dev/null
+++ b/lifecycle/livedata/eap/src/test/java/androidx/lifecycle/BuildLiveDataTest.kt
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.arch.core.executor.TaskExecutor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import kotlinx.coroutines.test.TestCoroutineContext
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.coroutineContext
+
+@ObsoleteCoroutinesApi
+@RunWith(JUnit4::class)
+class BuildLiveDataTest {
+ @ObsoleteCoroutinesApi
+ private val mainContext = TestCoroutineContext("test-main-context")
+ private val mainScope = CoroutineScope(mainContext)
+ @ObsoleteCoroutinesApi
+ private val testContext = TestCoroutineContext("test-other-context")
+
+ @ExperimentalCoroutinesApi
+ @Before
+ fun initMain() {
+ lateinit var mainThread: Thread
+ runBlocking(mainContext) {
+ mainThread = Thread.currentThread()
+ }
+ Dispatchers.setMain(
+ mainContext[ContinuationInterceptor.Key] as CoroutineDispatcher
+ )
+ ArchTaskExecutor.getInstance().setDelegate(
+ object : TaskExecutor() {
+ override fun executeOnDiskIO(runnable: Runnable) {
+ error("unsupported")
+ }
+
+ override fun postToMainThread(runnable: Runnable) {
+ mainScope.launch {
+ runnable.run()
+ }
+ }
+
+ override fun isMainThread(): Boolean {
+ return mainThread == Thread.currentThread()
+ }
+ }
+ )
+ }
+
+ @ExperimentalCoroutinesApi
+ @After
+ fun clear() {
+ advanceTimeBy(100000)
+ mainContext.assertExceptions("shouldn't have any exceptions") {
+ it.isEmpty()
+ }
+ testContext.assertExceptions("shouldn't have any exceptions") {
+ it.isEmpty()
+ }
+ ArchTaskExecutor.getInstance().setDelegate(null)
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun oneShot() {
+ val liveData = liveData {
+ yield(3)
+ }
+ triggerAllActions()
+ assertThat(liveData.value).isNull()
+ liveData.addObserver().assertItems(3)
+ }
+
+ @Test
+ fun removeObserverInBetween() {
+ val ld = liveData(timeoutInMs = 10) {
+ yield(1)
+ yield(2)
+ delay(1000)
+ yield(3)
+ }
+ ld.addObserver().apply {
+ assertItems(1, 2)
+ unsubscribe()
+ }
+ // trigger cancellation
+ mainContext.advanceTimeBy(100)
+ assertThat(ld.hasActiveObservers()).isFalse()
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems(2, 1, 2)
+ mainContext.advanceTimeBy(1001)
+ assertItems(2, 1, 2, 3)
+ }
+ }
+
+ @Test
+ fun removeObserverInBetween_largeTimeout() {
+ val ld = liveData(timeoutInMs = 10000) {
+ yield(1)
+ yield(2)
+ delay(1000)
+ yield(3)
+ }
+ ld.addObserver().apply {
+ assertItems(1, 2)
+ unsubscribe()
+ }
+ // advance some but not enough to cover the delay
+ mainContext.advanceTimeBy(500)
+ assertThat(ld.hasActiveObservers()).isFalse()
+ assertThat(ld.value).isEqualTo(2)
+ ld.addObserver().apply {
+ assertItems(2)
+ // adnvace enough to cover the rest of the delay
+ mainContext.advanceTimeBy(501)
+ assertItems(2, 3)
+ }
+ }
+
+ @Test
+ fun ignoreCancelledYields() {
+ val cancelMutex = Mutex(true)
+ val ld = liveData(timeoutInMs = 0, context = testContext) {
+ yield(1)
+ cancelMutex.withLock {
+ yield(2)
+ }
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems(1)
+ unsubscribe()
+ cancelMutex.unlock()
+ }
+ // let cancellation take place
+ triggerAllActions()
+ // yield should immediately trigger cancellation to happen
+ assertThat(ld.value).isEqualTo(1)
+ assertThat(ld.hasActiveObservers()).isFalse()
+ // now because it was cancelled, re-observing should dispatch 1,1,2
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems(1, 1, 2)
+ }
+ }
+
+ @Test
+ fun readInitialValue() {
+ val initial = AtomicReference<Int?>()
+ val ld = liveData<Int>(testContext) {
+ initial.set(initialValue)
+ }
+ runOnMain {
+ ld.value = 3
+ }
+ ld.addObserver()
+ triggerAllActions()
+ assertThat(initial.get()).isEqualTo(3)
+ }
+
+ @Test
+ fun readInitialValue_ignoreYielded() {
+ val initial = AtomicReference<Int?>()
+ val ld = liveData<Int>(testContext) {
+ yield(5)
+ initial.set(initialValue)
+ }
+ ld.addObserver()
+ triggerAllActions()
+ assertThat(initial.get()).isNull()
+ }
+
+ @Test
+ fun readInitialValue_keepYieldedFromBefore() {
+ val initial = AtomicReference<Int?>()
+ val ld = liveData<Int>(testContext, 10) {
+ if (initialValue == null) {
+ yield(5)
+ delay(500000) // wait for cancellation
+ }
+
+ initial.set(initialValue)
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems(5)
+ unsubscribe()
+ }
+ triggerAllActions()
+ // wait for it to be cancelled
+ advanceTimeBy(10)
+ assertThat(initial.get()).isNull()
+ ld.addObserver()
+ triggerAllActions()
+ assertThat(initial.get()).isEqualTo(5)
+ }
+
+ @Test
+ fun yieldSource_simple() {
+ val odds = liveData {
+ (1..9 step 2).forEach {
+ yield(it)
+ }
+ }
+ val ld = liveData {
+ yieldSource(odds)
+ }
+ ld.addObserver().apply {
+ assertItems(1, 3, 5, 7, 9)
+ }
+ }
+
+ @Test
+ fun yieldSource_switchTwo() {
+ val doneOddsYield = Mutex(true)
+ val odds = liveData {
+ (1..9 step 2).forEach {
+ yield(it)
+ }
+ doneOddsYield.unlock()
+ delay(1)
+ yield(-1)
+ }
+ val evens = liveData {
+ (2..10 step 2).forEach {
+ yield(it)
+ }
+ }
+ val ld = liveData(testContext) {
+ yieldSource(odds)
+ doneOddsYield.lock()
+ yieldSource(evens)
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems(1, 3, 5, 7, 9, 2, 4, 6, 8, 10)
+ }
+ }
+
+ @Test
+ fun yieldSource_yieldValue() {
+ val doneOddsYield = Mutex(true)
+ val odds = liveData(timeoutInMs = 0) {
+ (1..9 step 2).forEach {
+ yield(it)
+ }
+ doneOddsYield.unlock()
+ delay(1)
+ yield(-1)
+ }
+ val ld = liveData(testContext) {
+ yieldSource(odds)
+ doneOddsYield.lock()
+ yield(10)
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ advanceTimeBy(100)
+ assertItems(1, 3, 5, 7, 9, 10)
+ }
+ }
+
+ @Test
+ fun blockThrows() {
+ // use an exception handler instead of the test context exception handler to ensure that
+ // we do not re-run the block if its exception is gracefully caught
+ // TODO should we consider doing that ? But if we do, what is the rule? do we retry when
+ // it becomes active again or do we retry ourselves? better no do anything to be consistent.
+ val exception = CompletableDeferred<Throwable>()
+ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+ exception.complete(throwable)
+ }
+ val ld = liveData(testContext + exceptionHandler, 10) {
+ if (exception.isActive) {
+ throw IllegalArgumentException("i like to fail")
+ } else {
+ yield(3)
+ }
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems()
+ runBlocking {
+ assertThat(exception.await()).hasMessageThat().contains("i like to fail")
+ }
+ unsubscribe()
+ }
+ triggerAllActions()
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems()
+ }
+ }
+
+ @Test
+ fun blockCancelsItself() {
+ val didCancel = AtomicBoolean(false)
+ val unexpected = AtomicBoolean(false)
+
+ val ld = liveData<Int>(testContext, 10) {
+ if (didCancel.compareAndSet(false, true)) {
+ coroutineContext.cancel()
+ } else {
+ unexpected.set(true)
+ }
+ }
+ ld.addObserver().apply {
+ triggerAllActions()
+ assertItems()
+ unsubscribe()
+ }
+ assertThat(didCancel.get()).isTrue()
+ ld.addObserver()
+ // trigger cancelation
+ advanceTimeBy(11)
+ assertThat(unexpected.get()).isFalse()
+ }
+
+ @Test
+ fun blockThrows_switchMap() {
+ val exception = CompletableDeferred<Throwable>()
+ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+ exception.complete(throwable)
+ }
+ val src = MutableLiveData<Int>()
+ val ld = src.switchMap {
+ liveData(testContext + exceptionHandler) {
+ if (exception.isActive) {
+ throw IllegalArgumentException("i like to fail")
+ } else {
+ yield(3)
+ }
+ }
+ }
+ ld.addObserver().apply {
+ assertItems()
+ runOnMain {
+ src.value = 1
+ }
+ triggerAllActions()
+ runBlocking {
+ assertThat(exception.await()).hasMessageThat().contains("i like to fail")
+ }
+ runOnMain {
+ src.value = 2
+ }
+ triggerAllActions()
+ assertItems(3)
+ }
+ }
+
+ private fun triggerAllActions() {
+ do {
+ mainContext.triggerActions()
+ testContext.triggerActions()
+ val allIdle = listOf(mainContext, testContext).all {
+ it.isIdle()
+ }
+ } while (!allIdle)
+ }
+
+ private fun advanceTimeBy(time: Long) {
+ mainContext.advanceTimeBy(time)
+ testContext.advanceTimeBy(time)
+ triggerAllActions()
+ }
+
+ private fun TestCoroutineContext.isIdle(): Boolean {
+ val queueField = this::class.java
+ .getDeclaredField("queue")
+ queueField.isAccessible = true
+ val queue = queueField.get(this)
+ val peekMethod = queue::class.java
+ .getDeclaredMethod("peek")
+ val nextTask = peekMethod.invoke(queue) ?: return true
+ val timeField = nextTask::class.java.getDeclaredField("time")
+ timeField.isAccessible = true
+ val time = timeField.getLong(nextTask)
+ return time > now()
+ }
+
+ private fun <T> runOnMain(block: () -> T): T {
+ return runBlocking {
+ val async = mainScope.async {
+ block()
+ }
+ mainContext.triggerActions()
+ async.await()
+ }
+ }
+
+ private fun <T> LiveData<T>.addObserver(): CollectingObserver<T> {
+ return runOnMain {
+ val observer = CollectingObserver(this)
+ observeForever(observer)
+ observer
+ }
+ }
+
+ @Test
+ fun multipleValuesAndObservers() {
+ val ld = liveData {
+ yield(3)
+ yield(4)
+ }
+ ld.addObserver().assertItems(3, 4)
+ // re-observe, get latest value only
+ ld.addObserver().assertItems(4)
+ }
+
+ inner class CollectingObserver<T>(
+ private val liveData: LiveData<T>
+ ) : Observer<T> {
+ private var items = mutableListOf<T>()
+ override fun onChanged(t: T) {
+ items.add(t)
+ }
+
+ fun assertItems(vararg expected: T) {
+ assertThat(items).containsExactly(*expected)
+ }
+
+ fun unsubscribe() = runOnMain {
+ liveData.removeObserver(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/livedata/ktx/api/2.1.0-alpha04.txt b/lifecycle/livedata/ktx/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..a21c943
--- /dev/null
+++ b/lifecycle/livedata/ktx/api/2.1.0-alpha04.txt
@@ -0,0 +1,12 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class TransformationsKt {
+ ctor public TransformationsKt();
+ method public static inline <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
+ method public static inline <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends Y> transform);
+ method public static inline <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends androidx.lifecycle.LiveData<Y>> transform);
+ }
+
+}
+
diff --git a/lifecycle/livedata/ktx/api/res-2.1.0-alpha04.txt b/lifecycle/livedata/ktx/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/livedata/ktx/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/livedata/ktx/api/restricted_2.1.0-alpha04.txt b/lifecycle/livedata/ktx/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/livedata/ktx/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/process/api/2.1.0-alpha04.txt b/lifecycle/process/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..beea619
--- /dev/null
+++ b/lifecycle/process/api/2.1.0-alpha04.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
+ method public static androidx.lifecycle.LifecycleOwner get();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ }
+
+}
+
diff --git a/lifecycle/process/api/res-2.1.0-alpha04.txt b/lifecycle/process/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/process/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/process/api/restricted_2.1.0-alpha04.txt b/lifecycle/process/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..eab2976
--- /dev/null
+++ b/lifecycle/process/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1,15 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ProcessLifecycleOwnerInitializer extends android.content.ContentProvider {
+ ctor public ProcessLifecycleOwnerInitializer();
+ method public int delete(android.net.Uri, String!, String[]!);
+ method public String? getType(android.net.Uri);
+ method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues!);
+ method public boolean onCreate();
+ method public android.database.Cursor? query(android.net.Uri, String[]!, String!, String[]!, String!);
+ method public int update(android.net.Uri, android.content.ContentValues!, String!, String[]!);
+ }
+
+}
+
diff --git a/lifecycle/reactivestreams/api/2.1.0-alpha04.txt b/lifecycle/reactivestreams/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..f3d107a
--- /dev/null
+++ b/lifecycle/reactivestreams/api/2.1.0-alpha04.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class LiveDataReactiveStreams {
+ method public static <T> androidx.lifecycle.LiveData<T> fromPublisher(org.reactivestreams.Publisher<T>);
+ method public static <T> org.reactivestreams.Publisher<T> toPublisher(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.LiveData<T>);
+ }
+
+}
+
diff --git a/lifecycle/reactivestreams/api/res-2.1.0-alpha04.txt b/lifecycle/reactivestreams/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/reactivestreams/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/reactivestreams/api/restricted_2.1.0-alpha04.txt b/lifecycle/reactivestreams/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/reactivestreams/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/reactivestreams/ktx/api/2.1.0-alpha04.txt b/lifecycle/reactivestreams/ktx/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..659d07d
--- /dev/null
+++ b/lifecycle/reactivestreams/ktx/api/2.1.0-alpha04.txt
@@ -0,0 +1,11 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class LiveDataReactiveSteamsKt {
+ ctor public LiveDataReactiveSteamsKt();
+ method public static inline <T> androidx.lifecycle.LiveData<T> toLiveData(org.reactivestreams.Publisher<T>);
+ method public static inline <T> org.reactivestreams.Publisher<T> toPublisher(androidx.lifecycle.LiveData<T>, androidx.lifecycle.LifecycleOwner lifecycle);
+ }
+
+}
+
diff --git a/lifecycle/reactivestreams/ktx/api/res-2.1.0-alpha04.txt b/lifecycle/reactivestreams/ktx/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/reactivestreams/ktx/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/reactivestreams/ktx/api/restricted_2.1.0-alpha04.txt b/lifecycle/reactivestreams/ktx/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/reactivestreams/ktx/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/runtime/api/2.1.0-alpha04.txt b/lifecycle/runtime/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..0a652c0
--- /dev/null
+++ b/lifecycle/runtime/api/2.1.0-alpha04.txt
@@ -0,0 +1,20 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class LifecycleRegistry extends androidx.lifecycle.Lifecycle {
+ ctor public LifecycleRegistry(androidx.lifecycle.LifecycleOwner);
+ method public void addObserver(androidx.lifecycle.LifecycleObserver);
+ method public androidx.lifecycle.Lifecycle.State getCurrentState();
+ method public int getObserverCount();
+ method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event);
+ method @Deprecated @MainThread public void markState(androidx.lifecycle.Lifecycle.State);
+ method public void removeObserver(androidx.lifecycle.LifecycleObserver);
+ method @MainThread public void setCurrentState(androidx.lifecycle.Lifecycle.State);
+ }
+
+ @Deprecated public interface LifecycleRegistryOwner extends androidx.lifecycle.LifecycleOwner {
+ method @Deprecated public androidx.lifecycle.LifecycleRegistry getLifecycle();
+ }
+
+}
+
diff --git a/lifecycle/runtime/api/res-2.1.0-alpha04.txt b/lifecycle/runtime/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/runtime/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/runtime/api/restricted_2.1.0-alpha04.txt b/lifecycle/runtime/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..c79f82d
--- /dev/null
+++ b/lifecycle/runtime/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1,10 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ReportFragment extends android.app.Fragment {
+ ctor public ReportFragment();
+ method public static void injectIfNeededIn(android.app.Activity!);
+ }
+
+}
+
diff --git a/lifecycle/runtime/eap/README.md b/lifecycle/runtime/eap/README.md
new file mode 100644
index 0000000..1f4c9e7
--- /dev/null
+++ b/lifecycle/runtime/eap/README.md
@@ -0,0 +1,2 @@
+This is a temporary project for the coroutines EAP.
+Contents of this folder will be merged into ktx.
diff --git a/lifecycle/runtime/eap/api/1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..09e9ab4
--- /dev/null
+++ b/lifecycle/runtime/eap/api/1.0.0-alpha01.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class PausingDispatcherKt {
+ ctor public PausingDispatcherKt();
+ method public static suspend <T> Object? whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super error.NonExistentClass> p);
+ }
+
+}
+
diff --git a/lifecycle/runtime/eap/api/current.txt b/lifecycle/runtime/eap/api/current.txt
new file mode 100644
index 0000000..09e9ab4
--- /dev/null
+++ b/lifecycle/runtime/eap/api/current.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class PausingDispatcherKt {
+ ctor public PausingDispatcherKt();
+ method public static suspend <T> Object? whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super T> p);
+ method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super T>,?> block, kotlin.coroutines.experimental.Continuation<? super error.NonExistentClass> p);
+ }
+
+}
+
diff --git a/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/runtime/eap/api/res-1.0.0-alpha01.txt
diff --git a/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt b/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/runtime/eap/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/runtime/eap/api/restricted_current.txt b/lifecycle/runtime/eap/api/restricted_current.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/runtime/eap/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/runtime/eap/build.gradle b/lifecycle/runtime/eap/build.gradle
new file mode 100644
index 0000000..2ce5f78
--- /dev/null
+++ b/lifecycle/runtime/eap/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":lifecycle:lifecycle-common-eap"))
+ api(project(":lifecycle:lifecycle-common"))
+ api(KOTLIN_STDLIB)
+ api(KOTLIN_COROUTINES)
+ implementation(SUPPORT_ANNOTATIONS)
+
+ testImplementation(JUNIT)
+ testImplementation(TEST_EXT_JUNIT)
+ testImplementation(TEST_CORE)
+ testImplementation(TEST_RUNNER)
+ testImplementation(TRUTH)
+
+ androidTestImplementation project(':lifecycle:lifecycle-runtime')
+ androidTestImplementation(TRUTH)
+ androidTestImplementation(TEST_EXT_JUNIT)
+ androidTestImplementation(TEST_CORE)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(KOTLIN_COROUTINES_TEST)
+
+}
+
+supportLibrary {
+ name = "Android Lifecycle Kotlin Extensions"
+ publish = false
+ mavenVersion = LibraryVersions.LIFECYCLES_COROUTINES
+ mavenGroup = LibraryGroups.LIFECYCLE
+ inceptionYear = "2019"
+ description = "Kotlin extensions for 'lifecycle' artifact"
+ useMetalava = true
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt
new file mode 100644
index 0000000..395a840
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/Expectations.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import com.google.common.truth.Truth
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Partial copy from
+ * https://github.com/Kotlin/kotlinx.coroutines/blob/master/core/kotlinx-coroutines-core/test/TestBase.kt
+ * to track execution order.
+ */
+class Expectations {
+ private var counter = AtomicInteger(0)
+
+ fun expect(expected: Int) {
+ val order = counter.incrementAndGet()
+ Truth.assertThat(order).isEqualTo(expected)
+ }
+
+ fun expectUnreached() {
+ throw AssertionError("should've not reached here")
+ }
+
+ fun expectTotal(total: Int) {
+ Truth.assertThat(counter.get()).isEqualTo(total)
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
new file mode 100644
index 0000000..cd93f87
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+
+class FakeLifecycleOwner(initialState: Lifecycle.State? = null) : LifecycleOwner {
+ private val registry: LifecycleRegistry = LifecycleRegistry(this)
+
+ init {
+ initialState?.let {
+ setState(it)
+ }
+ }
+
+ override fun getLifecycle(): Lifecycle = registry
+
+ fun setState(state: Lifecycle.State) {
+ registry.markState(state)
+ }
+
+ suspend fun awaitExactObserverCount(count: Int, timeout: Long = 1000L): Boolean =
+ // just give job some time to start
+ withTimeoutOrNull(timeout) {
+ while (getObserverCount(count) != count) {
+ delay(50)
+ }
+ true
+ } ?: false
+
+ private suspend fun getObserverCount(count: Int): Int {
+ return withContext(Dispatchers.Main) {
+ registry.observerCount
+ }
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt
new file mode 100644
index 0000000..ed241df
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/PausingDispatcherTest.kt
@@ -0,0 +1,511 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.coroutines.yield
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+@InternalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PausingDispatcherTest {
+ // TODO update custom dispatchers with the new TestCoroutineContext once available
+ // https://github.com/Kotlin/kotlinx.coroutines/pull/890
+ private val taskTracker = TaskTracker()
+ // track uncaught exceptions on the test scope
+ private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+ testError = testError ?: throwable
+ }
+ // did we hit any error in the test scope
+ private var testError: Throwable? = null
+ // the executor for the testing scope that uses a different thread pool
+ private val testExecutor = TrackedExecutor(taskTracker, Executors.newFixedThreadPool(4))
+ private val testingScope =
+ CoroutineScope(testExecutor.asCoroutineDispatcher() + Job(null) + exceptionHandler)
+ private val owner = FakeLifecycleOwner(Lifecycle.State.RESUMED)
+ private val mainExecutor = TrackedExecutor(taskTracker, Executors.newSingleThreadExecutor())
+ // tracks execution order
+ private val expectations = Expectations()
+ private lateinit var mainThread: Thread
+
+ @ExperimentalCoroutinesApi
+ @Before
+ fun updateMainHandlerAndDispatcher() {
+ Dispatchers.setMain(mainExecutor.asCoroutineDispatcher())
+ runBlocking(Dispatchers.Main) {
+ // extract the main thread to field for assertions
+ mainThread = Thread.currentThread()
+ }
+ }
+
+ @ExperimentalCoroutinesApi
+ @After
+ fun clearHandlerAndDispatcher() {
+ waitTestingScopeChildren()
+ assertThat(mainExecutor.shutdown(10, TimeUnit.SECONDS)).isTrue()
+ assertThat(testExecutor.shutdown(10, TimeUnit.SECONDS)).isTrue()
+ assertThat(taskTracker.awaitIdle(10, TimeUnit.SECONDS)).isTrue()
+ Dispatchers.resetMain()
+ }
+
+ /**
+ * Ensure nothing in the testing scope is left behind w/o assertions
+ */
+ private fun waitTestingScopeChildren() {
+ runBlocking {
+ val testJob = testingScope.coroutineContext[Job]!!
+ do {
+ val children = testJob.children.toList()
+ assertThat(children.all {
+ withTimeoutOrNull(10_000) {
+ it.join()
+ true
+ } ?: false
+ })
+ } while (children.isNotEmpty())
+ assertThat(testJob.isActive)
+ assertThat(testError).isNull()
+ }
+ }
+
+ @Test
+ fun basic() {
+ val result = runBlocking {
+ owner.whenResumed {
+ assertThread()
+ 3
+ }
+ }
+ assertThat(result).isEqualTo(3)
+ }
+
+ @Test
+ fun yieldTest() {
+ runBlocking(Dispatchers.Main) {
+ owner.whenResumed {
+ expectations.expect(1)
+ launch {
+ expectations.expect(3)
+ yield()
+ expectations.expect(5)
+ }
+ expectations.expect(2)
+ launch {
+ expectations.expect(4)
+ }
+ }
+ expectations.expectTotal(5)
+ }
+ }
+
+ @Test
+ fun runInsideMain() {
+ val res = runBlocking(Dispatchers.Main) {
+ owner.whenResumed {
+ 2
+ }
+ }
+ assertThat(res).isEqualTo(2)
+ }
+
+ @Test
+ fun moveToAnotherDispatcher() {
+ val result = runBlocking {
+ owner.whenResumed {
+ assertThread()
+ val innerResult = withContext(testingScope.coroutineContext) {
+ log("running inner")
+ "hello"
+ }
+ assertThread()
+ log("received inner result $innerResult")
+ innerResult + innerResult
+ }
+ }
+ assertThat(result).isEqualTo("hellohello")
+ }
+
+ @Test
+ fun cancel() {
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ try {
+ expectations.expect(1)
+ delay(5000)
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(2)
+ }
+ }
+ }
+ drain()
+ expectations.expectTotal(1)
+ job.cancelAndJoin()
+ expectations.expectTotal(2)
+ }
+ }
+
+ @Test
+ fun throwException_thenRunAnother() {
+ runBlocking(testingScope.coroutineContext) {
+ try {
+ owner.whenResumed {
+ assertThread()
+ expectations.expect(1)
+ throw IllegalArgumentException(" fail")
+ }
+ @Suppress("UNREACHABLE_CODE")
+ expectations.expectUnreached()
+ } catch (ignored: IllegalArgumentException) {
+ }
+ owner.whenResumed {
+ expectations.expect(2)
+ }
+ }
+ expectations.expectTotal(2)
+ }
+
+ @Test
+ fun innerThrowException() {
+ runBlocking {
+ val job = testingScope.launch {
+ val res = runCatching {
+ owner.whenResumed {
+ try {
+ expectations.expect(1)
+ withContext(testingScope.coroutineContext) {
+ throw IllegalStateException("i fail")
+ }
+ @Suppress("UNREACHABLE_CODE")
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(2)
+ }
+ @Suppress("UNREACHABLE_CODE")
+ expectations.expectUnreached()
+ }
+ }
+ assertThat(res.exceptionOrNull()).hasMessageThat().isEqualTo("i fail")
+ }
+ job.join()
+ expectations.expectTotal(2)
+ }
+ }
+
+ @Test
+ fun pause_thenResume() {
+ pause()
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ expectations.expect(1)
+ }
+ }
+ drain()
+ expectations.expectTotal(0)
+ resume()
+ job.join()
+ expectations.expectTotal(1)
+ }
+ }
+
+ @Test
+ fun pause_thenFinish() {
+ pause()
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ try {
+ expectations.expectUnreached()
+ } finally {
+ expectations.expectUnreached()
+ }
+ }
+ }
+ drain()
+ expectations.expectTotal(0)
+ finish()
+ job.join()
+ // never started so shouldn't run finally either
+ expectations.expectTotal(0)
+ }
+ }
+
+ @Test
+ fun finishWhileDelayed() {
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ try {
+ expectations.expect(1)
+ delay(100000)
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(2)
+ assertThat(isActive).isFalse()
+ }
+ }
+ }
+ drain()
+ expectations.expectTotal(1)
+ finish()
+ job.join()
+ expectations.expectTotal(2)
+ }
+ }
+
+ @Test
+ fun innerScopeFailure() {
+ runBlocking {
+ owner.whenResumed {
+ val error = CompletableDeferred<Throwable>()
+ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+ error.complete(throwable)
+ }
+ launch(Job() + exceptionHandler) {
+ throw IllegalStateException("i fail")
+ }
+ val a2 = async {
+ expectations.expect(1)
+ }
+ assertThat(error.await()).hasMessageThat().contains("i fail")
+ a2.await()
+ }
+ expectations.expectTotal(1)
+ }
+ }
+
+ @Test
+ fun alreadyFinished() {
+ runBlocking {
+ finish()
+ launch {
+ owner.whenResumed {
+ expectations.expectUnreached()
+ }
+ }.join()
+ expectations.expectTotal(0)
+ }
+ }
+
+ @Test
+ fun catchFinishWhileDelayed() {
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ try {
+ expectations.expect(1)
+ delay(100000)
+ expectations.expectUnreached()
+ } catch (e: Exception) {
+ expectations.expect(2)
+ assertThat(isActive).isFalse()
+ } finally {
+ expectations.expect(3)
+ }
+ expectations.expect(4)
+ }
+ }
+ drain()
+ expectations.expectTotal(1)
+ finish()
+ job.join()
+ expectations.expectTotal(4)
+ }
+ }
+
+ @Test
+ fun pauseThenContinue() {
+ runBlocking {
+ val job = testingScope.launch {
+ owner.whenResumed {
+ expectations.expect(1)
+ withContext(testingScope.coroutineContext) {
+ pause()
+ }
+ expectations.expect(2)
+ }
+ expectations.expect(3)
+ }
+ drain()
+ expectations.expectTotal(1)
+ assertThat(owner.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ resume()
+ job.join()
+ expectations.expectTotal(3)
+ }
+ }
+
+ @Test
+ fun parentJobCancelled() {
+ runBlocking {
+ val parent = testingScope.launch {
+ owner.whenResumed {
+ try {
+ expectations.expect(1)
+ delay(5000)
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(2)
+ }
+ }
+ }
+ drain()
+ expectations.expectTotal(1)
+ parent.cancelAndJoin()
+ expectations.expectTotal(2)
+ }
+ }
+
+ @Test
+ fun innerJobCancelsParent() {
+ try {
+ runBlocking(testingScope.coroutineContext) {
+ owner.whenResumed {
+ throw IllegalStateException("i fail")
+ }
+ }
+ @Suppress("UNREACHABLE_CODE")
+ expectations.expectUnreached()
+ } catch (ex: IllegalStateException) {
+ assertThat(ex).hasMessageThat().isEqualTo("i fail")
+ }
+ }
+
+ @Test
+ fun lifecycleInsideLifecycle() {
+ runBlocking {
+ owner.whenResumed {
+ assertThread()
+ expectations.expect(1)
+ owner.whenResumed {
+ assertThread()
+ expectations.expect(2)
+ }
+ }
+ }
+ expectations.expectTotal(2)
+ }
+
+ @Test
+ fun lifecycleInsideLifecycle_innerFails() {
+ runBlocking {
+ val res = runCatching {
+ owner.whenResumed {
+ try {
+ assertThread()
+ expectations.expect(1)
+ owner.whenResumed {
+ assertThread()
+ expectations.expect(2)
+ try {
+ withContext(testingScope.coroutineContext) {
+ throw IllegalStateException("i fail")
+ }
+ @Suppress("UNREACHABLE_CODE")
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(3)
+ }
+ }
+ expectations.expectUnreached()
+ } finally {
+ expectations.expect(4)
+ }
+ }
+ }
+ assertThat(res.exceptionOrNull()).hasMessageThat().matches("i fail")
+ }
+ expectations.expectTotal(4)
+ }
+
+ @Test
+ fun cancelInnerCoroutine() {
+ runBlocking {
+ val job = launch {
+ owner.whenResumed {
+ withContext(testingScope.coroutineContext) {
+ delay(200_000)
+ expectations.expectUnreached()
+ }
+ expectations.expectUnreached()
+ }
+ }
+ job.cancelAndJoin()
+ expectations.expectTotal(0)
+ }
+ }
+
+ private fun pause() {
+ runBlocking(Dispatchers.Main) {
+ owner.setState(Lifecycle.State.STARTED)
+ }
+ }
+
+ private fun finish() {
+ runBlocking(Dispatchers.Main) {
+ owner.setState(Lifecycle.State.DESTROYED)
+ }
+ }
+
+ private fun resume() {
+ runBlocking(Dispatchers.Main) {
+ owner.setState(Lifecycle.State.RESUMED)
+ }
+ }
+
+ private fun assertThread() {
+ log("asserting looper")
+ assertThat(Thread.currentThread()).isSameAs(mainThread)
+ }
+
+ private fun log(msg: Any?) {
+ Log.d("TEST-RUN", "[${Thread.currentThread().name}] $msg")
+ }
+
+ private fun drain() {
+ assertThat(taskTracker.awaitIdle(10, TimeUnit.SECONDS)).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt
new file mode 100644
index 0000000..f56e505
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TaskTracker.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
+
+/**
+ * A simple counter on which we can await 0.
+ */
+class TaskTracker : TrackedExecutor.Callback {
+ private val lock = ReentrantLock()
+ private val idle = lock.newCondition()
+ private var counter = 0
+
+ override fun inc() {
+ lock.withLock {
+ counter++
+ }
+ }
+
+ override fun dec() {
+ lock.withLock {
+ counter--
+ if (counter == 0) {
+ idle.signalAll()
+ }
+ }
+ }
+
+ fun awaitIdle(time: Long, timeUnit: TimeUnit): Boolean {
+ lock.withLock {
+ if (counter == 0) {
+ return true
+ }
+ return idle.await(time, timeUnit)
+ }
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt
new file mode 100644
index 0000000..d33030c
--- /dev/null
+++ b/lifecycle/runtime/eap/src/androidTest/java/androidx/lifecycle/TrackedExecutor.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.TimeUnit
+
+/**
+ * An executor wrapper that tracks active tasks and reports back to a Callback when it changes.
+ */
+class TrackedExecutor(
+ private val callback: Callback,
+ private val delegate: ExecutorService
+) : Executor {
+ override fun execute(runnable: Runnable) {
+ callback.inc()
+ delegate.execute {
+ try {
+ runnable.run()
+ } finally {
+ callback.dec()
+ }
+ }
+ }
+
+ fun shutdown(time: Long, unit: TimeUnit): Boolean {
+ delegate.shutdown()
+ return delegate.awaitTermination(time, unit)
+ }
+
+ interface Callback {
+ fun inc()
+ fun dec()
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/main/AndroidManifest.xml b/lifecycle/runtime/eap/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c2bf90
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.lifecycle.ktx">
+</manifest>
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt
new file mode 100644
index 0000000..87e3d08
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/DispatchQueue.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import android.annotation.SuppressLint
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import kotlinx.coroutines.Dispatchers
+import java.util.ArrayDeque
+import java.util.Queue
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * Helper class for [PausingDispatcher] that tracks runnables which are enqueued to the dispatcher
+ * and also calls back the [PausingDispatcher] when the runnable should run.
+ */
+internal class DispatchQueue {
+ // handler thread
+ private var paused: Boolean = false
+ // handler thread
+ private var finished: Boolean = false
+
+ private val queue: Queue<Runnable> = ArrayDeque<Runnable>()
+
+ private val consumer = Runnable {
+ // this one runs inside Dispatchers.Main
+ // if it should run, grabs an item, runs it
+ // if it has more, will re-enqueue
+ // To avoid starving Dispatchers.Main, we don't consume more than 1
+ if (!canRun()) {
+ return@Runnable
+ }
+ val next = queue.poll() ?: return@Runnable
+ try {
+ next.run()
+ } finally {
+ maybeEnqueueConsumer()
+ }
+ }
+
+ @MainThread
+ fun pause() {
+ paused = true
+ }
+
+ @MainThread
+ fun resume() {
+ if (!paused) {
+ return
+ }
+ check(!finished) {
+ "Cannot resume a finished dispatcher"
+ }
+ paused = false
+ maybeEnqueueConsumer()
+ }
+
+ @MainThread
+ fun finish() {
+ finished = true
+ maybeEnqueueConsumer()
+ }
+
+ @MainThread
+ fun maybeEnqueueConsumer() {
+ if (queue.isNotEmpty()) {
+ Dispatchers.Main.dispatch(EmptyCoroutineContext, consumer)
+ }
+ }
+
+ @MainThread
+ private fun canRun() = finished || !paused
+
+ @AnyThread
+ @SuppressLint("WrongThread") // false negative, we are checking the thread
+ fun runOrEnqueue(runnable: Runnable) {
+ Dispatchers.Main.immediate.dispatch(EmptyCoroutineContext, Runnable {
+ enqueue(runnable)
+ })
+ }
+
+ @MainThread
+ private fun enqueue(runnable: Runnable) {
+ check(queue.offer(runnable)) {
+ "cannot enqueue any more runnables"
+ }
+ maybeEnqueueConsumer()
+ }
+}
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt
new file mode 100644
index 0000000..4edc2fb
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/LifecycleController.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import androidx.annotation.MainThread
+import kotlinx.coroutines.Job
+
+/**
+ * Attaches to a lifecycle and controls the [DispatchQueue]'s execution.
+ */
+@MainThread
+internal class LifecycleController(
+ private val lifecycle: Lifecycle,
+ private val minState: Lifecycle.State,
+ private val dispatchQueue: DispatchQueue,
+ parentJob: Job
+) {
+ private val observer = LifecycleEventObserver { source, _ ->
+ if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
+ // cancel job before resuming remaining coroutines so that they run in cancelled
+ // state
+ handleDestroy(parentJob)
+ } else if (source.lifecycle.currentState < minState) {
+ dispatchQueue.pause()
+ } else {
+ dispatchQueue.resume()
+ }
+ }
+
+ init {
+ // If Lifecycle is already destroyed (e.g. developer leaked the lifecycle), we won't get
+ // an event callback so we need to check for it before registering
+ // see: b/128749497 for details.
+ if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
+ handleDestroy(parentJob)
+ } else {
+ lifecycle.addObserver(observer)
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE") // avoid unnecessary method
+ private inline fun handleDestroy(parentJob: Job) {
+ parentJob.cancel()
+ finish()
+ }
+
+ /**
+ * Removes the observer and also marks the [DispatchQueue] as finished so that any remaining
+ * runnables can be executed.
+ */
+ @MainThread
+ fun finish() {
+ lifecycle.removeObserver(observer)
+ dispatchQueue.finish()
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt
new file mode 100644
index 0000000..d40e75d
--- /dev/null
+++ b/lifecycle/runtime/eap/src/main/java/androidx/lifecycle/PausingDispatcher.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.lifecycle
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.Runnable
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.CoroutineContext
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.CREATED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenCreated(block: suspend CoroutineScope.() -> T): T =
+ lifecycle.whenCreated(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.CREATED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenCreated(block: suspend CoroutineScope.() -> T): T {
+ return whenStateAtLeast(Lifecycle.State.CREATED, block)
+}
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.STARTED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenStarted(block: suspend CoroutineScope.() -> T): T =
+ lifecycle.whenStarted(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.STARTED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenStarted(block: suspend CoroutineScope.() -> T): T {
+ return whenStateAtLeast(Lifecycle.State.STARTED, block)
+}
+
+/**
+ * Runs the given block when the [LifecycleOwner]'s [Lifecycle] is at least in
+ * [Lifecycle.State.RESUMED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> LifecycleOwner.whenResumed(block: suspend CoroutineScope.() -> T): T =
+ lifecycle.whenResumed(block)
+
+/**
+ * Runs the given block when the [Lifecycle] is at least in [Lifecycle.State.RESUMED] state.
+ *
+ * @see Lifecycle.whenStateAtLeast for details
+ */
+suspend fun <T> Lifecycle.whenResumed(block: suspend CoroutineScope.() -> T): T {
+ return whenStateAtLeast(Lifecycle.State.RESUMED, block)
+}
+
+/**
+ * Runs the given [block] on a [CoroutineDispatcher] that executes the [block] on the main thread
+ * and suspends the execution unless the [Lifecycle]'s state is at least [minState].
+ *
+ * If the [Lifecycle] moves to a lesser state while the [block] is running, the [block] will
+ * be suspended until the [Lifecycle] reaches to a state greater or equal to [minState].
+ *
+ * Note that this won't effect any sub coroutine if they use a different [CoroutineDispatcher].
+ * However, the [block] will not resume execution when the sub coroutine finishes unless the
+ * [Lifecycle] is at least in [minState].
+ *
+ * If the [Lifecycle] is destroyed while the [block] is suspended, the [block] will be cancelled
+ * which will also cancel any child coroutine launched inside the [block].
+ *
+ * If you have a `try finally` block in your code, the `finally` might run after the [Lifecycle]
+ * moves outside the desired state. It is recommended to check the [Lifecycle.getCurrentState]
+ * before accessing the UI. Similarly, if you have a `catch` statement that might catch
+ * `CancellationException`, you should check the [Lifecycle.getCurrentState] before accessing the
+ * UI. See the sample below for more details.
+ *
+ * ```
+ * // running a block of code only if lifecycle is STARTED
+ * viewLifecycle.whenStateAtLeast(Lifecycle.State.STARTED) {
+ * // here, we are on the main thread and view lifecycle is guaranteed to be STARTED or RESUMED.
+ * // We can safely access our views.
+ * loadingBar.visibility = View.VISIBLE
+ * try {
+ * // we can call any suspend function
+ * val data = withContext(Dispatchers.IO) {
+ * // this will run in IO thread pool. It will keep running as long as Lifecycle
+ * // is not DESTROYED. If it is destroyed, this coroutine will be cancelled as well.
+ * // However, we CANNOT access Views here.
+ *
+ * // We are using withContext(Dispatchers.IO) here just for demonstration purposes.
+ * // Such code should live in your business logic classes and your UI should use a
+ * // ViewModel (or similar) to access it.
+ * api.getUser()
+ * }
+ * // this line will execute on the main thread and only if the lifecycle is in at least
+ * // STARTED state (STARTED is the parameter we've passed to whenStateAtLeast)
+ * // Because of this guarantee, we can safely access the UI again.
+ * loadingBar.visibility = View.GONE
+ * nameTextView.text = user.name
+ * lastNameTextView.text = user.lastName
+ * } catch(ex : UserNotFoundException) {
+ * // same as above, this code can safely access UI elements because it only runs if
+ * // view lifecycle is at least STARTED
+ * loadingBar.visibility = View.GONE
+ * showErrorDialog(ex)
+ * } catch(th : Throwable) {
+ * // Unlike the catch statement above, this catch statements it too generic and might
+ * // also catch the CancellationException. Before accessing UI, you should check isActive
+ * // or lifecycle state
+ * if (viewLifecycle.currentState >= Lifecycle.State.STARTED) {
+ * // here you can access the view because you've checked the coroutine is active
+ * }
+ * } finally {
+ * // in case of cancellation, this line might run even if the Lifecycle is not DESTROYED.
+ * // You cannot access Views here unless you check `isActive` or lifecycle state
+ * if (viewLifecycle.currentState >= Lifecycle.State.STARTED) {
+ * // safe to access views
+ * } else {
+ * // not safe to access views
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @param minState The desired minimum state to run the [block].
+ * @param block The block to run when the lifecycle is at least in [minState].
+ * @return <T> The return value of the [block]
+ */
+suspend fun <T> Lifecycle.whenStateAtLeast(
+ minState: Lifecycle.State,
+ block: suspend CoroutineScope.() -> T
+) = withContext(Dispatchers.Main) {
+ val job = coroutineContext[Job] ?: error("when[State] methods should have a parent job")
+ val dispatcher = PausingDispatcher()
+ val controller =
+ LifecycleController(this@whenStateAtLeast, minState, dispatcher.dispatchQueue, job)
+ try {
+ withContext(dispatcher, block)
+ } finally {
+ controller.finish()
+ }
+}
+
+/**
+ * A [CoroutineDispatcher] implementation that maintains a dispatch queue to be able to pause
+ * execution of coroutines.
+ *
+ * @see [DispatchQueue] and [Lifecycle.whenStateAtLeast] for details.
+ */
+internal class PausingDispatcher : CoroutineDispatcher() {
+ /**
+ * helper class to maintain state and enqueued continuations.
+ */
+ @JvmField
+ internal val dispatchQueue = DispatchQueue()
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatchQueue.runOrEnqueue(block)
+ }
+}
\ No newline at end of file
diff --git a/lifecycle/service/api/2.1.0-alpha04.txt b/lifecycle/service/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..a12d86e
--- /dev/null
+++ b/lifecycle/service/api/2.1.0-alpha04.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class LifecycleService extends android.app.Service implements androidx.lifecycle.LifecycleOwner {
+ ctor public LifecycleService();
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method @CallSuper public android.os.IBinder? onBind(android.content.Intent);
+ method @CallSuper public void onStart(android.content.Intent, int);
+ }
+
+ public class ServiceLifecycleDispatcher {
+ ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner);
+ method public androidx.lifecycle.Lifecycle getLifecycle();
+ method public void onServicePreSuperOnBind();
+ method public void onServicePreSuperOnCreate();
+ method public void onServicePreSuperOnDestroy();
+ method public void onServicePreSuperOnStart();
+ }
+
+}
+
diff --git a/lifecycle/service/api/res-2.1.0-alpha04.txt b/lifecycle/service/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/service/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/service/api/restricted_2.1.0-alpha04.txt b/lifecycle/service/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/service/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt b/lifecycle/viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
index cbef37e..e5c2fe7 100644
--- a/lifecycle/viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
+++ b/lifecycle/viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/SavedStateHandleTest.kt
@@ -42,6 +42,20 @@
@Test
@UiThreadTest
+ fun testSetNullGet() {
+ val handle = SavedStateHandle()
+ handle.set("foo", null)
+ assertThat(handle.get<String?>("foo")).isEqualTo(null)
+ val fooLd = handle.getLiveData<String>("foo")
+ assertThat(fooLd.value).isEqualTo(null)
+ fooLd.value = "another"
+ assertThat(handle.get<String?>("foo")).isEqualTo("another")
+ fooLd.value = null
+ assertThat(handle.get<String?>("foo")).isEqualTo(null)
+ }
+
+ @Test
+ @UiThreadTest
fun testSetObserve() {
val handle = SavedStateHandle()
val liveData = handle.getLiveData<Int>("a")
@@ -107,4 +121,4 @@
assertThat(accessor.keys().size).isEqualTo(2)
assertThat(accessor.keys()).containsExactly("s", "ld")
}
-}
\ No newline at end of file
+}
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateVMFactory.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateVMFactory.java
index 24ca34f..fdb630b 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateVMFactory.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateVMFactory.java
@@ -25,10 +25,10 @@
import androidx.savedstate.SavedStateRegistryOwner;
/**
- * Skeleton of {@link androidx.lifecycle.ViewModelProvider.KeyedFactory}
- * that creates {@link SavedStateHandle} for every requested {@link ViewModel}. The subclasses
+ * Skeleton of androidx.lifecycle.ViewModelProvider.KeyedFactory
+ * that creates {@link SavedStateHandle} for every requested {@link androidx.lifecycle.ViewModel}. The subclasses
* implement {@link #create(String, Class, SavedStateHandle)} to actually instantiate
- * {@code ViewModels}.
+ * {@code androidx.lifecycle.ViewModel}s.
*/
public abstract class AbstractSavedStateVMFactory extends ViewModelProvider.KeyedFactory {
static final String TAG_SAVED_STATE_HANDLE_CONTROLLER = "androidx.lifecycle.savedstate.vm.tag";
@@ -41,7 +41,7 @@
* Constructs this factory.
*
* @param owner {@link SavedStateRegistryOwner} that will provide restored state for created
- * {@link ViewModel ViewModels}
+ * {@link androidx.lifecycle.ViewModel ViewModels}
* @param defaultArgs values from this {@code Bundle} will be used as defaults by
* {@link SavedStateHandle} passed in {@link ViewModel ViewModels}
* if there is no previously saved state
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
index 5e7f389..cdcf7d7 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandle.java
@@ -37,7 +37,7 @@
import java.util.Set;
/**
- * A handle to saved state passed down to {@link ViewModel}. You should use
+ * A handle to saved state passed down to {@link androidx.lifecycle.ViewModel}. You should use
* {@link SavedStateVMFactory} if you want to receive this object in {@code ViewModel}'s
* constructor.
* <p>
@@ -45,11 +45,11 @@
* These values will persist after the process is killed by the system
* and remain available via the same object.
* <p>
- * You can read a value from it via {@link #get(String)} or observe it via {@link LiveData} returned
+ * You can read a value from it via {@link #get(String)} or observe it via {@link androidx.lifecycle.LiveData} returned
* by {@link #getLiveData(String)}.
* <p>
* You can write a value to it via {@link #set(String, Object)} or setting a value to
- * {@link MutableLiveData} returned by {@link #getLiveData(String)}.
+ * {@link androidx.lifecycle.MutableLiveData} returned by {@link #getLiveData(String)}.
*/
public final class SavedStateHandle {
final Map<String, Object> mRegular;
@@ -136,7 +136,7 @@
}
/**
- * Returns a {@link LiveData} that access data associated with the given key,.
+ * Returns a {@link androidx.lifecycle.LiveData} that access data associated with the given key,.
*/
@SuppressWarnings("unchecked")
@MainThread
@@ -196,6 +196,9 @@
}
private static void validateValue(Object value) {
+ if (value == null) {
+ return;
+ }
for (Class<?> cl : ACCEPTABLE_CLASSES) {
if (cl.isInstance(value)) {
return;
@@ -209,7 +212,7 @@
* Removes a value associated with the given key. If there is a {@link LiveData} associated
* with the given key, it will be removed as well.
* <p>
- * All changes to {@link LiveData} previously
+ * All changes to {@link androidx.lifecycle.LiveData} previously
* returned by {@link SavedStateHandle#getLiveData(String)} won't be reflected in
* the saved state. Also that {@code LiveData} won't receive any updates about new values
* associated by the given key.
diff --git a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
index bc219de..d2457ef1 100644
--- a/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
+++ b/lifecycle/viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateVMFactory.java
@@ -31,13 +31,13 @@
import java.util.Arrays;
/**
- * {@link ViewModelProvider.Factory} that can create ViewModels accessing and contributing
+ * {@link androidx.lifecycle.ViewModelProvider.Factory} that can create ViewModels accessing and contributing
* to a saved state via {@link SavedStateHandle} received in a constructor. If {@code defaultArgs}
* bundle was passed in {@link #SavedStateVMFactory(Fragment, Bundle)}
* or {@link #SavedStateVMFactory(FragmentActivity, Bundle)}, it will provide default values in
* {@code SavedStateHandle}.
* <p>
- * If ViewModel is instance of {@link AndroidViewModel}, it looks for a constructor that
+ * If ViewModel is instance of {@link androidx.lifecycle.AndroidViewModel}, it looks for a constructor that
* receives an {@link Application} and {@link SavedStateHandle} (in this order), otherwise
* it looks for a constructor that receives {@link SavedStateHandle} only.
*/
@@ -48,7 +48,7 @@
/**
* Creates {@link SavedStateVMFactory}.
* <p>
- * {@link ViewModel} created with this factory can access to saved state scoped to
+ * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state scoped to
* the given {@code fragment}.
*
* @param fragment scope of this fragment will be used for state saving
@@ -60,7 +60,7 @@
/**
* Creates {@link SavedStateVMFactory}.
* <p>
- * {@link ViewModel} created with this factory can access to saved state scoped to
+ * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state scoped to
* the given {@code fragment}.
*
* @param fragment scope of this fragment will be used for state saving
@@ -75,7 +75,7 @@
/**
* Creates {@link SavedStateVMFactory}.
* <p>
- * {@link ViewModel} created with this factory can access to saved state scoped to
+ * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state scoped to
* the given {@code activity}.
*
* @param activity scope of this activity will be used for state saving
@@ -87,7 +87,7 @@
/**
* Creates {@link SavedStateVMFactory}.
* <p>
- * {@link ViewModel} created with this factory can access to saved state scoped to
+ * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state scoped to
* the given {@code activity}.
*
* @param activity scope of this activity will be used for state saving
@@ -102,12 +102,12 @@
/**
* Creates {@link SavedStateVMFactory}.
* <p>
- * {@link ViewModel} created with this factory can access to saved state scoped to
+ * {@link androidx.lifecycle.ViewModel} created with this factory can access to saved state scoped to
* the given {@code activity}.
*
* @param application an application
* @param owner {@link SavedStateRegistryOwner} that will provide restored state for created
- * {@link ViewModel ViewModels}
+ * {@link androidx.lifecycle.ViewModel ViewModels}
* @param defaultArgs values from this {@code Bundle} will be used as defaults by
* {@link SavedStateHandle} if there is no previously saved state or previously saved state
* misses a value by such key.
diff --git a/lifecycle/viewmodel/api/2.1.0-alpha04.txt b/lifecycle/viewmodel/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..d30b55f
--- /dev/null
+++ b/lifecycle/viewmodel/api/2.1.0-alpha04.txt
@@ -0,0 +1,45 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public class AndroidViewModel extends androidx.lifecycle.ViewModel {
+ ctor public AndroidViewModel(android.app.Application);
+ method public <T extends android.app.Application> T getApplication();
+ }
+
+ public abstract class ViewModel {
+ ctor public ViewModel();
+ method protected void onCleared();
+ }
+
+ public class ViewModelProvider {
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStoreOwner, androidx.lifecycle.ViewModelProvider.Factory);
+ ctor public ViewModelProvider(androidx.lifecycle.ViewModelStore, androidx.lifecycle.ViewModelProvider.Factory);
+ method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(Class<T>);
+ method @MainThread public <T extends androidx.lifecycle.ViewModel> T get(String, Class<T>);
+ }
+
+ public static class ViewModelProvider.AndroidViewModelFactory extends androidx.lifecycle.ViewModelProvider.NewInstanceFactory {
+ ctor public ViewModelProvider.AndroidViewModelFactory(android.app.Application);
+ method public static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory getInstance(android.app.Application);
+ }
+
+ public static interface ViewModelProvider.Factory {
+ method public <T extends androidx.lifecycle.ViewModel> T create(Class<T>);
+ }
+
+ public static class ViewModelProvider.NewInstanceFactory implements androidx.lifecycle.ViewModelProvider.Factory {
+ ctor public ViewModelProvider.NewInstanceFactory();
+ method public <T extends androidx.lifecycle.ViewModel> T create(Class<T>);
+ }
+
+ public class ViewModelStore {
+ ctor public ViewModelStore();
+ method public final void clear();
+ }
+
+ public interface ViewModelStoreOwner {
+ method public androidx.lifecycle.ViewModelStore getViewModelStore();
+ }
+
+}
+
diff --git a/lifecycle/viewmodel/api/res-2.1.0-alpha04.txt b/lifecycle/viewmodel/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/viewmodel/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/viewmodel/api/restricted_2.1.0-alpha04.txt b/lifecycle/viewmodel/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/viewmodel/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/viewmodel/ktx/api/2.1.0-alpha04.txt b/lifecycle/viewmodel/ktx/api/2.1.0-alpha04.txt
new file mode 100644
index 0000000..ab9fc63
--- /dev/null
+++ b/lifecycle/viewmodel/ktx/api/2.1.0-alpha04.txt
@@ -0,0 +1,22 @@
+// Signature format: 3.0
+package androidx.lifecycle {
+
+ public final class ViewModelKt {
+ ctor public ViewModelKt();
+ method public static kotlinx.coroutines.CoroutineScope getViewModelScope(androidx.lifecycle.ViewModel);
+ }
+
+ public final class ViewModelLazy<VM extends androidx.lifecycle.ViewModel> implements kotlin.Lazy<VM> {
+ ctor public ViewModelLazy(kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory> factoryProducer);
+ method public VM getValue();
+ method public boolean isInitialized();
+ property public VM value;
+ }
+
+ public final class ViewModelProviderKt {
+ ctor public ViewModelProviderKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> VM! get(androidx.lifecycle.ViewModelProvider);
+ }
+
+}
+
diff --git a/lifecycle/viewmodel/ktx/api/current.txt b/lifecycle/viewmodel/ktx/api/current.txt
index 2c78583..ab9fc63 100644
--- a/lifecycle/viewmodel/ktx/api/current.txt
+++ b/lifecycle/viewmodel/ktx/api/current.txt
@@ -7,7 +7,7 @@
}
public final class ViewModelLazy<VM extends androidx.lifecycle.ViewModel> implements kotlin.Lazy<VM> {
- ctor public ViewModelLazy(kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStoreOwner> ownerProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory> factoryProducer);
+ ctor public ViewModelLazy(kotlin.reflect.KClass<VM> viewModelClass, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore> storeProducer, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory> factoryProducer);
method public VM getValue();
method public boolean isInitialized();
property public VM value;
diff --git a/lifecycle/viewmodel/ktx/api/res-2.1.0-alpha04.txt b/lifecycle/viewmodel/ktx/api/res-2.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lifecycle/viewmodel/ktx/api/res-2.1.0-alpha04.txt
diff --git a/lifecycle/viewmodel/ktx/api/restricted_2.1.0-alpha04.txt b/lifecycle/viewmodel/ktx/api/restricted_2.1.0-alpha04.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/lifecycle/viewmodel/ktx/api/restricted_2.1.0-alpha04.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/lifecycle/viewmodel/ktx/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt b/lifecycle/viewmodel/ktx/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt
index 127afe77..a5e4553 100644
--- a/lifecycle/viewmodel/ktx/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt
+++ b/lifecycle/viewmodel/ktx/src/androidTest/java/androidx/lifecycle/ViewModelTest.kt
@@ -18,8 +18,12 @@
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -53,4 +57,21 @@
val scope3 = vm.viewModelScope
Truth.assertThat(scope3).isSameAs(scope2)
}
+
+ @Test fun testJobIsSuperVisor() {
+ val vm = object : ViewModel() {}
+ val scope = vm.viewModelScope
+ val delayingDeferred = scope.async { delay(Long.MAX_VALUE) }
+ val failingDeferred = scope.async { throw Error() }
+
+ runBlocking {
+ try {
+ failingDeferred.await()
+ Assert.fail()
+ } catch (e: Error) {
+ }
+ Truth.assertThat(delayingDeferred.isActive).isTrue()
+ delayingDeferred.cancelAndJoin()
+ }
+ }
}
\ No newline at end of file
diff --git a/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt b/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt
index 74a0f00..0d8e203 100644
--- a/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt
+++ b/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModel.kt
@@ -18,7 +18,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import java.io.Closeable
import kotlin.coroutines.CoroutineContext
@@ -38,7 +38,7 @@
return scope
}
return setTagIfAbsent(JOB_KEY,
- CloseableCoroutineScope(Job() + Dispatchers.Main))
+ CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
diff --git a/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModelProvider.kt b/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModelProvider.kt
index 5d48562..ec960e6 100644
--- a/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModelProvider.kt
+++ b/lifecycle/viewmodel/ktx/src/main/java/androidx/lifecycle/ViewModelProvider.kt
@@ -32,15 +32,15 @@
* An implementation of [Lazy] used by [androidx.fragment.app.Fragment.viewModels] and
* [androidx.activity.ComponentActivity.viewmodels].
*
- * [ownerProducer] is a lambda that will be called during initialization, [VM] will be created
- * in the scope of returned [ViewModelStoreOwner].
+ * [storeProducer] is a lambda that will be called during initialization, [VM] will be created
+ * in the scope of returned [ViewModelStore].
*
* [factoryProducer] is a lambda that will be called during initialization,
* returned [ViewModelProvider.Factory] will be used for creation of [VM]
*/
-class ViewModelLazy<VM : ViewModel>(
+class ViewModelLazy<VM : ViewModel> (
private val viewModelClass: KClass<VM>,
- private val ownerProducer: () -> ViewModelStoreOwner,
+ private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory
) : Lazy<VM> {
private var cached: VM? = null
@@ -50,8 +50,10 @@
val viewModel = cached
return if (viewModel == null) {
val factory = factoryProducer()
- val owner = ownerProducer()
- ViewModelProvider(owner, factory).get(viewModelClass.java).also { cached = it }
+ val store = storeProducer()
+ ViewModelProvider(store, factory).get(viewModelClass.java).also {
+ cached = it
+ }
} else {
viewModel
}
diff --git a/lifecycle/viewmodel/ktx/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt b/lifecycle/viewmodel/ktx/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt
index 74a68ee..8788c56 100644
--- a/lifecycle/viewmodel/ktx/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt
+++ b/lifecycle/viewmodel/ktx/src/test/java/androidx/lifecycle/ViewModelLazyTest.kt
@@ -29,10 +29,10 @@
@Test
fun test() {
val factoryProducer = { TestFactory() }
- val owner = TestOwner()
- val vm by ViewModelLazy(TestVM::class, { owner }, factoryProducer)
+ val store = ViewModelStore()
+ val vm by ViewModelLazy(TestVM::class, { store }, factoryProducer)
assertThat(vm.prop).isEqualTo("spb")
- assertThat(owner.store.keys()).isNotEmpty()
+ assertThat(store.keys()).isNotEmpty()
}
class TestVM(val prop: String) : ViewModel()
@@ -40,9 +40,4 @@
class TestFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = TestVM("spb") as T
}
-
- class TestOwner : ViewModelStoreOwner {
- val store = ViewModelStore()
- override fun getViewModelStore() = store
- }
}
\ No newline at end of file
diff --git a/loader/api/1.1.0-beta02.ignore b/loader/api/1.1.0-beta02.ignore
new file mode 100644
index 0000000..4376069
--- /dev/null
+++ b/loader/api/1.1.0-beta02.ignore
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+DeprecationMismatch: androidx.loader.content.AsyncTaskLoader#dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]):
+ Method androidx.loader.content.AsyncTaskLoader.dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
+DeprecationMismatch: androidx.loader.content.CursorLoader#dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]):
+ Method androidx.loader.content.CursorLoader.dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
+
+
diff --git a/loader/api/1.1.0-beta02.txt b/loader/api/1.1.0-beta02.txt
new file mode 100644
index 0000000..8b3df8a
--- /dev/null
+++ b/loader/api/1.1.0-beta02.txt
@@ -0,0 +1,102 @@
+// Signature format: 3.0
+package androidx.loader.app {
+
+ public abstract class LoaderManager {
+ ctor public LoaderManager();
+ method @MainThread public abstract void destroyLoader(int);
+ method @Deprecated public abstract void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String[]!);
+ method public static void enableDebugLogging(boolean);
+ method public static <T extends androidx.lifecycle.LifecycleOwner & androidx.lifecycle.ViewModelStoreOwner> androidx.loader.app.LoaderManager getInstance(T);
+ method public abstract <D> androidx.loader.content.Loader<D>? getLoader(int);
+ method public boolean hasRunningLoaders();
+ method @MainThread public abstract <D> androidx.loader.content.Loader<D> initLoader(int, android.os.Bundle?, androidx.loader.app.LoaderManager.LoaderCallbacks<D>);
+ method public abstract void markForRedelivery();
+ method @MainThread public abstract <D> androidx.loader.content.Loader<D> restartLoader(int, android.os.Bundle?, androidx.loader.app.LoaderManager.LoaderCallbacks<D>);
+ }
+
+ public static interface LoaderManager.LoaderCallbacks<D> {
+ method @MainThread public androidx.loader.content.Loader<D> onCreateLoader(int, android.os.Bundle?);
+ method @MainThread public void onLoadFinished(androidx.loader.content.Loader<D>, D!);
+ method @MainThread public void onLoaderReset(androidx.loader.content.Loader<D>);
+ }
+
+}
+
+package androidx.loader.content {
+
+ public abstract class AsyncTaskLoader<D> extends androidx.loader.content.Loader<D> {
+ ctor public AsyncTaskLoader(android.content.Context);
+ method public void cancelLoadInBackground();
+ method protected java.util.concurrent.Executor getExecutor();
+ method public boolean isLoadInBackgroundCanceled();
+ method public abstract D? loadInBackground();
+ method public void onCanceled(D?);
+ method protected D? onLoadInBackground();
+ method public void setUpdateThrottle(long);
+ }
+
+ public class CursorLoader extends androidx.loader.content.AsyncTaskLoader<android.database.Cursor> {
+ ctor public CursorLoader(android.content.Context);
+ ctor public CursorLoader(android.content.Context, android.net.Uri, String[]?, String?, String[]?, String?);
+ method public void deliverResult(android.database.Cursor!);
+ method public String[]? getProjection();
+ method public String? getSelection();
+ method public String[]? getSelectionArgs();
+ method public String? getSortOrder();
+ method public android.net.Uri getUri();
+ method public android.database.Cursor! loadInBackground();
+ method public void onCanceled(android.database.Cursor!);
+ method public void setProjection(String[]?);
+ method public void setSelection(String?);
+ method public void setSelectionArgs(String[]?);
+ method public void setSortOrder(String?);
+ method public void setUri(android.net.Uri);
+ }
+
+ public class Loader<D> {
+ ctor public Loader(android.content.Context);
+ method @MainThread public void abandon();
+ method @MainThread public boolean cancelLoad();
+ method public void commitContentChanged();
+ method public String dataToString(D?);
+ method @MainThread public void deliverCancellation();
+ method @MainThread public void deliverResult(D?);
+ method @Deprecated public void dump(String!, java.io.FileDescriptor!, java.io.PrintWriter!, String[]!);
+ method @MainThread public void forceLoad();
+ method public android.content.Context getContext();
+ method public int getId();
+ method public boolean isAbandoned();
+ method public boolean isReset();
+ method public boolean isStarted();
+ method @MainThread protected void onAbandon();
+ method @MainThread protected boolean onCancelLoad();
+ method @MainThread public void onContentChanged();
+ method @MainThread protected void onForceLoad();
+ method @MainThread protected void onReset();
+ method @MainThread protected void onStartLoading();
+ method @MainThread protected void onStopLoading();
+ method @MainThread public void registerListener(int, androidx.loader.content.Loader.OnLoadCompleteListener<D>);
+ method @MainThread public void registerOnLoadCanceledListener(androidx.loader.content.Loader.OnLoadCanceledListener<D>);
+ method @MainThread public void reset();
+ method public void rollbackContentChanged();
+ method @MainThread public final void startLoading();
+ method @MainThread public void stopLoading();
+ method public boolean takeContentChanged();
+ method @MainThread public void unregisterListener(androidx.loader.content.Loader.OnLoadCompleteListener<D>);
+ method @MainThread public void unregisterOnLoadCanceledListener(androidx.loader.content.Loader.OnLoadCanceledListener<D>);
+ }
+
+ public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ ctor public Loader.ForceLoadContentObserver();
+ }
+
+ public static interface Loader.OnLoadCanceledListener<D> {
+ method public void onLoadCanceled(androidx.loader.content.Loader<D>);
+ }
+
+ public static interface Loader.OnLoadCompleteListener<D> {
+ method public void onLoadComplete(androidx.loader.content.Loader<D>, D?);
+ }
+
+}
+
diff --git a/loader/api/res-1.1.0-beta02.txt b/loader/api/res-1.1.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/loader/api/res-1.1.0-beta02.txt
diff --git a/loader/api/restricted_1.1.0-beta02.ignore b/loader/api/restricted_1.1.0-beta02.ignore
new file mode 100644
index 0000000..4376069
--- /dev/null
+++ b/loader/api/restricted_1.1.0-beta02.ignore
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+DeprecationMismatch: androidx.loader.content.AsyncTaskLoader#dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]):
+ Method androidx.loader.content.AsyncTaskLoader.dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
+DeprecationMismatch: androidx.loader.content.CursorLoader#dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]):
+ Method androidx.loader.content.CursorLoader.dump(String, java.io.FileDescriptor, java.io.PrintWriter, String[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
+
+
diff --git a/loader/api/restricted_1.1.0-beta02.txt b/loader/api/restricted_1.1.0-beta02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/loader/api/restricted_1.1.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/media/api/1.1.0-alpha03.txt b/media/api/1.1.0-alpha03.txt
new file mode 100644
index 0000000..c4e14c0
--- /dev/null
+++ b/media/api/1.1.0-alpha03.txt
@@ -0,0 +1,702 @@
+// Signature format: 3.0
+package android.support.v4.media {
+
+ public final class MediaBrowserCompat {
+ ctor public MediaBrowserCompat(android.content.Context!, android.content.ComponentName!, android.support.v4.media.MediaBrowserCompat.ConnectionCallback!, android.os.Bundle!);
+ method public void connect();
+ method public void disconnect();
+ method public android.os.Bundle? getExtras();
+ method public void getItem(String, android.support.v4.media.MediaBrowserCompat.ItemCallback);
+ method public String getRoot();
+ method public android.content.ComponentName getServiceComponent();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isConnected();
+ method public void search(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.SearchCallback);
+ method public void sendCustomAction(String, android.os.Bundle!, android.support.v4.media.MediaBrowserCompat.CustomActionCallback?);
+ method public void subscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void unsubscribe(String);
+ method public void unsubscribe(String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final String CUSTOM_ACTION_DOWNLOAD = "android.support.v4.media.action.DOWNLOAD";
+ field public static final String CUSTOM_ACTION_REMOVE_DOWNLOADED_FILE = "android.support.v4.media.action.REMOVE_DOWNLOADED_FILE";
+ field public static final String EXTRA_DOWNLOAD_PROGRESS = "android.media.browse.extra.DOWNLOAD_PROGRESS";
+ field public static final String EXTRA_MEDIA_ID = "android.media.browse.extra.MEDIA_ID";
+ field public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+ }
+
+ public static class MediaBrowserCompat.ConnectionCallback {
+ ctor public MediaBrowserCompat.ConnectionCallback();
+ method public void onConnected();
+ method public void onConnectionFailed();
+ method public void onConnectionSuspended();
+ }
+
+ public abstract static class MediaBrowserCompat.CustomActionCallback {
+ ctor public MediaBrowserCompat.CustomActionCallback();
+ method public void onError(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onProgressUpdate(String!, android.os.Bundle!, android.os.Bundle!);
+ method public void onResult(String!, android.os.Bundle!, android.os.Bundle!);
+ }
+
+ public abstract static class MediaBrowserCompat.ItemCallback {
+ ctor public MediaBrowserCompat.ItemCallback();
+ method public void onError(String);
+ method public void onItemLoaded(android.support.v4.media.MediaBrowserCompat.MediaItem!);
+ }
+
+ public static class MediaBrowserCompat.MediaItem implements android.os.Parcelable {
+ ctor public MediaBrowserCompat.MediaItem(android.support.v4.media.MediaDescriptionCompat, int);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaBrowserCompat.MediaItem! fromMediaItem(Object!);
+ method public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>! fromMediaItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public int getFlags();
+ method public String? getMediaId();
+ method public boolean isBrowsable();
+ method public boolean isPlayable();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaBrowserCompat.MediaItem>! CREATOR;
+ field public static final int FLAG_BROWSABLE = 1; // 0x1
+ field public static final int FLAG_PLAYABLE = 2; // 0x2
+ }
+
+ public abstract static class MediaBrowserCompat.SearchCallback {
+ ctor public MediaBrowserCompat.SearchCallback();
+ method public void onError(String, android.os.Bundle!);
+ method public void onSearchResult(String, android.os.Bundle!, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+ }
+
+ public abstract static class MediaBrowserCompat.SubscriptionCallback {
+ ctor public MediaBrowserCompat.SubscriptionCallback();
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+ method public void onChildrenLoaded(String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>, android.os.Bundle);
+ method public void onError(String);
+ method public void onError(String, android.os.Bundle);
+ }
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat! fromMediaDescription(Object!);
+ method public CharSequence? getDescription();
+ method public android.os.Bundle? getExtras();
+ method public android.graphics.Bitmap? getIconBitmap();
+ method public android.net.Uri? getIconUri();
+ method public Object! getMediaDescription();
+ method public String? getMediaId();
+ method public android.net.Uri? getMediaUri();
+ method public CharSequence? getSubtitle();
+ method public CharSequence? getTitle();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long BT_FOLDER_TYPE_ALBUMS = 2L; // 0x2L
+ field public static final long BT_FOLDER_TYPE_ARTISTS = 3L; // 0x3L
+ field public static final long BT_FOLDER_TYPE_GENRES = 4L; // 0x4L
+ field public static final long BT_FOLDER_TYPE_MIXED = 0L; // 0x0L
+ field public static final long BT_FOLDER_TYPE_PLAYLISTS = 5L; // 0x5L
+ field public static final long BT_FOLDER_TYPE_TITLES = 1L; // 0x1L
+ field public static final long BT_FOLDER_TYPE_YEARS = 6L; // 0x6L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat>! CREATOR;
+ field public static final String EXTRA_BT_FOLDER_TYPE = "android.media.extra.BT_FOLDER_TYPE";
+ field public static final String EXTRA_DOWNLOAD_STATUS = "android.media.extra.DOWNLOAD_STATUS";
+ field public static final long STATUS_DOWNLOADED = 2L; // 0x2L
+ field public static final long STATUS_DOWNLOADING = 1L; // 0x1L
+ field public static final long STATUS_NOT_DOWNLOADED = 0L; // 0x0L
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat! build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setDescription(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setExtras(android.os.Bundle?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconBitmap(android.graphics.Bitmap?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setIconUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaId(String?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setMediaUri(android.net.Uri?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setSubtitle(CharSequence?);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder! setTitle(CharSequence?);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(String!);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat! fromMediaMetadata(Object!);
+ method public android.graphics.Bitmap! getBitmap(String!);
+ method public android.os.Bundle! getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getLong(String!);
+ method public Object! getMediaMetadata();
+ method public android.support.v4.media.RatingCompat! getRating(String!);
+ method public String! getString(String!);
+ method public CharSequence! getText(String!);
+ method public java.util.Set<java.lang.String>! keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat>! CREATOR;
+ field public static final String METADATA_KEY_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
+ field public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String METADATA_KEY_BT_FOLDER_TYPE = "android.media.metadata.BT_FOLDER_TYPE";
+ field public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final String METADATA_KEY_DOWNLOAD_STATUS = "android.media.metadata.DOWNLOAD_STATUS";
+ field public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final String METADATA_KEY_MEDIA_URI = "android.media.metadata.MEDIA_URI";
+ field public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat!);
+ method public android.support.v4.media.MediaMetadataCompat! build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putBitmap(String!, android.graphics.Bitmap!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putLong(String!, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putRating(String!, android.support.v4.media.RatingCompat!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putString(String!, String!);
+ method public android.support.v4.media.MediaMetadataCompat.Builder! putText(String!, CharSequence!);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat! fromRating(Object!);
+ method public float getPercentRating();
+ method public Object! getRating();
+ method public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat! newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat! newStarRating(int, float);
+ method public static android.support.v4.media.RatingCompat! newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat! newUnratedRating(int);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat>! CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context!, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void addQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent!);
+ method public android.os.Bundle! getExtras();
+ method public long getFlags();
+ method public static android.support.v4.media.session.MediaControllerCompat! getMediaController(android.app.Activity);
+ method public Object! getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat! getMetadata();
+ method public String! getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo! getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat! getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>! getQueue();
+ method public CharSequence! getQueueTitle();
+ method public int getRatingType();
+ method public int getRepeatMode();
+ method public android.app.PendingIntent! getSessionActivity();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public int getShuffleMode();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls! getTransportControls();
+ method public boolean isCaptioningEnabled();
+ method public boolean isSessionReady();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler!);
+ method public void removeQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void removeQueueItemAt(int);
+ method public void sendCommand(String, android.os.Bundle?, android.os.ResultReceiver?);
+ method public static void setMediaController(android.app.Activity, android.support.v4.media.session.MediaControllerCompat!);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public abstract static class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo!);
+ method public void onCaptioningEnabledChanged(boolean);
+ method public void onExtrasChanged(android.os.Bundle!);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat!);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>!);
+ method public void onQueueTitleChanged(CharSequence!);
+ method public void onRepeatModeChanged(int);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(String!, android.os.Bundle!);
+ method public void onSessionReady();
+ method public void onShuffleModeChanged(int);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public androidx.media.AudioAttributesCompat getAudioAttributes();
+ method @Deprecated public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public abstract static class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(String!, android.os.Bundle!);
+ method public abstract void playFromSearch(String!, android.os.Bundle!);
+ method public abstract void playFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(String!, android.os.Bundle!);
+ method public abstract void prepareFromSearch(String!, android.os.Bundle!);
+ method public abstract void prepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!, android.os.Bundle!);
+ method public abstract void sendCustomAction(String!, android.os.Bundle!);
+ method public abstract void setCaptioningEnabled(boolean);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!);
+ method public abstract void setRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public abstract void setRepeatMode(int);
+ method public abstract void setShuffleMode(int);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ field public static final String EXTRA_LEGACY_STREAM_TYPE = "android.media.session.extra.LEGACY_STREAM_TYPE";
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context!, String!);
+ ctor public MediaSessionCompat(android.content.Context!, String!, android.content.ComponentName!, android.app.PendingIntent!);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public static android.support.v4.media.session.MediaSessionCompat! fromMediaSession(android.content.Context!, Object!);
+ method public android.support.v4.media.session.MediaControllerCompat! getController();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentControllerInfo();
+ method public Object! getMediaSession();
+ method public Object! getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getSessionToken();
+ method public boolean isActive();
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener!);
+ method public void sendSessionEvent(String!, android.os.Bundle!);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback!, android.os.Handler!);
+ method public void setCaptioningEnabled(boolean);
+ method public void setExtras(android.os.Bundle!);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent!);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat!);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat!);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(androidx.media.VolumeProviderCompat!);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>!);
+ method public void setQueueTitle(CharSequence!);
+ method public void setRatingType(int);
+ method public void setRepeatMode(int);
+ method public void setSessionActivity(android.app.PendingIntent!);
+ method public void setShuffleMode(int);
+ field public static final String ACTION_FLAG_AS_INAPPROPRIATE = "android.support.v4.media.session.action.FLAG_AS_INAPPROPRIATE";
+ field public static final String ACTION_FOLLOW = "android.support.v4.media.session.action.FOLLOW";
+ field public static final String ACTION_SKIP_AD = "android.support.v4.media.session.action.SKIP_AD";
+ field public static final String ACTION_UNFOLLOW = "android.support.v4.media.session.action.UNFOLLOW";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE";
+ field public static final String ARGUMENT_MEDIA_ATTRIBUTE_VALUE = "android.support.v4.media.session.ARGUMENT_MEDIA_ATTRIBUTE_VALUE";
+ field @Deprecated public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_QUEUE_COMMANDS = 4; // 0x4
+ field @Deprecated public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ field public static final int MEDIA_ATTRIBUTE_ALBUM = 1; // 0x1
+ field public static final int MEDIA_ATTRIBUTE_ARTIST = 0; // 0x0
+ field public static final int MEDIA_ATTRIBUTE_PLAYLIST = 2; // 0x2
+ }
+
+ public abstract static class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method public void onAddQueueItem(android.support.v4.media.MediaDescriptionCompat!, int);
+ method public void onCommand(String!, android.os.Bundle!, android.os.ResultReceiver!);
+ method public void onCustomAction(String!, android.os.Bundle!);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent!);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(String!, android.os.Bundle!);
+ method public void onPlayFromSearch(String!, android.os.Bundle!);
+ method public void onPlayFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(String!, android.os.Bundle!);
+ method public void onPrepareFromSearch(String!, android.os.Bundle!);
+ method public void onPrepareFromUri(android.net.Uri!, android.os.Bundle!);
+ method public void onRemoveQueueItem(android.support.v4.media.MediaDescriptionCompat!);
+ method @Deprecated public void onRemoveQueueItemAt(int);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetCaptioningEnabled(boolean);
+ method public void onSetRating(android.support.v4.media.RatingCompat!);
+ method public void onSetRating(android.support.v4.media.RatingCompat!, android.os.Bundle!);
+ method public void onSetRepeatMode(int);
+ method public void onSetShuffleMode(int);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static interface MediaSessionCompat.OnActiveChangeListener {
+ method public void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat!, long);
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem! fromQueueItem(Object!);
+ method public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>! fromQueueItemList(java.util.List<?>!);
+ method public android.support.v4.media.MediaDescriptionCompat! getDescription();
+ method public long getQueueId();
+ method public Object! getQueueItem();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem>! CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! fromToken(Object!);
+ method public Object! getToken();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token>! CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo>! CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat! fromPlaybackState(Object!);
+ method public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction>! getCustomActions();
+ method public int getErrorCode();
+ method public CharSequence! getErrorMessage();
+ method public android.os.Bundle? getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public Object! getPlaybackState();
+ method public long getPosition();
+ method public int getState();
+ method public static int toKeyCode(long);
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_CAPTIONING_ENABLED = 1048576L; // 0x100000L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SET_REPEAT_MODE = 262144L; // 0x40000L
+ field public static final long ACTION_SET_SHUFFLE_MODE = 2097152L; // 0x200000L
+ field @Deprecated public static final long ACTION_SET_SHUFFLE_MODE_ENABLED = 524288L; // 0x80000L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat>! CREATOR;
+ field public static final int ERROR_CODE_ACTION_ABORTED = 10; // 0xa
+ field public static final int ERROR_CODE_APP_ERROR = 1; // 0x1
+ field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = 3; // 0x3
+ field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = 5; // 0x5
+ field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = 8; // 0x8
+ field public static final int ERROR_CODE_END_OF_QUEUE = 11; // 0xb
+ field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = 7; // 0x7
+ field public static final int ERROR_CODE_NOT_SUPPORTED = 2; // 0x2
+ field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = 6; // 0x6
+ field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = 4; // 0x4
+ field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = 9; // 0x9
+ field public static final int ERROR_CODE_UNKNOWN_ERROR = 0; // 0x0
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int REPEAT_MODE_ALL = 2; // 0x2
+ field public static final int REPEAT_MODE_GROUP = 3; // 0x3
+ field public static final int REPEAT_MODE_INVALID = -1; // 0xffffffff
+ field public static final int REPEAT_MODE_NONE = 0; // 0x0
+ field public static final int REPEAT_MODE_ONE = 1; // 0x1
+ field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
+ field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
+ field public static final int SHUFFLE_MODE_INVALID = -1; // 0xffffffff
+ field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(String!, String!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction!);
+ method public android.support.v4.media.session.PlaybackStateCompat! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActions(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setBufferedPosition(long);
+ method @Deprecated public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setErrorMessage(int, CharSequence!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setExtras(android.os.Bundle!);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder! setState(int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction! fromCustomAction(Object!);
+ method public String! getAction();
+ method public Object! getCustomAction();
+ method public android.os.Bundle! getExtras();
+ method public int getIcon();
+ method public CharSequence! getName();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction>! CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(String!, CharSequence!, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction! build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder! setExtras(android.os.Bundle!);
+ }
+
+}
+
+package androidx.media {
+
+ public class AudioAttributesCompat implements androidx.versionedparcelable.VersionedParcelable {
+ method public int getContentType();
+ method public int getFlags();
+ method public int getLegacyStreamType();
+ method public int getUsage();
+ method public int getVolumeControlStream();
+ method public Object? unwrap();
+ method public static androidx.media.AudioAttributesCompat? wrap(Object);
+ field public static final int CONTENT_TYPE_MOVIE = 3; // 0x3
+ field public static final int CONTENT_TYPE_MUSIC = 2; // 0x2
+ field public static final int CONTENT_TYPE_SONIFICATION = 4; // 0x4
+ field public static final int CONTENT_TYPE_SPEECH = 1; // 0x1
+ field public static final int CONTENT_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
+ field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
+ field public static final int USAGE_ALARM = 4; // 0x4
+ field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
+ field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
+ field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd
+ field public static final int USAGE_ASSISTANT = 16; // 0x10
+ field public static final int USAGE_GAME = 14; // 0xe
+ field public static final int USAGE_MEDIA = 1; // 0x1
+ field public static final int USAGE_NOTIFICATION = 5; // 0x5
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+ field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+ field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
+ field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
+ field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
+ }
+
+ public static class AudioAttributesCompat.Builder {
+ ctor public AudioAttributesCompat.Builder();
+ ctor public AudioAttributesCompat.Builder(androidx.media.AudioAttributesCompat!);
+ method public androidx.media.AudioAttributesCompat! build();
+ method public androidx.media.AudioAttributesCompat.Builder! setContentType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setFlags(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setLegacyStreamType(int);
+ method public androidx.media.AudioAttributesCompat.Builder! setUsage(int);
+ }
+
+ public class AudioFocusRequestCompat {
+ method public androidx.media.AudioAttributesCompat getAudioAttributesCompat();
+ method public android.os.Handler getFocusChangeHandler();
+ method public int getFocusGain();
+ method public android.media.AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener();
+ method public boolean willPauseWhenDucked();
+ }
+
+ public static final class AudioFocusRequestCompat.Builder {
+ ctor public AudioFocusRequestCompat.Builder(int);
+ ctor public AudioFocusRequestCompat.Builder(androidx.media.AudioFocusRequestCompat);
+ method public androidx.media.AudioFocusRequestCompat! build();
+ method public androidx.media.AudioFocusRequestCompat.Builder setAudioAttributes(androidx.media.AudioAttributesCompat);
+ method public androidx.media.AudioFocusRequestCompat.Builder setFocusGain(int);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener);
+ method public androidx.media.AudioFocusRequestCompat.Builder setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler);
+ method public androidx.media.AudioFocusRequestCompat.Builder setWillPauseWhenDucked(boolean);
+ }
+
+ public final class AudioManagerCompat {
+ method public static int abandonAudioFocusRequest(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ method public static int requestAudioFocus(android.media.AudioManager, androidx.media.AudioFocusRequestCompat);
+ field public static final int AUDIOFOCUS_GAIN = 1; // 0x1
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2; // 0x2
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4; // 0x4
+ field public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3; // 0x3
+ }
+
+ public abstract class MediaBrowserServiceCompat extends android.app.Service {
+ ctor public MediaBrowserServiceCompat();
+ method public void dump(java.io.FileDescriptor!, java.io.PrintWriter!, String[]!);
+ method public final android.os.Bundle! getBrowserRootHints();
+ method public final androidx.media.MediaSessionManager.RemoteUserInfo getCurrentBrowserInfo();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getSessionToken();
+ method public void notifyChildrenChanged(String);
+ method public void notifyChildrenChanged(String, android.os.Bundle);
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public void onCustomAction(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<android.os.Bundle>);
+ method public abstract androidx.media.MediaBrowserServiceCompat.BrowserRoot? onGetRoot(String, int, android.os.Bundle?);
+ method public abstract void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>);
+ method public void onLoadChildren(String, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>, android.os.Bundle);
+ method public void onLoadItem(String!, androidx.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+ method public void onSearch(String, android.os.Bundle!, androidx.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>);
+ method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token!);
+ field public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
+ }
+
+ public static final class MediaBrowserServiceCompat.BrowserRoot {
+ ctor public MediaBrowserServiceCompat.BrowserRoot(String, android.os.Bundle?);
+ method public android.os.Bundle! getExtras();
+ method public String! getRootId();
+ field public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ field @Deprecated public static final String EXTRA_SUGGESTION_KEYWORDS = "android.service.media.extra.SUGGESTION_KEYWORDS";
+ }
+
+ public static class MediaBrowserServiceCompat.Result<T> {
+ method public void detach();
+ method public void sendError(android.os.Bundle!);
+ method public void sendProgressUpdate(android.os.Bundle!);
+ method public void sendResult(T!);
+ }
+
+ public final class MediaSessionManager {
+ method public static androidx.media.MediaSessionManager getSessionManager(android.content.Context);
+ method public boolean isTrustedForMediaControl(androidx.media.MediaSessionManager.RemoteUserInfo);
+ }
+
+ public static final class MediaSessionManager.RemoteUserInfo {
+ ctor public MediaSessionManager.RemoteUserInfo(String, int, int);
+ method public String getPackageName();
+ method public int getPid();
+ method public int getUid();
+ field public static final String LEGACY_CONTROLLER = "android.media.session.MediaController";
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method public final int getVolumeControl();
+ method public Object! getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(androidx.media.VolumeProviderCompat.Callback!);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public abstract static class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(androidx.media.VolumeProviderCompat!);
+ }
+
+}
+
+package androidx.media.app {
+
+ public class NotificationCompat {
+ }
+
+ public static class NotificationCompat.DecoratedMediaCustomViewStyle extends androidx.media.app.NotificationCompat.MediaStyle {
+ ctor public NotificationCompat.DecoratedMediaCustomViewStyle();
+ }
+
+ public static class NotificationCompat.MediaStyle extends androidx.core.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(androidx.core.app.NotificationCompat.Builder!);
+ method public static android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession(android.app.Notification!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setCancelButtonIntent(android.app.PendingIntent!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowActionsInCompactView(int...!);
+ method public androidx.media.app.NotificationCompat.MediaStyle! setShowCancelButton(boolean);
+ }
+
+}
+
+package androidx.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, long);
+ method public static android.app.PendingIntent! buildMediaButtonPendingIntent(android.content.Context!, android.content.ComponentName!, long);
+ method public static android.view.KeyEvent! handleIntent(android.support.v4.media.session.MediaSessionCompat!, android.content.Intent!);
+ method public void onReceive(android.content.Context!, android.content.Intent!);
+ }
+
+}
+
diff --git a/media/api/res-1.1.0-alpha03.txt b/media/api/res-1.1.0-alpha03.txt
new file mode 100644
index 0000000..7a1e44d
--- /dev/null
+++ b/media/api/res-1.1.0-alpha03.txt
@@ -0,0 +1,5 @@
+style TextAppearance_Compat_Notification_Info_Media
+style TextAppearance_Compat_Notification_Line2_Media
+style TextAppearance_Compat_Notification_Media
+style TextAppearance_Compat_Notification_Time_Media
+style TextAppearance_Compat_Notification_Title_Media
diff --git a/media/build.gradle b/media/build.gradle
index 8f04a69..16eb997 100644
--- a/media/build.gradle
+++ b/media/build.gradle
@@ -7,10 +7,8 @@
}
dependencies {
- api(project(":annotation"))
- api(project(":core"))
- implementation(project(":collection"))
- api("androidx.versionedparcelable:versionedparcelable:1.1.0-alpha01")
+ api("androidx.core:core:1.1.0-alpha05")
+ implementation("androidx.collection:collection:1.0.0")
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
diff --git a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
index 12f7083..903816f 100644
--- a/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media/src/main/java/android/support/v4/media/MediaBrowserCompat.java
@@ -15,7 +15,7 @@
*/
package android.support.v4.media;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
@@ -445,7 +445,7 @@
* String, Bundle)}
* @hide
*/
- @RestrictTo(LIBRARY)
+ @RestrictTo(LIBRARY_GROUP)
public @Nullable Bundle getNotifyChildrenChangedOptions() {
return mImpl.getNotifyChildrenChangedOptions();
}
diff --git a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
index 813f30f..3ddf989 100644
--- a/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media/src/main/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -17,6 +17,7 @@
package android.support.v4.media.session;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.app.Activity;
@@ -824,7 +825,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY)
+ @RestrictTo(LIBRARY_GROUP)
public IMediaControllerCallback getIControllerCallback() {
return mIControllerCallback;
}
diff --git a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
index b2da6c8..d9ffcd9 100644
--- a/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
+++ b/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java
@@ -17,6 +17,7 @@
package androidx.media;
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
import static androidx.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
@@ -1102,7 +1103,9 @@
mConnections.remove(b);
ConnectionRecord connection = null;
- for (ConnectionRecord pendingConnection : mPendingConnections) {
+ Iterator<ConnectionRecord> iter = mPendingConnections.iterator();
+ while (iter.hasNext()) {
+ ConnectionRecord pendingConnection = iter.next();
// Note: We cannot use Map/Set for mPendingConnections but List because
// multiple MediaBrowserCompats with the same UID can request connect.
if (pendingConnection.uid == uid) {
@@ -1114,7 +1117,7 @@
pendingConnection.pid, pendingConnection.uid,
rootHints, callbacks);
}
- mPendingConnections.remove(pendingConnection);
+ iter.remove();
}
}
if (connection == null) {
@@ -1301,7 +1304,7 @@
*
* @hide
*/
- @RestrictTo(LIBRARY)
+ @RestrictTo(LIBRARY_GROUP)
public void attachToBaseContext(Context base) {
attachBaseContext(base);
}
diff --git a/media/src/main/java/androidx/media/MediaSessionManager.java b/media/src/main/java/androidx/media/MediaSessionManager.java
index c8acdae..c75c47d 100644
--- a/media/src/main/java/androidx/media/MediaSessionManager.java
+++ b/media/src/main/java/androidx/media/MediaSessionManager.java
@@ -204,7 +204,7 @@
* met:
* <ol>
* <li>UID and package name are the same</li>
- * <li>One of the RemoteUserInfo's PID is {@link #UNKNOWN_PID} or both of RemoteUserInfo's
+ * <li>One of the RemoteUserInfo's PID is UNKNOWN_PID or both of RemoteUserInfo's
* PID are the same</li>
* </ol>
*
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 0f4d8e8..280ceb9 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -71,11 +71,13 @@
import android.support.v4.media.session.MediaControllerCompat.TransportControls;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
import android.view.KeyEvent;
import androidx.media.test.lib.CustomParcelable;
public class ClientBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "ClientBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
@@ -89,6 +91,7 @@
return;
}
int method = extras.getInt(KEY_METHOD_ID, 0);
+ Log.d(TAG, "action=" + intent.getAction() + ", method=" + method);
if (ACTION_CALL_MEDIA_CONTROLLER_METHOD.equals(intent.getAction()) && extras != null) {
Bundle arguments;
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
index 411f14d..8defe09 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -63,6 +63,7 @@
import static org.junit.Assert.fail;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.mediacompat.testlib.util.PollingCheck;
@@ -315,6 +316,54 @@
@Test
@MediumTest
+ public void testMultipleConnections() throws Exception {
+ final Context context = getInstrumentation().getTargetContext();
+ final StubConnectionCallback callback1 = new StubConnectionCallback();
+ final StubConnectionCallback callback2 = new StubConnectionCallback();
+ final StubConnectionCallback callback3 = new StubConnectionCallback();
+ final List<MediaBrowserCompat> browserList = new ArrayList<>();
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ MediaBrowserCompat browser1 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback1, new Bundle());
+ MediaBrowserCompat browser2 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback2, new Bundle());
+ MediaBrowserCompat browser3 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback3, new Bundle());
+
+ browserList.add(browser1);
+ browserList.add(browser2);
+ browserList.add(browser3);
+
+ browser1.connect();
+ browser2.connect();
+ browser3.connect();
+ }
+ });
+
+ try {
+ new PollingCheck(TIME_OUT_MS) {
+ @Override
+ protected boolean check() {
+ return callback1.mConnectedCount == 1
+ && callback2.mConnectedCount == 1
+ && callback3.mConnectedCount == 1;
+ }
+ }.run();
+ } finally {
+ for (int i = 0; i < browserList.size(); i++) {
+ MediaBrowserCompat browser = browserList.get(i);
+ if (browser.isConnected()) {
+ browser.disconnect();
+ }
+ }
+ }
+ }
+
+ @Test
+ @MediumTest
public void testSubscribe() throws Exception {
connectMediaBrowserService();
diff --git a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
index fe57150..185f0f5 100644
--- a/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
+++ b/media/version-compat-tests/current/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserTest.java
@@ -43,6 +43,7 @@
import static org.junit.Assert.fail;
import android.content.ComponentName;
+import android.content.Context;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.browse.MediaBrowser.MediaItem;
@@ -295,6 +296,54 @@
@Test
@MediumTest
+ public void testMultipleConnections() throws Exception {
+ final Context context = getInstrumentation().getTargetContext();
+ final StubConnectionCallback callback1 = new StubConnectionCallback();
+ final StubConnectionCallback callback2 = new StubConnectionCallback();
+ final StubConnectionCallback callback3 = new StubConnectionCallback();
+ final List<MediaBrowser> browserList = new ArrayList<>();
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ MediaBrowser browser1 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+ callback1, new Bundle());
+ MediaBrowser browser2 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+ callback2, new Bundle());
+ MediaBrowser browser3 = new MediaBrowser(context, TEST_BROWSER_SERVICE,
+ callback3, new Bundle());
+
+ browserList.add(browser1);
+ browserList.add(browser2);
+ browserList.add(browser3);
+
+ browser1.connect();
+ browser2.connect();
+ browser3.connect();
+ }
+ });
+
+ try {
+ new PollingCheck(TIME_OUT_MS) {
+ @Override
+ protected boolean check() {
+ return callback1.mConnectedCount == 1
+ && callback2.mConnectedCount == 1
+ && callback3.mConnectedCount == 1;
+ }
+ }.run();
+ } finally {
+ for (int i = 0; i < browserList.size(); i++) {
+ MediaBrowser browser = browserList.get(i);
+ if (browser.isConnected()) {
+ browser.disconnect();
+ }
+ }
+ }
+ }
+
+ @Test
+ @MediumTest
public void testSubscribe() throws Exception {
connectMediaBrowserService();
diff --git a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
index 56a99c2..1df961d 100644
--- a/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
+++ b/media/version-compat-tests/current/service/src/androidTest/java/android/support/mediacompat/service/MediaSessionCompatCallbackTest.java
@@ -132,6 +132,7 @@
// The maximum time to wait for an operation.
private static final long TIME_OUT_MS = 3000L;
+ private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 300L;
private static final long TEST_POSITION = 1000000L;
@@ -868,7 +869,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -878,10 +881,11 @@
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
callMediaControllerMethod(SET_VOLUME_TO, targetVolume, getApplicationContext(),
mSession.getSessionToken());
- new PollingCheck(TIME_OUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
@@ -904,7 +908,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -915,10 +921,11 @@
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
callMediaControllerMethod(ADJUST_VOLUME, direction, getApplicationContext(),
mSession.getSessionToken());
- new PollingCheck(TIME_OUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
diff --git a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
index 1baba08..5119908 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
+++ b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/ClientBroadcastReceiver.java
@@ -70,11 +70,13 @@
import android.support.v4.media.session.MediaControllerCompat.TransportControls;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
import android.view.KeyEvent;
import androidx.media.test.lib.CustomParcelable;
public class ClientBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "ClientBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
@@ -88,6 +90,7 @@
return;
}
int method = extras.getInt(KEY_METHOD_ID, 0);
+ Log.d(TAG, "action=" + intent.getAction() + ", method=" + method);
if (ACTION_CALL_MEDIA_CONTROLLER_METHOD.equals(intent.getAction()) && extras != null) {
Bundle arguments;
diff --git a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
index ffabc62..7a7d0c1 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
+++ b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/MediaBrowserCompatTest.java
@@ -59,6 +59,7 @@
import static org.junit.Assert.fail;
import android.content.ComponentName;
+import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -309,6 +310,54 @@
@Test
@MediumTest
+ public void testMultipleConnections() throws Exception {
+ final Context context = getInstrumentation().getTargetContext();
+ final StubConnectionCallback callback1 = new StubConnectionCallback();
+ final StubConnectionCallback callback2 = new StubConnectionCallback();
+ final StubConnectionCallback callback3 = new StubConnectionCallback();
+ final List<MediaBrowserCompat> browserList = new ArrayList<>();
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ MediaBrowserCompat browser1 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback1, new Bundle());
+ MediaBrowserCompat browser2 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback2, new Bundle());
+ MediaBrowserCompat browser3 = new MediaBrowserCompat(context, TEST_BROWSER_SERVICE,
+ callback3, new Bundle());
+
+ browserList.add(browser1);
+ browserList.add(browser2);
+ browserList.add(browser3);
+
+ browser1.connect();
+ browser2.connect();
+ browser3.connect();
+ }
+ });
+
+ try {
+ new PollingCheck(TIME_OUT_MS) {
+ @Override
+ protected boolean check() {
+ return callback1.mConnectedCount == 1
+ && callback2.mConnectedCount == 1
+ && callback3.mConnectedCount == 1;
+ }
+ }.run();
+ } finally {
+ for (int i = 0; i < browserList.size(); i++) {
+ MediaBrowserCompat browser = browserList.get(i);
+ if (browser.isConnected()) {
+ browser.disconnect();
+ }
+ }
+ }
+ }
+
+ @Test
+ @MediumTest
public void testSubscribe() throws Exception {
connectMediaBrowserService();
diff --git a/media/version-compat-tests/runtest.sh b/media/version-compat-tests/runtest.sh
index 184672f..f6321ea 100755
--- a/media/version-compat-tests/runtest.sh
+++ b/media/version-compat-tests/runtest.sh
@@ -28,6 +28,8 @@
CLIENT_VERSION=""
SERVICE_VERSION=""
OPTION_TEST_TARGET=""
+VERSION_COMBINATION=""
+DEVICE_SERIAL=""
function printRunTestUsage() {
echo "Usage: ./runtest.sh <version_combination_number> [option]"
@@ -40,6 +42,7 @@
echo ""
echo "Option:"
echo " -t <class/method>: Only run the specific test class/method."
+ echo " -s <serial>: Use device with the serial. Required if multiple devices are connected."
}
function runTest() {
@@ -53,11 +56,11 @@
./gradlew $SERVICE_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
echo "Installing the test apks"
- adb install -r "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
- adb install -r "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+ adb $DEVICE_SERIAL install -r "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+ adb $DEVICE_SERIAL install -r "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
echo "Running the tests"
- local test_command="adb shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
+ local test_command="adb $DEVICE_SERIAL shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
local client_test_runner="android.support.mediacompat.client.test/androidx.test.runner.AndroidJUnitRunner"
local service_test_runner="android.support.mediacompat.service.test/androidx.test.runner.AndroidJUnitRunner"
@@ -85,23 +88,39 @@
exit 1;
fi
-if [[ $# -eq 0 || $1 -le 0 || $1 -gt 4 ]]
-then
- printRunTestUsage
- exit 1;
-fi
-
-if [[ ${2} == "-t" ]]; then
- if [[ ${3} == *"client"* || ${3} == *"service"* ]]; then
- OPTION_TEST_TARGET="-e class ${3}"
- else
- echo "Wrong test class/method name. Aborting."
- echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
- exit 1;
- fi
-fi
-
case ${1} in
+ 1|2|3|4)
+ VERSION_COMBINATION=${1}
+ shift
+ ;;
+ *)
+ printRunTestUsage
+ exit 1;
+esac
+
+while (( "$#" )); do
+ case ${1} in
+ -t)
+ if [[ ${2} == *"client"* || ${2} == *"service"* ]]; then
+ OPTION_TEST_TARGET="-e class ${2}"
+ else
+ echo "Wrong test class/method name. Aborting."
+ echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
+ exit 1;
+ fi
+ shift 2
+ ;;
+ -s)
+ DEVICE_SERIAL="-s ${2}"
+ shift 2
+ ;;
+ *)
+ printRunTestUsage
+ exit 1;
+ esac
+done
+
+case ${VERSION_COMBINATION} in
1)
CLIENT_VERSION="tot"
SERVICE_VERSION="tot"
diff --git a/media2-widget/build.gradle b/media2-widget/build.gradle
index a26131a..7620fbd 100644
--- a/media2-widget/build.gradle
+++ b/media2-widget/build.gradle
@@ -25,9 +25,8 @@
dependencies {
api(project(":media2"))
api(project(":mediarouter"))
- // TODO: change to 1.1.0-alpha03 after release
- api(project(":appcompat"))
- api("androidx.palette:palette:1.0.0")
+ implementation("androidx.appcompat:appcompat:1.0.2")
+ implementation("androidx.palette:palette:1.0.0")
implementation(project(":concurrent:concurrent-futures"))
androidTestImplementation(TEST_EXT_JUNIT)
diff --git a/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java b/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
index e6acd7e..c381616 100644
--- a/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
+++ b/media2-widget/src/androidTest/java/androidx/media2/widget/MediaControlViewTest.java
@@ -462,7 +462,7 @@
}
private MediaItem createTestMediaItem2(Uri uri) {
- return new UriMediaItem.Builder(mVideoView.getContext(), uri).build();
+ return new UriMediaItem.Builder(uri).build();
}
private MediaController createController(MediaController.ControllerCallback callback) {
diff --git a/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java b/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
index a8d4819..4f01d40 100644
--- a/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
+++ b/media2-widget/src/androidTest/java/androidx/media2/widget/VideoViewTest.java
@@ -24,7 +24,7 @@
import static androidx.media2.widget.MediaControlView.EVENT_UPDATE_SUBTITLE_SELECTED;
import static androidx.media2.widget.MediaControlView.EVENT_UPDATE_TRACK_STATUS;
import static androidx.media2.widget.MediaControlView.KEY_SELECTED_SUBTITLE_INDEX;
-import static androidx.media2.widget.MediaControlView.KEY_SUBTITLE_TRACK_COUNT;
+import static androidx.media2.widget.MediaControlView.KEY_SUBTITLE_TRACK_LANGUAGE_LIST;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -74,6 +74,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -287,7 +288,7 @@
verify(mControllerCallback, timeout(TIME_OUT).atLeastOnce()).onCustomCommand(
any(MediaController.class),
argThat(new CommandMatcher(EVENT_UPDATE_TRACK_STATUS)),
- argThat(new CommandArgumentMatcher(KEY_SUBTITLE_TRACK_COUNT, 2)));
+ argThat(new CommandArgumentListMatcher(KEY_SUBTITLE_TRACK_LANGUAGE_LIST, 2)));
// Select the first subtitle track
Bundle extra = new Bundle();
@@ -345,6 +346,23 @@
}
}
+ class CommandArgumentListMatcher implements ArgumentMatcher<Bundle> {
+ final String mKey;
+ final int mExpectedSize;
+
+ CommandArgumentListMatcher(String key, int expectedSize) {
+ mKey = key;
+ mExpectedSize = expectedSize;
+ }
+
+ @Override
+ public boolean matches(Bundle argument) {
+ List<String> list = argument.getStringArrayList(mKey);
+ return (list == null && mExpectedSize == 0)
+ || (list != null && list.size() == mExpectedSize);
+ }
+ }
+
private void setKeepScreenOn() throws Throwable {
mActivityRule.runOnUiThread(new Runnable() {
@Override
@@ -380,7 +398,6 @@
Uri testVideoUri = Uri.parse(
"android.resource://" + mContext.getPackageName() + "/"
+ R.raw.testvideo_with_2_subtitle_tracks);
- return new UriMediaItem.Builder(mVideoView.getContext(), testVideoUri)
- .build();
+ return new UriMediaItem.Builder(testVideoUri).build();
}
}
diff --git a/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java b/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
index 853e0fd..37d53e9 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/MediaControlView.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -109,7 +110,6 @@
static final String KEY_VIDEO_TRACK_COUNT = "VideoTrackCount";
static final String KEY_AUDIO_TRACK_COUNT = "AudioTrackCount";
- static final String KEY_SUBTITLE_TRACK_COUNT = "SubtitleTrackCount";
static final String KEY_SUBTITLE_TRACK_LANGUAGE_LIST = "SubtitleTrackLanguageList";
static final String KEY_SELECTED_AUDIO_INDEX = "SelectedAudioIndex";
static final String KEY_SELECTED_SUBTITLE_INDEX = "SelectedSubtitleIndex";
@@ -174,7 +174,6 @@
private int mSettingsWindowMargin;
int mVideoTrackCount;
int mAudioTrackCount;
- int mSubtitleTrackCount;
int mSettingsMode;
int mSelectedSubtitleTrackIndex;
int mSelectedAudioTrackIndex;
@@ -188,8 +187,8 @@
long mNextSeekPosition;
boolean mDragging;
boolean mIsFullScreen;
+ boolean mIsShowingReplayButton;
boolean mOverflowIsShowing;
- boolean mIsStopped;
boolean mSeekAvailable;
boolean mIsAdvertisement;
boolean mNeedToHideBars;
@@ -205,6 +204,7 @@
// Relating to Center View
ViewGroup mCenterView;
+ private View mCenterViewBackground;
private View mEmbeddedTransportControls;
private View mMinimalTransportControls;
@@ -232,7 +232,6 @@
// Relating to Bottom Bar Right View
ViewGroup mBasicControls;
ViewGroup mExtraControls;
- ViewGroup mCustomButtons;
ImageButton mSubtitleButton;
ImageButton mFullScreenButton;
private TextView mAdRemainingView;
@@ -452,6 +451,8 @@
mTitleBar.setVisibility(
sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
+ mCenterViewBackground.setVisibility(
+ sizeType != SIZE_TYPE_FULL ? View.VISIBLE : View.INVISIBLE);
mEmbeddedTransportControls.setVisibility(
sizeType == SIZE_TYPE_EMBEDDED ? View.VISIBLE : View.INVISIBLE);
mMinimalTransportControls.setVisibility(
@@ -466,8 +467,6 @@
sizeType != SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
mMinimalFullScreenButton.setVisibility(
sizeType == SIZE_TYPE_MINIMAL ? View.VISIBLE : View.INVISIBLE);
- mCenterView.setVisibility(
- sizeType != SIZE_TYPE_FULL ? View.VISIBLE : View.INVISIBLE);
final int childLeft = getPaddingLeft();
final int childRight = childLeft + width;
@@ -515,14 +514,6 @@
}
@Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- // By default, show all bars and hide settings window and overflow view when view size is
- // changed.
- showAllBars();
- hideSettingsAndOverflow();
- }
-
- @Override
public void onVisibilityAggregated(boolean isVisible) {
super.onVisibilityAggregated(isVisible);
@@ -567,6 +558,7 @@
// Relating to Center View
mCenterView = findViewById(R.id.center_view);
+ mCenterViewBackground = findViewById(R.id.center_view_background);
mEmbeddedTransportControls = initTransportControls(R.id.embedded_transport_controls);
mMinimalTransportControls = initTransportControls(R.id.minimal_transport_controls);
@@ -599,7 +591,6 @@
// Relating to Bottom Bar Right View
mBasicControls = findViewById(R.id.basic_controls);
mExtraControls = findViewById(R.id.extra_controls);
- mCustomButtons = findViewById(R.id.custom_buttons);
mSubtitleButton = findViewById(R.id.subtitle);
mSubtitleButton.setOnClickListener(mSubtitleListener);
mFullScreenButton = findViewById(R.id.fullscreen);
@@ -636,6 +627,7 @@
R.dimen.mcv2_settings_offset);
mSettingsWindow = new PopupWindow(mSettingsListView, mEmbeddedSettingsItemWidth,
LayoutParams.WRAP_CONTENT, true);
+ mSettingsWindow.setBackgroundDrawable(new ColorDrawable());
mSettingsWindow.setOnDismissListener(mSettingsDismissListener);
float titleBarHeight = mResources.getDimension(R.dimen.mcv2_title_bar_height);
@@ -682,9 +674,7 @@
fadeInAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
- if (mSizeType != SIZE_TYPE_FULL) {
- mCenterView.setVisibility(View.VISIBLE);
- }
+ mCenterView.setVisibility(View.VISIBLE);
mMinimalFullScreenView.setVisibility(View.VISIBLE);
}
});
@@ -917,7 +907,7 @@
playPauseButton.setContentDescription(
mResources.getString(R.string.mcv2_play_button_desc));
} else {
- if (mIsStopped) {
+ if (mIsShowingReplayButton) {
mController.seekTo(0);
}
mController.play();
@@ -1032,8 +1022,8 @@
// Check if playback is currently stopped. In this case, update the pause button to
// show the play image instead of the replay image.
- if (mIsStopped) {
- updateForStoppedState(false);
+ if (mIsShowingReplayButton) {
+ updateReplayButton(false);
}
if (isCurrentMediaItemFromNetwork() && mController.isPlaying()) {
@@ -1099,14 +1089,13 @@
resetHideCallbacks();
removeCallbacks(mUpdateProgress);
- // If the media is currently stopped, rewinding will start the media from the
- // beginning. Instead, seek to 10 seconds before the end of the media.
- boolean stoppedWithDuration = mIsStopped && mDuration != 0;
+ // If replay button is shown, seek to 10 seconds before the end of the media.
+ boolean stoppedWithDuration = mIsShowingReplayButton && mDuration != 0;
long currentPosition = stoppedWithDuration ? mDuration : getLatestSeekPosition();
long seekPosition = Math.max(currentPosition - REWIND_TIME_MS, 0);
seekTo(seekPosition, /* shouldSeekNow= */ true);
if (stoppedWithDuration) {
- updateForStoppedState(/* isStopped= */ false);
+ updateReplayButton(/* toBeShown */ false);
}
}
};
@@ -1119,11 +1108,12 @@
long latestSeekPosition = getLatestSeekPosition();
seekTo(Math.min(latestSeekPosition + FORWARD_TIME_MS, mDuration), true);
- if (latestSeekPosition + FORWARD_TIME_MS >= mDuration) {
- // If the media is currently paused, fast-forwarding beyond the duration value will
- // not return a callback that updates the play/pause and ffwd buttons. Thus,
- // update the buttons manually here.
- updateForStoppedState(true);
+
+ // Note: In some edge cases, mDuration might be less than actual duration of
+ // the stream. If controller is in playing state, it should not show replay
+ // button even when the seekPosition >= mDuration.
+ if (latestSeekPosition + FORWARD_TIME_MS >= mDuration && !mController.isPlaying()) {
+ updateReplayButton(/* toBeShown */ true);
}
}
};
@@ -1160,8 +1150,6 @@
private final OnClickListener mFullScreenListener = new OnClickListener() {
@Override
public void onClick(View v) {
- resetHideCallbacks();
-
if (mOnFullScreenListener == null) {
return;
}
@@ -1255,16 +1243,8 @@
if (position != mSelectedSubtitleTrackIndex) {
if (position > 0) {
mController.showSubtitle(position - 1);
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_subtitle_on));
- mSubtitleButton.setContentDescription(
- mResources.getString(R.string.mcv2_cc_is_on));
} else {
mController.hideSubtitle();
- mSubtitleButton.setImageDrawable(
- mResources.getDrawable(R.drawable.ic_subtitle_off));
- mSubtitleButton.setContentDescription(
- mResources.getString(R.string.mcv2_cc_is_off));
}
}
dismissSettingsWindow();
@@ -1364,9 +1344,9 @@
break;
}
- // Update play/pause and ffwd buttons based on whether the media is currently stopped or
- // not.
- updateForStoppedState(mIsStopped);
+ // Update play/pause and ffwd buttons based on whether currently the replay button is shown
+ // or not.
+ updateReplayButton(mIsShowingReplayButton);
}
private View initTransportControls(int id) {
@@ -1581,25 +1561,6 @@
}
}
- private void showAllBars() {
- if (mUxState != UX_STATE_ALL_VISIBLE) {
- removeCallbacks(mHideMainBars);
- removeCallbacks(mHideProgressBar);
- // b/112570875
- post(mShowMainBars);
- } else {
- resetHideCallbacks();
- }
- }
-
- private void hideSettingsAndOverflow() {
- mSettingsWindow.dismiss();
- if (mOverflowIsShowing) {
- mOverflowIsShowing = false;
- mOverflowHideAnimator.start();
- }
- }
-
long getLatestSeekPosition() {
if (mNextSeekPosition != SEEK_POSITION_NOT_SET) {
return mNextSeekPosition;
@@ -1622,11 +1583,11 @@
mSubSettingsAdapter.setCheckPosition(mSelectedSpeedIndex);
}
- void updateForStoppedState(boolean isStopped) {
+ void updateReplayButton(boolean toBeShown) {
ImageButton playPauseButton = findControlButton(mSizeType, R.id.pause);
ImageButton ffwdButton = findControlButton(mSizeType, R.id.ffwd);
- if (isStopped) {
- mIsStopped = true;
+ if (toBeShown) {
+ mIsShowingReplayButton = true;
if (playPauseButton != null) {
playPauseButton.setImageDrawable(
mResources.getDrawable(R.drawable.ic_replay_circle_filled));
@@ -1638,7 +1599,7 @@
ffwdButton.setEnabled(false);
}
} else {
- mIsStopped = false;
+ mIsShowingReplayButton = false;
if (playPauseButton != null) {
if (mController.isPlaying()) {
playPauseButton.setImageDrawable(
@@ -1677,20 +1638,6 @@
mIconIds = iconIds;
}
- public void updateSubTexts(List<String> subTexts) {
- mSubTexts = subTexts;
- notifyDataSetChanged();
- }
-
- public String getMainText(int position) {
- if (mMainTexts != null) {
- if (position < mMainTexts.size()) {
- return mMainTexts.get(position);
- }
- }
- return RESOURCE_EMPTY;
- }
-
@Override
public int getCount() {
return (mMainTexts == null) ? 0 : mMainTexts.size();
@@ -2007,7 +1954,7 @@
removeCallbacks(mUpdateProgress);
post(mUpdateProgress);
resetHideCallbacks();
- updateForStoppedState(false);
+ updateReplayButton(false);
break;
case SessionPlayer.PLAYER_STATE_PAUSED:
playPauseButton.setImageDrawable(
@@ -2043,7 +1990,7 @@
}
@Override
- public void onSeekCompleted(MediaController controller, long position) {
+ public void onSeekCompleted(@NonNull MediaController controller, long position) {
if (DEBUG) {
Log.d(TAG, "onSeekCompleted(): " + position);
}
@@ -2062,11 +2009,14 @@
} else {
mCurrentSeekPosition = SEEK_POSITION_NOT_SET;
- // If the next seek position is not set, start to update progress.
- removeCallbacks(mUpdateProgress);
- removeCallbacks(mHideMainBars);
- post(mUpdateProgress);
- postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
+ // If the next seek position is not set and the progress bar thumb is not being
+ // dragged, start to update progress.
+ if (!mDragging) {
+ removeCallbacks(mUpdateProgress);
+ removeCallbacks(mHideMainBars);
+ post(mUpdateProgress);
+ postDelayedRunnable(mHideMainBars, mDelayedAnimationIntervalMs);
+ }
}
}
@@ -2083,11 +2033,11 @@
}
@Override
- public void onPlaybackCompleted(MediaController controller) {
+ public void onPlaybackCompleted(@NonNull MediaController controller) {
if (DEBUG) {
Log.d(TAG, "onPlaybackCompleted()");
}
- updateForStoppedState(true);
+ updateReplayButton(true);
// The progress bar and current time text may not have been updated.
mProgress.setProgress(MAX_PROGRESS);
mCurrentTime.setText(stringForTime(mDuration));
@@ -2116,7 +2066,7 @@
@Override
public void onPlaylistChanged(@NonNull MediaController controller,
- @NonNull List<MediaItem> list,
+ @Nullable List<MediaItem> list,
@Nullable MediaMetadata metadata) {
if (DEBUG) {
Log.d(TAG, "onPlaylistChanged(): list: " + list);
@@ -2167,6 +2117,7 @@
}
@Override
+ @NonNull
public SessionResult onCustomCommand(
@NonNull MediaController controller, @NonNull SessionCommand command,
@Nullable Bundle args) {
@@ -2198,31 +2149,29 @@
if (mVideoTrackCount == 0 && mAudioTrackCount > 0) {
mMediaType = MEDIA_TYPE_MUSIC;
}
- mSubtitleTrackCount = (args != null)
- ? args.getInt(KEY_SUBTITLE_TRACK_COUNT) : 0;
- List<String> subtitleTracksLanguageList = (args != null)
+ List<String> subtitleTracksList = (args != null)
? args.getStringArrayList(KEY_SUBTITLE_TRACK_LANGUAGE_LIST) : null;
- mSubtitleDescriptionsList = new ArrayList<String>();
- if (mSubtitleTrackCount > 0) {
- mSubtitleButton.setAlpha(1.0f);
- mSubtitleButton.setEnabled(true);
+ if (subtitleTracksList != null) {
+ mSubtitleDescriptionsList = new ArrayList<String>();
mSubtitleDescriptionsList.add(mResources.getString(
R.string.MediaControlView_subtitle_off_text));
- for (int i = 0; i < mSubtitleTrackCount; i++) {
- String lang = subtitleTracksLanguageList.get(i);
- String track;
+ for (int i = 0; i < subtitleTracksList.size(); i++) {
+ String lang = subtitleTracksList.get(i);
+ String trackDescription;
if (lang.equals("")) {
- track = mResources.getString(
+ trackDescription = mResources.getString(
R.string.MediaControlView_subtitle_track_number_text,
i + 1);
} else {
- track = mResources.getString(
+ trackDescription = mResources.getString(
R.string
.MediaControlView_subtitle_track_number_and_lang_text,
i + 1, lang);
}
- mSubtitleDescriptionsList.add(track);
+ mSubtitleDescriptionsList.add(trackDescription);
}
+ mSubtitleButton.setAlpha(1.0f);
+ mSubtitleButton.setEnabled(true);
} else {
if (mMediaType == MEDIA_TYPE_MUSIC) {
mSubtitleButton.setVisibility(View.GONE);
@@ -2244,7 +2193,8 @@
int selectedTrackIndex = args != null
? args.getInt(KEY_SELECTED_SUBTITLE_INDEX, -1)
: -1;
- if (selectedTrackIndex < 0 || selectedTrackIndex >= mSubtitleTrackCount) {
+ if (selectedTrackIndex < 0
+ || selectedTrackIndex >= mSubtitleDescriptionsList.size()) {
Log.w(TAG, "Selected subtitle track index (" + selectedTrackIndex
+ ") is out of range.");
break;
@@ -2253,12 +2203,20 @@
if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
}
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_on));
+ mSubtitleButton.setContentDescription(
+ mResources.getString(R.string.mcv2_cc_is_on));
break;
case EVENT_UPDATE_SUBTITLE_DESELECTED:
mSelectedSubtitleTrackIndex = 0;
if (mSettingsMode == SETTINGS_MODE_SUBTITLE_TRACK) {
mSubSettingsAdapter.setCheckPosition(mSelectedSubtitleTrackIndex);
}
+ mSubtitleButton.setImageDrawable(
+ mResources.getDrawable(R.drawable.ic_subtitle_off));
+ mSubtitleButton.setContentDescription(
+ mResources.getString(R.string.mcv2_cc_is_off));
break;
default:
return new SessionResult(
diff --git a/media2-widget/src/main/java/androidx/media2/widget/VideoView.java b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
index 2a16f90..0ab34d2 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/VideoView.java
@@ -49,7 +49,6 @@
import androidx.media2.MediaItem;
import androidx.media2.MediaMetadata;
import androidx.media2.MediaPlayer;
-import androidx.media2.MediaPlayer2;
import androidx.media2.MediaSession;
import androidx.media2.RemoteSessionPlayer;
import androidx.media2.SessionCommand;
@@ -106,7 +105,6 @@
* assign the custom media control widget using {@link #setMediaControlView}.
* <li> {@link VideoView} is integrated with {@link androidx.media2.MediaSession} and so
* it responses with media key events.
- * </p>
* </ul>
*
* <p>
@@ -199,7 +197,7 @@
int mSelectedAudioTrackIndex;
int mSelectedSubtitleTrackIndex;
- private SubtitleAnchorView mSubtitleAnchorView;
+ SubtitleAnchorView mSubtitleAnchorView;
private MediaRouter mMediaRouter;
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -298,9 +296,6 @@
if (DEBUG) {
Log.d(TAG, "onSurfaceTakeOverDone(). Now current view is: " + view);
}
- if (mCurrentState != STATE_PLAYING && mMediaSession != null) {
- mMediaSession.getPlayer().seekTo(mMediaSession.getPlayer().getCurrentPosition());
- }
if (view != mCurrentView) {
((View) mCurrentView).setVisibility(View.GONE);
mCurrentView = view;
@@ -325,10 +320,10 @@
public VideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- initialize(context, attrs, defStyleAttr);
+ initialize(context, attrs);
}
- private void initialize(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ private void initialize(Context context, @Nullable AttributeSet attrs) {
mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
mAudioAttributes = new AudioAttributesCompat.Builder()
@@ -469,7 +464,14 @@
/**
* Selects which view will be used to render video between SurfaceView and TextureView.
- *
+ * <p>
+ * Note: There are two known issues on API level 28+ devices.
+ * <ul>
+ * <li> When changing view type to SurfaceView from TextureView in "paused" playback state,
+ * a blank screen can be shown.
+ * <li> When changing view type to TextureView from SurfaceView repeatedly in "paused" playback
+ * state, the lastly rendered frame on TextureView can be shown.
+ * </ul>
* @param viewType the view type to render video
* <ul>
* <li>{@link #VIEW_TYPE_SURFACEVIEW}
@@ -524,7 +526,7 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // Note: MediaPlayer2 and MediaSession instances are created in onAttachedToWindow()
+ // Note: MediaPlayer and MediaSession instances are created in onAttachedToWindow()
// and closed in onDetachedFromWindow().
if (mMediaPlayer == null) {
mMediaPlayer = new VideoViewPlayer(getContext());
@@ -559,6 +561,7 @@
try {
mMediaPlayer.close();
} catch (Exception e) {
+ Log.e(TAG, "Encountered an exception while performing MediaPlayer.close()", e);
}
mMediaSession.close();
mMediaPlayer = null;
@@ -686,7 +689,35 @@
mMediaPlayer.setMediaItem(mMediaItem);
final Context context = getContext();
- mSubtitleController = new SubtitleController(context);
+ SubtitleController.Listener listener = new SubtitleController.Listener() {
+ @Override
+ public void onSubtitleTrackSelected(SubtitleTrack track) {
+ if (track == null) {
+ mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
+ mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
+ mSubtitleAnchorView.setVisibility(View.GONE);
+
+ mMediaSession.broadcastCustomCommand(new SessionCommand(
+ MediaControlView.EVENT_UPDATE_SUBTITLE_DESELECTED, null), null);
+ return;
+ }
+ int indexInSubtitleTrackList = mSubtitleTracks.indexOfValue(track);
+ if (indexInSubtitleTrackList >= 0) {
+ int indexInEntireTrackList =
+ mSubtitleTracks.keyAt(indexInSubtitleTrackList);
+ mMediaPlayer.selectTrack(indexInEntireTrackList);
+ mSelectedSubtitleTrackIndex = indexInEntireTrackList;
+ mSubtitleAnchorView.setVisibility(View.VISIBLE);
+
+ Bundle data = new Bundle();
+ data.putInt(MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
+ indexInSubtitleTrackList);
+ mMediaSession.broadcastCustomCommand(new SessionCommand(
+ MediaControlView.EVENT_UPDATE_SUBTITLE_SELECTED, null), data);
+ }
+ }
+ };
+ mSubtitleController = new SubtitleController(context, null, listener);
mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
mSubtitleController.registerRenderer(new Cea708CaptionRenderer(context));
mSubtitleController.setAnchor(mSubtitleAnchorView);
@@ -729,17 +760,7 @@
}
SubtitleTrack track = mSubtitleTracks.get(trackIndex);
if (track != null) {
- mMediaPlayer.selectTrack(trackIndex);
mSubtitleController.selectTrack(track);
- mSelectedSubtitleTrackIndex = trackIndex;
- mSubtitleAnchorView.setVisibility(View.VISIBLE);
-
- Bundle data = new Bundle();
- data.putInt(MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
- mSubtitleTracks.indexOfKey(trackIndex));
- mMediaSession.broadcastCustomCommand(
- new SessionCommand(MediaControlView.EVENT_UPDATE_SUBTITLE_SELECTED, null),
- data);
}
}
@@ -747,13 +768,7 @@
if (!isMediaPrepared() || mSelectedSubtitleTrackIndex == INVALID_TRACK_INDEX) {
return;
}
- mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
- mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
- mSubtitleAnchorView.setVisibility(View.GONE);
-
- mMediaSession.broadcastCustomCommand(
- new SessionCommand(MediaControlView.EVENT_UPDATE_SUBTITLE_DESELECTED, null),
- null);
+ mSubtitleController.selectTrack(null);
}
// TODO: move this method inside callback to make sure it runs inside the callback thread.
@@ -763,20 +778,19 @@
mAudioTrackIndices = new ArrayList<>();
mSubtitleTracks = new SparseArray<>();
ArrayList<String> subtitleTracksLanguageList = new ArrayList<>();
+ int selectedSubtitleTrackIndex = mSelectedSubtitleTrackIndex;
mSubtitleController.reset();
for (int i = 0; i < trackInfos.size(); ++i) {
int trackType = trackInfos.get(i).getTrackType();
- if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
+ if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
mVideoTrackIndices.add(i);
- } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
+ } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
mAudioTrackIndices.add(i);
- } else if (trackType == MediaPlayer2.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
SubtitleTrack track = mSubtitleController.addTrack(trackInfos.get(i).getFormat());
if (track != null) {
mSubtitleTracks.put(i, track);
- String language =
- (trackInfos.get(i).getLanguage().equals(SUBTITLE_TRACK_LANG_UNDEFINED))
- ? "" : trackInfos.get(i).getLanguage();
+ String language = trackInfos.get(i).getLanguage().getISO3Language();
subtitleTracksLanguageList.add(language);
}
}
@@ -785,11 +799,14 @@
if (mAudioTrackIndices.size() > 0) {
mSelectedAudioTrackIndex = 0;
}
+ // Re-select originally selected subtitle track since SubtitleController has been reset.
+ if (selectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
+ selectSubtitleTrack(selectedSubtitleTrackIndex);
+ }
Bundle data = new Bundle();
data.putInt(MediaControlView.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
data.putInt(MediaControlView.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
- data.putInt(MediaControlView.KEY_SUBTITLE_TRACK_COUNT, mSubtitleTracks.size());
data.putStringArrayList(MediaControlView.KEY_SUBTITLE_TRACK_LANGUAGE_LIST,
subtitleTracksLanguageList);
return data;
@@ -812,7 +829,7 @@
new MediaPlayer.PlayerCallback() {
@Override
public void onVideoSizeChanged(
- MediaPlayer mp, MediaItem dsd, VideoSize size) {
+ @NonNull MediaPlayer mp, @NonNull MediaItem dsd, @NonNull VideoSize size) {
if (DEBUG) {
Log.d(TAG, "onVideoSizeChanged(): size: " + size.getWidth() + "/"
+ size.getHeight());
@@ -836,7 +853,7 @@
@Override
public void onInfo(
- MediaPlayer mp, MediaItem dsd, int what, int extra) {
+ @NonNull MediaPlayer mp, @NonNull MediaItem dsd, int what, int extra) {
if (DEBUG) {
Log.d(TAG, "onInfo()");
}
@@ -852,7 +869,7 @@
}
return;
}
- if (what == MediaPlayer2.MEDIA_INFO_METADATA_UPDATE) {
+ if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
Bundle data = extractTrackInfoData();
if (data != null) {
mMediaSession.broadcastCustomCommand(
@@ -864,7 +881,8 @@
@Override
public void onError(
- MediaPlayer mp, MediaItem dsd, int frameworkErr, int implErr) {
+ @NonNull MediaPlayer mp, @NonNull MediaItem dsd, int frameworkErr,
+ int implErr) {
if (DEBUG) {
Log.d(TAG, "Error: " + frameworkErr + "," + implErr);
}
@@ -888,7 +906,8 @@
@Override
public void onSubtitleData(
- MediaPlayer mp, MediaItem dsd, SubtitleData data) {
+ @NonNull MediaPlayer mp, @NonNull MediaItem dsd,
+ @NonNull SubtitleData data) {
if (DEBUG) {
Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
+ ", getCurrentPosition: " + mp.getCurrentPosition()
@@ -941,6 +960,15 @@
}
}
+ @Override
+ public void onPlaybackCompleted(@NonNull SessionPlayer player) {
+ if (player != mMediaPlayer) {
+ Log.d(TAG, "onPlaybackCompleted() is ignored. player is already gone.");
+ }
+ mCurrentState = STATE_PLAYBACK_COMPLETED;
+ mTargetState = STATE_PLAYBACK_COMPLETED;
+ }
+
private void onPrepared(SessionPlayer player) {
if (DEBUG) {
Log.d(TAG, "OnPreparedListener(): "
@@ -982,11 +1010,6 @@
}
}
}
-
- private void onCompletion(MediaPlayer mp, MediaItem dsd) {
- mCurrentState = STATE_PLAYBACK_COMPLETED;
- mTargetState = STATE_PLAYBACK_COMPLETED;
- }
};
class MediaSessionCallback extends MediaSession.SessionCallback {
@@ -1040,6 +1063,7 @@
}
@Override
+ @NonNull
public SessionResult onCustomCommand(@NonNull MediaSession session,
@NonNull MediaSession.ControllerInfo controller,
@NonNull SessionCommand customCommand, @Nullable Bundle args) {
@@ -1054,13 +1078,14 @@
}
switch (customCommand.getCustomCommand()) {
case MediaControlView.COMMAND_SHOW_SUBTITLE:
- int subtitleIndex = args != null ? args.getInt(
+ int indexInSubtitleTrackList = args != null ? args.getInt(
MediaControlView.KEY_SELECTED_SUBTITLE_INDEX,
INVALID_TRACK_INDEX) : INVALID_TRACK_INDEX;
- if (subtitleIndex != INVALID_TRACK_INDEX) {
- int subtitleTrackIndex = mSubtitleTracks.keyAt(subtitleIndex);
- if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
- selectSubtitleTrack(subtitleTrackIndex);
+ if (indexInSubtitleTrackList != INVALID_TRACK_INDEX) {
+ int indexInEntireTrackList =
+ mSubtitleTracks.keyAt(indexInSubtitleTrackList);
+ if (indexInEntireTrackList != mSelectedSubtitleTrackIndex) {
+ selectSubtitleTrack(indexInEntireTrackList);
}
}
break;
@@ -1140,7 +1165,6 @@
}
}
- @SuppressLint("RestrictedApi")
MediaMetadata extractMetadata(MediaItem mediaItem, boolean isMusic) {
MediaMetadataRetriever retriever = null;
String path = "";
@@ -1151,12 +1175,10 @@
Uri uri = ((UriMediaItem) mediaItem).getUri();
// Save file name as title since the file may not have a title Metadata.
- if (UriUtil.isFromNetwork(uri)) {
- path = uri.getPath();
- } else if ("file".equals(uri.getScheme())) {
+ if ("file".equals(uri.getScheme())) {
path = uri.getLastPathSegment();
} else {
- // TODO: needs default title. b/120515913
+ path = uri.toString();
}
retriever = new MediaMetadataRetriever();
retriever.setDataSource(mContext, uri);
diff --git a/media2-widget/src/main/java/androidx/media2/widget/VideoViewPlayer.java b/media2-widget/src/main/java/androidx/media2/widget/VideoViewPlayer.java
index 5ba6ef6..930b9b0 100644
--- a/media2-widget/src/main/java/androidx/media2/widget/VideoViewPlayer.java
+++ b/media2-widget/src/main/java/androidx/media2/widget/VideoViewPlayer.java
@@ -18,81 +18,17 @@
import android.content.Context;
-import androidx.media2.MediaItem;
-import androidx.media2.MediaMetadata;
import androidx.media2.MediaPlayer;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.ArrayList;
-import java.util.List;
-
class VideoViewPlayer extends MediaPlayer {
VideoViewPlayer(Context context) {
super(context);
}
- private MediaItem mMediaItem;
-
@Override
public ListenableFuture<PlayerResult> seekTo(long position) {
return super.seekTo(position, SEEK_CLOSEST);
}
-
- ///////////////////////////////////////////////////////////////////////////////////////
- // Workarounds for not throwing UnsupportedOperationException.
- // TODO: Remove overrides below.
- ///////////////////////////////////////////////////////////////////////////////////////
- @Override
- public List<MediaItem> getPlaylist() {
- try {
- return super.getPlaylist();
- } catch (Exception e) {
- ArrayList<MediaItem> list = new ArrayList<>();
- list.add(getCurrentMediaItem());
- return list;
- }
- }
-
- @Override
- public MediaMetadata getPlaylistMetadata() {
- try {
- return super.getPlaylistMetadata();
- } catch (Exception e) {
- return null;
- }
- }
-
- @Override
- public int getRepeatMode() {
- try {
- return super.getRepeatMode();
- } catch (Exception e) {
- return REPEAT_MODE_NONE;
- }
- }
-
- @Override
- public int getShuffleMode() {
- try {
- return super.getShuffleMode();
- } catch (Exception e) {
- return SHUFFLE_MODE_NONE;
- }
- }
-
- @Override
- public ListenableFuture<PlayerResult> setMediaItem(MediaItem item) {
- mMediaItem = item;
- return super.setMediaItem(item);
- }
-
- @Override
- public MediaItem getCurrentMediaItem() {
- try {
- return super.getCurrentMediaItem();
- } catch (Exception e) {
- return mMediaItem;
- }
- }
}
diff --git a/media2-widget/src/main/res/layout/media_controller.xml b/media2-widget/src/main/res/layout/media_controller.xml
index a61317d..bc49531 100644
--- a/media2-widget/src/main/res/layout/media_controller.xml
+++ b/media2-widget/src/main/res/layout/media_controller.xml
@@ -19,9 +19,14 @@
android:id="@+id/center_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/center_view_background"
android:layoutDirection="ltr">
+ <View
+ android:id="@+id/center_view_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/center_view_background" />
+
<include
android:id="@+id/embedded_transport_controls"
android:layout_width="wrap_content"
@@ -54,10 +59,10 @@
<TextView
android:id="@+id/title_text"
android:gravity="center_vertical"
- android:ellipsize="end"
+ android:ellipsize="middle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxLines="1"
+ android:singleLine="true"
android:paddingStart="@dimen/mcv2_embedded_icon_padding"
android:paddingEnd="@dimen/mcv2_embedded_icon_padding"
android:textSize="15sp"
@@ -206,12 +211,6 @@
android:visibility="gone"
android:layoutDirection="ltr">
- <LinearLayout
- android:id="@+id/custom_buttons"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal" />
-
<ImageButton
android:id="@+id/settings"
style="@style/BottomBarButton.Settings" />
diff --git a/media2/api/1.0.0-alpha05.txt b/media2/api/1.0.0-alpha05.txt
index 331fa20..d753947 100644
--- a/media2/api/1.0.0-alpha05.txt
+++ b/media2/api/1.0.0-alpha05.txt
@@ -316,7 +316,7 @@
method public float getMaxPlayerVolume();
method public int getNextMediaItemIndex();
method public androidx.media2.PlaybackParams getPlaybackParams();
- method public float getPlaybackSpeed();
+ method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
method public int getPlayerState();
method public float getPlayerVolume();
method public java.util.List<androidx.media2.MediaItem>? getPlaylist();
@@ -331,6 +331,7 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> pause();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> play();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> prepare();
+ method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.MediaPlayer.PlayerCallback);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> removePlaylistItem(@IntRange(from=0) int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem);
method public void reset();
@@ -339,11 +340,11 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> selectTrack(int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAudioAttributes(androidx.media.AudioAttributesCompat);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAudioSessionId(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setMediaItem(androidx.media2.MediaItem);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(@FloatRange(from=0, to=1) float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem>, androidx.media2.MediaMetadata?);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setRepeatMode(int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setShuffleMode(int);
@@ -351,6 +352,7 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToNextPlaylistItem();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPlaylistItem(@IntRange(from=0) int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPreviousPlaylistItem();
+ method public void unregisterPlayerCallback(androidx.media2.MediaPlayer.PlayerCallback);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata?);
field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
@@ -360,6 +362,7 @@
field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+ field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
@@ -383,7 +386,7 @@
public static final class MediaPlayer.TrackInfo {
method public android.media.MediaFormat? getFormat();
- method public String getLanguage();
+ method public java.util.Locale getLanguage();
method public int getTrackType();
field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
@@ -501,8 +504,8 @@
ctor public PlaybackParams.Builder(androidx.media2.PlaybackParams);
method public androidx.media2.PlaybackParams build();
method public androidx.media2.PlaybackParams.Builder setAudioFallbackMode(int);
- method public androidx.media2.PlaybackParams.Builder setPitch(float);
- method public androidx.media2.PlaybackParams.Builder setSpeed(float);
+ method public androidx.media2.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE) float);
+ method public androidx.media2.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
}
public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
@@ -624,7 +627,7 @@
field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
- field public static final long UNKNOWN_TIME = -1L; // 0xffffffffffffffffL
+ field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
}
public abstract static class SessionPlayer.PlayerCallback {
@@ -724,14 +727,13 @@
public class UriMediaItem extends androidx.media2.MediaItem {
method public android.net.Uri getUri();
- method public android.content.Context getUriContext();
method public java.util.List<java.net.HttpCookie>? getUriCookies();
method public java.util.Map<java.lang.String,java.lang.String>? getUriHeaders();
}
public static final class UriMediaItem.Builder extends androidx.media2.MediaItem.Builder {
- ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri);
- ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
+ ctor public UriMediaItem.Builder(android.net.Uri);
+ ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
method public androidx.media2.UriMediaItem build();
method public androidx.media2.UriMediaItem.Builder setEndPosition(long);
method public androidx.media2.UriMediaItem.Builder setMetadata(androidx.media2.MediaMetadata?);
diff --git a/media2/api/current.txt b/media2/api/current.txt
index 331fa20..d753947 100644
--- a/media2/api/current.txt
+++ b/media2/api/current.txt
@@ -316,7 +316,7 @@
method public float getMaxPlayerVolume();
method public int getNextMediaItemIndex();
method public androidx.media2.PlaybackParams getPlaybackParams();
- method public float getPlaybackSpeed();
+ method @FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) public float getPlaybackSpeed();
method public int getPlayerState();
method public float getPlayerVolume();
method public java.util.List<androidx.media2.MediaItem>? getPlaylist();
@@ -331,6 +331,7 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> pause();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> play();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> prepare();
+ method public void registerPlayerCallback(java.util.concurrent.Executor, androidx.media2.MediaPlayer.PlayerCallback);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> removePlaylistItem(@IntRange(from=0) int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> replacePlaylistItem(int, androidx.media2.MediaItem);
method public void reset();
@@ -339,11 +340,11 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> selectTrack(int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAudioAttributes(androidx.media.AudioAttributesCompat);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAudioSessionId(int);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setAuxEffectSendLevel(@FloatRange(from=0, to=1) float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setMediaItem(androidx.media2.MediaItem);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackParams(androidx.media2.PlaybackParams);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(float);
- method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaybackSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlayerVolume(@FloatRange(from=0, to=1) float);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setPlaylist(java.util.List<androidx.media2.MediaItem>, androidx.media2.MediaMetadata?);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setRepeatMode(int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> setShuffleMode(int);
@@ -351,6 +352,7 @@
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToNextPlaylistItem();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPlaylistItem(@IntRange(from=0) int);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> skipToPreviousPlaylistItem();
+ method public void unregisterPlayerCallback(androidx.media2.MediaPlayer.PlayerCallback);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media2.SessionPlayer.PlayerResult> updatePlaylistMetadata(androidx.media2.MediaMetadata?);
field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
@@ -360,6 +362,7 @@
field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+ field public static final int NO_TRACK_SELECTED = -2147483648; // 0x80000000
field public static final int PLAYER_ERROR_IO = -1004; // 0xfffffc14
field public static final int PLAYER_ERROR_MALFORMED = -1007; // 0xfffffc11
field public static final int PLAYER_ERROR_TIMED_OUT = -110; // 0xffffff92
@@ -383,7 +386,7 @@
public static final class MediaPlayer.TrackInfo {
method public android.media.MediaFormat? getFormat();
- method public String getLanguage();
+ method public java.util.Locale getLanguage();
method public int getTrackType();
field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
@@ -501,8 +504,8 @@
ctor public PlaybackParams.Builder(androidx.media2.PlaybackParams);
method public androidx.media2.PlaybackParams build();
method public androidx.media2.PlaybackParams.Builder setAudioFallbackMode(int);
- method public androidx.media2.PlaybackParams.Builder setPitch(float);
- method public androidx.media2.PlaybackParams.Builder setSpeed(float);
+ method public androidx.media2.PlaybackParams.Builder setPitch(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE) float);
+ method public androidx.media2.PlaybackParams.Builder setSpeed(@FloatRange(from=0.0f, to=java.lang.Float.MAX_VALUE, fromInclusive=false) float);
}
public interface Rating extends androidx.versionedparcelable.VersionedParcelable {
@@ -624,7 +627,7 @@
field public static final int SHUFFLE_MODE_ALL = 1; // 0x1
field public static final int SHUFFLE_MODE_GROUP = 2; // 0x2
field public static final int SHUFFLE_MODE_NONE = 0; // 0x0
- field public static final long UNKNOWN_TIME = -1L; // 0xffffffffffffffffL
+ field public static final long UNKNOWN_TIME = -9223372036854775808L; // 0x8000000000000000L
}
public abstract static class SessionPlayer.PlayerCallback {
@@ -724,14 +727,13 @@
public class UriMediaItem extends androidx.media2.MediaItem {
method public android.net.Uri getUri();
- method public android.content.Context getUriContext();
method public java.util.List<java.net.HttpCookie>? getUriCookies();
method public java.util.Map<java.lang.String,java.lang.String>? getUriHeaders();
}
public static final class UriMediaItem.Builder extends androidx.media2.MediaItem.Builder {
- ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri);
- ctor public UriMediaItem.Builder(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
+ ctor public UriMediaItem.Builder(android.net.Uri);
+ ctor public UriMediaItem.Builder(android.net.Uri, java.util.Map<java.lang.String,java.lang.String>?, java.util.List<java.net.HttpCookie>?);
method public androidx.media2.UriMediaItem build();
method public androidx.media2.UriMediaItem.Builder setEndPosition(long);
method public androidx.media2.UriMediaItem.Builder setMetadata(androidx.media2.MediaMetadata?);
diff --git a/media2/build.gradle b/media2/build.gradle
index 9c4a928..d3afeab 100644
--- a/media2/build.gradle
+++ b/media2/build.gradle
@@ -9,10 +9,9 @@
dependencies {
api(project(":media"))
- api('androidx.versionedparcelable:versionedparcelable:1.1.0-alpha01')
api(GUAVA_LISTENABLE_FUTURE)
implementation(project(":concurrent:concurrent-futures"))
- implementation(project(":collection"))
+ implementation("androidx.collection:collection:1.0.0")
compileOnly(CHECKER_FRAMEWORK)
// Depend on media2-exoplayer so that the library groupId is set to match media2.
implementation(project(":media2-exoplayer"))
diff --git a/media2/src/androidTest/AndroidManifest.xml b/media2/src/androidTest/AndroidManifest.xml
index 2c52d70..7da62aa 100644
--- a/media2/src/androidTest/AndroidManifest.xml
+++ b/media2/src/androidTest/AndroidManifest.xml
@@ -55,7 +55,6 @@
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
-
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
diff --git a/media2/src/androidTest/java/androidx/media2/MediaControllerTest.java b/media2/src/androidTest/java/androidx/media2/MediaControllerTest.java
index 533eec9..dc49b7a 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaControllerTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaControllerTest.java
@@ -36,6 +36,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -77,6 +78,7 @@
@FlakyTest
public class MediaControllerTest extends MediaSessionTestBase {
private static final String TAG = "MediaControllerTest";
+ private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
PendingIntent mIntent;
MediaSession mSession;
@@ -916,7 +918,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -931,9 +935,10 @@
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
@@ -956,7 +961,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -972,9 +979,10 @@
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
index cd59aca..15eb2ba 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2DrmTestBase.java
@@ -302,8 +302,7 @@
mPlayer.setEventCallback(mExecutor, mECb);
Log.v(TAG, "playLoadedVideo: setMediaItem()");
- mPlayer.setMediaItem(
- new UriMediaItem.Builder(mContext, file).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
mSetDataSourceCallCompleted.waitForSignal();
if (mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR) {
throw new PrepareFailedException();
@@ -557,8 +556,7 @@
mPlayer.setEventCallback(mExecutor, mECb);
Log.v(TAG, "playLoadedVideo: setMediaItem()");
- mPlayer.setMediaItem(
- new UriMediaItem.Builder(mContext, file).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
Log.v(TAG, "playLoadedVideo: prepare()");
mPlayer.prepare();
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
index 1ac7d97..d7827d1 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2Test.java
@@ -219,7 +219,7 @@
// test stop and restart
mp.reset();
mp.setEventCallback(mExecutor, ecb);
- mp.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+ mp.setMediaItem(new UriMediaItem.Builder(uri).build());
onPrepareCalled.reset();
mp.prepare();
onPrepareCalled.waitForSignal();
@@ -2335,7 +2335,7 @@
Uri uri = Uri.parse(outputFileLocation);
MediaPlayer2 mp = MediaPlayer2.create(mActivity);
try {
- mp.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+ mp.setMediaItem(new UriMediaItem.Builder(uri).build());
mp.prepare();
Thread.sleep(SLEEP_TIME);
playAndStop(mp);
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
index c473620..13dd1154 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayer2TestBase.java
@@ -121,7 +121,7 @@
new AudioAttributesCompat.Builder().build();
mp.setAudioAttributes(aa);
mp.setAudioSessionId(audioSessionId);
- mp.setMediaItem(new UriMediaItem.Builder(context, uri).build());
+ mp.setMediaItem(new UriMediaItem.Builder(uri).build());
if (holder != null) {
mp.setSurface(holder.getSurface());
}
@@ -417,7 +417,7 @@
final Uri uri = Uri.parse(path);
for (int i = 0; i < STREAM_RETRIES; i++) {
try {
- mPlayer.setMediaItem(new UriMediaItem.Builder(mContext, uri).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(uri).build());
playLoadedVideo(width, height, playTime);
playedSuccessfully = true;
break;
@@ -449,8 +449,7 @@
boolean playedSuccessfully = false;
for (int i = 0; i < STREAM_RETRIES; i++) {
try {
- mPlayer.setMediaItem(new UriMediaItem.Builder(
- mContext, uri, headers, cookies).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(uri, headers, cookies).build());
playLoadedVideo(width, height, playTime);
playedSuccessfully = true;
break;
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
index 0195c56..ca8caec 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerDrmTest.java
@@ -402,7 +402,7 @@
mPlayer.registerPlayerCallback(mExecutor, mECb);
Log.v(TAG, "playLoadedVideo: setMediaItem()");
ListenableFuture<PlayerResult> future =
- mPlayer.setMediaItem(new UriMediaItem.Builder(mContext, file).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
assertEquals(PlayerResult.RESULT_SUCCESS, future.get().getResultCode());
SurfaceHolder surfaceHolder = mActivity.getSurfaceHolder();
@@ -645,8 +645,7 @@
mPlayer.registerPlayerCallback(mExecutor, mECb);
Log.v(TAG, "playLoadedVideo: setMediaItem()");
- mPlayer.setMediaItem(
- new UriMediaItem.Builder(mContext, file).build());
+ mPlayer.setMediaItem(new UriMediaItem.Builder(file).build());
Log.v(TAG, "playLoadedVideo: prepare()");
ListenableFuture<PlayerResult> future = mPlayer.prepare();
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
index 8213785..f1b30da 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerTest.java
@@ -825,7 +825,7 @@
try {
mPlayer.setMediaItem(null);
fail();
- } catch (IllegalArgumentException e) {
+ } catch (NullPointerException e) {
// Expected exception
}
}
diff --git a/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
index 50cbb19..9a19cb9 100644
--- a/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
+++ b/media2/src/androidTest/java/androidx/media2/MediaPlayerTestBase.java
@@ -117,7 +117,7 @@
.build();
return mPlayer.setMediaItem(new UriMediaItem.Builder(
- mContext, testVideoUri).build()).get().getResultCode()
+ testVideoUri).build()).get().getResultCode()
== androidx.media2.SessionPlayer.PlayerResult.RESULT_SUCCESS;
}
diff --git a/media2/src/main/java/androidx/media2/AudioFocusHandler.java b/media2/src/main/java/androidx/media2/AudioFocusHandler.java
index 324623ee..74f884d 100644
--- a/media2/src/main/java/androidx/media2/AudioFocusHandler.java
+++ b/media2/src/main/java/androidx/media2/AudioFocusHandler.java
@@ -45,7 +45,7 @@
* @hide
*/
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-@RestrictTo(Scope.LIBRARY)
+@RestrictTo(Scope.LIBRARY_GROUP_PREFIX)
public class AudioFocusHandler {
private static final String TAG = "AudioFocusHandler";
private static final boolean DEBUG = true;
diff --git a/media2/src/main/java/androidx/media2/ConnectionResult.java b/media2/src/main/java/androidx/media2/ConnectionResult.java
index 1b9fb9b..6aa0eb2 100644
--- a/media2/src/main/java/androidx/media2/ConnectionResult.java
+++ b/media2/src/main/java/androidx/media2/ConnectionResult.java
@@ -16,7 +16,6 @@
package androidx.media2;
-import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.os.IBinder;
import android.os.SystemClock;
@@ -39,7 +38,6 @@
* All fields here are effectively final. Do not modify.
*/
@VersionedParcelize(isCustom = true)
-@SuppressLint("RestrictedApi")
class ConnectionResult extends CustomVersionedParcelable {
@ParcelField(0)
int mVersion;
diff --git a/media2/src/main/java/androidx/media2/LibraryResult.java b/media2/src/main/java/androidx/media2/LibraryResult.java
index ebf745f..6b06a46 100644
--- a/media2/src/main/java/androidx/media2/LibraryResult.java
+++ b/media2/src/main/java/androidx/media2/LibraryResult.java
@@ -19,7 +19,6 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-import android.annotation.SuppressLint;
import android.os.SystemClock;
import androidx.annotation.IntDef;
@@ -42,7 +41,6 @@
* {@link androidx.media2.MediaLibraryService.MediaLibrarySession} and {@link MediaBrowser}.
*/
@VersionedParcelize(isCustom = true)
-@SuppressLint("RestrictedApi")
public class LibraryResult extends CustomVersionedParcelable implements RemoteResult {
/**
* @hide
@@ -166,7 +164,7 @@
/**
* Gets the completion time of the command. Being more specific, it's the same as
- * {@link android.os.SystemClock#elapsedRealtime()} when the command is completed.
+ * {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
*
* @return completion time of the command
*/
diff --git a/media2/src/main/java/androidx/media2/MediaBrowserImplLegacy.java b/media2/src/main/java/androidx/media2/MediaBrowserImplLegacy.java
index fb14397..637fa5e 100644
--- a/media2/src/main/java/androidx/media2/MediaBrowserImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaBrowserImplLegacy.java
@@ -25,7 +25,6 @@
import static androidx.media2.MediaMetadata.METADATA_KEY_MEDIA_ID;
import static androidx.media2.MediaMetadata.METADATA_KEY_PLAYABLE;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat;
@@ -382,7 +381,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void onChildrenLoaded(final String parentId,
List<MediaBrowserCompat.MediaItem> children, final Bundle options) {
if (TextUtils.isEmpty(parentId)) {
diff --git a/media2/src/main/java/androidx/media2/MediaController.java b/media2/src/main/java/androidx/media2/MediaController.java
index a1c4039..f63a57d 100644
--- a/media2/src/main/java/androidx/media2/MediaController.java
+++ b/media2/src/main/java/androidx/media2/MediaController.java
@@ -302,7 +302,15 @@
}
/**
- * Requests that the player start or resume playback.
+ * Requests that the player starts or resumes playback.
+ * <p>
+ * If the player state is {@link SessionPlayer#PLAYER_STATE_IDLE}, the session would also call
+ * {@link SessionPlayer#prepare} and then {@link SessionPlayer#play} to start playback. If you
+ * want to have finer grained control of the playback start call {@link #prepare} manually
+ * before this. Calling {@link #prepare} in advance would help this method to start playback
+ * faster and also help to take audio focus at the last moment.
+ *
+ * @see #prepare
*/
@NonNull
public ListenableFuture<SessionResult> play() {
@@ -313,7 +321,10 @@
}
/**
- * Requests that the player pause playback.
+ * Requests that the player pauses playback.
+ * <p>
+ * This would transfer the player state from {@link SessionPlayer#PLAYER_STATE_PLAYING} to
+ * {@link SessionPlayer#PLAYER_STATE_PAUSED}.
*/
@NonNull
public ListenableFuture<SessionResult> pause() {
@@ -324,11 +335,15 @@
}
/**
- * Requests that the player prepare the media items for playback. In other words, other
- * sessions can continue to play during the prepare of this session. This method can be used
- * to speed up the start of the playback. Once the prepare is done, the player will change
- * its playback state to {@link SessionPlayer#PLAYER_STATE_PAUSED}. Afterwards, {@link #play}
- * can be called to start playback.
+ * Requests that the player prepares the media items for playback.
+ * <p>
+ * This would transfer the player state from {@link SessionPlayer#PLAYER_STATE_IDLE} to
+ * {@link SessionPlayer#PLAYER_STATE_PAUSED}.
+ * <p>
+ * Playback can be started without this. But this provides finer grained control of playback
+ * start. See {@link #play} for details.
+ *
+ * @see #play
*/
@NonNull
public ListenableFuture<SessionResult> prepare() {
diff --git a/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java b/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
index d88a058..769d2a3 100644
--- a/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/MediaControllerImplLegacy.java
@@ -548,7 +548,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public ListenableFuture<SessionResult> setRating(@NonNull String mediaId,
@NonNull Rating rating) {
synchronized (mLock) {
@@ -936,7 +935,6 @@
sendCommand(command, null, receiver);
}
- @SuppressLint("RestrictedApi")
private void sendCommand(String command, Bundle args, ResultReceiver receiver) {
if (args == null) {
args = new Bundle();
diff --git a/media2/src/main/java/androidx/media2/MediaItem.java b/media2/src/main/java/androidx/media2/MediaItem.java
index c2745b0..26f69714 100644
--- a/media2/src/main/java/androidx/media2/MediaItem.java
+++ b/media2/src/main/java/androidx/media2/MediaItem.java
@@ -19,7 +19,6 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.util.Log;
@@ -61,7 +60,6 @@
* This object is thread safe.
*/
@VersionedParcelize(isCustom = true)
-@SuppressLint("RestrictedApi")
public class MediaItem extends CustomVersionedParcelable {
private static final String TAG = "MediaItem";
@@ -321,7 +319,6 @@
*/
@RestrictTo(LIBRARY)
@Override
- @SuppressLint("RestrictedApi")
public void onPreParceling(boolean isStream) {
if (getClass() != MediaItem.class) {
throw new RuntimeException("MediaItem's subclasses shouldn't be parcelized.");
diff --git a/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java b/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
index ba62ce5..8cab20e 100644
--- a/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaLibrarySessionImplBase.java
@@ -19,7 +19,6 @@
import static androidx.media2.LibraryResult.RESULT_ERROR_UNKNOWN;
import static androidx.media2.LibraryResult.RESULT_SUCCESS;
-import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.os.RemoteException;
@@ -179,7 +178,6 @@
return returnedResult;
}
- @SuppressLint("RestrictedApi")
private boolean isValidItem(MediaItem item) {
if (item == null) {
throw new RuntimeException("Item shouldn't be null for the success");
diff --git a/media2/src/main/java/androidx/media2/MediaMetadata.java b/media2/src/main/java/androidx/media2/MediaMetadata.java
index 0244fde..b10807e 100644
--- a/media2/src/main/java/androidx/media2/MediaMetadata.java
+++ b/media2/src/main/java/androidx/media2/MediaMetadata.java
@@ -52,7 +52,7 @@
* {@link androidx.media2.widget.MediaControlView} will show title from the metadata.
* <p>
* The {@link MediaLibrarySession} would require some metadata values when it provides
- * {@link MediaItem}s to {@link MediaBrowser}.
+ * {@link androidx.media2.MediaItem}s to {@link MediaBrowser}.
* <p>
* Topics covered here:
* <ol>
@@ -117,7 +117,7 @@
// - Also support MediaDescription features. MediaDescription is deprecated instead because
// it was insufficient for controller to display media contents. (e.g. duration is missing)
// TODO: Remove once the minSdkVersion is lowered enough.
-@SuppressLint({"ObsoleteSdkInt", "RestrictedApi"})
+@SuppressLint("ObsoleteSdkInt")
@VersionedParcelize(isCustom = true)
public final class MediaMetadata extends CustomVersionedParcelable {
private static final String TAG = "MediaMetadata";
diff --git a/media2/src/main/java/androidx/media2/MediaNotificationHandler.java b/media2/src/main/java/androidx/media2/MediaNotificationHandler.java
index dbaabc6..bc63d9f 100644
--- a/media2/src/main/java/androidx/media2/MediaNotificationHandler.java
+++ b/media2/src/main/java/androidx/media2/MediaNotificationHandler.java
@@ -24,7 +24,6 @@
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -51,7 +50,7 @@
* @hide
*/
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class MediaNotificationHandler extends
MediaSession.SessionCallback.ForegroundServiceEventCallback {
private static final int NOTIFICATION_ID = 1001;
@@ -67,7 +66,6 @@
private final NotificationCompat.Action mSkipToPrevAction;
private final NotificationCompat.Action mSkipToNextAction;
- @SuppressLint("RestrictedApi")
public MediaNotificationHandler(MediaSessionService service) {
mServiceInstance = service;
mStartSelfIntent = new Intent(mServiceInstance, mServiceInstance.getClass());
@@ -98,7 +96,6 @@
* @param state player state
*/
@Override
- @SuppressLint("RestrictedApi")
public void onPlayerStateChanged(MediaSession session,
@SessionPlayer.PlayerState int state) {
MediaSessionService.MediaNotification mediaNotification =
@@ -124,13 +121,11 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void onSessionClosed(MediaSession session) {
mServiceInstance.removeSession(session);
stopForegroundServiceIfNeeded();
}
- @SuppressLint("RestrictedApi")
private void stopForegroundServiceIfNeeded() {
List<MediaSession> sessions = mServiceInstance.getSessions();
for (int i = 0; i < sessions.size(); i++) {
@@ -147,7 +142,6 @@
/**
* Creates a default media style notification for {@link MediaSessionService}.
*/
- @SuppressLint("RestrictedApi")
public MediaSessionService.MediaNotification onUpdateNotification(MediaSession session) {
ensureNotificationChannel();
@@ -195,14 +189,12 @@
return new MediaSessionService.MediaNotification(NOTIFICATION_ID, notification);
}
- @SuppressLint("RestrictedApi")
private NotificationCompat.Action createNotificationAction(int iconResId, int titleResId,
@PlaybackStateCompat.Actions long action) {
CharSequence title = mServiceInstance.getResources().getText(titleResId);
return new NotificationCompat.Action(iconResId, title, createPendingIntent(action));
}
- @SuppressLint("RestrictedApi")
private PendingIntent createPendingIntent(@PlaybackStateCompat.Actions long action) {
int keyCode = PlaybackStateCompat.toKeyCode(action);
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer.java b/media2/src/main/java/androidx/media2/MediaPlayer.java
index 5b2c128..418bae2 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer.java
@@ -25,9 +25,9 @@
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_INFO_SKIPPED;
import static androidx.media2.SessionPlayer.PlayerResult.RESULT_SUCCESS;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.SurfaceTexture;
+import android.media.AudioManager;
import android.media.DeniedByServerException;
import android.media.MediaDrm;
import android.media.MediaDrmException;
@@ -36,6 +36,7 @@
import android.util.Log;
import android.view.Surface;
+import androidx.annotation.FloatRange;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
@@ -61,6 +62,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -126,6 +128,10 @@
* <td>This is to handle error</td></tr>
* </table>
* <p>
+ * If an {@link AudioAttributesCompat} is not specified by {@link #setAudioAttributes},
+ * {@link #getAudioAttributes} will return {@code null} and the default audio focus behavior will
+ * follow the {@code null} case on the table above.
+ * <p>
* For more information about the audio focus, take a look at
* <a href="{@docRoot}guide/topics/media-apps/audio-focus.html">Managing audio focus</a>
* <p>
@@ -278,7 +284,7 @@
* has already been played indicates that the next 30 percent of the
* content to play has been buffered.
*
- * The {@code extra} parameter in {@link PlayerCallback#onInfo} is the
+ * <p>The {@code extra} parameter in {@link PlayerCallback#onInfo} is the
* percentage (0-100) of the content that has been buffered or played thus far.
* @see PlayerCallback#onInfo
*/
@@ -425,6 +431,13 @@
@RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface SeekMode {}
+ /**
+ * The return value of {@link #getSelectedTrack} when there is no selected track for the given
+ * type.
+ * @see #getSelectedTrack(int)
+ */
+ public static final int NO_TRACK_SELECTED = Integer.MIN_VALUE;
+
private static final int CALL_COMPLETE_PLAYLIST_BASE = -1000;
private static final int END_OF_PLAYLIST = -1;
private static final int NO_MEDIA_ITEM = -2;
@@ -653,7 +666,11 @@
@GuardedBy("mPlaylistLock")
private boolean mSetMediaItemCalled;
- @SuppressLint("RestrictedApi")
+ /**
+ * Constructor to create a MediaPlayer instance.
+ *
+ * @param context A {@link Context} that will be used to resolve {@link UriMediaItem}.
+ */
public MediaPlayer(@NonNull Context context) {
mState = PLAYER_STATE_IDLE;
mPlayer = MediaPlayer2.create(context);
@@ -694,7 +711,6 @@
@Override
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> play() {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -723,7 +739,6 @@
@Override
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> pause() {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -745,13 +760,20 @@
/**
* Prepares the media items for playback.
+ * <p>
+ * After setting the media items and the display surface, you need to call this method.
+ * During this preparation, the player may allocate resources required to play, such as audio
+ * and video decoders.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@Override
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> prepare() {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -776,7 +798,6 @@
@Override
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> seekTo(final long position) {
PendingFuture<PlayerResult> pendingFuture =
new PendingFuture<PlayerResult>(mExecutor, true) {
@@ -798,8 +819,9 @@
@Override
@NonNull
- @SuppressLint("RestrictedApi")
- public ListenableFuture<PlayerResult> setPlaybackSpeed(final float playbackSpeed) {
+ public ListenableFuture<PlayerResult> setPlaybackSpeed(
+ @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
+ final float playbackSpeed) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
List<ResolvableFuture<PlayerResult>> onExecute() {
@@ -822,7 +844,6 @@
@NonNull
@Override
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> setAudioAttributes(
@NonNull final AudioAttributesCompat attr) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@@ -844,44 +865,54 @@
}
@Override
+ @PlayerState
public int getPlayerState() {
synchronized (mStateLock) {
return mState;
}
}
- @SuppressLint("RestrictedApi")
@Override
public long getCurrentPosition() {
try {
- return mPlayer.getCurrentPosition();
+ final long pos = mPlayer.getCurrentPosition();
+ if (pos >= 0) {
+ return pos;
+ }
} catch (IllegalStateException e) {
- return UNKNOWN_TIME;
+ // fall-through.
}
+ return UNKNOWN_TIME;
}
@Override
- @SuppressLint("RestrictedApi")
public long getDuration() {
try {
- return mPlayer.getDuration();
+ final long duration = mPlayer.getDuration();
+ if (duration >= 0) {
+ return duration;
+ }
} catch (IllegalStateException e) {
- return UNKNOWN_TIME;
+ // fall-through.
}
+ return UNKNOWN_TIME;
}
@Override
- @SuppressLint("RestrictedApi")
public long getBufferedPosition() {
try {
- return mPlayer.getBufferedPosition();
+ final long pos = mPlayer.getBufferedPosition();
+ if (pos >= 0) {
+ return pos;
+ }
} catch (IllegalStateException e) {
- return UNKNOWN_TIME;
+ // fall-through.
}
+ return UNKNOWN_TIME;
}
@Override
- @SuppressLint("RestrictedApi")
+ @BuffState
public int getBufferingState() {
Integer buffState;
synchronized (mStateLock) {
@@ -891,7 +922,7 @@
}
@Override
- @SuppressLint("RestrictedApi")
+ @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false)
public float getPlaybackSpeed() {
try {
return mPlayer.getPlaybackParams().getSpeed();
@@ -902,7 +933,6 @@
@Override
@Nullable
- @SuppressLint("RestrictedApi")
public AudioAttributesCompat getAudioAttributes() {
try {
return mPlayer.getAudioAttributes();
@@ -915,7 +945,7 @@
@NonNull
public ListenableFuture<PlayerResult> setMediaItem(@NonNull final MediaItem item) {
if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ throw new NullPointerException("item shouldn't be null");
}
if (item instanceof FileMediaItem) {
if (((FileMediaItem) item).isClosed()) {
@@ -945,8 +975,10 @@
@Override
public ListenableFuture<PlayerResult> setPlaylist(
@NonNull final List<MediaItem> playlist, @Nullable final MediaMetadata metadata) {
- if (playlist == null || playlist.isEmpty()) {
- throw new IllegalArgumentException("playlist shouldn't be null or empty");
+ if (playlist == null) {
+ throw new NullPointerException("playlist shouldn't be null");
+ } else if (playlist.isEmpty()) {
+ throw new IllegalArgumentException("playlist shouldn't be empty");
}
String errorString = null;
for (MediaItem item : playlist) {
@@ -1008,7 +1040,7 @@
public ListenableFuture<PlayerResult> addPlaylistItem(
final int index, @NonNull final MediaItem item) {
if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ throw new NullPointerException("item shouldn't be null");
}
if (item instanceof FileMediaItem) {
if (((FileMediaItem) item).isClosed()) {
@@ -1124,7 +1156,7 @@
public ListenableFuture<PlayerResult> replacePlaylistItem(
final int index, @NonNull final MediaItem item) {
if (item == null) {
- throw new IllegalArgumentException("item shouldn't be null");
+ throw new NullPointerException("item shouldn't be null");
}
if (item instanceof FileMediaItem) {
if (((FileMediaItem) item).isClosed()) {
@@ -1398,7 +1430,6 @@
@Override
@Nullable
- @SuppressLint("RestrictedApi")
public MediaItem getCurrentMediaItem() {
return mPlayer.getCurrentMediaItem();
}
@@ -1450,7 +1481,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void close() throws Exception {
reset();
mAudioFocusHandler.close();
@@ -1472,7 +1502,6 @@
* this method, you will have to initialize it again by setting the
* media item and calling {@link #prepare()}.
*/
- @SuppressLint("RestrictedApi")
public void reset() {
// Cancel the pending commands.
synchronized (mPendingCommands) {
@@ -1508,10 +1537,11 @@
/**
* Sets the {@link Surface} to be used as the sink for the video portion of
- * the media. Setting a
- * Surface will un-set any Surface or SurfaceHolder that was previously set.
+ * the media.
+ * <p>
+ * Setting a Surface will un-set any Surface or SurfaceHolder that was previously set.
* A null surface will result in only the audio track being played.
- *
+ * <p>
* If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
* returned from {@link SurfaceTexture#getTimestamp()} will have an
* unspecified zero point. These timestamps cannot be directly compared
@@ -1519,14 +1549,17 @@
* source, or multiple runs of the same program. The timestamp is normally
* monotonically increasing and is unaffected by time-of-day adjustments,
* but it is reset when the position is set.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @param surface The {@link Surface} to be used for the video portion of
* the media.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> setSurface(@Nullable final Surface surface) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1548,17 +1581,26 @@
/**
* Sets the volume of the audio of the media to play, expressed as a linear multiplier
* on the audio samples.
+ * <p>
* Note that this volume is specific to the player, and is separate from stream volume
- * used across the platform.<br>
+ * used across the platform.
+ * <p>
* A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified
* gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player.
+ * <p>
+ * The default player volume is 1.0f.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- public ListenableFuture<PlayerResult> setPlayerVolume(final float volume) {
+ public ListenableFuture<PlayerResult> setPlayerVolume(
+ @FloatRange(from = 0, to = 1) final float volume) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
List<ResolvableFuture<PlayerResult>> onExecute() {
@@ -1575,7 +1617,6 @@
* @return the current volume of this player to this player. Note that it does not take into
* account the associated stream volume.
*/
- @SuppressLint("RestrictedApi")
public float getPlayerVolume() {
return mPlayer.getPlayerVolume();
}
@@ -1583,7 +1624,6 @@
/**
* @return the maximum volume that can be used in {@link #setPlayerVolume(float)}.
*/
- @SuppressLint("RestrictedApi")
public float getMaxPlayerVolume() {
return mPlayer.getMaxPlayerVolume();
}
@@ -1592,14 +1632,14 @@
/**
* Returns the size of the video.
*
- * @return the size of the video. The width and height of size could be 0 if there is no video,
- * no display surface was set, or the size has not been determined yet.
+ * @return the size of the video. The width and height of size could be 0 if there is no video
+ * or the size has not been determined yet.
* The {@link PlayerCallback} can be registered via {@link #registerPlayerCallback} to
* receive a notification {@link PlayerCallback#onVideoSizeChanged} when the size
* is available.
*/
- @SuppressLint("RestrictedApi")
- public @NonNull VideoSize getVideoSize() {
+ @NonNull
+ public VideoSize getVideoSize() {
return new VideoSize(mPlayer.getVideoWidth(), mPlayer.getVideoHeight());
}
@@ -1613,25 +1653,22 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(21)
- @SuppressLint("RestrictedApi")
public PersistableBundle getMetrics() {
return mPlayer.getMetrics();
}
/**
- * Sets playback rate using {@link PlaybackParams}. The player sets its internal
- * PlaybackParams to the given input. This does not change the player state. For example,
- * if this is called with the speed of 2.0f in {@link #PLAYER_STATE_PAUSED}, the player will
- * just update internal property and stay paused. Once the client calls {@link #play()}
- * afterwards, the player will start playback with the given speed. Calling this with zero
- * speed is not allowed.
+ * Sets playback params using {@link PlaybackParams}.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @param params the playback params.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> setPlaybackParams(@NonNull final PlaybackParams params) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1657,7 +1694,6 @@
* @return the playback params.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public PlaybackParams getPlaybackParams() {
return mPlayer.getPlaybackParams();
}
@@ -1670,18 +1706,21 @@
* is kept. When current seekTo is completed, the queued request will be processed if
* that request is different from just-finished seekTo operation, i.e., the requested
* position or mode is different.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
- * @param msec the offset in milliseconds from the start to seek to.
+ * @param position the offset in milliseconds from the start to seek to.
* When seeking to the given time position, there is no guarantee that the media item
* has a frame located at the position. When this happens, a frame nearby will be rendered.
* The value should be in the range of start and end positions defined in {@link MediaItem}.
* @param mode the mode indicating where exactly to seek to.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
- public ListenableFuture<PlayerResult> seekTo(final long msec, @SeekMode final int mode) {
+ public ListenableFuture<PlayerResult> seekTo(final long position, @SeekMode final int mode) {
PendingFuture<PlayerResult> pendingFuture =
new PendingFuture<PlayerResult>(mExecutor, true) {
@Override
@@ -1691,7 +1730,7 @@
int mp2SeekMode = sSeekModeMap.containsKey(mode)
? sSeekModeMap.get(mode) : MediaPlayer2.SEEK_NEXT_SYNC;
synchronized (mPendingCommands) {
- Object token = mPlayer.seekTo(msec, mp2SeekMode);
+ Object token = mPlayer.seekTo(position, mp2SeekMode);
addPendingCommandLocked(MediaPlayer2.CALL_COMPLETED_SEEK_TO, future, token);
}
futures.add(future);
@@ -1721,7 +1760,6 @@
* @see MediaTimestamp
*/
@Nullable
- @SuppressLint("RestrictedApi")
public MediaTimestamp getTimestamp() {
return mPlayer.getTimestamp();
}
@@ -1741,10 +1779,15 @@
* by calling this method.
* <p>This method must be called before {@link #setMediaItem} and {@link #setPlaylist} methods.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
+ *
+ * @see AudioManager#generateAudioSessionId
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> setAudioSessionId(final int sessionId) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1768,10 +1811,9 @@
* Returns the audio session ID.
*
* @return the audio session ID. {@see #setAudioSessionId(int)}
- * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was
- * contructed.
+ * Note that the audio session ID is 0 only if a problem occurred when the MediaPlayer2 was
+ * constructed.
*/
- @SuppressLint("RestrictedApi")
public int getAudioSessionId() {
return mPlayer.getAudioSessionId();
}
@@ -1789,10 +1831,13 @@
* <p>This method must be called before {@link #setMediaItem} and {@link #setPlaylist} methods.
* @param effectId system wide unique id of the effect to attach
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> attachAuxEffect(final int effectId) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1823,13 +1868,18 @@
* so an appropriate conversion from linear UI input x to level is:
* x == 0 -> level = 0
* 0 < x <= R -> level = 10^(72*(x-R)/20/R)
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
+ *
* @param level send level scalar
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
- public ListenableFuture<PlayerResult> setAuxEffectSendLevel(final float level) {
+ public ListenableFuture<PlayerResult> setAuxEffectSendLevel(
+ @FloatRange(from = 0, to = 1) final float level) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
List<ResolvableFuture<PlayerResult>> onExecute() {
@@ -1854,7 +1904,6 @@
* @return List of track info. The total number of tracks is the size of the list.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public List<TrackInfo> getTrackInfo() {
List<MediaPlayer2.TrackInfo> list = mPlayer.getTrackInfo();
List<TrackInfo> trackList = new ArrayList<>();
@@ -1881,16 +1930,16 @@
* @see #selectTrack(int)
* @see #deselectTrack(int)
*/
- @SuppressLint("RestrictedApi")
- public int getSelectedTrack(int trackType) {
- return mPlayer.getSelectedTrack(trackType);
+ public int getSelectedTrack(@TrackInfo.MediaTrackType int trackType) {
+ final int ret = mPlayer.getSelectedTrack(trackType);
+ return (ret < 0) ? NO_TRACK_SELECTED : ret;
}
/**
* Selects a track.
* <p>
- * If the player is in invalid state, {@link PlayerResult#RESULT_ERROR_INVALID_STATE} will be
- * reported with {@link PlayerResult}.
+ * If the player is in invalid state, {@link androidx.media2.SessionPlayer.PlayerResult#RESULT_ERROR_INVALID_STATE} will be
+ * reported with {@link androidx.media2.SessionPlayer.PlayerResult}.
* If a player is in <em>Playing</em> state, the selected track is presented immediately.
* If a player is not in Playing state, it just marks the track to be played.
* </p>
@@ -1909,13 +1958,16 @@
* @param index the index of the track to be selected. The valid range of the index
* is 0..total number of track - 1. The total number of tracks as well as the type of
* each individual track can be found by calling {@link #getTrackInfo()} method.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @see #getTrackInfo
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> selectTrack(final int index) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1944,13 +1996,16 @@
* @param index the index of the track to be deselected. The valid range of the index
* is 0..total number of tracks - 1. The total number of tracks as well as the type of
* each individual track can be found by calling {@link #getTrackInfo()} method.
+ * <p>
+ * On success, a {@link androidx.media2.SessionPlayer.PlayerResult} is returned with
+ * the current media item when the command completed.
*
* @see #getTrackInfo
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link PlayerResult} will be delivered when the command completes.
+ * {@link androidx.media2.SessionPlayer.PlayerResult} will be delivered when the command
+ * completed.
*/
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<PlayerResult> deselectTrack(final int index) {
PendingFuture<PlayerResult> pendingFuture = new PendingFuture<PlayerResult>(mExecutor) {
@Override
@@ -1971,6 +2026,29 @@
}
/**
+ * Register {@link PlayerCallback} to listen changes.
+ *
+ * @param executor a callback Executor
+ * @param callback a PlayerCallback
+ * @throws IllegalArgumentException if executor or callback is {@code null}.
+ */
+ public void registerPlayerCallback(
+ @NonNull /*@CallbackExecutor*/ Executor executor,
+ @NonNull PlayerCallback callback) {
+ super.registerPlayerCallback(executor, callback);
+ }
+
+ /**
+ * Unregister the previously registered {@link PlayerCallback}.
+ *
+ * @param callback the callback to be removed
+ * @throws IllegalArgumentException if the callback is {@code null}.
+ */
+ public void unregisterPlayerCallback(@NonNull PlayerCallback callback) {
+ super.unregisterPlayerCallback(callback);
+ }
+
+ /**
* Retrieves the DRM Info associated with the current media item.
*
* @throws IllegalStateException if called before being prepared
@@ -1978,7 +2056,6 @@
*/
@Nullable
@RestrictTo(LIBRARY_GROUP_PREFIX)
- @SuppressLint("RestrictedApi")
public DrmInfo getDrmInfo() {
MediaPlayer2.DrmInfo info = mPlayer.getDrmInfo();
return info == null ? null : new DrmInfo(info);
@@ -2005,13 +2082,12 @@
* from the source through {#link getDrmInfo} or registering
* {@link PlayerCallback#onDrmInfo}.
* @return a {@link ListenableFuture} which represents the pending completion of the command.
- * {@link DrmResult} will be delivered when the command completes.
+ * {@link DrmResult} will be delivered when the command completed.
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
// This is an asynchronous call.
@NonNull
- @SuppressLint("RestrictedApi")
public ListenableFuture<DrmResult> prepareDrm(@NonNull final UUID uuid) {
PendingFuture<DrmResult> pendingFuture = new PendingFuture<DrmResult>(mExecutor) {
@Override
@@ -2041,7 +2117,6 @@
* @throws NoDrmSchemeException if there is no active DRM session to release
* @hide
*/
- @SuppressLint("RestrictedApi")
@RestrictTo(LIBRARY_GROUP_PREFIX)
public void releaseDrm() throws NoDrmSchemeException {
try {
@@ -2091,7 +2166,6 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@NonNull
- @SuppressLint("RestrictedApi")
public MediaDrm.KeyRequest getDrmKeyRequest(
@Nullable byte[] keySetId, @Nullable byte[] initData,
@Nullable String mimeType, int keyType,
@@ -2111,6 +2185,7 @@
* response is for an offline key request, a key-set identifier is returned that
* can be used to later restore the keys to a new session with the method
* {@link #restoreDrmKeys}.
+ * <p>
* When the response is for a streaming or release request, null is returned.
*
* @param keySetId When the response is for a release request, keySetId identifies
@@ -2127,7 +2202,6 @@
*/
@Nullable
@RestrictTo(LIBRARY_GROUP_PREFIX)
- @SuppressLint("RestrictedApi")
public byte[] provideDrmKeyResponse(
@Nullable byte[] keySetId, @NonNull byte[] response)
throws NoDrmSchemeException, DeniedByServerException {
@@ -2145,7 +2219,6 @@
* @param keySetId identifies the saved key set to restore
* @hide
*/
- @SuppressLint("RestrictedApi")
@RestrictTo(LIBRARY_GROUP_PREFIX)
public void restoreDrmKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException {
try {
@@ -2167,7 +2240,6 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@NonNull
- @SuppressLint("RestrictedApi")
public String getDrmPropertyString(@NonNull String propertyName) throws NoDrmSchemeException {
try {
return mPlayer.getDrmPropertyString(propertyName);
@@ -2188,7 +2260,6 @@
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
- @SuppressLint("RestrictedApi")
public void setDrmPropertyString(@NonNull String propertyName, @NonNull String value)
throws NoDrmSchemeException {
try {
@@ -2201,13 +2272,13 @@
/**
* Register a callback to be invoked for configuration of the DRM object before
* the session is created.
+ * <p>
* The callback will be invoked synchronously during the execution
* of {@link #prepareDrm(UUID uuid)}.
*
* @param listener the callback that will be run
* @hide
*/
- @SuppressLint("RestrictedApi")
@RestrictTo(LIBRARY_GROUP_PREFIX)
public void setOnDrmConfigHelper(@Nullable final OnDrmConfigHelper listener) {
mPlayer.setOnDrmConfigHelper(listener == null ? null :
@@ -2314,7 +2385,6 @@
return futures;
}
- @SuppressLint("RestrictedApi")
private ResolvableFuture<PlayerResult> setMediaItemInternal(MediaItem item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
@@ -2327,7 +2397,7 @@
return future;
}
- @SuppressWarnings({"WeakerAccess", "RestrictedApi"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
ResolvableFuture<PlayerResult> setNextMediaItemInternal(MediaItem item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
@@ -2338,7 +2408,7 @@
return future;
}
- @SuppressWarnings({"WeakerAccess", "RestrictedApi"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
ResolvableFuture<PlayerResult> skipToNextInternal() {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
@@ -2349,7 +2419,7 @@
return future;
}
- @SuppressWarnings({"WeakerAccess", "RestrictedApi"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
ResolvableFuture<PlayerResult> setPlayerVolumeInternal(float volume) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
synchronized (mPendingCommands) {
@@ -2365,7 +2435,7 @@
return createFutureForResultCode(resultCode, null);
}
- @SuppressWarnings({"WeakerAccess", "RestrictedApi"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
ResolvableFuture<PlayerResult> createFutureForResultCode(int resultCode, MediaItem item) {
ResolvableFuture<PlayerResult> future = ResolvableFuture.create();
future.set(new PlayerResult(resultCode,
@@ -2447,7 +2517,7 @@
return (value > maxValue) ? maxValue : value;
}
- @SuppressWarnings({"WeakerAccess", "RestrictedApi"}) /* synthetic access */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
void handleCallComplete(MediaPlayer2 mp, final MediaItem item, int what, int status) {
PendingCommand expected;
synchronized (mPendingCommands) {
@@ -2682,7 +2752,7 @@
public abstract static class PlayerCallback extends SessionPlayer.PlayerCallback {
/**
* Called to indicate the video size
- *
+ * <p>
* The video size (width and height) could be 0 if there was no video,
* no display surface was set, or the value was not determined yet.
*
@@ -2738,6 +2808,7 @@
/**
* Called when a discontinuity in the normal progression of the media time is detected.
+ * <p>
* The "normal progression" of media time is defined as the expected increase of the
* playback position when playing media, relative to the playback speed (for instance every
* second, media time increases by two seconds when playing at 2x).<br>
@@ -2797,6 +2868,20 @@
public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
public static final int MEDIA_TRACK_TYPE_METADATA = 5;
+ /**
+ * @hide
+ */
+ @IntDef(flag = false, /*prefix = "PLAYER_ERROR",*/ value = {
+ MEDIA_TRACK_TYPE_UNKNOWN,
+ MEDIA_TRACK_TYPE_VIDEO,
+ MEDIA_TRACK_TYPE_AUDIO,
+ MEDIA_TRACK_TYPE_SUBTITLE,
+ MEDIA_TRACK_TYPE_METADATA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ public @interface MediaTrackType {}
+
private final int mTrackType;
private final MediaFormat mFormat;
@@ -2804,20 +2889,21 @@
* Gets the track type.
* @return TrackType which indicates if the track is video, audio, timed text.
*/
- public int getTrackType() {
+ public @MediaTrackType int getTrackType() {
return mTrackType;
}
/**
* Gets the language code of the track.
- * @return a language code in either way of ISO-639-1 or ISO-639-2.
- * When the language is unknown or could not be determined,
- * ISO-639-2 language code, "und", is returned.
+ * @return {@link Locale} which includes the language information.
*/
@NonNull
- public String getLanguage() {
- String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
- return language == null ? "und" : language;
+ public Locale getLanguage() {
+ String language = mFormat != null ? mFormat.getString(MediaFormat.KEY_LANGUAGE) : null;
+ if (language == null) {
+ language = "und";
+ }
+ return new Locale(language);
}
/**
@@ -2903,7 +2989,7 @@
* is opened. This facilitates configuration of the properties, like
* 'securityLevel', which has to be set after DRM scheme creation but
* before the DRM session is opened.
- *
+ * <p>
* The only allowed DRM calls in this listener are {@link #getDrmPropertyString}
* and {@link #setDrmPropertyString}.
* @hide
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2.java b/media2/src/main/java/androidx/media2/MediaPlayer2.java
index f8b05bb..690d3c1 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2.java
@@ -16,10 +16,8 @@
package androidx.media2;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.DeniedByServerException;
@@ -226,8 +224,7 @@
*
* @hide
*/
-@RestrictTo(LIBRARY)
-@SuppressLint("RestrictedApi")
+@RestrictTo(LIBRARY_GROUP_PREFIX)
public abstract class MediaPlayer2 {
/**
@@ -246,24 +243,18 @@
if (Build.VERSION.SDK_INT <= 27 || DEBUG_USE_EXOPLAYER) {
return new ExoPlayerMediaPlayer2Impl(context);
} else {
- return new MediaPlayer2Impl();
+ return new MediaPlayer2Impl(context);
}
}
- /**
- * @hide
- */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
- public MediaPlayer2() { }
+ protected MediaPlayer2() { }
/**
* Cancels the asynchronous call previously submitted.
*
* @param token the token which is returned from the asynchronous call.
* @return {@code false} if the task could not be cancelled; {@code true} otherwise.
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public abstract boolean cancel(Object token);
/**
@@ -292,8 +283,7 @@
* Starts or resumes playback. If playback had previously been paused,
* playback will continue from where it was paused. If playback had
* reached end of stream and been paused, or never started before,
- * playback will start at the beginning. If the source had not been
- * prepared, the player will prepare the source and play.
+ * playback will start at the beginning.
*
* @return a token which can be used to cancel the operation later with {@link #cancel}.
*/
@@ -333,7 +323,6 @@
* @return a token which can be used to cancel the operation later with {@link #cancel}.
*/
// This is an asynchronous call.
- @SuppressLint("RestrictedApi")
public Object seekTo(long msec) {
return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */);
}
@@ -529,9 +518,8 @@
/**
* Returns the width of the video.
*
- * @return the width of the video, or 0 if there is no video,
- * no display surface was set, or the width has not been determined
- * yet. The {@link EventCallback} can be registered via
+ * @return the width of the video, or 0 if there is no video or the width has not been
+ * determined yet. The {@link EventCallback} can be registered via
* {@link #setEventCallback(Executor, EventCallback)} to provide a
* notification {@link EventCallback#onVideoSizeChanged} when the width
* is available.
@@ -541,9 +529,8 @@
/**
* Returns the height of the video.
*
- * @return the height of the video, or 0 if there is no video,
- * no display surface was set, or the height has not been determined
- * yet. The {@link EventCallback} can be registered via
+ * @return the height of the video, or 0 if there is no video or the height has not been
+ * determined yet. The {@link EventCallback} can be registered via
* {@link #setEventCallback(Executor, EventCallback)} to provide a
* notification {@link EventCallback#onVideoSizeChanged} when the height is
* available.
@@ -633,7 +620,6 @@
SEEK_CLOSEST,
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface SeekMode {}
/**
@@ -780,8 +766,6 @@
public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
- /** @hide */
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
@@ -1035,10 +1019,10 @@
PLAYER_STATE_PLAYING,
PLAYER_STATE_ERROR})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface MediaPlayer2State {}
- /** Unspecified media player error.
+ /**
+ * Unspecified media player error.
* @see EventCallback#onError
*/
public static final int MEDIA_ERROR_UNKNOWN = 1;
@@ -1066,12 +1050,11 @@
*/
public static final int MEDIA_ERROR_TIMED_OUT = -110;
- /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
+ /**
+ * Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
* system/core/include/utils/Errors.h
* @see EventCallback#onError
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int MEDIA_ERROR_SYSTEM = -2147483648;
/**
@@ -1086,75 +1069,84 @@
MEDIA_ERROR_SYSTEM
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface MediaError {}
- /** Unspecified media player info.
+ /**
+ * Unspecified media player info.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_UNKNOWN = 1;
- /** The player just started the playback of this media item.
+ /**
+ * The player just started the playback of this media item.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_DATA_SOURCE_START = 2;
- /** The player just pushed the very first video frame for rendering.
+ /**
+ * The player just pushed the very first video frame for rendering.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
- /** The player just rendered the very first audio sample.
+ /**
+ * The player just rendered the very first audio sample.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
- /** The player just completed the playback of this media item.
+ /**
+ * The player just completed the playback of this media item.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_DATA_SOURCE_END = 5;
- /** The player just completed the playback of all the media items set by {@link #setMediaItem}
- * , {@link #setNextMediaItem} and {@link #setNextMediaItems}.
+ /**
+ * The player just completed the playback of all the media items set by {@link #setMediaItem},
+ * {@link #setNextMediaItem} and {@link #setNextMediaItems}.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_DATA_SOURCE_LIST_END = 6;
- /** The player just completed an iteration of playback loop. This event is sent only when
- * looping is enabled by {@link #loopCurrent}.
+ /**
+ * The player just completed an iteration of playback loop. This event is sent only when
+ * looping is enabled by {@link #loopCurrent}.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_DATA_SOURCE_REPEAT = 7;
- /** The player just prepared a media item.
+ /**
+ * The player just prepared a media item.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_PREPARED = 100;
- /** The video is too complex for the decoder: it can't decode frames fast
- * enough. Possibly only the audio plays fine at this stage.
+ /**
+ * The video is too complex for the decoder: it can't decode frames fast
+ * enough. Possibly only the audio plays fine at this stage.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
- /** MediaPlayer2 is temporarily pausing playback internally in order to
+ /**
+ * MediaPlayer2 is temporarily pausing playback internally in order to
* buffer more data.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_BUFFERING_START = 701;
- /** MediaPlayer2 is resuming playback after filling buffers.
+ /**
+ * MediaPlayer2 is resuming playback after filling buffers.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_BUFFERING_END = 702;
- /** Estimated network bandwidth information (kbps) is available; currently this event fires
+ /**
+ * Estimated network bandwidth information (kbps) is available; currently this event fires
* simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
* when playing network files.
* @see EventCallback#onInfo
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
/**
@@ -1170,55 +1162,60 @@
*/
public static final int MEDIA_INFO_BUFFERING_UPDATE = 704;
- /** Bad interleaving means that a media has been improperly interleaved or
+ /**
+ * Bad interleaving means that a media has been improperly interleaved or
* not interleaved at all, e.g has all the video samples first then all the
* audio ones. Video is playing but a lot of disk seeks may be happening.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
- /** The media cannot be seeked (e.g live stream)
+ /**
+ * The media cannot be seeked (e.g live stream)
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
- /** A new set of metadata is available.
+ /**
+ * A new set of metadata is available.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_METADATA_UPDATE = 802;
- /** A new set of external-only metadata is available. Used by
- * JAVA framework to avoid triggering track scanning.
- * @hide
+ /**
+ * A new set of external-only metadata is available. Used by
+ * JAVA framework to avoid triggering track scanning.
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803;
- /** Informs that audio is not playing. Note that playback of the video
+ /**
+ * Informs that audio is not playing. Note that playback of the video
* is not interrupted.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
- /** Informs that video is not playing. Note that playback of the audio
+ /**
+ * Informs that video is not playing. Note that playback of the audio
* is not interrupted.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
- /** Failed to handle timed text track properly.
+ /**
+ * Failed to handle timed text track properly.
* @see EventCallback#onInfo
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
- /** Subtitle track was not supported by the media framework.
+ /**
+ * Subtitle track was not supported by the media framework.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
- /** Reading the subtitle track takes too long.
+ /**
+ * Reading the subtitle track takes too long.
* @see EventCallback#onInfo
*/
public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
@@ -1251,121 +1248,133 @@
MEDIA_INFO_SUBTITLE_TIMED_OUT
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface MediaInfo {}
//--------------------------------------------------------------------------
- /** The player just completed a call {@link #attachAuxEffect}.
+ /**
+ * The player just completed a call {@link #attachAuxEffect}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_ATTACH_AUX_EFFECT = 1;
- /** The player just completed a call {@link #deselectTrack}.
+ /**
+ * The player just completed a call {@link #deselectTrack}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_DESELECT_TRACK = 2;
- /** The player just completed a call {@link #loopCurrent}.
+ /**
+ * The player just completed a call {@link #loopCurrent}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_LOOP_CURRENT = 3;
- /** The player just completed a call {@link #pause}.
+ /**
+ * The player just completed a call {@link #pause}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_PAUSE = 4;
- /** The player just completed a call {@link #play}.
+ /**
+ * The player just completed a call {@link #play}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_PLAY = 5;
- /** The player just completed a call {@link #prepare}.
+ /**
+ * The player just completed a call {@link #prepare}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_PREPARE = 6;
- /** The player just completed a call {@link #seekTo}.
+ /**
+ * The player just completed a call {@link #seekTo}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SEEK_TO = 14;
- /** The player just completed a call {@link #selectTrack}.
+ /**
+ * The player just completed a call {@link #selectTrack}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SELECT_TRACK = 15;
- /** The player just completed a call {@link #setAudioAttributes}.
+ /**
+ * The player just completed a call {@link #setAudioAttributes}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_AUDIO_ATTRIBUTES = 16;
- /** The player just completed a call {@link #setAudioSessionId}.
+ /**
+ * The player just completed a call {@link #setAudioSessionId}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_AUDIO_SESSION_ID = 17;
- /** The player just completed a call {@link #setAuxEffectSendLevel}.
+ /**
+ * The player just completed a call {@link #setAuxEffectSendLevel}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL = 18;
- /** The player just completed a call {@link #setMediaItem}.
+ /**
+ * The player just completed a call {@link #setMediaItem}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_DATA_SOURCE = 19;
- /** The player just completed a call {@link #setNextMediaItem}.
+ /**
+ * The player just completed a call {@link #setNextMediaItem}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCE = 22;
- /** The player just completed a call {@link #setNextMediaItems}.
+ /**
+ * The player just completed a call {@link #setNextMediaItems}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_NEXT_DATA_SOURCES = 23;
- /** The player just completed a call {@link #setPlaybackParams}.
+ /**
+ * The player just completed a call {@link #setPlaybackParams}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_PLAYBACK_PARAMS = 24;
- /** The player just completed a call {@link #setPlayerVolume}.
+ /**
+ * The player just completed a call {@link #setPlayerVolume}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_PLAYER_VOLUME = 26;
- /** The player just completed a call {@link #setSurface}.
+ /**
+ * The player just completed a call {@link #setSurface}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SET_SURFACE = 27;
- /** The player just completed a call {@link #skipToNext}.
+ /**
+ * The player just completed a call {@link #skipToNext}.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_COMPLETED_SKIP_TO_NEXT = 29;
/**
* The start of the methods which have separate call complete callback.
- *
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int SEPARATE_CALL_COMPLETE_CALLBACK_START = 1000;
- /** The player just completed a call {@code notifyWhenCommandLabelReached}.
+ /**
+ * The player just completed a call {@code notifyWhenCommandLabelReached}.
* @see EventCallback#onCommandLabelReached
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED =
SEPARATE_CALL_COMPLETE_CALLBACK_START;
- /** The player just completed a call {@link #prepareDrm}.
+ /**
+ * The player just completed a call {@link #prepareDrm}.
* @see EventCallback#onCommandLabelReached
- * @hide
*/
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int CALL_COMPLETED_PREPARE_DRM =
SEPARATE_CALL_COMPLETE_CALLBACK_START + 1;
@@ -1395,40 +1404,46 @@
CALL_COMPLETED_PREPARE_DRM,
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface CallCompleted {}
- /** Status code represents that call is completed without an error.
+ /**
+ * Status code represents that call is completed without an error.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_NO_ERROR = 0;
- /** Status code represents that call is ended with an unknown error.
+ /**
+ * Status code represents that call is ended with an unknown error.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_ERROR_UNKNOWN = Integer.MIN_VALUE;
- /** Status code represents that the player is not in valid state for the operation.
+ /**
+ * Status code represents that the player is not in valid state for the operation.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_INVALID_OPERATION = 1;
- /** Status code represents that the argument is illegal.
+ /**
+ * Status code represents that the argument is illegal.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_BAD_VALUE = 2;
- /** Status code represents that the operation is not allowed.
+ /**
+ * Status code represents that the operation is not allowed.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_PERMISSION_DENIED = 3;
- /** Status code represents a file or network related operation error.
+ /**
+ * Status code represents a file or network related operation error.
* @see EventCallback#onCallCompleted
*/
public static final int CALL_STATUS_ERROR_IO = 4;
- /** Status code represents that the player skipped the call. For example, a {@link #seekTo}
+ /**
+ * Status code represents that the player skipped the call. For example, a {@link #seekTo}
* request may be skipped if it is followed by another {@link #seekTo} request.
* @see EventCallback#onCallCompleted
*/
@@ -1446,7 +1461,6 @@
CALL_STATUS_ERROR_IO,
CALL_STATUS_SKIPPED})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface CallStatus {}
// Modular DRM begin
@@ -1568,7 +1582,6 @@
PREPARE_DRM_STATUS_RESOURCE_BUSY,
})
@Retention(RetentionPolicy.SOURCE)
- @RestrictTo(LIBRARY_GROUP_PREFIX)
public @interface PrepareDrmStatusCode {}
/**
diff --git a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
index dd94224..7816fe1 100644
--- a/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
+++ b/media2/src/main/java/androidx/media2/MediaPlayer2Impl.java
@@ -17,7 +17,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-import android.annotation.SuppressLint;
+import android.content.Context;
import android.media.AudioAttributes;
import android.media.DeniedByServerException;
import android.media.MediaDataSource;
@@ -68,7 +68,6 @@
*/
@RequiresApi(28)
@RestrictTo(LIBRARY_GROUP_PREFIX)
-@SuppressLint("RestrictedApi")
public final class MediaPlayer2Impl extends MediaPlayer2 {
private static final String TAG = "MediaPlayer2Impl";
@@ -140,6 +139,7 @@
MediaPlayerSourceQueue mPlayer;
private HandlerThread mHandlerThread;
+ private final Context mContext;
private final Handler mEndPositionHandler;
private final Handler mTaskHandler;
@SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -176,7 +176,8 @@
* to free the resources. If not released, too many MediaPlayer2Impl instances may
* result in an exception.</p>
*/
- public MediaPlayer2Impl() {
+ public MediaPlayer2Impl(Context context) {
+ mContext = context;
mHandlerThread = new HandlerThread("MediaPlayer2TaskThread");
mHandlerThread.start();
Looper looper = mHandlerThread.getLooper();
@@ -453,7 +454,7 @@
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
- static void handleDataSource(MediaPlayerSource src)
+ void handleDataSource(MediaPlayerSource src)
throws IOException {
final MediaItem item = src.getDSD();
Preconditions.checkArgument(item != null, "the MediaItem cannot be null");
@@ -490,7 +491,7 @@
} else if (item instanceof UriMediaItem) {
UriMediaItem uitem = (UriMediaItem) item;
player.setDataSource(
- uitem.getUriContext(),
+ mContext,
uitem.getUri(),
uitem.getUriHeaders(),
uitem.getUriCookies());
diff --git a/media2/src/main/java/androidx/media2/MediaSession.java b/media2/src/main/java/androidx/media2/MediaSession.java
index ed4d99f..cca81ce 100644
--- a/media2/src/main/java/androidx/media2/MediaSession.java
+++ b/media2/src/main/java/androidx/media2/MediaSession.java
@@ -401,8 +401,9 @@
* receives {@link MediaController.ControllerCallback#onDisconnected(MediaController)} and
* cannot be used.
* <p>
- * The controller hasn't connected yet, so sending commands in this method to the controller
- * will fail. For initial setup for the controller, use {@link #onPostConnect}.
+ * The controller hasn't connected yet in this method, so calls to the controller
+ * (e.g. {@link #sendCustomCommand}, {@link #setCustomLayout}) would be ignored. Override
+ * {@link #onPostConnect} for the custom initialization for the controller instead.
*
* @param session the session for this event
* @param controller controller information.
@@ -420,6 +421,10 @@
/**
* Called immediately after a controller is connected. This is a convenient method to add
* custom initialization between the session and a controller.
+ * <p>
+ * Note that calls to the controller (e.g. {@link #sendCustomCommand},
+ * {@link #setCustomLayout}) work here but don't work in {@link #onConnect} because the
+ * controller hasn't connected yet in {@link #onConnect}.
*
* @param session the session for this event
* @param controller controller information.
diff --git a/media2/src/main/java/androidx/media2/MediaSessionImplBase.java b/media2/src/main/java/androidx/media2/MediaSessionImplBase.java
index dc26a9f..20c292c 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionImplBase.java
@@ -178,28 +178,31 @@
MediaSessionService.SERVICE_INTERFACE);
}
sComponentNamesInitialized = true;
- mbrComponent = sServiceComponentName;
}
+ mbrComponent = sServiceComponentName;
}
if (mbrComponent == null) {
// No service to revive playback after it's dead.
// Create a PendingIntent that points to the runtime broadcast receiver.
- mbrComponent = new ComponentName(context, context.getClass());
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, mSessionUri);
intent.setPackage(context.getPackageName());
mMediaButtonIntent = PendingIntent.getBroadcast(
context, 0 /* requestCode */, intent, 0 /* flags */);
+ // Creates a dummy ComponentName for MediaSessionCompat in pre-L.
+ // TODO: Replace this with the MediaButtonReceiver class.
+ mbrComponent = new ComponentName(context, context.getClass());
+
// Create and register a BroadcastReceiver for receiving PendingIntent.
// TODO: Introduce MediaButtonReceiver in AndroidManifest instead of this,
// or register only one receiver for all sessions.
mBroadcastReceiver = new MediaButtonReceiver();
- context.registerReceiver(mBroadcastReceiver,
- new IntentFilter(Intent.ACTION_MEDIA_BUTTON));
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
+ filter.addDataScheme(mSessionUri.getScheme());
+ context.registerReceiver(mBroadcastReceiver, filter);
} else {
// Has MediaSessionService to revive playback after it's dead.
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setData(mSessionUri);
+ Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON, mSessionUri);
intent.setComponent(mbrComponent);
if (Build.VERSION.SDK_INT >= 26) {
mMediaButtonIntent = PendingIntent.getForegroundService(mContext, 0, intent, 0);
@@ -1431,7 +1434,6 @@
session.dispatchRemoteControllerTaskWithoutReturn(task);
}
- @SuppressLint("RestrictedApi")
private void updateDurationIfNeeded(@NonNull final SessionPlayer player,
@Nullable final MediaItem item) {
if (item == null) {
diff --git a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
index f1a0b4b..d4ca16b 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionLegacyStub.java
@@ -23,7 +23,6 @@
import static androidx.media2.SessionCommand.COMMAND_VERSION_CURRENT;
import static androidx.media2.SessionResult.RESULT_SUCCESS;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
@@ -68,7 +67,6 @@
new SparseArray<>();
static {
- @SuppressLint("RestrictedApi")
SessionCommandGroup group = new SessionCommandGroup.Builder()
.addAllPlayerCommands(COMMAND_VERSION_CURRENT)
.addAllVolumeCommands(COMMAND_VERSION_CURRENT)
@@ -354,7 +352,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void onSetRating(final RatingCompat rating, Bundle extras) {
if (rating == null) {
return;
@@ -434,7 +431,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void onRemoveQueueItem(final MediaDescriptionCompat description) {
if (description == null) {
return;
diff --git a/media2/src/main/java/androidx/media2/MediaSessionServiceImplBase.java b/media2/src/main/java/androidx/media2/MediaSessionServiceImplBase.java
index 982fdcb..5025ef9 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionServiceImplBase.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionServiceImplBase.java
@@ -18,7 +18,6 @@
import static android.app.Service.START_STICKY;
-import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
@@ -60,7 +59,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void onCreate(MediaSessionService service) {
synchronized (mLock) {
mInstance = service;
@@ -107,7 +105,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public void addSession(final MediaSession session) {
final MediaSession old;
synchronized (mLock) {
@@ -168,7 +165,6 @@
}
@Override
- @SuppressLint("RestrictedApi")
public MediaNotification onUpdateNotification(MediaSession session) {
final MediaNotificationHandler handler;
synchronized (mLock) {
diff --git a/media2/src/main/java/androidx/media2/MediaSessionServiceLegacyStub.java b/media2/src/main/java/androidx/media2/MediaSessionServiceLegacyStub.java
index ee7011e..dab23c2 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionServiceLegacyStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionServiceLegacyStub.java
@@ -16,7 +16,6 @@
package androidx.media2;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
@@ -40,7 +39,6 @@
final MediaSessionManager mManager;
- @SuppressLint("RestrictedApi")
MediaSessionServiceLegacyStub(Context context, MediaSessionImpl sessionImpl,
MediaSessionCompat.Token token) {
super();
diff --git a/media2/src/main/java/androidx/media2/MediaSessionStub.java b/media2/src/main/java/androidx/media2/MediaSessionStub.java
index 1de652f..378cd41 100644
--- a/media2/src/main/java/androidx/media2/MediaSessionStub.java
+++ b/media2/src/main/java/androidx/media2/MediaSessionStub.java
@@ -24,7 +24,6 @@
import static androidx.media2.SessionResult.RESULT_ERROR_INVALID_STATE;
import static androidx.media2.SessionResult.RESULT_SUCCESS;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
@@ -77,7 +76,6 @@
new SparseArray<>();
static {
- @SuppressLint("RestrictedApi")
SessionCommandGroup group = new SessionCommandGroup.Builder()
.addAllPlayerCommands(COMMAND_VERSION_CURRENT)
.addAllVolumeCommands(COMMAND_VERSION_CURRENT)
diff --git a/media2/src/main/java/androidx/media2/MediaUtils.java b/media2/src/main/java/androidx/media2/MediaUtils.java
index f9f8151..023c119 100644
--- a/media2/src/main/java/androidx/media2/MediaUtils.java
+++ b/media2/src/main/java/androidx/media2/MediaUtils.java
@@ -128,7 +128,6 @@
* @param item2 an item.
* @return The newly created media item.
*/
- @SuppressLint("RestrictedApi")
public static MediaBrowserCompat.MediaItem convertToMediaItem(MediaItem item2) {
if (item2 == null) {
return null;
@@ -315,7 +314,6 @@
* Convert a list of {@link MediaItem} to a list of {@link QueueItem}. The index of the item
* would be used as the queue ID to match the behavior of {@link MediaController}.
*/
- @SuppressLint("RestrictedApi")
public static List<QueueItem> convertToQueueItemList(List<MediaItem> items) {
if (items == null) {
return null;
@@ -799,7 +797,6 @@
* @return the converted session command group
*/
@NonNull
- @SuppressLint("RestrictedApi")
public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags,
PlaybackStateCompat state) {
SessionCommandGroup.Builder commandsBuilder = new SessionCommandGroup.Builder();
@@ -851,7 +848,6 @@
* @param item
* @return
*/
- @SuppressLint("RestrictedApi")
public static ParcelImpl toParcelable(VersionedParcelable item) {
if (item instanceof MediaItem) {
return new MediaItemParcelImpl((MediaItem) item);
@@ -863,7 +859,6 @@
* Media2 version of {@link ParcelUtils#fromParcelable(Parcelable)}.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
- @SuppressLint("RestrictedApi")
public static <T extends VersionedParcelable> T fromParcelable(ParcelImpl p) {
return ParcelUtils.<T>fromParcelable(p);
}
@@ -871,7 +866,6 @@
@SuppressLint("RestrictedApi")
private static class MediaItemParcelImpl extends ParcelImpl {
private final MediaItem mItem;
- @SuppressLint("RestrictedApi")
MediaItemParcelImpl(MediaItem item) {
// Up-cast (possibly MediaItem's subclass object) item to MediaItem for the
// writeToParcel(). The copied media item will be only used when it's sent across the
diff --git a/media2/src/main/java/androidx/media2/ParcelImplListSlice.java b/media2/src/main/java/androidx/media2/ParcelImplListSlice.java
index 0996678..300790b 100644
--- a/media2/src/main/java/androidx/media2/ParcelImplListSlice.java
+++ b/media2/src/main/java/androidx/media2/ParcelImplListSlice.java
@@ -185,14 +185,8 @@
}
@Override
- @SuppressLint("RestrictedApi")
public int describeContents() {
- int contents = 0;
- final List<ParcelImpl> list = getList();
- for (int i = 0; i < list.size(); i++) {
- contents |= list.get(i).describeContents();
- }
- return contents;
+ return 0;
}
public static final Parcelable.Creator<ParcelImplListSlice> CREATOR =
diff --git a/media2/src/main/java/androidx/media2/PlaybackParams.java b/media2/src/main/java/androidx/media2/PlaybackParams.java
index 2b93cd6..b3d4380 100644
--- a/media2/src/main/java/androidx/media2/PlaybackParams.java
+++ b/media2/src/main/java/androidx/media2/PlaybackParams.java
@@ -21,6 +21,7 @@
import android.media.AudioTrack;
import android.os.Build;
+import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -36,8 +37,19 @@
* Used by {@link MediaPlayer} {@link MediaPlayer#getPlaybackParams()} and
* {@link MediaPlayer#setPlaybackParams(PlaybackParams)}
* to control playback behavior.
- * <p> <strong>audio fallback mode:</strong>
- * select out-of-range parameter handling.
+ * <p>
+ * PlaybackParams returned by {@link MediaPlayer#getPlaybackParams()} will always have values.
+ * In case of {@link MediaPlayer#setPlaybackParams}, the player will not update the param if the
+ * value is not set. For example, if pitch is set while speed is not set, only pitch will be
+ * updated.
+ * <p>
+ * Note that the speed value does not change the player state. For example, if
+ * {@link MediaPlayer#getPlaybackParams()} is called with the speed of 2.0f in
+ * {@link MediaPlayer#PLAYER_STATE_PAUSED}, the player will just update internal property and stay
+ * paused. Once {@link MediaPlayer#play()} is called afterwards, the player will start
+ * playback with the given speed. Calling this with zero speed is not allowed.
+ * <p>
+ * <strong>audio fallback mode:</strong> select out-of-range parameter handling.
* <ul>
* <li> {@link PlaybackParams#AUDIO_FALLBACK_MODE_DEFAULT}:
* System will determine best handling. </li>
@@ -219,7 +231,8 @@
* @return this <code>Builder</code> instance.
* @throws IllegalArgumentException if the pitch is negative.
*/
- public @NonNull Builder setPitch(float pitch) {
+ public @NonNull Builder setPitch(
+ @FloatRange(from = 0.0f, to = Float.MAX_VALUE) float pitch) {
if (pitch < 0.f) {
throw new IllegalArgumentException("pitch must not be negative");
}
@@ -236,7 +249,14 @@
*
* @return this <code>Builder</code> instance.
*/
- public @NonNull Builder setSpeed(float speed) {
+ public @NonNull Builder setSpeed(
+ @FloatRange(from = 0.0f, to = Float.MAX_VALUE, fromInclusive = false) float speed) {
+ if (speed == 0.f) {
+ throw new IllegalArgumentException("0 speed is not allowed.");
+ }
+ if (speed < 0.f) {
+ throw new IllegalArgumentException("negative speed is not supported.");
+ }
if (Build.VERSION.SDK_INT >= 23) {
mPlaybackParams.setSpeed(speed);
} else {
diff --git a/media2/src/main/java/androidx/media2/SessionCommand.java b/media2/src/main/java/androidx/media2/SessionCommand.java
index fb891ac..b2dbb1e 100644
--- a/media2/src/main/java/androidx/media2/SessionCommand.java
+++ b/media2/src/main/java/androidx/media2/SessionCommand.java
@@ -16,7 +16,6 @@
package androidx.media2;
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.net.Uri;
@@ -63,7 +62,7 @@
/**
* @hide
*/
- @RestrictTo(LIBRARY)
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
public static final int COMMAND_VERSION_CURRENT = COMMAND_VERSION_1;
/**
diff --git a/media2/src/main/java/androidx/media2/SessionPlayer.java b/media2/src/main/java/androidx/media2/SessionPlayer.java
index 329278f..528b31b 100644
--- a/media2/src/main/java/androidx/media2/SessionPlayer.java
+++ b/media2/src/main/java/androidx/media2/SessionPlayer.java
@@ -115,24 +115,26 @@
* performed from an <a href="#InvalidStates">invalid state</a>. In these cases the player
* may transition to this state.
* </ol>
+ * Subclasses may have extra methods to reset the player state to {@link #PLAYER_STATE_IDLE} from
+ * other states. Take a look at documentations of specific subclass that you're interested in.
* <p>
*
* <h3 id="InvalidStates">Invalid method calls</h3>
* The only method you safely call from the {@link #PLAYER_STATE_ERROR} is {@link #close()}.
- * Any other methods might throw an exception or return meaningless data.
+ * Any other methods might return meaningless data.
* <p>
* Subclasses of the SessionPlayer may have extra methods that are safe to be called in the error
* state and/or provide a method to recover from the error state. Take a look at documentations of
- * specific class that you're interested in.
+ * specific subclass that you're interested in.
* <p>
- * Most methods can be called from any non-Error state. They will either perform their work or
- * silently have no effect. The following table lists the methods that aren't guaranteed to
- * successfully running if they're called from the associated invalid states.
+ * Most methods can be called from any non-Error state. In case they're called in invalid state,
+ * the implementation should ignore and would return {@link PlayerResult} with
+ * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}. The following table lists the methods that
+ * aren't guaranteed to successfully running if they're called from the associated invalid states.
* <p>
* <table>
* <tr><th>Method Name</th> <th>Invalid States</th></tr>
- * <tr><td>setMediaItem</td> <td>{Paused, Playing}</td></tr>
- * <tr><td>setPlaylist</td> <td>{Paused, Playing}</td></tr>
+ * <tr><td>setAudioAttributes</td> <td>{Paused, Playing}</td></tr>
* <tr><td>prepare</td> <td>{Paused, Playing}</td></tr>
* <tr><td>play</td> <td>{Idle}</td></tr>
* <tr><td>pause</td> <td>{Idle}</td></tr>
@@ -274,7 +276,10 @@
*/
public static final int SHUFFLE_MODE_GROUP = 2;
- public static final long UNKNOWN_TIME = -1;
+ /**
+ * Value indicating the time is unknown
+ */
+ public static final long UNKNOWN_TIME = Long.MIN_VALUE;
/**
* Media item index is invalid. This value will be returned when the corresponding media item
@@ -287,23 +292,47 @@
private final List<Pair<PlayerCallback, Executor>> mCallbacks = new ArrayList<>();
/**
- * Plays the playback.
+ * Starts or resumes playback.
+ * <p>
+ * On success, this transfers the player state to {@link #PLAYER_STATE_PLAYING} and
+ * a {@link PlayerResult} should be returned with the current media item when the command
+ * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
+ * it should be ignored and a {@link PlayerResult} should be returned with
+ * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*/
public abstract @NonNull ListenableFuture<PlayerResult> play();
/**
* Pauses playback.
+ * <p>
+ * On success, this transfers the player state to {@link #PLAYER_STATE_PAUSED} and
+ * a {@link PlayerResult} should be returned with the current media item when the command
+ * was completed. If it is called in {@link #PLAYER_STATE_IDLE} or {@link #PLAYER_STATE_ERROR},
+ * it should be ignored and a {@link PlayerResult} should be returned with
+ * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*/
public abstract @NonNull ListenableFuture<PlayerResult> pause();
/**
* Prepares the media items for playback. During this time, the player may allocate resources
- * required to play, such as audio and video decoders.
+ * required to play, such as audio and video decoders. Before calling this API, sets media
+ * item(s) through either {@link #setMediaItem} or {@link #setPlaylist}.
+ * <p>
+ * On success, this transfers the player state from {@link #PLAYER_STATE_IDLE} to
+ * {@link #PLAYER_STATE_PAUSED} and a {@link PlayerResult} should be returned with the prepared
+ * media item when the command completed. If it's not called in {@link #PLAYER_STATE_IDLE},
+ * it is ignored and {@link PlayerResult} should be returned with
+ * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*/
public abstract @NonNull ListenableFuture<PlayerResult> prepare();
/**
* Seeks to the specified position. Moves the playback head to the specified position.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed. If it's called in {@link #PLAYER_STATE_IDLE}, it is ignored and
+ * a {@link PlayerResult} should be returned with
+ * {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
*
* @param position the new playback position in ms. The value should be in the range of start
* and end positions defined in {@link MediaItem}.
@@ -311,12 +340,18 @@
public abstract @NonNull ListenableFuture<PlayerResult> seekTo(long position);
/**
- * Sets the playback speed. A value of {@code 1.0f} is the default playback value.
+ * Sets the playback speed. A value of {@code 1.0f} is the default playback value, and a
+ * negative value indicates reverse playback.
* <p>
- * After changing the playback speed, it is recommended to query the actual speed supported
- * by the player, see {@link #getPlaybackSpeed()}.
+ * Supported playback speed range may differ per player. So it is recommended to query the
+ * actual speed supported by the player after changing the playback speed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param playbackSpeed playback speed
+ * @see #getPlaybackSpeed()
+ * @see PlayerCallback#onPlaybackSpeedChanged(SessionPlayer, float)
*/
public abstract @NonNull ListenableFuture<PlayerResult> setPlaybackSpeed(float playbackSpeed);
@@ -324,7 +359,11 @@
* Sets the {@link AudioAttributesCompat} to be used during the playback of the media.
* <p>
* You must call this method in {@link #PLAYER_STATE_IDLE} in order for the audio attributes to
- * become effective thereafter.
+ * become effective thereafter. Otherwise, the call would be ignored and {@link PlayerResult}
+ * should be returned with {@link PlayerResult#RESULT_ERROR_INVALID_STATE}.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param attributes non-null <code>AudioAttributes</code>.
*/
@@ -358,13 +397,15 @@
public abstract long getDuration();
/**
- * Gets the buffered position of current playback, or {@link #UNKNOWN_TIME} if unknown.
+ * Gets the position for how much has been buffered, or {@link #UNKNOWN_TIME} if unknown.
+ *
* @return the buffered position in ms, or {@link #UNKNOWN_TIME}.
*/
public abstract long getBufferedPosition();
/**
* Returns the current buffering state of the player.
+ * <p>
* During the buffering, see {@link #getBufferedPosition()} for the quantifying the amount
* already buffered.
*
@@ -374,7 +415,8 @@
public abstract @BuffState int getBufferingState();
/**
- * Gets the actual playback speed to be used by the player when playing.
+ * Gets the actual playback speed to be used by the player when playing. A value of {@code 1.0f}
+ * is the default playback value, and a negative value indicates reverse playback.
* <p>
* Note that it may differ from the speed set in {@link #setPlaybackSpeed(float)}.
*
@@ -386,6 +428,9 @@
* Sets a list of {@link MediaItem} with metadata. Use this or {@link #setMediaItem} to specify
* which items to play.
* <p>
+ * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
+ * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
+ * <p>
* Ensure uniqueness of each {@link MediaItem} in the playlist so the session can uniquely
* identity individual items. All {@link MediaItem}s shouldn't be {@code null} as well.
* <p>
@@ -400,6 +445,9 @@
* <p>
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* when a media item in the playlist is a {@link FileMediaItem}.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the first media item of the
+ * playlist when the command completed.
*
* @param list A list of {@link MediaItem} objects to set as a play list.
* @throws IllegalArgumentException if the given list is {@code null} or empty, or has
@@ -423,6 +471,9 @@
* {@link #skipToPlaylistItem}, {@link #skipToNextPlaylistItem}, or
* {@link #skipToPreviousPlaylistItem} instead of this method.
* <p>
+ * This can be called multiple times in any states other than {@link #PLAYER_STATE_ERROR}. This
+ * would override previous {@link #setMediaItem} or {@link #setPlaylist} calls.
+ * <p>
* It's recommended to fill {@link MediaMetadata} in {@link MediaItem} especially for the
* duration information with the key {@link MediaMetadata#METADATA_KEY_DURATION}. Without the
* duration information in the metadata, session will do extra work to get the duration and send
@@ -434,6 +485,8 @@
* <p>
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with {@code item} set.
*
* @param item the descriptor of media item you want to play
* @return a {@link ListenableFuture} which represents the pending completion of the command.
@@ -460,6 +513,8 @@
* <p>
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with {@code item} added.
*
* @param index the index of the item you want to add in the playlist
* @param item the media item you want to add
@@ -476,6 +531,8 @@
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)} when it's
* completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with {@code item} removed.
*
* @param index the index of the item you want to remove in the playlist
* @see PlayerCallback#onPlaylistChanged(SessionPlayer, List, MediaMetadata)
@@ -493,6 +550,8 @@
* <p>
* The implementation must close the {@link ParcelFileDescriptor} in the {@link FileMediaItem}
* if the given media item is a {@link FileMediaItem}.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with {@code item} set.
*
* @param index the index of the item to replace in the playlist
* @param item the new item
@@ -507,6 +566,9 @@
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
@@ -518,6 +580,9 @@
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
*/
@@ -529,6 +594,9 @@
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)} when it's
* completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param index The index of the item you want to play in the playlist
* @see PlayerCallback#onCurrentMediaItemChanged(SessionPlayer, MediaItem)
@@ -542,6 +610,9 @@
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)} when it's
* completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param metadata metadata of the playlist
* @see PlayerCallback#onPlaylistMetadataChanged(SessionPlayer, MediaMetadata)
@@ -554,6 +625,9 @@
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onRepeatModeChanged(SessionPlayer, int)} when it's completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param repeatMode repeat mode
* @see #REPEAT_MODE_NONE
@@ -570,6 +644,9 @@
* <p>
* The implementation must notify registered callbacks with
* {@link PlayerCallback#onShuffleModeChanged(SessionPlayer, int)} when it's completed.
+ * <p>
+ * On success, a {@link PlayerResult} should be returned with the current media item when the
+ * command completed.
*
* @param shuffleMode The shuffle mode
* @see #SHUFFLE_MODE_NONE
@@ -911,7 +988,7 @@
* Constructor that uses the current system clock as the completion time.
*
* @param resultCode result code. Recommends to use the standard code defined here.
- * @param item media item when the command is completed
+ * @param item media item when the command completed
*/
// Note: resultCode is intentionally not annotated for subclass to return extra error codes.
public PlayerResult(int resultCode, @Nullable MediaItem item) {
@@ -952,7 +1029,7 @@
/**
* Gets the completion time of the command. Being more specific, it's the same as
- * {@link android.os.SystemClock#elapsedRealtime()} when the command is completed.
+ * {@link android.os.SystemClock#elapsedRealtime()} when the command completed.
*
* @return completion time of the command
*/
@@ -964,9 +1041,9 @@
/**
* Gets the {@link MediaItem} for which the command was executed. In other words, this is
* the item sent as an argument of the command if any, otherwise the current media item when
- * the command was completed.
+ * the command completed.
*
- * @return media item when the command is completed. Can be {@code null} for an error, or
+ * @return media item when the command completed. Can be {@code null} for an error, or
* the current media item was {@code null}.
*/
@Override
diff --git a/media2/src/main/java/androidx/media2/SessionResult.java b/media2/src/main/java/androidx/media2/SessionResult.java
index dac076d..719b2be 100644
--- a/media2/src/main/java/androidx/media2/SessionResult.java
+++ b/media2/src/main/java/androidx/media2/SessionResult.java
@@ -178,7 +178,7 @@
/**
* Gets the completion time of the command. Being more specific, it's the same as
- * {@link SystemClock#elapsedRealtime()} when the command is completed.
+ * {@link SystemClock#elapsedRealtime()} when the command completed.
*
* @return completion time of the command
*/
@@ -189,7 +189,7 @@
/**
* Gets the {@link MediaItem} for which the command was executed. In other words, this is
- * the current media item when the command was completed.
+ * the current media item when the command completed.
* <p>
* Can be {@code null} for many reasons. For examples,
* <ul>
@@ -198,7 +198,7 @@
* <li>Command is irrelevant with the media item (e.g. custom command).
* </ul>
*
- * @return media item when the command is completed. Can be {@code null} for an error, the
+ * @return media item when the command completed. Can be {@code null} for an error, the
* current media item was {@code null}, or any other reason.
*/
@Override
diff --git a/media2/src/main/java/androidx/media2/SessionTokenImplLegacy.java b/media2/src/main/java/androidx/media2/SessionTokenImplLegacy.java
index 975f9a1..bce372b 100644
--- a/media2/src/main/java/androidx/media2/SessionTokenImplLegacy.java
+++ b/media2/src/main/java/androidx/media2/SessionTokenImplLegacy.java
@@ -22,7 +22,6 @@
import static androidx.media2.SessionToken.TYPE_SESSION;
import static androidx.media2.SessionToken.TYPE_SESSION_LEGACY;
-import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.os.Bundle;
import android.support.v4.media.session.MediaSessionCompat;
@@ -40,7 +39,6 @@
import androidx.versionedparcelable.VersionedParcelize;
@VersionedParcelize(isCustom = true)
-@SuppressLint("RestrictedApi")
final class SessionTokenImplLegacy extends CustomVersionedParcelable implements SessionTokenImpl {
// Don't mark mLegacyToken @ParcelField, because we need to use toBundle()/fromBundle() instead
// of the writeToParcel()/Parcelable.Creator for sending extra binder.
diff --git a/media2/src/main/java/androidx/media2/UriMediaItem.java b/media2/src/main/java/androidx/media2/UriMediaItem.java
index a867f40..f7d55b6 100644
--- a/media2/src/main/java/androidx/media2/UriMediaItem.java
+++ b/media2/src/main/java/androidx/media2/UriMediaItem.java
@@ -16,7 +16,6 @@
package androidx.media2;
-import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -55,9 +54,6 @@
@NonParcelField
@SuppressWarnings("WeakerAccess") /* synthetic access */
List<HttpCookie> mUriCookies;
- @NonParcelField
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- Context mUriContext;
/**
* Used for VersionedParcelable
@@ -71,7 +67,6 @@
mUri = builder.mUri;
mUriHeader = builder.mUriHeader;
mUriCookies = builder.mUriCookies;
- mUriContext = builder.mUriContext;
}
/**
@@ -105,14 +100,6 @@
}
/**
- * Return the Context used for resolving the Uri of this media item.
- * @return the Context used for resolving the Uri of this media item
- */
- public @NonNull Context getUriContext() {
- return mUriContext;
- }
-
- /**
* This Builder class simplifies the creation of a {@link UriMediaItem} object.
*/
public static final class Builder extends MediaItem.Builder {
@@ -123,17 +110,14 @@
Map<String, String> mUriHeader;
@SuppressWarnings("WeakerAccess") /* synthetic access */
List<HttpCookie> mUriCookies;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- Context mUriContext;
/**
* Creates a new Builder object with a content Uri.
*
- * @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
*/
- public Builder(@NonNull Context context, @NonNull Uri uri) {
- this(context, uri, null, null);
+ public Builder(@NonNull Uri uri) {
+ this(uri, null, null);
}
/**
@@ -147,7 +131,6 @@
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
* disallow or allow cross domain redirection.
*
- * @param context the Context to use when resolving the Uri
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
* The headers must not include cookies. Instead, use the cookies param.
@@ -155,12 +138,10 @@
* @throws IllegalArgumentException if the cookie handler is not of CookieManager type
* when cookies are provided.
*/
- public Builder(@NonNull Context context, @NonNull Uri uri,
- @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
- Preconditions.checkNotNull(context, "context cannot be null");
+ public Builder(@NonNull Uri uri, @Nullable Map<String, String> headers,
+ @Nullable List<HttpCookie> cookies) {
Preconditions.checkNotNull(uri, "uri cannot be null");
mUri = uri;
- mUriContext = context;
if (cookies != null) {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
@@ -177,7 +158,6 @@
if (cookies != null) {
mUriCookies = new ArrayList<HttpCookie>(cookies);
}
- mUriContext = context;
}
// Override just to change return type.
diff --git a/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
index 289dabb..59235e0 100644
--- a/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
+++ b/media2/src/main/java/androidx/media2/exoplayer/ExoPlayerWrapper.java
@@ -47,6 +47,7 @@
import androidx.media2.exoplayer.external.DefaultLoadControl;
import androidx.media2.exoplayer.external.ExoPlaybackException;
import androidx.media2.exoplayer.external.ExoPlayerFactory;
+import androidx.media2.exoplayer.external.Format;
import androidx.media2.exoplayer.external.Player;
import androidx.media2.exoplayer.external.SimpleExoPlayer;
import androidx.media2.exoplayer.external.analytics.AnalyticsCollector;
@@ -56,6 +57,7 @@
import androidx.media2.exoplayer.external.audio.AudioProcessor;
import androidx.media2.exoplayer.external.audio.AuxEffectInfo;
import androidx.media2.exoplayer.external.audio.DefaultAudioSink;
+import androidx.media2.exoplayer.external.decoder.DecoderCounters;
import androidx.media2.exoplayer.external.metadata.Metadata;
import androidx.media2.exoplayer.external.metadata.MetadataOutput;
import androidx.media2.exoplayer.external.source.ClippingMediaSource;
@@ -69,7 +71,7 @@
import androidx.media2.exoplayer.external.upstream.DefaultDataSourceFactory;
import androidx.media2.exoplayer.external.util.MimeTypes;
import androidx.media2.exoplayer.external.util.Util;
-import androidx.media2.exoplayer.external.video.VideoListener;
+import androidx.media2.exoplayer.external.video.VideoRendererEventListener;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -162,6 +164,7 @@
private final Runnable mPollBufferRunnable;
private SimpleExoPlayer mPlayer;
+ private Handler mPlayerHandler;
private DefaultAudioSink mAudioSink;
private TrackSelector mTrackSelector;
private MediaItemQueue mMediaItemQueue;
@@ -321,9 +324,10 @@
public void setAudioAttributes(AudioAttributesCompat audioAttributes) {
mHasAudioAttributes = true;
mPlayer.setAudioAttributes(ExoPlayerUtils.getAudioAttributes(audioAttributes));
+
// Reset the audio session ID, as it gets cleared by setting audio attributes.
if (mAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
- mAudioSink.setAudioSessionId(mAudioSessionId);
+ updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
}
}
@@ -334,7 +338,9 @@
public void setAudioSessionId(int audioSessionId) {
mAudioSessionId = audioSessionId;
- mAudioSink.setAudioSessionId(mAudioSessionId);
+ if (mPlayer != null) {
+ updatePlayerAudioSessionId(mPlayerHandler, mAudioSink, mAudioSessionId);
+ }
}
public int getAudioSessionId() {
@@ -463,9 +469,11 @@
mBandwidthMeter,
new AnalyticsCollector.Factory(),
mLooper);
+ mPlayerHandler = new Handler(mPlayer.getPlaybackLooper());
mMediaItemQueue = new MediaItemQueue(mContext, mPlayer, mListener);
mPlayer.addListener(listener);
- mPlayer.addVideoListener(listener);
+ // TODO(b/80232248): Switch to AnalyticsListener once default methods work.
+ mPlayer.setVideoDebugListener(listener);
mPlayer.addMetadataOutput(listener);
mVideoWidth = 0;
mVideoHeight = 0;
@@ -538,10 +546,7 @@
maybeNotifyReadyEvents();
break;
case Player.STATE_ENDED:
- if (playWhenReady) {
- mMediaItemQueue.onPlayerEnded();
- mPlayer.setPlayWhenReady(false);
- }
+ maybeNotifyEndedEvents();
break;
case Player.STATE_IDLE:
// Do nothing.
@@ -672,9 +677,22 @@
}
}
+ private void maybeNotifyEndedEvents() {
+ if (mPendingSeek) {
+ // The seek operation resulted in transitioning to the ended state.
+ mPendingSeek = false;
+ mListener.onSeekCompleted();
+ }
+ if (mPlayer.getPlayWhenReady()) {
+ mMediaItemQueue.onPlayerEnded();
+ mPlayer.setPlayWhenReady(false);
+ }
+ }
+
@SuppressWarnings("WeakerAccess") /* synthetic access */
final class ComponentListener extends Player.DefaultEventListener
- implements VideoListener, AudioListener, TextRenderer.Output, MetadataOutput {
+ implements VideoRendererEventListener, AudioListener,
+ TextRenderer.Output, MetadataOutput {
// DefaultEventListener implementation.
@@ -704,7 +722,7 @@
handlePlayerError(error);
}
- // VideoListener implementation.
+ // VideoRendererEventListener implementation.
@Override
public void onVideoSizeChanged(
@@ -716,12 +734,29 @@
}
@Override
- public void onRenderedFirstFrame() {
+ public void onVideoInputFormatChanged(Format format) {
+ if (MimeTypes.isVideo(format.sampleMimeType)) {
+ handleVideoSizeChanged(format.width, format.height, format.pixelWidthHeightRatio);
+ }
+ }
+
+ @Override
+ public void onRenderedFirstFrame(@Nullable Surface surface) {
handleRenderedFirstFrame();
}
@Override
- public void onSurfaceSizeChanged(int width, int height) {}
+ public void onVideoEnabled(DecoderCounters counters) {}
+
+ @Override
+ public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs,
+ long initializationDurationMs) {}
+
+ @Override
+ public void onDroppedFrames(int count, long elapsedMs) {}
+
+ @Override
+ public void onVideoDisabled(DecoderCounters counters) {}
// AudioListener implementation.
@@ -765,6 +800,19 @@
}
}
+ private static void updatePlayerAudioSessionId(
+ Handler playerHandler,
+ final DefaultAudioSink audioSink,
+ final int audioSessionId) {
+ // DefaultAudioSink is not thread-safe, so post the update to the playback thread.
+ playerHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ audioSink.setAudioSessionId(audioSessionId);
+ }
+ });
+ }
+
private static final class MediaItemInfo {
final MediaItem mMediaItem;
diff --git a/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java b/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
index 7b246fa..80028ca 100644
--- a/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
+++ b/media2/src/main/java/androidx/media2/subtitle/SubtitleController.java
@@ -523,7 +523,10 @@
}
}
- interface Listener {
+ /**
+ * Listener for when subtitle track has been selected.
+ */
+ public interface Listener {
/**
* Called when a subtitle track has been selected.
*
diff --git a/media2/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java b/media2/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java
index 61441b0..19b8e6a 100644
--- a/media2/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java
+++ b/media2/version-compat-tests/common/src/main/java/androidx/media2/test/common/TestUtils.java
@@ -30,6 +30,7 @@
public class TestUtils {
public static final int TIMEOUT_MS = 1000;
+ public static final int PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS = 3000;
/**
* Compares contents of two bundles.
diff --git a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
index de1421a..9151175 100644
--- a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
+++ b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
@@ -30,7 +30,7 @@
import static androidx.media2.test.common.CommonConstants.KEY_SPEED;
import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
import static androidx.media2.test.common.CommonConstants.MEDIA2_SESSION_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.TIMEOUT_MS;
+import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
import static junit.framework.TestCase.fail;
@@ -507,7 +507,8 @@
if (bound) {
try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
}
diff --git a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
index 071f423..5c9e0e3 100644
--- a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
+++ b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSessionCompat.java
@@ -22,7 +22,7 @@
import static androidx.media2.test.common.CommonConstants.KEY_QUEUE;
import static androidx.media2.test.common.CommonConstants.KEY_SESSION_COMPAT_TOKEN;
import static androidx.media2.test.common.CommonConstants.MEDIA_SESSION_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.TIMEOUT_MS;
+import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
import static junit.framework.TestCase.fail;
@@ -253,7 +253,8 @@
if (bound) {
try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
}
diff --git a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
index 618c257..a9be9f5 100644
--- a/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
+++ b/media2/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerTest.java
@@ -34,6 +34,7 @@
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
+import android.util.Log;
import androidx.media.AudioAttributesCompat;
import androidx.media2.MediaController;
@@ -64,6 +65,9 @@
@LargeTest
public class MediaControllerTest extends MediaSessionTestBase {
+ static final String TAG = "MediaControllerTest";
+ private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
+
final List<RemoteMediaSession> mRemoteSessionList = new ArrayList<>();
AudioManager mAudioManager;
@@ -139,7 +143,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -154,9 +160,10 @@
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
@@ -181,7 +188,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -197,9 +206,10 @@
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
index e6f8da2..9b5bf2d 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaBrowserCompat.java
@@ -18,7 +18,7 @@
import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_BROWSER_COMPAT;
import static androidx.media2.test.common.CommonConstants.MEDIA_BROWSER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.TIMEOUT_MS;
+import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
import static junit.framework.TestCase.fail;
@@ -206,7 +206,8 @@
if (bound) {
try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
}
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
index 5b7e5f4..06cf4d5 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaController.java
@@ -18,7 +18,7 @@
import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA2_CONTROLLER;
import static androidx.media2.test.common.CommonConstants.MEDIA2_CONTROLLER_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.TIMEOUT_MS;
+import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
import static junit.framework.TestCase.fail;
@@ -384,7 +384,8 @@
if (bound) {
try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
}
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
index d13e523..121185fc 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/RemoteMediaControllerCompat.java
@@ -19,7 +19,7 @@
import static androidx.media2.test.common.CommonConstants.ACTION_MEDIA_CONTROLLER_COMPAT;
import static androidx.media2.test.common.CommonConstants.KEY_ARGUMENTS;
import static androidx.media2.test.common.CommonConstants.MEDIA_CONTROLLER_COMPAT_PROVIDER_SERVICE;
-import static androidx.media2.test.common.TestUtils.TIMEOUT_MS;
+import static androidx.media2.test.common.TestUtils.PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS;
import static junit.framework.TestCase.fail;
@@ -370,7 +370,8 @@
if (bound) {
try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ mCountDownLatch.await(PROVIDER_SERVICE_CONNECTION_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
Log.e(TAG, "InterruptedException while waiting for onServiceConnected.", ex);
}
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
index 8a44064..b217031 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaItemTest.java
@@ -83,7 +83,7 @@
public MediaItem create(Context context) {
final MediaMetadata testMetadata = new MediaMetadata.Builder()
.putString("MediaItemTest", "MediaItemTest").build();
- return new UriMediaItem.Builder(context, Uri.parse("test://test"))
+ return new UriMediaItem.Builder(Uri.parse("test://test"))
.setMetadata(testMetadata)
.setStartPosition(1)
.setEndPosition(1000)
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
index 912c37d..c7fcb1e 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackTestWithMediaControllerCompat.java
@@ -39,6 +39,7 @@
import android.support.v4.media.RatingCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.media.AudioAttributesCompat;
@@ -81,6 +82,7 @@
@LargeTest
public class MediaSessionCallbackTestWithMediaControllerCompat extends MediaSessionTestBase {
private static final String TAG = "MediaSessionCallbackTestWithMediaControllerCompat";
+ private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
private static final String EXPECTED_CONTROLLER_PACKAGE_NAME =
(Build.VERSION.SDK_INT < 21 || Build.VERSION.SDK_INT >= 24)
@@ -406,7 +408,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -421,9 +425,10 @@
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
mController.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
@@ -446,7 +451,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -462,9 +469,10 @@
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
mController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
diff --git a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
index 59349bf..d430240 100644
--- a/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
+++ b/media2/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCompatCallbackTestWithMediaController.java
@@ -36,6 +36,7 @@
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.MediaSessionCompat.QueueItem;
import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
import androidx.media.VolumeProviderCompat;
import androidx.media2.MediaController;
@@ -76,6 +77,7 @@
// The maximum time to wait for an operation.
private static final long TIME_OUT_MS = 3000L;
+ private static final long VOLUME_CHANGE_TIMEOUT_MS = 5000L;
PendingIntent mIntent;
MediaSessionCompat mSession;
@@ -372,7 +374,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -382,9 +386,10 @@
final int originalVolume = mAudioManager.getStreamVolume(stream);
final int targetVolume = originalVolume == minVolume
? originalVolume + 1 : originalVolume - 1;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
controller.setVolumeTo(targetVolume, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
@@ -407,7 +412,9 @@
// 'Do Not Disturb' or 'Volume limit'.
final int stream = AudioManager.STREAM_ALARM;
final int maxVolume = mAudioManager.getStreamMaxVolume(stream);
- final int minVolume = 1;
+ final int minVolume =
+ Build.VERSION.SDK_INT >= 28 ? mAudioManager.getStreamMinVolume(stream) : 0;
+ Log.d(TAG, "maxVolume=" + maxVolume + ", minVolume=" + minVolume);
if (maxVolume <= minVolume) {
return;
}
@@ -419,9 +426,10 @@
final int direction = originalVolume == minVolume
? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER;
final int targetVolume = originalVolume + direction;
+ Log.d(TAG, "originalVolume=" + originalVolume + ", targetVolume=" + targetVolume);
controller.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
- new PollingCheck(TIMEOUT_MS) {
+ new PollingCheck(VOLUME_CHANGE_TIMEOUT_MS) {
@Override
protected boolean check() {
return targetVolume == mAudioManager.getStreamVolume(stream);
diff --git a/media2/version-compat-tests/runtest.sh b/media2/version-compat-tests/runtest.sh
index a303057..b015445 100755
--- a/media2/version-compat-tests/runtest.sh
+++ b/media2/version-compat-tests/runtest.sh
@@ -28,6 +28,8 @@
CLIENT_VERSION=""
SERVICE_VERSION=""
OPTION_TEST_TARGET=""
+VERSION_COMBINATION=""
+DEVICE_SERIAL=""
function printRunTestUsage() {
echo "Usage: ./runtest.sh <version_combination_number> [option]"
@@ -41,6 +43,7 @@
echo ""
echo "Option:"
echo " -t <class/method>: Only run the specific test class/method."
+ echo " -s <serial>: Use device with the serial. Required if multiple devices are connected."
}
function runTest() {
@@ -54,11 +57,11 @@
./gradlew $SERVICE_MODULE_NAME:assembleDebugAndroidTest || { echo "Build failed. Aborting."; exit 1; }
echo "Installing the test apks"
- adb install -r "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
- adb install -r "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+ adb $DEVICE_SERIAL install -r "../../out/dist/$CLIENT_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
+ adb $DEVICE_SERIAL install -r "../../out/dist/$SERVICE_MODULE_NAME.apk" || { echo "Apk installation failed. Aborting."; exit 1; }
echo "Running the tests"
- local test_command="adb shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
+ local test_command="adb $DEVICE_SERIAL shell am instrument -w -e debug false -e client_version $CLIENT_VERSION -e service_version $SERVICE_VERSION"
local client_test_runner="androidx.media2.test.client.test/androidx.test.runner.AndroidJUnitRunner"
local service_test_runner="androidx.media2.test.service.test/androidx.test.runner.AndroidJUnitRunner"
@@ -86,24 +89,40 @@
exit 1;
fi
-if [[ $# -eq 0 || $1 -le 0 || $1 -gt 4 ]]
-then
- printRunTestUsage
- exit 1;
-fi
-
-if [[ ${2} == "-t" ]]; then
- if [[ ${3} == *"client"* || ${3} == *"service"* ]]; then
- OPTION_TEST_TARGET="-e class ${3}"
- else
- echo "Wrong test class/method name. Aborting."
- echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
- exit 1;
- fi
-fi
-
case ${1} in
1)
+ VERSION_COMBINATION=${1}
+ shift
+ ;;
+ *)
+ printRunTestUsage
+ exit 1;
+esac
+
+while (( "$#" )); do
+ case ${1} in
+ -t)
+ if [[ ${2} == *"client"* || ${2} == *"service"* ]]; then
+ OPTION_TEST_TARGET="-e class ${2}"
+ else
+ echo "Wrong test class/method name. Aborting."
+ echo "It should be in the form of \"<FULL_CLASS_NAME>[#METHOD_NAME]\"."
+ exit 1;
+ fi
+ shift 2
+ ;;
+ -s)
+ DEVICE_SERIAL="-s ${2}"
+ shift 2
+ ;;
+ *)
+ printRunTestUsage
+ exit 1;
+ esac
+done
+
+case ${VERSION_COMBINATION} in
+ 1)
CLIENT_VERSION="tot"
SERVICE_VERSION="tot"
runTest
diff --git a/mediarouter/api/1.1.0-alpha03.txt b/mediarouter/api/1.1.0-alpha03.txt
new file mode 100644
index 0000000..f0c7243
--- /dev/null
+++ b/mediarouter/api/1.1.0-alpha03.txt
@@ -0,0 +1,522 @@
+// Signature format: 3.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context!);
+ method public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View! onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton! onCreateMediaRouteButton();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!);
+ ctor public MediaRouteButton(android.content.Context!, android.util.AttributeSet!, int);
+ method public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context!);
+ ctor public MediaRouteChooserDialog(android.content.Context!, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog! onCreateChooserDialog(android.content.Context!, android.os.Bundle!);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context!);
+ ctor public MediaRouteControllerDialog(android.content.Context!, int);
+ method public android.view.View! getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View! onCreateMediaControlView(android.os.Bundle!);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog! onCreateControllerDialog(android.content.Context!, android.os.Bundle!);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter! getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback! onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus! fromBundle(android.os.Bundle!);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle! getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(int);
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus!);
+ method public androidx.mediarouter.media.MediaItemStatus! build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder! setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor! fromBundle(android.os.Bundle!);
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter>! getControlFilters();
+ method public String! getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle! getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String! getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender! getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(String!, String!);
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilter(android.content.IntentFilter!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addControlFilters(java.util.Collection<android.content.IntentFilter>!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDescription(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIconUri(android.net.Uri!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setId(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setName(String!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setSettingsActivity(android.content.IntentSender!);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector!, boolean);
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest! fromBundle(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaRouteSelector! getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context! getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler! getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata! getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest!);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String!);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String>?);
+ method public abstract void setOnDynamicRoutesChangedListener(java.util.concurrent.Executor, androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.OnDynamicRoutesChangedListener);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor!);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder! setSelectionState(int);
+ }
+
+ public static interface MediaRouteProvider.DynamicGroupRouteController.OnDynamicRoutesChangedListener {
+ method public void onRoutesChanged(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController!, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor>!);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent!, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor! fromBundle(android.os.Bundle!);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoute(androidx.mediarouter.media.MediaRouteDescriptor!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor>!);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor! build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder! setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider! getMediaRouteProvider();
+ method public android.os.IBinder! onBind(android.content.Intent!);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider! onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle! asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector!);
+ method public static androidx.mediarouter.media.MediaRouteSelector! fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String>! getControlCategories();
+ method public boolean hasControlCategory(String!);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter>!);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector!, androidx.mediarouter.media.MediaRouter.Callback!);
+ method public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void addRemoteControlClient(Object);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo! getBluetoothRoute();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method public static androidx.mediarouter.media.MediaRouter! getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSessionToken();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo>! getProviders();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo>! getRoutes();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method public void removeRemoteControlClient(Object);
+ method public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void setMediaSession(Object!);
+ method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat!);
+ method public void unselect(int);
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.ProviderInfo!);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String!, android.os.Bundle!);
+ method public void onResult(android.os.Bundle!);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName! getComponentName();
+ method public String! getPackageName();
+ method public androidx.mediarouter.media.MediaRouteProvider! getProviderInstance();
+ method public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo>! getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter>! getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri! getIconUri();
+ method public String getId();
+ method public String! getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo! getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method public boolean isSelected();
+ method public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
+ method public void select();
+ method public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public boolean supportsControlAction(String, String);
+ method public boolean supportsControlCategory(String);
+ method public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle! asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus! fromBundle(android.os.Bundle!);
+ method public android.os.Bundle! getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(int);
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus!);
+ method public androidx.mediarouter.media.MediaSessionStatus! build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setExtras(android.os.Bundle!);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder! setTimestamp(long);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context!, androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method public void endSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void enqueue(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public String! getSessionId();
+ method public void getSessionStatus(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void getStatus(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void play(android.net.Uri!, String!, android.os.Bundle!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void release();
+ method public void remove(String!, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void resume(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void seek(String!, long, android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback!);
+ method public void sendMessage(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener!);
+ method public void setSessionId(String!);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback!);
+ method public void startSession(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ method public void stop(android.os.Bundle!, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback!);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String!, int, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String!, android.os.Bundle!);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!, String!, androidx.mediarouter.media.MediaItemStatus!);
+ method public void onSessionChanged(String!);
+ method public void onSessionStatusChanged(android.os.Bundle!, String!, androidx.mediarouter.media.MediaSessionStatus!);
+ }
+
+}
+
diff --git a/mediarouter/api/res-1.1.0-alpha03.txt b/mediarouter/api/res-1.1.0-alpha03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mediarouter/api/res-1.1.0-alpha03.txt
diff --git a/mediarouter/api/restricted_1.1.0-alpha03.txt b/mediarouter/api/restricted_1.1.0-alpha03.txt
new file mode 100644
index 0000000..fc748c3
--- /dev/null
+++ b/mediarouter/api/restricted_1.1.0-alpha03.txt
@@ -0,0 +1,82 @@
+// Signature format: 3.0
+package androidx.mediarouter.app {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MediaRouteCastDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteCastDialog(android.content.Context!);
+ ctor public MediaRouteCastDialog(android.content.Context!, int);
+ method public android.support.v4.media.session.MediaSessionCompat.Token! getMediaSession();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo>);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.app.MediaRouteDevicePickerDialog! onCreateDevicePickerDialog(android.content.Context!);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteSelector! getRouteSelector();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.app.MediaRouteCastDialog! onCreateCastDialog(android.content.Context!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MediaRouteDevicePickerDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteDevicePickerDialog(android.content.Context!);
+ ctor public MediaRouteDevicePickerDialog(android.content.Context!, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaRouteDescriptor {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.List<java.lang.String>! getGroupMemberIds();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMaxClientVersion();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMinClientVersion();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addGroupMemberId(String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteDescriptor.Builder! addGroupMemberIds(java.util.Collection<java.lang.String>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteDescriptor.Builder! removeGroupMemberId(String!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setMaxClientVersion(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteDescriptor.Builder! setMinClientVersion(int);
+ }
+
+ public abstract class MediaRouteProvider {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String, String);
+ }
+
+ @IntDef({androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.UNSELECTING, androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.UNSELECTED, androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.SELECTING, androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.SELECTED}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.SelectionState {
+ }
+
+ public final class MediaRouter {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addMemberToSelectedRoute(androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void removeMemberFromSelectedRoute(androidx.mediarouter.media.MediaRouter.RouteInfo!);
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? getDynamicGroupController();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo> getMemberRoutes();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getPresentationDisplayId();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.mediarouter.media.MediaRouteProvider! getProviderInstance();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getSelectionState();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isDefaultOrBluetooth();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isDynamicRoute();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isGroup();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isGroupable();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isTransferable();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isUnselectable();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int PRESENTATION_DISPLAY_ID_NONE = -1; // 0xffffffff
+ }
+
+}
+
diff --git a/mediarouter/build.gradle b/mediarouter/build.gradle
index 213f9cd..98b4ae4 100644
--- a/mediarouter/build.gradle
+++ b/mediarouter/build.gradle
@@ -8,9 +8,9 @@
dependencies {
api(project(":media"))
- api(project(":appcompat"))
- api("androidx.palette:palette:1.0.0")
- api(project(":recyclerview"))
+ implementation("androidx.appcompat:appcompat:1.0.2")
+ implementation("androidx.palette:palette:1.0.0")
+ implementation("androidx.recyclerview:recyclerview:1.0.0")
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
@@ -28,6 +28,10 @@
'api24'
]
}
+
+ buildTypes.all {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
}
supportLibrary {
diff --git a/mediarouter/lint-baseline.xml b/mediarouter/lint-baseline.xml
deleted file mode 100644
index 787c098..0000000
--- a/mediarouter/lint-baseline.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="4" by="lint 3.0.0">
-
- <issue
- id="MissingSuperCall"
- message="Overriding method should call `super.jumpDrawablesToCurrentState`"
- errorLine1=" public void jumpDrawablesToCurrentState() {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/mediarouter/app/MediaRouteButton.java"
- line="379"
- column="17"/>
- </issue>
-
- <issue
- id="LongLogTag"
- message="The logging tag can be at most 23 characters, was 24 (MediaRouteActionProvider)"
- errorLine1=" Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +"
- errorLine2=" ~~~">
- <location
- file="src/main/java/androidx/mediarouter/app/MediaRouteActionProvider.java"
- line="250"
- column="19"/>
- </issue>
-
-</issues>
diff --git a/mediarouter/proguard-rules.pro b/mediarouter/proguard-rules.pro
new file mode 100644
index 0000000..5016e8f
--- /dev/null
+++ b/mediarouter/proguard-rules.pro
@@ -0,0 +1,16 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Prevent MediaRouteActionProvider from being removed or renamed.
+-keep class androidx.mediarouter.app.MediaRouteActionProvider { public <init>(...); }
diff --git a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteActionProvider.java b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteActionProvider.java
index 6dd4e27..aa86677 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteActionProvider.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteActionProvider.java
@@ -129,7 +129,7 @@
* @see #setRouteSelector
*/
public class MediaRouteActionProvider extends ActionProvider {
- private static final String TAG = "MediaRouteActionProvider";
+ private static final String TAG = "MRActionProvider";
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
diff --git a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
index 2ff24e0..fbdc209 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
@@ -437,15 +437,11 @@
@Override
public void jumpDrawablesToCurrentState() {
- // We can't call super to handle the background so we do it ourselves.
- //super.jumpDrawablesToCurrentState();
- if (getBackground() != null) {
- DrawableCompat.jumpToCurrentState(getBackground());
- }
+ super.jumpDrawablesToCurrentState();
// Handle our own remote indicator.
if (mRemoteIndicator != null) {
- DrawableCompat.jumpToCurrentState(mRemoteIndicator);
+ mRemoteIndicator.jumpToCurrentState();
}
}
diff --git a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteExpandCollapseButton.java b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteExpandCollapseButton.java
index 5c5cf65..b3b1e34 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteExpandCollapseButton.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteExpandCollapseButton.java
@@ -23,15 +23,15 @@
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.ImageButton;
+import androidx.appcompat.widget.AppCompatImageButton;
import androidx.core.content.ContextCompat;
import androidx.mediarouter.R;
/**
* Chevron/Caret button to expand/collapse group volume list with animation.
*/
-class MediaRouteExpandCollapseButton extends ImageButton {
+class MediaRouteExpandCollapseButton extends AppCompatImageButton {
final AnimationDrawable mExpandAnimationDrawable;
final AnimationDrawable mCollapseAnimationDrawable;
final String mExpandGroupDescription;
diff --git a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteVolumeSlider.java b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteVolumeSlider.java
index 1915ea4..2dff118 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteVolumeSlider.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteVolumeSlider.java
@@ -62,13 +62,16 @@
mThumb.setColorFilter(mProgressAndThumbColor, PorterDuff.Mode.SRC_IN);
mThumb.setAlpha(alpha);
- LayerDrawable ld = (LayerDrawable) getProgressDrawable();
- Drawable progressDrawable = ld.findDrawableByLayerId(android.R.id.progress);
- Drawable backgroundDrawable = ld.findDrawableByLayerId(android.R.id.background);
+ Drawable progressDrawable = getProgressDrawable();
+ if (progressDrawable instanceof LayerDrawable) {
+ LayerDrawable ld = (LayerDrawable) getProgressDrawable();
+ progressDrawable = ld.findDrawableByLayerId(android.R.id.progress);
+ Drawable backgroundDrawable = ld.findDrawableByLayerId(android.R.id.background);
+ backgroundDrawable.setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_IN);
+ }
progressDrawable.setColorFilter(mProgressAndThumbColor, PorterDuff.Mode.SRC_IN);
- backgroundDrawable.setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_IN);
- ld.setAlpha(alpha);
+ progressDrawable.setAlpha(alpha);
}
@Override
diff --git a/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java b/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
index 7abbf27..dd0bb00 100644
--- a/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
+++ b/mediarouter/src/main/java/androidx/mediarouter/media/RegisteredMediaRouteProvider.java
@@ -276,7 +276,11 @@
mBound = false;
disconnect();
- getContext().unbindService(this);
+ try {
+ getContext().unbindService(this);
+ } catch (IllegalArgumentException ex) {
+ Log.e(TAG, this + ": unbindService failed", ex);
+ }
}
}
diff --git a/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt b/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
index 4b1e8bd..268be2c 100644
--- a/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
+++ b/navigation/benchmark/src/androidTest/java/androidx/navigation/NavInflaterBenchmark.kt
@@ -38,16 +38,14 @@
@Test
fun inflateSimple() {
- val state = benchmarkRule.state
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
navInflater.inflate(androidx.navigation.benchmark.test.R.navigation.nav_simple)
}
}
@Test
fun inflateDeepLink() {
- val state = benchmarkRule.state
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
navInflater.inflate(androidx.navigation.benchmark.test.R.navigation.nav_deep_link)
}
}
diff --git a/navigation/common/api/2.0.0.txt b/navigation/common/api/2.0.0.txt
new file mode 100644
index 0000000..6dd3420
--- /dev/null
+++ b/navigation/common/api/2.0.0.txt
@@ -0,0 +1,192 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+ ctor public ActionOnlyNavDirections(int);
+ method public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public final class NavAction {
+ ctor public NavAction(@IdRes int);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?, android.os.Bundle?);
+ method public android.os.Bundle? getDefaultArguments();
+ method public int getDestinationId();
+ method public androidx.navigation.NavOptions? getNavOptions();
+ method public void setDefaultArguments(android.os.Bundle?);
+ method public void setNavOptions(androidx.navigation.NavOptions?);
+ }
+
+ public interface NavArgs {
+ }
+
+ public final class NavArgument {
+ method public Object? getDefaultValue();
+ method public androidx.navigation.NavType<?> getType();
+ method public boolean isDefaultValuePresent();
+ method public boolean isNullable();
+ }
+
+ public static final class NavArgument.Builder {
+ ctor public NavArgument.Builder();
+ method public androidx.navigation.NavArgument build();
+ method public androidx.navigation.NavArgument.Builder setDefaultValue(Object?);
+ method public androidx.navigation.NavArgument.Builder setIsNullable(boolean);
+ method public androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<?>);
+ }
+
+ public class NavDestination {
+ ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ ctor public NavDestination(String);
+ method public final void addArgument(String, androidx.navigation.NavArgument);
+ method public final void addDeepLink(String);
+ method public final androidx.navigation.NavAction? getAction(@IdRes int);
+ method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+ method @IdRes public final int getId();
+ method public final CharSequence? getLabel();
+ method public final String getNavigatorName();
+ method public final androidx.navigation.NavGraph? getParent();
+ method @CallSuper public void onInflate(android.content.Context, android.util.AttributeSet);
+ method protected static <C> Class<? extends C> parseClassFromName(android.content.Context, String, Class<? extends C>);
+ method public final void putAction(@IdRes int, @IdRes int);
+ method public final void putAction(@IdRes int, androidx.navigation.NavAction);
+ method public final void removeAction(@IdRes int);
+ method public final void removeArgument(String);
+ method public final void setId(@IdRes int);
+ method public final void setLabel(CharSequence?);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface NavDestination.ClassType {
+ method public abstract Class value();
+ }
+
+ public interface NavDirections {
+ method @IdRes public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> {
+ ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph>);
+ method public final void addAll(androidx.navigation.NavGraph);
+ method public final void addDestination(androidx.navigation.NavDestination);
+ method public final void addDestinations(java.util.Collection<androidx.navigation.NavDestination>);
+ method public final void addDestinations(androidx.navigation.NavDestination...);
+ method public final void clear();
+ method public final androidx.navigation.NavDestination? findNode(@IdRes int);
+ method @IdRes public final int getStartDestination();
+ method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+ method public final void remove(androidx.navigation.NavDestination);
+ method public final void setStartDestination(@IdRes int);
+ }
+
+ @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+ ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ public final class NavOptions {
+ method @AnimRes @AnimatorRes public int getEnterAnim();
+ method @AnimRes @AnimatorRes public int getExitAnim();
+ method @AnimRes @AnimatorRes public int getPopEnterAnim();
+ method @AnimRes @AnimatorRes public int getPopExitAnim();
+ method @IdRes public int getPopUpTo();
+ method public boolean isPopUpToInclusive();
+ method public boolean shouldLaunchSingleTop();
+ }
+
+ public static final class NavOptions.Builder {
+ ctor public NavOptions.Builder();
+ method public androidx.navigation.NavOptions build();
+ method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean);
+ method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int, boolean);
+ }
+
+ public abstract class NavType<T> {
+ method public static androidx.navigation.NavType<?> fromArgType(String?, String?);
+ method public abstract T? get(android.os.Bundle, String);
+ method public abstract String getName();
+ method public boolean isNullableAllowed();
+ method public abstract T parseValue(String);
+ method public abstract void put(android.os.Bundle, String, T?);
+ field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+ field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+ field public static final androidx.navigation.NavType<int[]> IntArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+ field public static final androidx.navigation.NavType<long[]> LongArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+ field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+ field public static final androidx.navigation.NavType<java.lang.String> StringType;
+ }
+
+ public static final class NavType.EnumType<D extends java.lang.Enum> extends androidx.navigation.NavType.SerializableType<D> {
+ ctor public NavType.EnumType(Class<D>);
+ }
+
+ public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.ParcelableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+ ctor public NavType.ParcelableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.SerializableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+ ctor public NavType.SerializableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ ctor public Navigator();
+ method public abstract D createDestination();
+ method public abstract androidx.navigation.NavDestination? navigate(D, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void onRestoreState(android.os.Bundle);
+ method public android.os.Bundle? onSaveState();
+ method public abstract boolean popBackStack();
+ }
+
+ public static interface Navigator.Extras {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface Navigator.Name {
+ method public abstract String value();
+ }
+
+ public class NavigatorProvider {
+ ctor public NavigatorProvider();
+ method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T>);
+ method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String);
+ }
+
+}
+
diff --git a/navigation/common/api/2.1.0-alpha01.txt b/navigation/common/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..6dd3420
--- /dev/null
+++ b/navigation/common/api/2.1.0-alpha01.txt
@@ -0,0 +1,192 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+ ctor public ActionOnlyNavDirections(int);
+ method public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public final class NavAction {
+ ctor public NavAction(@IdRes int);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?, android.os.Bundle?);
+ method public android.os.Bundle? getDefaultArguments();
+ method public int getDestinationId();
+ method public androidx.navigation.NavOptions? getNavOptions();
+ method public void setDefaultArguments(android.os.Bundle?);
+ method public void setNavOptions(androidx.navigation.NavOptions?);
+ }
+
+ public interface NavArgs {
+ }
+
+ public final class NavArgument {
+ method public Object? getDefaultValue();
+ method public androidx.navigation.NavType<?> getType();
+ method public boolean isDefaultValuePresent();
+ method public boolean isNullable();
+ }
+
+ public static final class NavArgument.Builder {
+ ctor public NavArgument.Builder();
+ method public androidx.navigation.NavArgument build();
+ method public androidx.navigation.NavArgument.Builder setDefaultValue(Object?);
+ method public androidx.navigation.NavArgument.Builder setIsNullable(boolean);
+ method public androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<?>);
+ }
+
+ public class NavDestination {
+ ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ ctor public NavDestination(String);
+ method public final void addArgument(String, androidx.navigation.NavArgument);
+ method public final void addDeepLink(String);
+ method public final androidx.navigation.NavAction? getAction(@IdRes int);
+ method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+ method @IdRes public final int getId();
+ method public final CharSequence? getLabel();
+ method public final String getNavigatorName();
+ method public final androidx.navigation.NavGraph? getParent();
+ method @CallSuper public void onInflate(android.content.Context, android.util.AttributeSet);
+ method protected static <C> Class<? extends C> parseClassFromName(android.content.Context, String, Class<? extends C>);
+ method public final void putAction(@IdRes int, @IdRes int);
+ method public final void putAction(@IdRes int, androidx.navigation.NavAction);
+ method public final void removeAction(@IdRes int);
+ method public final void removeArgument(String);
+ method public final void setId(@IdRes int);
+ method public final void setLabel(CharSequence?);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface NavDestination.ClassType {
+ method public abstract Class value();
+ }
+
+ public interface NavDirections {
+ method @IdRes public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> {
+ ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph>);
+ method public final void addAll(androidx.navigation.NavGraph);
+ method public final void addDestination(androidx.navigation.NavDestination);
+ method public final void addDestinations(java.util.Collection<androidx.navigation.NavDestination>);
+ method public final void addDestinations(androidx.navigation.NavDestination...);
+ method public final void clear();
+ method public final androidx.navigation.NavDestination? findNode(@IdRes int);
+ method @IdRes public final int getStartDestination();
+ method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+ method public final void remove(androidx.navigation.NavDestination);
+ method public final void setStartDestination(@IdRes int);
+ }
+
+ @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+ ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ public final class NavOptions {
+ method @AnimRes @AnimatorRes public int getEnterAnim();
+ method @AnimRes @AnimatorRes public int getExitAnim();
+ method @AnimRes @AnimatorRes public int getPopEnterAnim();
+ method @AnimRes @AnimatorRes public int getPopExitAnim();
+ method @IdRes public int getPopUpTo();
+ method public boolean isPopUpToInclusive();
+ method public boolean shouldLaunchSingleTop();
+ }
+
+ public static final class NavOptions.Builder {
+ ctor public NavOptions.Builder();
+ method public androidx.navigation.NavOptions build();
+ method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean);
+ method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int, boolean);
+ }
+
+ public abstract class NavType<T> {
+ method public static androidx.navigation.NavType<?> fromArgType(String?, String?);
+ method public abstract T? get(android.os.Bundle, String);
+ method public abstract String getName();
+ method public boolean isNullableAllowed();
+ method public abstract T parseValue(String);
+ method public abstract void put(android.os.Bundle, String, T?);
+ field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+ field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+ field public static final androidx.navigation.NavType<int[]> IntArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+ field public static final androidx.navigation.NavType<long[]> LongArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+ field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+ field public static final androidx.navigation.NavType<java.lang.String> StringType;
+ }
+
+ public static final class NavType.EnumType<D extends java.lang.Enum> extends androidx.navigation.NavType.SerializableType<D> {
+ ctor public NavType.EnumType(Class<D>);
+ }
+
+ public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.ParcelableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+ ctor public NavType.ParcelableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.SerializableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+ ctor public NavType.SerializableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ ctor public Navigator();
+ method public abstract D createDestination();
+ method public abstract androidx.navigation.NavDestination? navigate(D, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void onRestoreState(android.os.Bundle);
+ method public android.os.Bundle? onSaveState();
+ method public abstract boolean popBackStack();
+ }
+
+ public static interface Navigator.Extras {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface Navigator.Name {
+ method public abstract String value();
+ }
+
+ public class NavigatorProvider {
+ ctor public NavigatorProvider();
+ method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T>);
+ method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String);
+ }
+
+}
+
diff --git a/navigation/common/api/2.1.0-alpha02.txt b/navigation/common/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..6dd3420
--- /dev/null
+++ b/navigation/common/api/2.1.0-alpha02.txt
@@ -0,0 +1,192 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActionOnlyNavDirections implements androidx.navigation.NavDirections {
+ ctor public ActionOnlyNavDirections(int);
+ method public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public final class NavAction {
+ ctor public NavAction(@IdRes int);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?);
+ ctor public NavAction(@IdRes int, androidx.navigation.NavOptions?, android.os.Bundle?);
+ method public android.os.Bundle? getDefaultArguments();
+ method public int getDestinationId();
+ method public androidx.navigation.NavOptions? getNavOptions();
+ method public void setDefaultArguments(android.os.Bundle?);
+ method public void setNavOptions(androidx.navigation.NavOptions?);
+ }
+
+ public interface NavArgs {
+ }
+
+ public final class NavArgument {
+ method public Object? getDefaultValue();
+ method public androidx.navigation.NavType<?> getType();
+ method public boolean isDefaultValuePresent();
+ method public boolean isNullable();
+ }
+
+ public static final class NavArgument.Builder {
+ ctor public NavArgument.Builder();
+ method public androidx.navigation.NavArgument build();
+ method public androidx.navigation.NavArgument.Builder setDefaultValue(Object?);
+ method public androidx.navigation.NavArgument.Builder setIsNullable(boolean);
+ method public androidx.navigation.NavArgument.Builder setType(androidx.navigation.NavType<?>);
+ }
+
+ public class NavDestination {
+ ctor public NavDestination(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ ctor public NavDestination(String);
+ method public final void addArgument(String, androidx.navigation.NavArgument);
+ method public final void addDeepLink(String);
+ method public final androidx.navigation.NavAction? getAction(@IdRes int);
+ method public final java.util.Map<java.lang.String,androidx.navigation.NavArgument> getArguments();
+ method @IdRes public final int getId();
+ method public final CharSequence? getLabel();
+ method public final String getNavigatorName();
+ method public final androidx.navigation.NavGraph? getParent();
+ method @CallSuper public void onInflate(android.content.Context, android.util.AttributeSet);
+ method protected static <C> Class<? extends C> parseClassFromName(android.content.Context, String, Class<? extends C>);
+ method public final void putAction(@IdRes int, @IdRes int);
+ method public final void putAction(@IdRes int, androidx.navigation.NavAction);
+ method public final void removeAction(@IdRes int);
+ method public final void removeArgument(String);
+ method public final void setId(@IdRes int);
+ method public final void setLabel(CharSequence?);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface NavDestination.ClassType {
+ method public abstract Class value();
+ }
+
+ public interface NavDirections {
+ method @IdRes public int getActionId();
+ method public android.os.Bundle getArguments();
+ }
+
+ public class NavGraph extends androidx.navigation.NavDestination implements java.lang.Iterable<androidx.navigation.NavDestination> {
+ ctor public NavGraph(androidx.navigation.Navigator<? extends androidx.navigation.NavGraph>);
+ method public final void addAll(androidx.navigation.NavGraph);
+ method public final void addDestination(androidx.navigation.NavDestination);
+ method public final void addDestinations(java.util.Collection<androidx.navigation.NavDestination>);
+ method public final void addDestinations(androidx.navigation.NavDestination...);
+ method public final void clear();
+ method public final androidx.navigation.NavDestination? findNode(@IdRes int);
+ method @IdRes public final int getStartDestination();
+ method public final java.util.Iterator<androidx.navigation.NavDestination> iterator();
+ method public final void remove(androidx.navigation.NavDestination);
+ method public final void setStartDestination(@IdRes int);
+ }
+
+ @androidx.navigation.Navigator.Name("navigation") public class NavGraphNavigator extends androidx.navigation.Navigator<androidx.navigation.NavGraph> {
+ ctor public NavGraphNavigator(androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.NavGraph, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ public final class NavOptions {
+ method @AnimRes @AnimatorRes public int getEnterAnim();
+ method @AnimRes @AnimatorRes public int getExitAnim();
+ method @AnimRes @AnimatorRes public int getPopEnterAnim();
+ method @AnimRes @AnimatorRes public int getPopExitAnim();
+ method @IdRes public int getPopUpTo();
+ method public boolean isPopUpToInclusive();
+ method public boolean shouldLaunchSingleTop();
+ }
+
+ public static final class NavOptions.Builder {
+ ctor public NavOptions.Builder();
+ method public androidx.navigation.NavOptions build();
+ method public androidx.navigation.NavOptions.Builder setEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setLaunchSingleTop(boolean);
+ method public androidx.navigation.NavOptions.Builder setPopEnterAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopExitAnim(@AnimRes @AnimatorRes int);
+ method public androidx.navigation.NavOptions.Builder setPopUpTo(@IdRes int, boolean);
+ }
+
+ public abstract class NavType<T> {
+ method public static androidx.navigation.NavType<?> fromArgType(String?, String?);
+ method public abstract T? get(android.os.Bundle, String);
+ method public abstract String getName();
+ method public boolean isNullableAllowed();
+ method public abstract T parseValue(String);
+ method public abstract void put(android.os.Bundle, String, T?);
+ field public static final androidx.navigation.NavType<boolean[]> BoolArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Boolean> BoolType;
+ field public static final androidx.navigation.NavType<float[]> FloatArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Float> FloatType;
+ field public static final androidx.navigation.NavType<int[]> IntArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> IntType;
+ field public static final androidx.navigation.NavType<long[]> LongArrayType;
+ field public static final androidx.navigation.NavType<java.lang.Long> LongType;
+ field public static final androidx.navigation.NavType<java.lang.Integer> ReferenceType;
+ field public static final androidx.navigation.NavType<java.lang.String[]> StringArrayType;
+ field public static final androidx.navigation.NavType<java.lang.String> StringType;
+ }
+
+ public static final class NavType.EnumType<D extends java.lang.Enum> extends androidx.navigation.NavType.SerializableType<D> {
+ ctor public NavType.EnumType(Class<D>);
+ }
+
+ public static final class NavType.ParcelableArrayType<D extends android.os.Parcelable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.ParcelableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static final class NavType.ParcelableType<D> extends androidx.navigation.NavType<D> {
+ ctor public NavType.ParcelableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public static final class NavType.SerializableArrayType<D extends java.io.Serializable> extends androidx.navigation.NavType<D[]> {
+ ctor public NavType.SerializableArrayType(Class<D>);
+ method public D[]? get(android.os.Bundle, String);
+ method public String getName();
+ method public D[] parseValue(String);
+ method public void put(android.os.Bundle, String, D[]?);
+ }
+
+ public static class NavType.SerializableType<D extends java.io.Serializable> extends androidx.navigation.NavType<D> {
+ ctor public NavType.SerializableType(Class<D>);
+ method public D? get(android.os.Bundle, String);
+ method public String getName();
+ method public D parseValue(String);
+ method public void put(android.os.Bundle, String, D?);
+ }
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ ctor public Navigator();
+ method public abstract D createDestination();
+ method public abstract androidx.navigation.NavDestination? navigate(D, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void onRestoreState(android.os.Bundle);
+ method public android.os.Bundle? onSaveState();
+ method public abstract boolean popBackStack();
+ }
+
+ public static interface Navigator.Extras {
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE}) public static @interface Navigator.Name {
+ method public abstract String value();
+ }
+
+ public class NavigatorProvider {
+ ctor public NavigatorProvider();
+ method public final androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method @CallSuper public androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? addNavigator(String, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>);
+ method public final <T extends androidx.navigation.Navigator<?>> T getNavigator(Class<T>);
+ method @CallSuper public <T extends androidx.navigation.Navigator<?>> T getNavigator(String);
+ }
+
+}
+
diff --git a/navigation/common/api/res-2.0.0.txt b/navigation/common/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/api/res-2.0.0.txt
diff --git a/navigation/common/api/res-2.1.0-alpha01.txt b/navigation/common/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/api/res-2.1.0-alpha01.txt
diff --git a/navigation/common/api/res-2.1.0-alpha02.txt b/navigation/common/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/api/res-2.1.0-alpha02.txt
diff --git a/navigation/common/api/restricted_2.0.0.txt b/navigation/common/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..37d70c2
--- /dev/null
+++ b/navigation/common/api/restricted_2.0.0.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
+ method public void onPopBackStack(androidx.navigation.Navigator);
+ }
+
+}
+
diff --git a/navigation/common/api/restricted_2.1.0-alpha01.txt b/navigation/common/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..37d70c2
--- /dev/null
+++ b/navigation/common/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
+ method public void onPopBackStack(androidx.navigation.Navigator);
+ }
+
+}
+
diff --git a/navigation/common/api/restricted_2.1.0-alpha02.txt b/navigation/common/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..37d70c2
--- /dev/null
+++ b/navigation/common/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1,17 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public abstract class Navigator<D extends androidx.navigation.NavDestination> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void addOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void dispatchOnNavigatorBackPress();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressAdded();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected void onBackPressRemoved();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void removeOnNavigatorBackPressListener(androidx.navigation.Navigator.OnNavigatorBackPressListener);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static interface Navigator.OnNavigatorBackPressListener {
+ method public void onPopBackStack(androidx.navigation.Navigator);
+ }
+
+}
+
diff --git a/navigation/common/build.gradle b/navigation/common/build.gradle
index 9aa6e63..9663e67 100644
--- a/navigation/common/build.gradle
+++ b/navigation/common/build.gradle
@@ -31,8 +31,8 @@
}
dependencies {
- api(NAV_SUPPORT_COMPAT)
- implementation(NAV_SUPPORT_COLLECTIONS)
+ api("androidx.core:core:1.0.1")
+ implementation(ANDROIDX_COLLECTION)
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/navigation/common/ktx/api/2.0.0.txt b/navigation/common/ktx/api/2.0.0.txt
new file mode 100644
index 0000000..6da57db
--- /dev/null
+++ b/navigation/common/ktx/api/2.0.0.txt
@@ -0,0 +1,129 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+ ctor public AnimBuilder();
+ method public int getEnter();
+ method public int getExit();
+ method public int getPopEnter();
+ method public int getPopExit();
+ method public void setEnter(int p);
+ method public void setExit(int p);
+ method public void setPopEnter(int p);
+ method public void setPopExit(int p);
+ property public final int enter;
+ property public final int exit;
+ property public final int popEnter;
+ property public final int popExit;
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+ ctor public NavActionBuilder();
+ method public int getDestinationId();
+ method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ method public void setDestinationId(int p);
+ property public final int destinationId;
+ }
+
+ public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+ ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+ method public Args getValue();
+ method public boolean isInitialized();
+ property public Args value;
+ }
+
+ public final class NavArgsLazyKt {
+ ctor public NavArgsLazyKt();
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+ ctor public NavArgumentBuilder();
+ method public androidx.navigation.NavArgument build();
+ method public Object? getDefaultValue();
+ method public boolean getNullable();
+ method public androidx.navigation.NavType<?> getType();
+ method public void setDefaultValue(Object? value);
+ method public void setNullable(boolean value);
+ method public void setType(androidx.navigation.NavType<?> value);
+ property public final Object? defaultValue;
+ property public final boolean nullable;
+ property public final androidx.navigation.NavType<?> type;
+ }
+
+ @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+ method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+ method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+ method public D build();
+ method public final void deepLink(String uriPattern);
+ method public final int getId();
+ method public final CharSequence? getLabel();
+ method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+ method public final void setLabel(CharSequence? p);
+ property public final CharSequence? label;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavDestinationDsl {
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ method public void addDestination(androidx.navigation.NavDestination destination);
+ method public androidx.navigation.NavGraph build();
+ method public <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+ method public androidx.navigation.NavigatorProvider getProvider();
+ method public operator void unaryPlus(androidx.navigation.NavDestination);
+ }
+
+ public final class NavGraphBuilderKt {
+ ctor public NavGraphBuilderKt();
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavGraphKt {
+ ctor public NavGraphKt();
+ method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+ ctor public NavOptionsBuilder();
+ method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+ method public boolean getLaunchSingleTop();
+ method public int getPopUpTo();
+ method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void setLaunchSingleTop(boolean p);
+ method public void setPopUpTo(int value);
+ property public final boolean launchSingleTop;
+ property public final int popUpTo;
+ }
+
+ public final class NavOptionsBuilderKt {
+ ctor public NavOptionsBuilderKt();
+ method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavOptionsDsl {
+ }
+
+ public final class NavigatorProviderKt {
+ ctor public NavigatorProviderKt();
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+ method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+ ctor public PopUpToBuilder();
+ method public boolean getInclusive();
+ method public void setInclusive(boolean p);
+ property public final boolean inclusive;
+ }
+
+}
+
diff --git a/navigation/common/ktx/api/2.1.0-alpha01.txt b/navigation/common/ktx/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..6da57db
--- /dev/null
+++ b/navigation/common/ktx/api/2.1.0-alpha01.txt
@@ -0,0 +1,129 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+ ctor public AnimBuilder();
+ method public int getEnter();
+ method public int getExit();
+ method public int getPopEnter();
+ method public int getPopExit();
+ method public void setEnter(int p);
+ method public void setExit(int p);
+ method public void setPopEnter(int p);
+ method public void setPopExit(int p);
+ property public final int enter;
+ property public final int exit;
+ property public final int popEnter;
+ property public final int popExit;
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+ ctor public NavActionBuilder();
+ method public int getDestinationId();
+ method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ method public void setDestinationId(int p);
+ property public final int destinationId;
+ }
+
+ public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+ ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+ method public Args getValue();
+ method public boolean isInitialized();
+ property public Args value;
+ }
+
+ public final class NavArgsLazyKt {
+ ctor public NavArgsLazyKt();
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+ ctor public NavArgumentBuilder();
+ method public androidx.navigation.NavArgument build();
+ method public Object? getDefaultValue();
+ method public boolean getNullable();
+ method public androidx.navigation.NavType<?> getType();
+ method public void setDefaultValue(Object? value);
+ method public void setNullable(boolean value);
+ method public void setType(androidx.navigation.NavType<?> value);
+ property public final Object? defaultValue;
+ property public final boolean nullable;
+ property public final androidx.navigation.NavType<?> type;
+ }
+
+ @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+ method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+ method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+ method public D build();
+ method public final void deepLink(String uriPattern);
+ method public final int getId();
+ method public final CharSequence? getLabel();
+ method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+ method public final void setLabel(CharSequence? p);
+ property public final CharSequence? label;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavDestinationDsl {
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ method public void addDestination(androidx.navigation.NavDestination destination);
+ method public androidx.navigation.NavGraph build();
+ method public <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+ method public androidx.navigation.NavigatorProvider getProvider();
+ method public operator void unaryPlus(androidx.navigation.NavDestination);
+ }
+
+ public final class NavGraphBuilderKt {
+ ctor public NavGraphBuilderKt();
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavGraphKt {
+ ctor public NavGraphKt();
+ method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+ ctor public NavOptionsBuilder();
+ method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+ method public boolean getLaunchSingleTop();
+ method public int getPopUpTo();
+ method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void setLaunchSingleTop(boolean p);
+ method public void setPopUpTo(int value);
+ property public final boolean launchSingleTop;
+ property public final int popUpTo;
+ }
+
+ public final class NavOptionsBuilderKt {
+ ctor public NavOptionsBuilderKt();
+ method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavOptionsDsl {
+ }
+
+ public final class NavigatorProviderKt {
+ ctor public NavigatorProviderKt();
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+ method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+ ctor public PopUpToBuilder();
+ method public boolean getInclusive();
+ method public void setInclusive(boolean p);
+ property public final boolean inclusive;
+ }
+
+}
+
diff --git a/navigation/common/ktx/api/2.1.0-alpha02.txt b/navigation/common/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..6da57db
--- /dev/null
+++ b/navigation/common/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,129 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.NavOptionsDsl public final class AnimBuilder {
+ ctor public AnimBuilder();
+ method public int getEnter();
+ method public int getExit();
+ method public int getPopEnter();
+ method public int getPopExit();
+ method public void setEnter(int p);
+ method public void setExit(int p);
+ method public void setPopEnter(int p);
+ method public void setPopExit(int p);
+ property public final int enter;
+ property public final int exit;
+ property public final int popEnter;
+ property public final int popExit;
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavActionBuilder {
+ ctor public NavActionBuilder();
+ method public int getDestinationId();
+ method public void navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ method public void setDestinationId(int p);
+ property public final int destinationId;
+ }
+
+ public final class NavArgsLazy<Args extends androidx.navigation.NavArgs> implements kotlin.Lazy<Args> {
+ ctor public NavArgsLazy(kotlin.reflect.KClass<Args> navArgsClass, kotlin.jvm.functions.Function0<android.os.Bundle> argumentProducer);
+ method public Args getValue();
+ method public boolean isInitialized();
+ property public Args value;
+ }
+
+ public final class NavArgsLazyKt {
+ ctor public NavArgsLazyKt();
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavArgumentBuilder {
+ ctor public NavArgumentBuilder();
+ method public androidx.navigation.NavArgument build();
+ method public Object? getDefaultValue();
+ method public boolean getNullable();
+ method public androidx.navigation.NavType<?> getType();
+ method public void setDefaultValue(Object? value);
+ method public void setNullable(boolean value);
+ method public void setType(androidx.navigation.NavType<?> value);
+ property public final Object? defaultValue;
+ property public final boolean nullable;
+ property public final androidx.navigation.NavType<?> type;
+ }
+
+ @androidx.navigation.NavDestinationDsl public class NavDestinationBuilder<D extends androidx.navigation.NavDestination> {
+ ctor public NavDestinationBuilder(androidx.navigation.Navigator<? extends D> navigator, @IdRes int id);
+ method public final void action(int actionId, kotlin.jvm.functions.Function1<? super androidx.navigation.NavActionBuilder,kotlin.Unit> actionBuilder);
+ method public final void argument(String name, kotlin.jvm.functions.Function1<? super androidx.navigation.NavArgumentBuilder,kotlin.Unit> argumentBuilder);
+ method public D build();
+ method public final void deepLink(String uriPattern);
+ method public final int getId();
+ method public final CharSequence? getLabel();
+ method protected final androidx.navigation.Navigator<? extends D> getNavigator();
+ method public final void setLabel(CharSequence? p);
+ property public final CharSequence? label;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavDestinationDsl {
+ }
+
+ @androidx.navigation.NavDestinationDsl public final class NavGraphBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.NavGraph> {
+ ctor public NavGraphBuilder(androidx.navigation.NavigatorProvider provider, @IdRes int id, @IdRes int startDestination);
+ method public void addDestination(androidx.navigation.NavDestination destination);
+ method public androidx.navigation.NavGraph build();
+ method public <D extends androidx.navigation.NavDestination> void destination(androidx.navigation.NavDestinationBuilder<? extends D> navDestination);
+ method public androidx.navigation.NavigatorProvider getProvider();
+ method public operator void unaryPlus(androidx.navigation.NavDestination);
+ }
+
+ public final class NavGraphBuilderKt {
+ ctor public NavGraphBuilderKt();
+ method public static inline androidx.navigation.NavGraph navigation(androidx.navigation.NavigatorProvider, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method public static inline void navigation(androidx.navigation.NavGraphBuilder, @IdRes int id, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavGraphKt {
+ ctor public NavGraphKt();
+ method public static operator boolean contains(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator androidx.navigation.NavDestination get(androidx.navigation.NavGraph, @IdRes int id);
+ method public static inline operator void minusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavDestination node);
+ method public static inline operator void plusAssign(androidx.navigation.NavGraph, androidx.navigation.NavGraph other);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class NavOptionsBuilder {
+ ctor public NavOptionsBuilder();
+ method public void anim(kotlin.jvm.functions.Function1<? super androidx.navigation.AnimBuilder,kotlin.Unit> animBuilder);
+ method public boolean getLaunchSingleTop();
+ method public int getPopUpTo();
+ method public void popUpTo(@IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.PopUpToBuilder,kotlin.Unit> popUpToBuilder);
+ method public void setLaunchSingleTop(boolean p);
+ method public void setPopUpTo(int value);
+ property public final boolean launchSingleTop;
+ property public final int popUpTo;
+ }
+
+ public final class NavOptionsBuilderKt {
+ ctor public NavOptionsBuilderKt();
+ method public static androidx.navigation.NavOptions navOptions(kotlin.jvm.functions.Function1<? super androidx.navigation.NavOptionsBuilder,kotlin.Unit> optionsBuilder);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface NavOptionsDsl {
+ }
+
+ public final class NavigatorProviderKt {
+ ctor public NavigatorProviderKt();
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, String name);
+ method public static inline operator <T extends androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>> T get(androidx.navigation.NavigatorProvider, kotlin.reflect.KClass<T> clazz);
+ method public static inline operator void plusAssign(androidx.navigation.NavigatorProvider, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ method public static inline operator androidx.navigation.Navigator<? extends androidx.navigation.NavDestination>? set(androidx.navigation.NavigatorProvider, String name, androidx.navigation.Navigator<? extends androidx.navigation.NavDestination> navigator);
+ }
+
+ @androidx.navigation.NavOptionsDsl public final class PopUpToBuilder {
+ ctor public PopUpToBuilder();
+ method public boolean getInclusive();
+ method public void setInclusive(boolean p);
+ property public final boolean inclusive;
+ }
+
+}
+
diff --git a/navigation/common/ktx/api/res-2.0.0.txt b/navigation/common/ktx/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/ktx/api/res-2.0.0.txt
diff --git a/navigation/common/ktx/api/res-2.1.0-alpha01.txt b/navigation/common/ktx/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/ktx/api/res-2.1.0-alpha01.txt
diff --git a/navigation/common/ktx/api/res-2.1.0-alpha02.txt b/navigation/common/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/common/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/common/ktx/api/restricted_2.0.0.txt b/navigation/common/ktx/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/common/ktx/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/common/ktx/api/restricted_2.1.0-alpha01.txt b/navigation/common/ktx/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/common/ktx/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/common/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/common/ktx/build.gradle b/navigation/common/ktx/build.gradle
index ca21a30..2b496a5 100644
--- a/navigation/common/ktx/build.gradle
+++ b/navigation/common/ktx/build.gradle
@@ -38,7 +38,7 @@
dependencies {
api(project(":navigation:navigation-common"))
- implementation(NAV_SUPPORT_COLLECTIONS)
+ implementation(ANDROIDX_COLLECTION)
api(KOTLIN_STDLIB)
testImplementation(JUNIT)
diff --git a/navigation/common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt b/navigation/common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt
new file mode 100644
index 0000000..fff7f49
--- /dev/null
+++ b/navigation/common/src/androidTest/java/androidx/navigation/AddInDefaultArgsTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation
+
+import android.os.Bundle
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+private val stringArgument = "stringArg" to NavArgument.Builder()
+ .setType(NavType.StringType)
+ .setIsNullable(true)
+ .build()
+private val stringArgumentWithDefault = "stringArg" to NavArgument.Builder()
+ .setType(NavType.StringType)
+ .setDefaultValue("aaa")
+ .build()
+private val intArgument = "intArg" to NavArgument.Builder()
+ .setType(NavType.IntType)
+ .setDefaultValue(123)
+ .build()
+
+@SmallTest
+@RunWith(Parameterized::class)
+class AddInDefaultArgsTest(
+ private val arguments: Map<String, NavArgument>,
+ private val args: Bundle
+) {
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "arguments={0}, bundle={1}")
+ fun data() = mutableListOf<Array<Any?>>().apply {
+ arrayOf(
+ // Test with an empty set of arguments
+ mapOf(),
+ // Test with an argument with no default value
+ mapOf(stringArgument),
+ // Test with arguments where only some have default values
+ mapOf(stringArgument, intArgument),
+ // Test with arguments that have default values
+ mapOf(stringArgumentWithDefault, intArgument)
+ ).forEach { arguments: Map<String, NavArgument> ->
+ // Run with a null Bundle
+ add(arrayOf(arguments, Bundle.EMPTY))
+ // Run with a Bundle with a different argument
+ add(arrayOf(arguments, Bundle().apply { putString("customArg", "custom") }))
+ // Run with a Bundle with an overriding argument
+ add(arrayOf(arguments, Bundle().apply { putString("stringArg", "bbb") }))
+ }
+ }
+ }
+
+ @Test
+ fun addInDefaultArgs() {
+ val destination = NoOpNavigator().createDestination()
+ arguments.forEach { entry ->
+ destination.addArgument(entry.key, entry.value)
+ }
+
+ val nullableArgs = if (args != Bundle.EMPTY) { args } else { null }
+ val bundle = destination.addInDefaultArgs(nullableArgs)
+
+ if (args == Bundle.EMPTY && arguments.isEmpty()) {
+ assertWithMessage("Null args + null destination arguments should give a null Bundle")
+ .that(bundle)
+ .isNull()
+ } else {
+ assertThat(bundle)
+ .isNotNull()
+ // Assert that the args take precedence
+ args.keySet()?.forEach { key ->
+ assertThat(bundle!![key])
+ .isEqualTo(args[key])
+ }
+ // Assert that arguments with default values not in the args
+ // are present in the Bundle
+ arguments
+ .filterKeys { !args.containsKey(it) }
+ .filterValues { it.isDefaultValuePresent }
+ .forEach { entry ->
+ assertThat(bundle!![entry.key])
+ .isEqualTo(entry.value.defaultValue)
+ }
+ }
+ }
+}
diff --git a/navigation/common/src/main/java/androidx/navigation/NavBackStackEntry.java b/navigation/common/src/main/java/androidx/navigation/NavBackStackEntry.java
index fb5b671..5ce94a2 100644
--- a/navigation/common/src/main/java/androidx/navigation/NavBackStackEntry.java
+++ b/navigation/common/src/main/java/androidx/navigation/NavBackStackEntry.java
@@ -21,6 +21,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.util.UUID;
+
/**
* Representation of an entry in the back stack of a {@link NavController}.
*/
@@ -28,6 +30,10 @@
private final NavDestination mDestination;
private final Bundle mArgs;
+ // Internal unique name for this navBackStackEntry;
+ @NonNull
+ UUID mId = UUID.randomUUID();
+
NavBackStackEntry(@NonNull NavDestination destination, @Nullable Bundle args) {
mDestination = destination;
mArgs = args;
diff --git a/navigation/common/src/main/java/androidx/navigation/NavDestination.java b/navigation/common/src/main/java/androidx/navigation/NavDestination.java
index 94d6e90..658fb97 100644
--- a/navigation/common/src/main/java/androidx/navigation/NavDestination.java
+++ b/navigation/common/src/main/java/androidx/navigation/NavDestination.java
@@ -478,15 +478,15 @@
*/
@Nullable
Bundle addInDefaultArgs(@Nullable Bundle args) {
+ if (args == null && (mArguments == null || mArguments.isEmpty())) {
+ return null;
+ }
Bundle defaultArgs = new Bundle();
if (mArguments != null) {
for (Map.Entry<String, NavArgument> argument : mArguments.entrySet()) {
argument.getValue().putDefaultValue(argument.getKey(), defaultArgs);
}
}
- if (args == null && defaultArgs.isEmpty()) {
- return null;
- }
if (args != null) {
defaultArgs.putAll(args);
if (mArguments != null) {
diff --git a/navigation/fragment/api/2.0.0.txt b/navigation/fragment/api/2.0.0.txt
new file mode 100644
index 0000000..b81501d
--- /dev/null
+++ b/navigation/fragment/api/2.0.0.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+ @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigator(android.content.Context, androidx.fragment.app.FragmentManager, int);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+ method public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Fragment.class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination>);
+ method public final String getClassName();
+ method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String);
+ }
+
+ public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+ }
+
+ public static final class FragmentNavigator.Extras.Builder {
+ ctor public FragmentNavigator.Extras.Builder();
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View, String);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String>);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+ }
+
+ public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+ ctor public NavHostFragment();
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int);
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int, android.os.Bundle?);
+ method protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ method public final androidx.navigation.NavController getNavController();
+ }
+
+}
+
diff --git a/navigation/fragment/api/2.1.0-alpha01.txt b/navigation/fragment/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..8cd8b69
--- /dev/null
+++ b/navigation/fragment/api/2.1.0-alpha01.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+ @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigator(android.content.Context, androidx.fragment.app.FragmentManager, int);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+ method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Fragment.class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination>);
+ method public final String getClassName();
+ method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String);
+ }
+
+ public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+ }
+
+ public static final class FragmentNavigator.Extras.Builder {
+ ctor public FragmentNavigator.Extras.Builder();
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View, String);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String>);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+ }
+
+ public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+ ctor public NavHostFragment();
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int);
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int, android.os.Bundle?);
+ method protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ method public final androidx.navigation.NavController getNavController();
+ }
+
+}
+
diff --git a/navigation/fragment/api/2.1.0-alpha02.txt b/navigation/fragment/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..8cd8b69
--- /dev/null
+++ b/navigation/fragment/api/2.1.0-alpha02.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+ @androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigator(android.content.Context, androidx.fragment.app.FragmentManager, int);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
+ method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Fragment.class) public static class FragmentNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public FragmentNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public FragmentNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination>);
+ method public final String getClassName();
+ method public final androidx.navigation.fragment.FragmentNavigator.Destination setClassName(String);
+ }
+
+ public static final class FragmentNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public java.util.Map<android.view.View,java.lang.String> getSharedElements();
+ }
+
+ public static final class FragmentNavigator.Extras.Builder {
+ ctor public FragmentNavigator.Extras.Builder();
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElement(android.view.View, String);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras.Builder addSharedElements(java.util.Map<android.view.View,java.lang.String>);
+ method public androidx.navigation.fragment.FragmentNavigator.Extras build();
+ }
+
+ public class NavHostFragment extends androidx.fragment.app.Fragment implements androidx.navigation.NavHost {
+ ctor public NavHostFragment();
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int);
+ method public static androidx.navigation.fragment.NavHostFragment create(@NavigationRes int, android.os.Bundle?);
+ method protected androidx.navigation.Navigator<? extends androidx.navigation.fragment.FragmentNavigator.Destination> createFragmentNavigator();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ method public final androidx.navigation.NavController getNavController();
+ }
+
+}
+
diff --git a/navigation/fragment/api/current.txt b/navigation/fragment/api/current.txt
index b81501d..8cd8b69 100644
--- a/navigation/fragment/api/current.txt
+++ b/navigation/fragment/api/current.txt
@@ -4,7 +4,7 @@
@androidx.navigation.Navigator.Name("fragment") public class FragmentNavigator extends androidx.navigation.Navigator<androidx.navigation.fragment.FragmentNavigator.Destination> {
ctor public FragmentNavigator(android.content.Context, androidx.fragment.app.FragmentManager, int);
method public androidx.navigation.fragment.FragmentNavigator.Destination createDestination();
- method public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
+ method @Deprecated public androidx.fragment.app.Fragment instantiateFragment(android.content.Context, androidx.fragment.app.FragmentManager, String, android.os.Bundle?);
method public androidx.navigation.NavDestination? navigate(androidx.navigation.fragment.FragmentNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
method public boolean popBackStack();
}
diff --git a/navigation/fragment/api/res-2.0.0.txt b/navigation/fragment/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/api/res-2.0.0.txt
diff --git a/navigation/fragment/api/res-2.1.0-alpha01.txt b/navigation/fragment/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/api/res-2.1.0-alpha01.txt
diff --git a/navigation/fragment/api/res-2.1.0-alpha02.txt b/navigation/fragment/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/api/res-2.1.0-alpha02.txt
diff --git a/navigation/fragment/api/restricted_2.0.0.txt b/navigation/fragment/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/api/restricted_2.1.0-alpha01.txt b/navigation/fragment/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/api/restricted_2.1.0-alpha02.txt b/navigation/fragment/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/build.gradle b/navigation/fragment/build.gradle
index 76b2ed6..6e279e7 100644
--- a/navigation/fragment/build.gradle
+++ b/navigation/fragment/build.gradle
@@ -31,7 +31,7 @@
}
dependencies {
- api(NAV_SUPPORT_FRAGMENTS)
+ api(project(":fragment"))
api(project(":navigation:navigation-runtime"))
testImplementation(JUNIT)
diff --git a/navigation/fragment/ktx/api/2.0.0.txt b/navigation/fragment/ktx/api/2.0.0.txt
new file mode 100644
index 0000000..b8b9aba
--- /dev/null
+++ b/navigation/fragment/ktx/api/2.0.0.txt
@@ -0,0 +1,31 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+ public final class FragmentKt {
+ ctor public FragmentKt();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavArgsLazyKt {
+ ctor public FragmentNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+ }
+
+ public final class FragmentNavigatorDestinationBuilderKt {
+ ctor public FragmentNavigatorDestinationBuilderKt();
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,kotlin.Unit>! builder);
+ }
+
+ public final class FragmentNavigatorExtrasKt {
+ ctor public FragmentNavigatorExtrasKt();
+ method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+ }
+
+}
+
diff --git a/navigation/fragment/ktx/api/2.1.0-alpha01.txt b/navigation/fragment/ktx/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..b8b9aba
--- /dev/null
+++ b/navigation/fragment/ktx/api/2.1.0-alpha01.txt
@@ -0,0 +1,31 @@
+// Signature format: 3.0
+package androidx.navigation.fragment {
+
+ public final class FragmentKt {
+ ctor public FragmentKt();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavArgsLazyKt {
+ ctor public FragmentNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+ }
+
+ public final class FragmentNavigatorDestinationBuilderKt {
+ ctor public FragmentNavigatorDestinationBuilderKt();
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,kotlin.Unit>! builder);
+ }
+
+ public final class FragmentNavigatorExtrasKt {
+ ctor public FragmentNavigatorExtrasKt();
+ method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+ }
+
+}
+
diff --git a/navigation/fragment/ktx/api/2.1.0-alpha02.txt b/navigation/fragment/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..5cc721c
--- /dev/null
+++ b/navigation/fragment/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class NavGraphViewModelLazyKt {
+ ctor public NavGraphViewModelLazyKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! navGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
+ }
+
+}
+
+package androidx.navigation.fragment {
+
+ public final class FragmentKt {
+ ctor public FragmentKt();
+ method public static androidx.navigation.NavController findNavController(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavArgsLazyKt {
+ ctor public FragmentNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(androidx.fragment.app.Fragment);
+ }
+
+ public final class FragmentNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.fragment.FragmentNavigator.Destination> {
+ ctor public FragmentNavigatorDestinationBuilder(androidx.navigation.fragment.FragmentNavigator navigator, @IdRes int id, kotlin.reflect.KClass<? extends androidx.fragment.app.Fragment> fragmentClass);
+ method public androidx.navigation.fragment.FragmentNavigator.Destination build();
+ }
+
+ public final class FragmentNavigatorDestinationBuilderKt {
+ ctor public FragmentNavigatorDestinationBuilderKt();
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id);
+ method public static inline <reified F extends androidx.fragment.app.Fragment> void fragment(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.fragment.FragmentNavigatorDestinationBuilder,kotlin.Unit>! builder);
+ }
+
+ public final class FragmentNavigatorExtrasKt {
+ ctor public FragmentNavigatorExtrasKt();
+ method public static androidx.navigation.fragment.FragmentNavigator.Extras FragmentNavigatorExtras(kotlin.Pair<? extends android.view.View,java.lang.String>... sharedElements);
+ }
+
+}
+
diff --git a/navigation/fragment/ktx/api/current.txt b/navigation/fragment/ktx/api/current.txt
index b8b9aba..5cc721c 100644
--- a/navigation/fragment/ktx/api/current.txt
+++ b/navigation/fragment/ktx/api/current.txt
@@ -1,4 +1,13 @@
// Signature format: 3.0
+package androidx.navigation {
+
+ public final class NavGraphViewModelLazyKt {
+ ctor public NavGraphViewModelLazyKt();
+ method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<VM>! navGraphViewModels(androidx.fragment.app.Fragment, @IdRes int navGraphId, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>! factoryProducer = null);
+ }
+
+}
+
package androidx.navigation.fragment {
public final class FragmentKt {
diff --git a/navigation/fragment/ktx/api/res-2.0.0.txt b/navigation/fragment/ktx/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/ktx/api/res-2.0.0.txt
diff --git a/navigation/fragment/ktx/api/res-2.1.0-alpha01.txt b/navigation/fragment/ktx/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/ktx/api/res-2.1.0-alpha01.txt
diff --git a/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt b/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/fragment/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/fragment/ktx/api/restricted_2.0.0.txt b/navigation/fragment/ktx/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/ktx/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/ktx/api/restricted_2.1.0-alpha01.txt b/navigation/fragment/ktx/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/ktx/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/fragment/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/fragment/ktx/build.gradle b/navigation/fragment/ktx/build.gradle
index 81beeaa..98450ec 100644
--- a/navigation/fragment/ktx/build.gradle
+++ b/navigation/fragment/ktx/build.gradle
@@ -36,7 +36,11 @@
api(project(":navigation:navigation-fragment"))
// Ensure that the -ktx dependency graph mirrors the Java dependency graph
api(project(":navigation:navigation-runtime-ktx"))
+ api(project(":fragment-ktx"))
+ api(project(":lifecycle:lifecycle-viewmodel-ktx"))
api(KOTLIN_STDLIB)
+ androidTestImplementation(project(":fragment-testing"))
+ androidTestImplementation(project(":navigation:navigation-testing"))
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
androidTestImplementation(TEST_RUNNER)
diff --git a/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
new file mode 100644
index 0000000..130b21c
--- /dev/null
+++ b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/NavGraphViewModelLazyTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.testing.launchFragmentInContainer
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelStore
+import androidx.navigation.NavController
+import androidx.navigation.Navigation
+import androidx.navigation.navGraphViewModels
+import androidx.navigation.navigation
+import androidx.navigation.plusAssign
+import androidx.navigation.testing.TestNavigator
+import androidx.navigation.testing.test
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NavGraphViewModelLazyTest {
+ private val navController =
+ NavController(
+ ApplicationProvider.getApplicationContext() as Context
+ ).apply {
+ navigatorProvider += TestNavigator()
+ }
+
+ @Test
+ fun vmInitialization() {
+ val scenario = launchFragmentInContainer<TestVMFragment>()
+ navController.setViewModelStore(ViewModelStore())
+ scenario.onFragment { fragment ->
+ Navigation.setViewNavController(fragment.requireView(), navController)
+ }
+ val navGraph = navController.navigatorProvider.navigation(
+ id = GRAPH_ID,
+ startDestination = DESTINATION_ID
+ ) {
+ test(DESTINATION_ID)
+ }
+ navController.setGraph(navGraph, null)
+
+ scenario.onFragment { fragment ->
+ assertThat(fragment.viewModel).isNotNull()
+ }
+ }
+}
+
+class TestVMFragment : Fragment() {
+ val viewModel: TestViewModel by navGraphViewModels(GRAPH_ID)
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return View(activity)
+ }
+}
+
+class TestViewModel : ViewModel()
+
+private const val GRAPH_ID = 1
+private const val DESTINATION_ID = 2
\ No newline at end of file
diff --git a/navigation/fragment/ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt b/navigation/fragment/ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
new file mode 100644
index 0000000..30ebf4d
--- /dev/null
+++ b/navigation/fragment/ktx/src/main/java/androidx/navigation/NavGraphViewModelLazy.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation
+
+import androidx.annotation.IdRes
+import androidx.annotation.MainThread
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.createViewModelLazy
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStore
+import androidx.navigation.fragment.findNavController
+
+/**
+ * Returns a property delegate to access a [ViewModel] scoped to a navigation graph present on the
+ * {@link NavController} back stack:
+ * ```
+ * class MyFragment : Fragment() {
+ * val viewmodel: MainViewModel by navGraphViewModels(R.navigation.main)
+ * }
+ * ```
+ *
+ * Custom [ViewModelProvider.Factory] can be defined via [factoryProducer] parameter,
+ * factory returned by it will be used to create [ViewModel]:
+ * ```
+ * class MyFragment : Fragment() {
+ * val viewmodel: MainViewModel by navGraphViewModels(R.navigation.main) { myFactory }
+ * }
+ * ```
+ *
+ * This property can be accessed only after this NavGraph is on the NavController back stack,
+ * and an attempt access prior to that will result in an IllegalArgumentException.
+ *
+ * @param navGraphId ID of a NavGraph that exists on the {@link NavController} back stack
+ */
+@MainThread
+inline fun <reified VM : ViewModel> Fragment.navGraphViewModels(
+ @IdRes navGraphId: Int,
+ noinline factoryProducer: (() -> ViewModelProvider.Factory)? = null
+): Lazy<VM> {
+ val storeProducer: () -> ViewModelStore = { findNavController().getViewModelStore(navGraphId) }
+ return createViewModelLazy(VM::class, storeProducer, factoryProducer)
+}
\ No newline at end of file
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
index 07c19b3..b65f205 100644
--- a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.kt
@@ -17,7 +17,9 @@
package androidx.navigation.fragment
import android.os.Bundle
+import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentFactory
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavOptions
@@ -87,6 +89,31 @@
@UiThreadTest
@Test
+ fun testNavigateWithFragmentFactory() {
+ fragmentManager.fragmentFactory = NonEmptyFragmentFactory()
+ val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
+ val destination = fragmentNavigator.createDestination().apply {
+ id = INITIAL_FRAGMENT
+ className = NonEmptyConstructorFragment::class.java.name
+ }
+
+ assertThat(fragmentNavigator.navigate(destination, null, null, null))
+ .isEqualTo(destination)
+ fragmentManager.executePendingTransactions()
+ val fragment = fragmentManager.findFragmentById(R.id.container)
+ assertWithMessage("Fragment should be added")
+ .that(fragment)
+ .isNotNull()
+ assertWithMessage("Fragment should be the correct type")
+ .that(fragment)
+ .isInstanceOf(NonEmptyConstructorFragment::class.java)
+ assertWithMessage("Fragment should be the primary navigation Fragment")
+ .that(fragment)
+ .isSameAs(fragmentManager.primaryNavigationFragment)
+ }
+
+ @UiThreadTest
+ @Test
fun testNavigateTwice() {
val fragmentNavigator = FragmentNavigator(emptyActivity, fragmentManager, R.id.container)
val destination = fragmentNavigator.createDestination().apply {
@@ -200,6 +227,7 @@
fragmentManager.executePendingTransactions()
val fragment = fragmentManager.findFragmentById(R.id.container)
assertNotNull("Fragment should be added", fragment)
+ val lifecycle = fragment!!.lifecycle
assertThat(fragmentNavigator.navigate(destination, null,
NavOptions.Builder().setLaunchSingleTop(true).build(), null))
@@ -214,7 +242,7 @@
assertNotEquals("Replacement should be a new instance", fragment,
replacementFragment)
assertEquals("Old instance should be destroyed", Lifecycle.State.DESTROYED,
- fragment!!.lifecycle.currentState)
+ lifecycle.currentState)
}
@UiThreadTest
@@ -244,6 +272,7 @@
assertWithMessage("Fragment should be added")
.that(fragment)
.isNotNull()
+ val lifecycle = fragment!!.lifecycle
assertThat(fragmentNavigator.navigate(destination, null,
NavOptions.Builder().setLaunchSingleTop(true).build(), null))
@@ -263,7 +292,7 @@
.that(replacementFragment)
.isNotSameAs(fragment)
assertWithMessage("Old instance should be destroyed")
- .that(fragment!!.lifecycle.currentState)
+ .that(lifecycle.currentState)
.isEqualTo(Lifecycle.State.DESTROYED)
assertThat(fragmentNavigator.popBackStack())
@@ -272,10 +301,9 @@
assertWithMessage("Initial Fragment should be on top of back stack after pop")
.that(fragmentManager.findFragmentById(R.id.container))
.isSameAs(initialFragment)
- // TODO enable after fixing b/124332597 and moving to depend on AndroidX
- /*assertWithMessage("Initial Fragment should be the primary navigation Fragment")
+ assertWithMessage("Initial Fragment should be the primary navigation Fragment")
.that(fragmentManager.primaryNavigationFragment)
- .isSameAs(initialFragment)*/
+ .isSameAs(initialFragment)
}
@UiThreadTest
@@ -681,3 +709,16 @@
setContentView(R.layout.empty_activity)
}
}
+
+class NonEmptyConstructorFragment(val test: String) : Fragment()
+
+class NonEmptyFragmentFactory : FragmentFactory() {
+ override fun instantiate(
+ classLoader: ClassLoader,
+ className: String
+ ) = if (className == NonEmptyConstructorFragment::class.java.name) {
+ NonEmptyConstructorFragment("test")
+ } else {
+ super.instantiate(classLoader, className)
+ }
+}
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
index 573846a..9c12554 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentFactory;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavController;
@@ -166,7 +167,8 @@
}
/**
- * Instantiates the Fragment.
+ * Instantiates the Fragment via the FragmentManager's
+ * {@link androidx.fragment.app.FragmentFactory}.
*
* Note that this method is <strong>not</strong> responsible for calling
* {@link Fragment#setArguments(Bundle)} on the returned Fragment instance.
@@ -176,12 +178,18 @@
* @param className The Fragment to instantiate
* @param args The Fragment's arguments, if any
* @return A new fragment instance.
+ * @deprecated Set a custom {@link androidx.fragment.app.FragmentFactory} via
+ * {@link FragmentManager#setFragmentFactory(FragmentFactory)} to control
+ * instantiation of Fragments.
*/
+ @SuppressWarnings("DeprecatedIsStillUsed") // needed to maintain forward compatibility
+ @Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
- @SuppressWarnings("unused") @NonNull FragmentManager fragmentManager,
- @NonNull String className, @Nullable Bundle args) {
- return Fragment.instantiate(context, className, args);
+ @NonNull FragmentManager fragmentManager,
+ @NonNull String className, @SuppressWarnings("unused") @Nullable Bundle args) {
+ return fragmentManager.getFragmentFactory().instantiate(
+ context.getClassLoader(), className);
}
/**
@@ -209,6 +217,7 @@
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
+ //noinspection deprecation needed to maintain forward compatibility
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
@@ -410,7 +419,6 @@
* @param className The class name of the Fragment to show when you navigate to this
* destination
* @return this {@link Destination}
- * @see #instantiateFragment(Context, FragmentManager, String, Bundle)
*/
@NonNull
public final Destination setClassName(@NonNull String className) {
@@ -422,7 +430,6 @@
* Gets the Fragment's class name associated with this destination
*
* @throws IllegalStateException when no Fragment class was set.
- * @see #instantiateFragment(Context, FragmentManager, String, Bundle)
*/
@NonNull
public final String getClassName() {
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index 53294d1..1ad37a2 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -204,6 +204,7 @@
final Context context = requireContext();
mNavController = new NavController(context);
+ mNavController.setViewModelStore(getViewModelStore());
mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
Bundle navState = null;
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/package-info.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/package-info.java
index e54a465..023da44 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/package-info.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/package-info.java
@@ -17,7 +17,7 @@
/**
* The {@link androidx.navigation.fragment.NavHostFragment} provides a
* {@link androidx.navigation.NavHost} suitable for using
- * {@link android.support.v4.app.Fragment Fragments} as destinations in your navigation graphs via
+ * {@link androidx.fragment.app.Fragment Fragments} as destinations in your navigation graphs via
* <fragment%gt; elements. Navigating to a Fragment will replace the contents of the
* NavHostFragment.
* <p>
diff --git a/navigation/runtime/api/2.0.0.txt b/navigation/runtime/api/2.0.0.txt
new file mode 100644
index 0000000..32f2c7d
--- /dev/null
+++ b/navigation/runtime/api/2.0.0.txt
@@ -0,0 +1,102 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigator(android.content.Context);
+ method public static void applyPopAnimationsToPendingTransition(android.app.Activity);
+ method public androidx.navigation.ActivityNavigator.Destination createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Activity.class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination>);
+ method public final String? getAction();
+ method public final android.content.ComponentName? getComponent();
+ method public final android.net.Uri? getData();
+ method public final String? getDataPattern();
+ method public final android.content.Intent? getIntent();
+ method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
+ method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
+ method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+ }
+
+ public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+ method public int getFlags();
+ }
+
+ public static final class ActivityNavigator.Extras.Builder {
+ ctor public ActivityNavigator.Extras.Builder();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int);
+ method public androidx.navigation.ActivityNavigator.Extras build();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
+ }
+
+ public class NavController {
+ ctor public NavController(android.content.Context);
+ method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavDestination? getCurrentDestination();
+ method public androidx.navigation.NavGraph getGraph();
+ method public androidx.navigation.NavInflater getNavInflater();
+ method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+ method public boolean handleDeepLink(android.content.Intent?);
+ method public void navigate(@IdRes int);
+ method public void navigate(@IdRes int, android.os.Bundle?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void navigate(androidx.navigation.NavDirections);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.NavOptions?);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.Navigator.Extras);
+ method public boolean navigateUp();
+ method public boolean popBackStack();
+ method public boolean popBackStack(@IdRes int, boolean);
+ method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method @CallSuper public void restoreState(android.os.Bundle?);
+ method @CallSuper public android.os.Bundle? saveState();
+ method @CallSuper public void setGraph(@NavigationRes int);
+ method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+ }
+
+ public static interface NavController.OnDestinationChangedListener {
+ method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
+ }
+
+ public final class NavDeepLinkBuilder {
+ ctor public NavDeepLinkBuilder(android.content.Context);
+ method public android.app.PendingIntent createPendingIntent();
+ method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+ method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle?);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity>);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph);
+ }
+
+ public interface NavHost {
+ method public androidx.navigation.NavController getNavController();
+ }
+
+ public final class NavInflater {
+ ctor public NavInflater(android.content.Context, androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph inflate(@NavigationRes int);
+ }
+
+ public final class Navigation {
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
+ }
+
+}
+
diff --git a/navigation/runtime/api/2.1.0-alpha01.txt b/navigation/runtime/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..f34f225
--- /dev/null
+++ b/navigation/runtime/api/2.1.0-alpha01.txt
@@ -0,0 +1,103 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigator(android.content.Context);
+ method public static void applyPopAnimationsToPendingTransition(android.app.Activity);
+ method public androidx.navigation.ActivityNavigator.Destination createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Activity.class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination>);
+ method public final String? getAction();
+ method public final android.content.ComponentName? getComponent();
+ method public final android.net.Uri? getData();
+ method public final String? getDataPattern();
+ method public final android.content.Intent? getIntent();
+ method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
+ method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
+ method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+ }
+
+ public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+ method public int getFlags();
+ }
+
+ public static final class ActivityNavigator.Extras.Builder {
+ ctor public ActivityNavigator.Extras.Builder();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int);
+ method public androidx.navigation.ActivityNavigator.Extras build();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
+ }
+
+ public class NavController {
+ ctor public NavController(android.content.Context);
+ method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavDestination? getCurrentDestination();
+ method public androidx.navigation.NavGraph getGraph();
+ method public androidx.navigation.NavInflater getNavInflater();
+ method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+ method public boolean handleDeepLink(android.content.Intent?);
+ method public void navigate(@IdRes int);
+ method public void navigate(@IdRes int, android.os.Bundle?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void navigate(androidx.navigation.NavDirections);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.NavOptions?);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.Navigator.Extras);
+ method public boolean navigateUp();
+ method public boolean popBackStack();
+ method public boolean popBackStack(@IdRes int, boolean);
+ method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method @CallSuper public void restoreState(android.os.Bundle?);
+ method @CallSuper public android.os.Bundle? saveState();
+ method @CallSuper public void setGraph(@NavigationRes int);
+ method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+ }
+
+ public static interface NavController.OnDestinationChangedListener {
+ method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
+ }
+
+ public final class NavDeepLinkBuilder {
+ ctor public NavDeepLinkBuilder(android.content.Context);
+ method public android.app.PendingIntent createPendingIntent();
+ method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+ method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle?);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity>);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph);
+ }
+
+ public interface NavHost {
+ method public androidx.navigation.NavController getNavController();
+ }
+
+ public final class NavInflater {
+ ctor public NavInflater(android.content.Context, androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph inflate(@NavigationRes int);
+ }
+
+ public final class Navigation {
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections);
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
+ }
+
+}
+
diff --git a/navigation/runtime/api/2.1.0-alpha02.txt b/navigation/runtime/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..9a70d38
--- /dev/null
+++ b/navigation/runtime/api/2.1.0-alpha02.txt
@@ -0,0 +1,107 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ @androidx.navigation.Navigator.Name("activity") public class ActivityNavigator extends androidx.navigation.Navigator<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigator(android.content.Context);
+ method public static void applyPopAnimationsToPendingTransition(android.app.Activity);
+ method public androidx.navigation.ActivityNavigator.Destination createDestination();
+ method public androidx.navigation.NavDestination? navigate(androidx.navigation.ActivityNavigator.Destination, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public boolean popBackStack();
+ }
+
+ @androidx.navigation.NavDestination.ClassType(Activity.class) public static class ActivityNavigator.Destination extends androidx.navigation.NavDestination {
+ ctor public ActivityNavigator.Destination(androidx.navigation.NavigatorProvider);
+ ctor public ActivityNavigator.Destination(androidx.navigation.Navigator<? extends androidx.navigation.ActivityNavigator.Destination>);
+ method public final String? getAction();
+ method public final android.content.ComponentName? getComponent();
+ method public final android.net.Uri? getData();
+ method public final String? getDataPattern();
+ method public final android.content.Intent? getIntent();
+ method public final String? getTargetPackage();
+ method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
+ method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
+ method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
+ method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+ method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String?);
+ }
+
+ public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
+ method public androidx.core.app.ActivityOptionsCompat? getActivityOptions();
+ method public int getFlags();
+ }
+
+ public static final class ActivityNavigator.Extras.Builder {
+ ctor public ActivityNavigator.Extras.Builder();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder addFlags(int);
+ method public androidx.navigation.ActivityNavigator.Extras build();
+ method public androidx.navigation.ActivityNavigator.Extras.Builder setActivityOptions(androidx.core.app.ActivityOptionsCompat);
+ }
+
+ public class NavController {
+ ctor public NavController(android.content.Context);
+ method public void addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method public androidx.navigation.NavDeepLinkBuilder createDeepLink();
+ method public androidx.navigation.NavDestination? getCurrentDestination();
+ method public androidx.navigation.NavGraph getGraph();
+ method public androidx.navigation.NavInflater getNavInflater();
+ method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
+ method public boolean handleDeepLink(android.content.Intent?);
+ method public void navigate(@IdRes int);
+ method public void navigate(@IdRes int, android.os.Bundle?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?);
+ method public void navigate(@IdRes int, android.os.Bundle?, androidx.navigation.NavOptions?, androidx.navigation.Navigator.Extras?);
+ method public void navigate(androidx.navigation.NavDirections);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.NavOptions?);
+ method public void navigate(androidx.navigation.NavDirections, androidx.navigation.Navigator.Extras);
+ method public boolean navigateUp();
+ method public boolean popBackStack();
+ method public boolean popBackStack(@IdRes int, boolean);
+ method public void removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener);
+ method @CallSuper public void restoreState(android.os.Bundle?);
+ method @CallSuper public android.os.Bundle? saveState();
+ method @CallSuper public void setGraph(@NavigationRes int);
+ method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph);
+ method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ method public void setViewModelStore(androidx.lifecycle.ViewModelStore);
+ field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
+ }
+
+ public static interface NavController.OnDestinationChangedListener {
+ method public void onDestinationChanged(androidx.navigation.NavController, androidx.navigation.NavDestination, android.os.Bundle?);
+ }
+
+ public final class NavDeepLinkBuilder {
+ ctor public NavDeepLinkBuilder(android.content.Context);
+ method public android.app.PendingIntent createPendingIntent();
+ method public androidx.core.app.TaskStackBuilder createTaskStackBuilder();
+ method public androidx.navigation.NavDeepLinkBuilder setArguments(android.os.Bundle?);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(Class<? extends android.app.Activity>);
+ method public androidx.navigation.NavDeepLinkBuilder setComponentName(android.content.ComponentName);
+ method public androidx.navigation.NavDeepLinkBuilder setDestination(@IdRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(@NavigationRes int);
+ method public androidx.navigation.NavDeepLinkBuilder setGraph(androidx.navigation.NavGraph);
+ }
+
+ public interface NavHost {
+ method public androidx.navigation.NavController getNavController();
+ }
+
+ public final class NavInflater {
+ ctor public NavInflater(android.content.Context, androidx.navigation.NavigatorProvider);
+ method public androidx.navigation.NavGraph inflate(@NavigationRes int);
+ }
+
+ public final class Navigation {
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections);
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
+ }
+
+}
+
diff --git a/navigation/runtime/api/current.txt b/navigation/runtime/api/current.txt
index 32f2c7d..9a70d38 100644
--- a/navigation/runtime/api/current.txt
+++ b/navigation/runtime/api/current.txt
@@ -17,11 +17,13 @@
method public final android.net.Uri? getData();
method public final String? getDataPattern();
method public final android.content.Intent? getIntent();
+ method public final String? getTargetPackage();
method public final androidx.navigation.ActivityNavigator.Destination setAction(String?);
method public final androidx.navigation.ActivityNavigator.Destination setComponentName(android.content.ComponentName?);
method public final androidx.navigation.ActivityNavigator.Destination setData(android.net.Uri?);
method public final androidx.navigation.ActivityNavigator.Destination setDataPattern(String?);
method public final androidx.navigation.ActivityNavigator.Destination setIntent(android.content.Intent?);
+ method public final androidx.navigation.ActivityNavigator.Destination setTargetPackage(String?);
}
public static final class ActivityNavigator.Extras implements androidx.navigation.Navigator.Extras {
@@ -44,6 +46,7 @@
method public androidx.navigation.NavGraph getGraph();
method public androidx.navigation.NavInflater getNavInflater();
method public androidx.navigation.NavigatorProvider getNavigatorProvider();
+ method public androidx.lifecycle.ViewModelStore getViewModelStore(@IdRes int);
method public boolean handleDeepLink(android.content.Intent?);
method public void navigate(@IdRes int);
method public void navigate(@IdRes int, android.os.Bundle?);
@@ -62,6 +65,7 @@
method @CallSuper public void setGraph(@NavigationRes int, android.os.Bundle?);
method @CallSuper public void setGraph(androidx.navigation.NavGraph);
method @CallSuper public void setGraph(androidx.navigation.NavGraph, android.os.Bundle?);
+ method public void setViewModelStore(androidx.lifecycle.ViewModelStore);
field public static final String KEY_DEEP_LINK_INTENT = "android-support-nav:controller:deepLinkIntent";
}
@@ -93,6 +97,7 @@
public final class Navigation {
method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int);
method public static android.view.View.OnClickListener createNavigateOnClickListener(@IdRes int, android.os.Bundle?);
+ method public static android.view.View.OnClickListener createNavigateOnClickListener(androidx.navigation.NavDirections);
method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int);
method public static androidx.navigation.NavController findNavController(android.view.View);
method public static void setViewNavController(android.view.View, androidx.navigation.NavController?);
diff --git a/navigation/runtime/api/res-2.0.0.txt b/navigation/runtime/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/api/res-2.0.0.txt
diff --git a/navigation/runtime/api/res-2.1.0-alpha01.txt b/navigation/runtime/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/api/res-2.1.0-alpha01.txt
diff --git a/navigation/runtime/api/res-2.1.0-alpha02.txt b/navigation/runtime/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/api/res-2.1.0-alpha02.txt
diff --git a/navigation/runtime/api/restricted_2.0.0.txt b/navigation/runtime/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/api/restricted_2.1.0-alpha01.txt b/navigation/runtime/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/api/restricted_2.1.0-alpha02.txt b/navigation/runtime/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/build.gradle b/navigation/runtime/build.gradle
index 8de105e..d828cb7 100644
--- a/navigation/runtime/build.gradle
+++ b/navigation/runtime/build.gradle
@@ -31,8 +31,8 @@
}
dependencies {
- api(NAV_SUPPORT_CORE_UTILS)
api(project(":navigation:navigation-common"))
+ api(project(":lifecycle:lifecycle-viewmodel"))
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/navigation/runtime/ktx/api/2.0.0.txt b/navigation/runtime/ktx/api/2.0.0.txt
new file mode 100644
index 0000000..2967c7a
--- /dev/null
+++ b/navigation/runtime/ktx/api/2.0.0.txt
@@ -0,0 +1,57 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+ }
+
+ public final class ActivityNavArgsLazyKt {
+ ctor public ActivityNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+ }
+
+ public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+ method public androidx.navigation.ActivityNavigator.Destination build();
+ method public String? getAction();
+ method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+ method public android.net.Uri? getData();
+ method public String? getDataPattern();
+ method public void setAction(String? p);
+ method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? p);
+ method public void setData(android.net.Uri? p);
+ method public void setDataPattern(String? p);
+ property public final String? action;
+ property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+ property public final android.net.Uri? data;
+ property public final String? dataPattern;
+ }
+
+ public final class ActivityNavigatorDestinationBuilderKt {
+ ctor public ActivityNavigatorDestinationBuilderKt();
+ method public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ActivityNavigatorExtrasKt {
+ ctor public ActivityNavigatorExtrasKt();
+ method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(androidx.core.app.ActivityOptionsCompat? activityOptions = null, int flags = 0);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavHostKt {
+ ctor public NavHostKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ }
+
+}
+
diff --git a/navigation/runtime/ktx/api/2.1.0-alpha01.txt b/navigation/runtime/ktx/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..2967c7a
--- /dev/null
+++ b/navigation/runtime/ktx/api/2.1.0-alpha01.txt
@@ -0,0 +1,57 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+ }
+
+ public final class ActivityNavArgsLazyKt {
+ ctor public ActivityNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+ }
+
+ public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+ method public androidx.navigation.ActivityNavigator.Destination build();
+ method public String? getAction();
+ method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+ method public android.net.Uri? getData();
+ method public String? getDataPattern();
+ method public void setAction(String? p);
+ method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? p);
+ method public void setData(android.net.Uri? p);
+ method public void setDataPattern(String? p);
+ property public final String? action;
+ property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+ property public final android.net.Uri? data;
+ property public final String? dataPattern;
+ }
+
+ public final class ActivityNavigatorDestinationBuilderKt {
+ ctor public ActivityNavigatorDestinationBuilderKt();
+ method public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ActivityNavigatorExtrasKt {
+ ctor public ActivityNavigatorExtrasKt();
+ method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(androidx.core.app.ActivityOptionsCompat? activityOptions = null, int flags = 0);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavHostKt {
+ ctor public NavHostKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ }
+
+}
+
diff --git a/navigation/runtime/ktx/api/2.1.0-alpha02.txt b/navigation/runtime/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..7827a96
--- /dev/null
+++ b/navigation/runtime/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,60 @@
+// Signature format: 3.0
+package androidx.navigation {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static androidx.navigation.NavController findNavController(android.app.Activity, @IdRes int viewId);
+ }
+
+ public final class ActivityNavArgsLazyKt {
+ ctor public ActivityNavArgsLazyKt();
+ method @MainThread public static inline <reified Args extends androidx.navigation.NavArgs> androidx.navigation.NavArgsLazy<Args>! navArgs(android.app.Activity);
+ }
+
+ public final class ActivityNavigatorDestinationBuilder extends androidx.navigation.NavDestinationBuilder<androidx.navigation.ActivityNavigator.Destination> {
+ ctor public ActivityNavigatorDestinationBuilder(androidx.navigation.ActivityNavigator navigator, @IdRes int id);
+ method public androidx.navigation.ActivityNavigator.Destination build();
+ method public String? getAction();
+ method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
+ method public android.net.Uri? getData();
+ method public String? getDataPattern();
+ method public String? getTargetPackage();
+ method public void setAction(String? p);
+ method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? p);
+ method public void setData(android.net.Uri? p);
+ method public void setDataPattern(String? p);
+ method public void setTargetPackage(String? p);
+ property public final String? action;
+ property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
+ property public final android.net.Uri? data;
+ property public final String? dataPattern;
+ property public final String? targetPackage;
+ }
+
+ public final class ActivityNavigatorDestinationBuilderKt {
+ ctor public ActivityNavigatorDestinationBuilderKt();
+ method public static inline void activity(androidx.navigation.NavGraphBuilder, @IdRes int id, kotlin.jvm.functions.Function1<? super androidx.navigation.ActivityNavigatorDestinationBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ActivityNavigatorExtrasKt {
+ ctor public ActivityNavigatorExtrasKt();
+ method public static androidx.navigation.ActivityNavigator.Extras ActivityNavigatorExtras(androidx.core.app.ActivityOptionsCompat? activityOptions = null, int flags = 0);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavController, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class NavHostKt {
+ ctor public NavHostKt();
+ method public static inline androidx.navigation.NavGraph createGraph(androidx.navigation.NavHost, @IdRes int id = 0, @IdRes int startDestination, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ }
+
+ public final class ViewKt {
+ ctor public ViewKt();
+ method public static androidx.navigation.NavController findNavController(android.view.View);
+ }
+
+}
+
diff --git a/navigation/runtime/ktx/api/current.txt b/navigation/runtime/ktx/api/current.txt
index 2967c7a..7827a96 100644
--- a/navigation/runtime/ktx/api/current.txt
+++ b/navigation/runtime/ktx/api/current.txt
@@ -18,14 +18,17 @@
method public kotlin.reflect.KClass<? extends android.app.Activity>? getActivityClass();
method public android.net.Uri? getData();
method public String? getDataPattern();
+ method public String? getTargetPackage();
method public void setAction(String? p);
method public void setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>? p);
method public void setData(android.net.Uri? p);
method public void setDataPattern(String? p);
+ method public void setTargetPackage(String? p);
property public final String? action;
property public final kotlin.reflect.KClass<? extends android.app.Activity>? activityClass;
property public final android.net.Uri? data;
property public final String? dataPattern;
+ property public final String? targetPackage;
}
public final class ActivityNavigatorDestinationBuilderKt {
diff --git a/navigation/runtime/ktx/api/res-2.0.0.txt b/navigation/runtime/ktx/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/ktx/api/res-2.0.0.txt
diff --git a/navigation/runtime/ktx/api/res-2.1.0-alpha01.txt b/navigation/runtime/ktx/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/ktx/api/res-2.1.0-alpha01.txt
diff --git a/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt b/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/runtime/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/runtime/ktx/api/restricted_2.0.0.txt b/navigation/runtime/ktx/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/ktx/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/ktx/api/restricted_2.1.0-alpha01.txt b/navigation/runtime/ktx/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/ktx/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/runtime/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
index 0f3b0d7..506771e 100644
--- a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
@@ -20,6 +20,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -46,6 +47,21 @@
}
@Test
+ fun activityPackage() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ targetPackage = PACKAGE_NAME
+ }
+ }
+ assertWithMessage("Destination should be added to the graph")
+ .that(DESTINATION_ID in graph)
+ .isTrue()
+ assertWithMessage("Destination should have package name set")
+ .that((graph[DESTINATION_ID] as ActivityNavigator.Destination).targetPackage)
+ .isEqualTo(PACKAGE_NAME)
+ }
+
+ @Test
fun activityClass() {
val graph = navController.createGraph(startDestination = DESTINATION_ID) {
activity(DESTINATION_ID) {
@@ -103,6 +119,7 @@
}
private const val DESTINATION_ID = 1
+private const val PACKAGE_NAME = "com.example"
private const val LABEL = "Test"
private const val ACTION = "ACTION_TEST"
private val DATA = Uri.parse("http://www.example.com")
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt
index bbc7f4c..dd3b631 100644
--- a/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt
@@ -45,6 +45,8 @@
) : NavDestinationBuilder<ActivityNavigator.Destination>(navigator, id) {
private val context = navigator.context
+ var targetPackage: String? = null
+
var activityClass: KClass<out Activity>? = null
var action: String? = null
@@ -55,6 +57,7 @@
override fun build(): ActivityNavigator.Destination =
super.build().also { destination ->
+ destination.targetPackage = targetPackage
activityClass?.let { clazz ->
destination.setComponentName(ComponentName(context, clazz.java))
}
diff --git a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
index 59c6db9..3641132 100644
--- a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
+++ b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -20,6 +20,7 @@
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
+import androidx.lifecycle.ViewModelStore
import androidx.navigation.test.R
import androidx.navigation.testing.TestNavigator
import androidx.navigation.testing.test
@@ -718,6 +719,60 @@
verifyNoMoreInteractions(onDestinationChangedListener)
}
+ @Test
+ fun testGetViewModelStore() {
+ val navController = createNavController()
+ navController.setViewModelStore(ViewModelStore())
+ val navGraph = navController.navigatorProvider.navigation(
+ id = 1,
+ startDestination = R.id.start_test
+ ) {
+ test(R.id.start_test)
+ }
+ navController.setGraph(navGraph, null)
+
+ val store = navController.getViewModelStore(navGraph.id)
+ assertThat(store).isNotNull()
+ }
+
+ @Test
+ fun testGetViewModelStoreNoGraph() {
+ val navController = createNavController()
+ navController.setViewModelStore(ViewModelStore())
+ val navGraphId = 1
+
+ try {
+ navController.getViewModelStore(navGraphId)
+ fail(
+ "Attempting to get ViewModelStore for navGraph not on back stack should throw " +
+ "IllegalArgumentException"
+ )
+ } catch (e: IllegalArgumentException) {
+ assertThat(e)
+ .hasMessageThat().contains(
+ "No NavGraph with ID $navGraphId is on the NavController's back stack"
+ )
+ }
+ }
+
+ @Test
+ fun testGetViewModelStoreSameGraph() {
+ val navController = createNavController()
+ navController.setViewModelStore(ViewModelStore())
+ val provider = navController.navigatorProvider
+ val graph = provider.navigation(1, startDestination = 1) {
+ navigation(1, startDestination = 2) {
+ test(2)
+ }
+ }
+
+ navController.setGraph(graph, null)
+ val viewStore = navController.getViewModelStore(graph.id)
+
+ assertThat(viewStore).isNotNull()
+ assertThat(navController.getViewModelStore(graph.id)).isSameAs(viewStore)
+ }
+
private fun createNavController(): NavController {
val navController = NavController(ApplicationProvider.getApplicationContext())
val navigator = TestNavigator()
diff --git a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
new file mode 100644
index 0000000..aecc55e
--- /dev/null
+++ b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerViewModelTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation
+
+import androidx.lifecycle.ViewModelStore
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.UUID
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NavControllerViewModelTest {
+
+ @Test
+ fun testGetViewModelStore() {
+ val navGraphId = UUID.randomUUID()
+ val viewModel = NavControllerViewModel()
+ val viewModelStore = viewModel.getViewModelStore(navGraphId)
+ assertThat(viewModel.getViewModelStore(navGraphId)).isSameAs(viewModelStore)
+ }
+
+ @Test
+ fun testGetInstance() {
+ val viewModelStore = ViewModelStore()
+ val viewModel = NavControllerViewModel.getInstance(viewModelStore)
+ assertThat(NavControllerViewModel.getInstance(viewModelStore)).isSameAs(viewModel)
+ }
+
+ @Test
+ fun testClear() {
+ val viewModel = NavControllerViewModel.getInstance(ViewModelStore())
+ val navGraphId = UUID.randomUUID()
+ val viewModelStore = viewModel.getViewModelStore(navGraphId)
+ assertThat(viewModelStore).isNotNull()
+
+ viewModel.clear(navGraphId)
+ assertThat(viewModel.getViewModelStore(navGraphId)).isNotSameAs(viewModelStore)
+ }
+
+ @Test
+ fun testOnCleared() {
+ val baseViewModelStore = ViewModelStore()
+ val viewModel = NavControllerViewModel.getInstance(baseViewModelStore)
+ val navGraphId = UUID.randomUUID()
+ val navGraphViewModelStore = viewModel.getViewModelStore(navGraphId)
+ assertThat(navGraphViewModelStore).isNotNull()
+
+ baseViewModelStore.clear()
+ assertThat(viewModel.getViewModelStore(navGraphId)).isNotSameAs(navGraphViewModelStore)
+ }
+}
diff --git a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
index b3a9bee..98ea6fed 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
@@ -230,10 +230,18 @@
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.ActivityNavigator);
+ String targetPackage = a.getString(R.styleable.ActivityNavigator_targetPackage);
+ if (targetPackage != null) {
+ targetPackage = targetPackage.replace(NavInflater.APPLICATION_ID_PLACEHOLDER,
+ context.getPackageName());
+ }
+ setTargetPackage(targetPackage);
String className = a.getString(R.styleable.ActivityNavigator_android_name);
if (className != null) {
- setComponentName(new ComponentName(context,
- parseClassFromName(context, className, Activity.class)));
+ if (className.charAt(0) == '.') {
+ className = context.getPackageName() + className;
+ }
+ setComponentName(new ComponentName(context, className));
}
setAction(a.getString(R.styleable.ActivityNavigator_action));
String data = a.getString(R.styleable.ActivityNavigator_data);
@@ -265,6 +273,36 @@
}
/**
+ * Set an explicit application package name that limits
+ * the components this destination will navigate to.
+ * <p>
+ * When inflated from XML, you can use <code>${applicationId}</code> as the
+ * package name to automatically use {@link Context#getPackageName()}.
+ *
+ * @param packageName packageName to set
+ * @return this {@link Destination}
+ */
+ @NonNull
+ public final Destination setTargetPackage(@Nullable String packageName) {
+ if (mIntent == null) {
+ mIntent = new Intent();
+ }
+ mIntent.setPackage(packageName);
+ return this;
+ }
+
+ /**
+ * Get the explicit application package name associated with this destination, if any
+ */
+ @Nullable
+ public final String getTargetPackage() {
+ if (mIntent == null) {
+ return null;
+ }
+ return mIntent.getPackage();
+ }
+
+ /**
* Set an explicit {@link ComponentName} to navigate to.
*
* @param name The component name of the Activity to start.
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavController.java b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
index 6833814..5fd4295 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
@@ -30,6 +30,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.TaskStackBuilder;
+import androidx.lifecycle.ViewModelStore;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -81,6 +82,8 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final Deque<NavBackStackEntry> mBackStack = new ArrayDeque<>();
+ private NavControllerViewModel mViewModel;
+
private final NavigatorProvider mNavigatorProvider = new NavigatorProvider() {
@Nullable
@Override
@@ -306,7 +309,10 @@
boolean popped = false;
for (Navigator navigator : popOperations) {
if (navigator.popBackStack()) {
- mBackStack.removeLast();
+ NavBackStackEntry entry = mBackStack.removeLast();
+ if (mViewModel != null) {
+ mViewModel.clear(entry.mId);
+ }
popped = true;
} else {
// The pop did not complete successfully, so stop immediately
@@ -961,4 +967,46 @@
mBackStackIdsToRestore = navState.getIntArray(KEY_BACK_STACK_IDS);
mBackStackArgsToRestore = navState.getParcelableArray(KEY_BACK_STACK_ARGS);
}
+
+ /**
+ * Sets the parent ViewModelStore used by the NavController to store ViewModels at the
+ * navigation graph level. This is required to call {@link #getViewModelStore} and
+ * should generally be called for you by your {@link NavHost}.
+ *
+ * @param viewModelStore ViewModelStore used to store ViewModels at the navigation graph level
+ */
+ public void setViewModelStore(@NonNull ViewModelStore viewModelStore) {
+ mViewModel = NavControllerViewModel.getInstance(viewModelStore);
+ }
+
+ /**
+ * Gets the view model for a NavGraph. If a view model does not exist it will create and
+ * store one.
+ *
+ * @param navGraphId ID of a NavGraph that exists on the back stack
+ * @throws IllegalStateException if called before {@link #setViewModelStore}.
+ * @throws IllegalArgumentException if the NavGraph is not on the back stack
+ */
+ @NonNull
+ public ViewModelStore getViewModelStore(@IdRes int navGraphId) {
+ if (mViewModel == null) {
+ throw new IllegalStateException("You must call setViewModelStore() before calling "
+ + "getViewModelStore().");
+ }
+ NavBackStackEntry lastFromBackStack = null;
+ Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
+ while (iterator.hasNext()) {
+ NavBackStackEntry entry = iterator.next();
+ NavDestination destination = entry.getDestination();
+ if (destination instanceof NavGraph && destination.getId() == navGraphId) {
+ lastFromBackStack = entry;
+ break;
+ }
+ }
+ if (lastFromBackStack == null) {
+ throw new IllegalArgumentException("No NavGraph with ID " + navGraphId + " is on the "
+ + "NavController's back stack");
+ }
+ return mViewModel.getViewModelStore(lastFromBackStack.mId);
+ }
}
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavControllerViewModel.java b/navigation/runtime/src/main/java/androidx/navigation/NavControllerViewModel.java
new file mode 100644
index 0000000..7e19c53
--- /dev/null
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavControllerViewModel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.UUID;
+
+/**
+ * NavControllerViewModel is the always up to date view of the NavController's
+ * non configuration state
+ */
+class NavControllerViewModel extends ViewModel {
+
+ private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ NavControllerViewModel viewModel = new NavControllerViewModel();
+ return (T) viewModel;
+ }
+ };
+
+ @NonNull
+ static NavControllerViewModel getInstance(ViewModelStore viewModelStore) {
+ ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore, FACTORY);
+ return viewModelProvider.get(NavControllerViewModel.class);
+ }
+
+ private final HashMap<UUID, ViewModelStore> mViewModelStores = new HashMap<>();
+
+ void clear(@NonNull UUID backStackEntryUUID) {
+ // Clear and remove the NavGraph's ViewModelStore
+ ViewModelStore viewModelStore = mViewModelStores.remove(backStackEntryUUID);
+ if (viewModelStore != null) {
+ viewModelStore.clear();
+ }
+ }
+
+ @Override
+ protected void onCleared() {
+ for (UUID key: mViewModelStores.keySet()) {
+ clear(key);
+ }
+ }
+
+ @NonNull
+ ViewModelStore getViewModelStore(@NonNull UUID backStackEntryUUID) {
+ ViewModelStore viewModelStore = mViewModelStores.get(backStackEntryUUID);
+ if (viewModelStore == null) {
+ viewModelStore = new ViewModelStore();
+ mViewModelStores.put(backStackEntryUUID, viewModelStore);
+ }
+ return viewModelStore;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("NavControllerViewModel{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append("} ViewModelStores (");
+ Iterator<UUID> viewModelStoreIterator = mViewModelStores.keySet().iterator();
+ while (viewModelStoreIterator.hasNext()) {
+ sb.append(viewModelStoreIterator.next());
+ if (viewModelStoreIterator.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+}
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java b/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
index 2b108d4..f0a63b7 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
@@ -43,7 +43,7 @@
private static final String TAG_DEEP_LINK = "deepLink";
private static final String TAG_ACTION = "action";
private static final String TAG_INCLUDE = "include";
- private static final String APPLICATION_ID_PLACEHOLDER = "${applicationId}";
+ static final String APPLICATION_ID_PLACEHOLDER = "${applicationId}";
private static final ThreadLocal<TypedValue> sTmpValue = new ThreadLocal<>();
diff --git a/navigation/runtime/src/main/java/androidx/navigation/Navigation.java b/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
index 6f1d4e5..827047e 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
@@ -122,6 +122,24 @@
}
/**
+ * Create an {@link android.view.View.OnClickListener} for navigating
+ * to a destination via a generated {@link NavDirections}.
+ *
+ * @param directions directions that describe this navigation operation
+ * @return a new click listener for setting on an arbitrary view
+ */
+ @NonNull
+ public static View.OnClickListener createNavigateOnClickListener(
+ @NonNull final NavDirections directions) {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ findNavController(view).navigate(directions);
+ }
+ };
+ }
+
+ /**
* Associates a NavController with the given View, allowing developers to use
* {@link #findNavController(View)} and {@link #findNavController(Activity, int)} with that
* View or any of its children to retrieve the NavController.
diff --git a/navigation/runtime/src/main/java/androidx/navigation/package-info.java b/navigation/runtime/src/main/java/androidx/navigation/package-info.java
index 40fb1cb..7be37d4 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/package-info.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/package-info.java
@@ -17,7 +17,7 @@
/**
* Navigation is a framework for navigating between 'destinations' within an Android
* application that provides a consistent API whether destinations are implemented as
- * {@link android.support.v4.app.Fragment Fragments}, {@link android.app.Activity Activities}, or
+ * {@link androidx.fragment.app.Fragment Fragments}, {@link android.app.Activity Activities}, or
* other components.
* <p>
* There are 3 major components in Navigation.
diff --git a/navigation/runtime/src/main/res/values/attrs.xml b/navigation/runtime/src/main/res/values/attrs.xml
index b36e607..59945eb 100644
--- a/navigation/runtime/src/main/res/values/attrs.xml
+++ b/navigation/runtime/src/main/res/values/attrs.xml
@@ -17,6 +17,7 @@
<attr name="action" format="string" />
<attr name="data" format="string" />
<attr name="dataPattern" format="string" />
+ <attr name="targetPackage" format="string" />
</declare-styleable>
<declare-styleable name="NavInclude">
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/JavaNavWriterTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/JavaNavWriterTest.kt
index 7c43326..e2d0d45 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/JavaNavWriterTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/JavaNavWriterTest.kt
@@ -67,6 +67,11 @@
private fun assertCompilesWithoutError(javaFileObject: JavaFileObject) {
JavaSourcesSubject.assertThat(
loadSourceFileObject("a.b.R", "a/b"),
+ loadSourceFileObject("a.b.secondreallyreallyreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage.R", "a/b/secondreallyreallyreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage"),
JavaFileObjects.forSourceString("androidx.annotation.NonNull",
"package androidx.annotation; public @interface NonNull {}"),
JavaFileObjects.forSourceString("androidx.annotation.Nullable",
@@ -144,6 +149,25 @@
}
@Test
+ fun testDirectionsClassGeneration_longPackage() {
+ val funAction = Action(ResReference("a.b.secondreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage", "id", "next"), id("destA"),
+ listOf())
+
+ val dest = Destination(null, ClassName.get("a.b.reallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage", "LongPackageFragment"), "fragment", listOf(),
+ listOf(funAction))
+
+ val actual = generateDirectionsCodeFile(dest, emptyList(), true).toJavaFileObject()
+ JavaSourcesSubject.assertThat(actual).parsesAs("a.b.reallyreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage.LongPackageFragmentDirections")
+ assertCompilesWithoutError(actual)
+ }
+
+ @Test
fun testDirectionsClassGeneration_sanitizedNames() {
val nextAction = Action(id("next_action"), id("destA"),
listOf(
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
index dfc2b93..3a5dc96 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/KotlinNavWriterTest.kt
@@ -118,6 +118,24 @@
}
@Test
+ fun testDirectionsClassGeneration_longPackage() {
+ val funAction = Action(ResReference("a.b.secondreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage", "id", "next"), id("destA"),
+ listOf())
+
+ val dest = Destination(null, ClassName.get("a.b.reallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage", "LongPackageFragment"), "fragment", listOf(),
+ listOf(funAction))
+
+ val actual = generateDirectionsCodeFile(dest, emptyList(), false)
+ assertThat(actual.toString()).parsesAs("a.b.reallyreallyreallyreallyreally" +
+ "reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreally" +
+ "longpackage.LongPackageFragmentDirections")
+ }
+
+ @Test
fun testArgumentsClassGeneration() {
val dest = Destination(null, ClassName.get("a.b", "MainFragment"), "fragment", listOf(
Argument("main", StringType),
diff --git a/navigation/safe-args-generator/src/tests/test-data/a/b/secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage/R.java b/navigation/safe-args-generator/src/tests/test-data/a/b/secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage/R.java
new file mode 100644
index 0000000..607a6a9
--- /dev/null
+++ b/navigation/safe-args-generator/src/tests/test-data/a/b/secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage/R.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package a.b.secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage;
+
+// fake R class to compile against for WriterTest
+public class R {
+
+ public static final class id {
+ public static final int next = 0x7f060002;
+ }
+}
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/tests/test-data/expected/java_nav_writer_test/LongPackageFragmentDirections.java b/navigation/safe-args-generator/src/tests/test-data/expected/java_nav_writer_test/LongPackageFragmentDirections.java
new file mode 100644
index 0000000..27cc370
--- /dev/null
+++ b/navigation/safe-args-generator/src/tests/test-data/expected/java_nav_writer_test/LongPackageFragmentDirections.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package a.b.reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage;
+
+import a.b.secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage.R;
+import androidx.annotation.NonNull;
+import androidx.navigation.ActionOnlyNavDirections;
+import androidx.navigation.NavDirections;
+
+public class LongPackageFragmentDirections {
+ private LongPackageFragmentDirections() {
+ }
+
+ @NonNull
+ public static NavDirections next() {
+ return new ActionOnlyNavDirections(R.id.next);
+ }
+}
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/tests/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt b/navigation/safe-args-generator/src/tests/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt
new file mode 100644
index 0000000..3a6d0d7
--- /dev/null
+++ b/navigation/safe-args-generator/src/tests/test-data/expected/kotlin_nav_writer_test/LongPackageFragmentDirections.kt
@@ -0,0 +1,11 @@
+package a.b.reallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage
+
+import a.b.secondreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylongpackage.R
+import androidx.navigation.ActionOnlyNavDirections
+import androidx.navigation.NavDirections
+
+class LongPackageFragmentDirections private constructor() {
+ companion object {
+ fun next(): NavDirections = ActionOnlyNavDirections(R.id.next)
+ }
+}
diff --git a/navigation/ui/api/2.0.0.txt b/navigation/ui/api/2.0.0.txt
new file mode 100644
index 0000000..629226a
--- /dev/null
+++ b/navigation/ui/api/2.0.0.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class AppBarConfiguration {
+ method public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+ method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+ method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+ }
+
+ public static final class AppBarConfiguration.Builder {
+ ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph);
+ ctor public AppBarConfiguration.Builder(android.view.Menu);
+ ctor public AppBarConfiguration.Builder(int...);
+ ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer>);
+ method public androidx.navigation.ui.AppBarConfiguration build();
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout?);
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener?);
+ }
+
+ public static interface AppBarConfiguration.OnNavigateUpListener {
+ method public boolean onNavigateUp();
+ }
+
+ public final class NavigationUI {
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/navigation/ui/api/2.1.0-alpha01.txt b/navigation/ui/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..629226a
--- /dev/null
+++ b/navigation/ui/api/2.1.0-alpha01.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class AppBarConfiguration {
+ method public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+ method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+ method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+ }
+
+ public static final class AppBarConfiguration.Builder {
+ ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph);
+ ctor public AppBarConfiguration.Builder(android.view.Menu);
+ ctor public AppBarConfiguration.Builder(int...);
+ ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer>);
+ method public androidx.navigation.ui.AppBarConfiguration build();
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout?);
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener?);
+ }
+
+ public static interface AppBarConfiguration.OnNavigateUpListener {
+ method public boolean onNavigateUp();
+ }
+
+ public final class NavigationUI {
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/navigation/ui/api/2.1.0-alpha02.txt b/navigation/ui/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..629226a
--- /dev/null
+++ b/navigation/ui/api/2.1.0-alpha02.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class AppBarConfiguration {
+ method public androidx.drawerlayout.widget.DrawerLayout? getDrawerLayout();
+ method public androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener? getFallbackOnNavigateUpListener();
+ method public java.util.Set<java.lang.Integer> getTopLevelDestinations();
+ }
+
+ public static final class AppBarConfiguration.Builder {
+ ctor public AppBarConfiguration.Builder(androidx.navigation.NavGraph);
+ ctor public AppBarConfiguration.Builder(android.view.Menu);
+ ctor public AppBarConfiguration.Builder(int...);
+ ctor public AppBarConfiguration.Builder(java.util.Set<java.lang.Integer>);
+ method public androidx.navigation.ui.AppBarConfiguration build();
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setDrawerLayout(androidx.drawerlayout.widget.DrawerLayout?);
+ method public androidx.navigation.ui.AppBarConfiguration.Builder setFallbackOnNavigateUpListener(androidx.navigation.ui.AppBarConfiguration.OnNavigateUpListener?);
+ }
+
+ public static interface AppBarConfiguration.OnNavigateUpListener {
+ method public boolean onNavigateUp();
+ }
+
+ public final class NavigationUI {
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout?);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar, androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration);
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController);
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController);
+ }
+
+}
+
diff --git a/navigation/ui/api/res-2.0.0.txt b/navigation/ui/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/api/res-2.0.0.txt
diff --git a/navigation/ui/api/res-2.1.0-alpha01.txt b/navigation/ui/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/api/res-2.1.0-alpha01.txt
diff --git a/navigation/ui/api/res-2.1.0-alpha02.txt b/navigation/ui/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/api/res-2.1.0-alpha02.txt
diff --git a/navigation/ui/api/restricted_2.0.0.txt b/navigation/ui/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/api/restricted_2.1.0-alpha01.txt b/navigation/ui/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/api/restricted_2.1.0-alpha02.txt b/navigation/ui/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/build.gradle b/navigation/ui/build.gradle
index 5773283..9f9805c 100644
--- a/navigation/ui/build.gradle
+++ b/navigation/ui/build.gradle
@@ -36,7 +36,7 @@
dependencies {
api(project(":navigation:navigation-runtime"))
- api(NAV_SUPPORT_DESIGN)
+ api(MATERIAL)
testImplementation(JUNIT)
testImplementation(MOCKITO_CORE)
diff --git a/navigation/ui/ktx/api/2.0.0.txt b/navigation/ui/ktx/api/2.0.0.txt
new file mode 100644
index 0000000..d20eb8f
--- /dev/null
+++ b/navigation/ui/ktx/api/2.0.0.txt
@@ -0,0 +1,51 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class AppBarConfigurationKt {
+ ctor public AppBarConfigurationKt();
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ }
+
+ public final class BottomNavigationViewKt {
+ ctor public BottomNavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class CollapsingToolbarLayoutKt {
+ ctor public CollapsingToolbarLayoutKt();
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class MenuItemKt {
+ ctor public MenuItemKt();
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+ }
+
+ public final class NavigationViewKt {
+ ctor public NavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class ToolbarKt {
+ ctor public ToolbarKt();
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+}
+
diff --git a/navigation/ui/ktx/api/2.1.0-alpha01.txt b/navigation/ui/ktx/api/2.1.0-alpha01.txt
new file mode 100644
index 0000000..d20eb8f
--- /dev/null
+++ b/navigation/ui/ktx/api/2.1.0-alpha01.txt
@@ -0,0 +1,51 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class AppBarConfigurationKt {
+ ctor public AppBarConfigurationKt();
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ }
+
+ public final class BottomNavigationViewKt {
+ ctor public BottomNavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class CollapsingToolbarLayoutKt {
+ ctor public CollapsingToolbarLayoutKt();
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class MenuItemKt {
+ ctor public MenuItemKt();
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+ }
+
+ public final class NavigationViewKt {
+ ctor public NavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class ToolbarKt {
+ ctor public ToolbarKt();
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+}
+
diff --git a/navigation/ui/ktx/api/2.1.0-alpha02.txt b/navigation/ui/ktx/api/2.1.0-alpha02.txt
new file mode 100644
index 0000000..d20eb8f
--- /dev/null
+++ b/navigation/ui/ktx/api/2.1.0-alpha02.txt
@@ -0,0 +1,51 @@
+// Signature format: 3.0
+package androidx.navigation.ui {
+
+ public final class ActivityKt {
+ ctor public ActivityKt();
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupActionBarWithNavController(androidx.appcompat.app.AppCompatActivity, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class AppBarConfigurationKt {
+ ctor public AppBarConfigurationKt();
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(androidx.navigation.NavGraph navGraph, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(android.view.Menu topLevelMenu, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ method public static inline androidx.navigation.ui.AppBarConfiguration AppBarConfiguration(java.util.Set<java.lang.Integer> topLevelDestinationIds, androidx.drawerlayout.widget.DrawerLayout? drawerLayout = null, kotlin.jvm.functions.Function0<java.lang.Boolean> fallbackOnNavigateUpListener = { false });
+ }
+
+ public final class BottomNavigationViewKt {
+ ctor public BottomNavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.bottomnavigation.BottomNavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class CollapsingToolbarLayoutKt {
+ ctor public CollapsingToolbarLayoutKt();
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(com.google.android.material.appbar.CollapsingToolbarLayout, androidx.appcompat.widget.Toolbar toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+ public final class MenuItemKt {
+ ctor public MenuItemKt();
+ method public static boolean onNavDestinationSelected(android.view.MenuItem, androidx.navigation.NavController navController);
+ }
+
+ public final class NavControllerKt {
+ ctor public NavControllerKt();
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static boolean navigateUp(androidx.navigation.NavController, androidx.navigation.ui.AppBarConfiguration appBarConfiguration);
+ }
+
+ public final class NavigationViewKt {
+ ctor public NavigationViewKt();
+ method public static void setupWithNavController(com.google.android.material.navigation.NavigationView, androidx.navigation.NavController navController);
+ }
+
+ public final class ToolbarKt {
+ ctor public ToolbarKt();
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.drawerlayout.widget.DrawerLayout? drawerLayout);
+ method public static void setupWithNavController(androidx.appcompat.widget.Toolbar, androidx.navigation.NavController navController, androidx.navigation.ui.AppBarConfiguration configuration = AppBarConfiguration(navController.graph));
+ }
+
+}
+
diff --git a/navigation/ui/ktx/api/res-2.0.0.txt b/navigation/ui/ktx/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/ktx/api/res-2.0.0.txt
diff --git a/navigation/ui/ktx/api/res-2.1.0-alpha01.txt b/navigation/ui/ktx/api/res-2.1.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/ktx/api/res-2.1.0-alpha01.txt
diff --git a/navigation/ui/ktx/api/res-2.1.0-alpha02.txt b/navigation/ui/ktx/api/res-2.1.0-alpha02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/navigation/ui/ktx/api/res-2.1.0-alpha02.txt
diff --git a/navigation/ui/ktx/api/restricted_2.0.0.txt b/navigation/ui/ktx/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/ktx/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/ktx/api/restricted_2.1.0-alpha01.txt b/navigation/ui/ktx/api/restricted_2.1.0-alpha01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/ktx/api/restricted_2.1.0-alpha01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt b/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/navigation/ui/ktx/api/restricted_2.1.0-alpha02.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/paging/common/api/2.2.0-alpha01.ignore b/paging/common/api/2.2.0-alpha01.ignore
new file mode 100644
index 0000000..e7f0267
--- /dev/null
+++ b/paging/common/api/2.2.0-alpha01.ignore
@@ -0,0 +1,13 @@
+// Baseline format: 1.0
+ChangedAbstract: androidx.paging.PagedList#detach():
+ Method androidx.paging.PagedList.detach has changed 'abstract' qualifier
+ChangedAbstract: androidx.paging.PagedList#isDetached():
+ Method androidx.paging.PagedList.isDetached has changed 'abstract' qualifier
+
+
+RemovedMethod: androidx.paging.PositionalDataSource#computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int):
+ Removed method androidx.paging.PositionalDataSource.computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams,int)
+RemovedMethod: androidx.paging.PositionalDataSource#computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int):
+ Removed method androidx.paging.PositionalDataSource.computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams,int,int)
+
+
diff --git a/paging/common/api/2.2.0-alpha01.txt b/paging/common/api/2.2.0-alpha01.txt
index cf13c47..3c9f6d2 100644
--- a/paging/common/api/2.2.0-alpha01.txt
+++ b/paging/common/api/2.2.0-alpha01.txt
@@ -5,8 +5,9 @@
method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
- method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ method public boolean isRetryableError(Throwable);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
}
@@ -21,10 +22,13 @@
method @AnyThread public void onInvalidated();
}
- public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
- method public abstract Key getKey(Value);
+ public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
+ ctor public ItemKeyedDataSource();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key>);
method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
@@ -34,7 +38,6 @@
ctor public ItemKeyedDataSource.LoadCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>);
- method public void onRetryableError(Throwable);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
@@ -42,22 +45,114 @@
method public abstract void onResult(java.util.List<Value>, int, int);
}
- public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+ public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ }
+
+ public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
+ ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ }
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ ctor public ListenableItemKeyedDataSource();
+ method public abstract Key? getKey(Value);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key>);
+ }
+
+ public static class ListenableItemKeyedDataSource.InitialResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V>, int, int);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
- public static class ItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ public static class ListenableItemKeyedDataSource.LoadParams<Key> {
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
field public final Key key;
field public final int requestedLoadSize;
}
- public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ public static class ListenableItemKeyedDataSource.Result<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ ctor public ListenablePageKeyedDataSource();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key>);
+ }
+
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value>, int, int, Key?, Key?);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value>, Key?, Key?);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ }
+
+ public static class ListenablePageKeyedDataSource.LoadParams<Key> {
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ field public final Key key;
+ field public final int requestedLoadSize;
+ }
+
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value>, Key?);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ ctor public ListenablePositionalDataSource();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ }
+
+ public static class ListenablePositionalDataSource.InitialResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V>, int, int);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V>, int);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenablePositionalDataSource.LoadInitialParams {
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ field public final int pageSize;
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ field public final int requestedStartPosition;
+ }
+
+ public static class ListenablePositionalDataSource.LoadRangeParams {
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ field public final int loadSize;
+ field public final int startPosition;
+ }
+
+ public static class ListenablePositionalDataSource.RangeResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
+ ctor public PageKeyedDataSource();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key>);
method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value>);
method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
@@ -67,7 +162,6 @@
ctor public PageKeyedDataSource.LoadCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>, Key?);
- method public void onRetryableError(Throwable);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
@@ -75,32 +169,27 @@
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>, int, int, Key?, Key?);
method public abstract void onResult(java.util.List<Value>, Key?, Key?);
- method public void onRetryableError(Throwable);
}
- public static class PageKeyedDataSource.LoadInitialParams<Key> {
+ public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
- field public final boolean placeholdersEnabled;
- field public final int requestedLoadSize;
}
- public static class PageKeyedDataSource.LoadParams<Key> {
+ public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
ctor public PageKeyedDataSource.LoadParams(Key, int);
- field public final Key key;
- field public final int requestedLoadSize;
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
method public void addWeakCallback(java.util.List<T>?, androidx.paging.PagedList.Callback);
method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
- method public void detach();
+ method public abstract void detach();
method public T? get(int);
method public androidx.paging.PagedList.Config getConfig();
method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
- method public boolean isDetached();
+ method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int);
method public void removeWeakCallback(androidx.paging.PagedList.Callback);
@@ -120,8 +209,9 @@
public static final class PagedList.Builder<Key, Value> {
ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, androidx.paging.PagedList.Config);
ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, int);
- method @WorkerThread public androidx.paging.PagedList<Value> build();
- method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback?);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>?);
method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor);
method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key?);
method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor);
@@ -171,10 +261,13 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
- public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
+ public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
+ ctor public PositionalDataSource();
+ method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
+ method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T>);
method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V>);
method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>>);
@@ -185,28 +278,20 @@
method public void onError(Throwable);
method public abstract void onResult(java.util.List<T>, int, int);
method public abstract void onResult(java.util.List<T>, int);
- method public void onRetryableError(Throwable);
}
- public static class PositionalDataSource.LoadInitialParams {
+ public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
- field public final int pageSize;
- field public final boolean placeholdersEnabled;
- field public final int requestedLoadSize;
- field public final int requestedStartPosition;
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<T>);
- method public void onRetryableError(Throwable);
}
- public static class PositionalDataSource.LoadRangeParams {
+ public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
ctor public PositionalDataSource.LoadRangeParams(int, int);
- field public final int loadSize;
- field public final int startPosition;
}
}
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index cf13c47..3c9f6d2 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -5,8 +5,9 @@
method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
- method public abstract <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
+ method public boolean isRetryableError(Throwable);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
}
@@ -21,10 +22,13 @@
method @AnyThread public void onInvalidated();
}
- public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
- method public abstract Key getKey(Value);
+ public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
+ ctor public ItemKeyedDataSource();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key>);
method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
@@ -34,7 +38,6 @@
ctor public ItemKeyedDataSource.LoadCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>);
- method public void onRetryableError(Throwable);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
@@ -42,22 +45,114 @@
method public abstract void onResult(java.util.List<Value>, int, int);
}
- public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+ public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ }
+
+ public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
+ ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ }
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ ctor public ListenableItemKeyedDataSource();
+ method public abstract Key? getKey(Value);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key>);
+ }
+
+ public static class ListenableItemKeyedDataSource.InitialResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V>, int, int);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
- public static class ItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ public static class ListenableItemKeyedDataSource.LoadParams<Key> {
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
field public final Key key;
field public final int requestedLoadSize;
}
- public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ public static class ListenableItemKeyedDataSource.Result<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ ctor public ListenablePageKeyedDataSource();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key>);
+ }
+
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value>, int, int, Key?, Key?);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value>, Key?, Key?);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ }
+
+ public static class ListenablePageKeyedDataSource.LoadParams<Key> {
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ field public final Key key;
+ field public final int requestedLoadSize;
+ }
+
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value>, Key?);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ ctor public ListenablePositionalDataSource();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ }
+
+ public static class ListenablePositionalDataSource.InitialResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V>, int, int);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V>, int);
+ method public boolean equals(Object?);
+ }
+
+ public static class ListenablePositionalDataSource.LoadInitialParams {
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ field public final int pageSize;
+ field public final boolean placeholdersEnabled;
+ field public final int requestedLoadSize;
+ field public final int requestedStartPosition;
+ }
+
+ public static class ListenablePositionalDataSource.LoadRangeParams {
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ field public final int loadSize;
+ field public final int startPosition;
+ }
+
+ public static class ListenablePositionalDataSource.RangeResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V>);
+ method public boolean equals(Object?);
+ }
+
+ public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
+ ctor public PageKeyedDataSource();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key>);
method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key>, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key>);
method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value>);
method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue>);
method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>>);
@@ -67,7 +162,6 @@
ctor public PageKeyedDataSource.LoadCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>, Key?);
- method public void onRetryableError(Throwable);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
@@ -75,32 +169,27 @@
method public void onError(Throwable);
method public abstract void onResult(java.util.List<Value>, int, int, Key?, Key?);
method public abstract void onResult(java.util.List<Value>, Key?, Key?);
- method public void onRetryableError(Throwable);
}
- public static class PageKeyedDataSource.LoadInitialParams<Key> {
+ public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
- field public final boolean placeholdersEnabled;
- field public final int requestedLoadSize;
}
- public static class PageKeyedDataSource.LoadParams<Key> {
+ public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
ctor public PageKeyedDataSource.LoadParams(Key, int);
- field public final Key key;
- field public final int requestedLoadSize;
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
method public void addWeakCallback(java.util.List<T>?, androidx.paging.PagedList.Callback);
method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
- method public void detach();
+ method public abstract void detach();
method public T? get(int);
method public androidx.paging.PagedList.Config getConfig();
method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
- method public boolean isDetached();
+ method public abstract boolean isDetached();
method public boolean isImmutable();
method public void loadAround(int);
method public void removeWeakCallback(androidx.paging.PagedList.Callback);
@@ -120,8 +209,9 @@
public static final class PagedList.Builder<Key, Value> {
ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, androidx.paging.PagedList.Config);
ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value>, int);
- method @WorkerThread public androidx.paging.PagedList<Value> build();
- method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback?);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>?);
method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor);
method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key?);
method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor);
@@ -171,10 +261,13 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
- public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
+ public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
+ ctor public PositionalDataSource();
+ method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
+ method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T>);
method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V>);
method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>>);
@@ -185,28 +278,20 @@
method public void onError(Throwable);
method public abstract void onResult(java.util.List<T>, int, int);
method public abstract void onResult(java.util.List<T>, int);
- method public void onRetryableError(Throwable);
}
- public static class PositionalDataSource.LoadInitialParams {
+ public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
- field public final int pageSize;
- field public final boolean placeholdersEnabled;
- field public final int requestedLoadSize;
- field public final int requestedStartPosition;
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
method public void onError(Throwable);
method public abstract void onResult(java.util.List<T>);
- method public void onRetryableError(Throwable);
}
- public static class PositionalDataSource.LoadRangeParams {
+ public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
ctor public PositionalDataSource.LoadRangeParams(int, int);
- field public final int loadSize;
- field public final int startPosition;
}
}
diff --git a/paging/common/api/restricted_2.2.0-alpha01.ignore b/paging/common/api/restricted_2.2.0-alpha01.ignore
deleted file mode 100644
index c564305..0000000
--- a/paging/common/api/restricted_2.2.0-alpha01.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.paging.TiledDataSource#TiledDataSource():
- Removed constructor androidx.paging.TiledDataSource()
-
-
diff --git a/paging/common/api/restricted_2.2.0-alpha01.txt b/paging/common/api/restricted_2.2.0-alpha01.txt
index 48a4397..9440866 100644
--- a/paging/common/api/restricted_2.2.0-alpha01.txt
+++ b/paging/common/api/restricted_2.2.0-alpha01.txt
@@ -2,6 +2,7 @@
package androidx.paging {
@Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+ ctor @Deprecated public TiledDataSource();
method @Deprecated @WorkerThread public abstract int countItems();
method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
method @Deprecated @WorkerThread public abstract java.util.List<T>? loadRange(int, int);
@@ -23,6 +24,7 @@
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
+ method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<I>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
}
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index 48a4397..9440866 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -2,6 +2,7 @@
package androidx.paging {
@Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+ ctor @Deprecated public TiledDataSource();
method @Deprecated @WorkerThread public abstract int countItems();
method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
method @Deprecated @WorkerThread public abstract java.util.List<T>? loadRange(int, int);
@@ -23,6 +24,7 @@
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
+ method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<I>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
}
diff --git a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt b/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
index 882c820..860a2dd 100644
--- a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
+++ b/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
@@ -43,9 +43,9 @@
initialKey: Key? = null
): PagedList<Value> {
return PagedList.Builder(dataSource, config)
- .setNotifyExecutor(notifyExecutor)
- .setFetchExecutor(fetchExecutor)
- .setBoundaryCallback(boundaryCallback)
- .setInitialKey(initialKey)
- .build()
+ .setNotifyExecutor(notifyExecutor)
+ .setFetchExecutor(fetchExecutor)
+ .setBoundaryCallback(boundaryCallback)
+ .setInitialKey(initialKey)
+ .build()
}
diff --git a/paging/common/ktx/src/test/java/PagedListTest.kt b/paging/common/ktx/src/test/java/PagedListTest.kt
index caa483a..ed4d20d 100644
--- a/paging/common/ktx/src/test/java/PagedListTest.kt
+++ b/paging/common/ktx/src/test/java/PagedListTest.kt
@@ -17,11 +17,12 @@
import androidx.paging.Config
import androidx.paging.PagedList
import androidx.paging.PositionalDataSource
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import java.util.concurrent.Executor
@RunWith(JUnit4::class)
class PagedListTest {
@@ -30,8 +31,8 @@
val pagedList = PagedList(
dataSource = dataSource,
config = config,
- fetchExecutor = executor,
- notifyExecutor = executor
+ fetchExecutor = DirectExecutor.INSTANCE,
+ notifyExecutor = DirectExecutor.INSTANCE
)
assertEquals(dataSource, pagedList.dataSource)
@@ -44,14 +45,14 @@
params: LoadInitialParams,
callback: LoadInitialCallback<String>
) {
+ callback.onResult(listOf("a"), 0, 1)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ fail()
}
}
private val config = Config(10)
-
- private val executor = Executor { runnable -> runnable.run() }
}
}
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java b/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java
deleted file mode 100644
index 5cde0af..0000000
--- a/paging/common/src/main/java/androidx/paging/ContiguousDataSource.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.concurrent.Executor;
-
-abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
- @Override
- boolean isContiguous() {
- return true;
- }
-
- abstract void dispatchLoadInitial(
- @Nullable Key key,
- int initialLoadSize,
- int pageSize,
- boolean enablePlaceholders,
- @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver);
-
- abstract void dispatchLoadAfter(
- int currentEndIndex,
- @NonNull Value currentEndItem,
- int pageSize,
- @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver);
-
- abstract void dispatchLoadBefore(
- int currentBeginIndex,
- @NonNull Value currentBeginItem,
- int pageSize,
- @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver);
-
- /**
- * Get the key from either the position, or item, or null if position/item invalid.
- * <p>
- * Position may not match passed item's position - if trying to query the key from a position
- * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
- */
- abstract Key getKey(int position, Value item);
-
- boolean supportsPageDropping() {
- return true;
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
index 7233b7e..d5ad40d 100644
--- a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
+++ b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
@@ -16,7 +16,6 @@
package androidx.paging;
-import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -24,9 +23,10 @@
import java.util.List;
import java.util.concurrent.Executor;
-class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback {
+class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback,
+ Pager.PageConsumer<V> {
@SuppressWarnings("WeakerAccess") /* synthetic access */
- final ContiguousDataSource<K, V> mDataSource;
+ final DataSource<K, V> mDataSource;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mPrependItemsRequested = 0;
@@ -39,160 +39,164 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
final boolean mShouldTrim;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
- // Creation thread for initial synchronous load, otherwise main thread
- // Safe to access main thread only state - no other thread has reference during construction
- @AnyThread
- @Override
- public void onPageResult(@PageResult.ResultType int resultType,
- @NonNull PageResult<V> pageResult) {
- if (pageResult.isInvalid()) {
- detach();
- return;
+ /**
+ * Given a page result, apply or drop it, and return whether more loading is needed.
+ */
+ @Override
+ public boolean onPageResult(@NonNull LoadType type,
+ @NonNull DataSource.BaseResult<V> pageResult) {
+ boolean continueLoading = false;
+ @NonNull List<V> page = pageResult.data;
+
+
+ // if we end up trimming, we trim from side that's furthest from most recent access
+ boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();
+
+ // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
+ boolean skipNewPage = mShouldTrim
+ && mStorage.shouldPreTrimNewPage(
+ mConfig.maxSize, mRequiredRemainder, page.size());
+
+ if (type == LoadType.END) {
+ if (skipNewPage && !trimFromFront) {
+ // don't append this data, drop it
+ mAppendItemsRequested = 0;
+ } else {
+ mStorage.appendPage(page, ContiguousPagedList.this);
+ mAppendItemsRequested -= page.size();
+ if (mAppendItemsRequested > 0 && page.size() != 0) {
+ continueLoading = true;
+ }
}
-
- if (isDetached()) {
- // No op, have detached
- return;
+ } else if (type == LoadType.START) {
+ if (skipNewPage && trimFromFront) {
+ // don't append this data, drop it
+ mPrependItemsRequested = 0;
+ } else {
+ mStorage.prependPage(page, ContiguousPagedList.this);
+ mPrependItemsRequested -= page.size();
+ if (mPrependItemsRequested > 0 && page.size() != 0) {
+ continueLoading = true;
+ }
}
+ } else {
+ throw new IllegalArgumentException("unexpected result type " + type);
+ }
- List<V> page = pageResult.page;
- if (resultType == PageResult.INIT) {
- mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
- pageResult.positionOffset, ContiguousPagedList.this);
- // TODO: signal that this list is ready to be dispatched to observer
-
- if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
- // Because the ContiguousPagedList wasn't initialized with a last load position,
- // initialize it to the middle of the initial load
- mLastLoad =
- pageResult.leadingNulls + pageResult.positionOffset + page.size() / 2;
+ if (mShouldTrim) {
+ // Try and trim, but only if the side being trimmed isn't actually fetching.
+ // For simplicity (both of impl here, and contract w/ DataSource) we don't
+ // allow fetches in same direction - this means reading the load state is safe.
+ if (trimFromFront) {
+ if (mPager.mLoadStateManager.getStart() != LoadState.LOADING) {
+ if (mStorage.trimFromFront(
+ mReplacePagesWithNulls,
+ mConfig.maxSize,
+ mRequiredRemainder,
+ ContiguousPagedList.this)) {
+ // trimmed from front, ensure we can fetch in that dir
+ mPager.mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
+ }
}
} else {
- // if we end up trimming, we trim from side that's furthest from most recent access
- boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();
-
- // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
- boolean skipNewPage = mShouldTrim
- && mStorage.shouldPreTrimNewPage(
- mConfig.maxSize, mRequiredRemainder, page.size());
-
- if (resultType == PageResult.APPEND) {
- if (skipNewPage && !trimFromFront) {
- // don't append this data, drop it
- mAppendItemsRequested = 0;
- mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
- } else {
- mStorage.appendPage(page, ContiguousPagedList.this);
- }
- } else if (resultType == PageResult.PREPEND) {
- if (skipNewPage && trimFromFront) {
- // don't append this data, drop it
- mPrependItemsRequested = 0;
- mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
- } else {
- mStorage.prependPage(page, ContiguousPagedList.this);
- }
- } else {
- throw new IllegalArgumentException("unexpected resultType " + resultType);
- }
-
- if (mShouldTrim) {
- // Try and trim, but only if the side being trimmed isn't actually fetching.
- // For simplicity (both of impl here, and contract w/ DataSource) we don't want
- // simultaneous fetches in same direction.
- if (trimFromFront) {
- if (mLoadStateManager.getStart() != LoadState.LOADING) {
- if (mStorage.trimFromFront(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- // trimmed from front, ensure we can fetch in that dir
- mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
- }
- }
- } else {
- if (mLoadStateManager.getEnd() != LoadState.LOADING) {
- if (mStorage.trimFromEnd(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
- }
- }
+ if (mPager.mLoadStateManager.getEnd() != LoadState.LOADING) {
+ if (mStorage.trimFromEnd(
+ mReplacePagesWithNulls,
+ mConfig.maxSize,
+ mRequiredRemainder,
+ ContiguousPagedList.this)) {
+ mPager.mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
}
}
}
-
- if (mBoundaryCallback != null) {
- boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty
- && resultType == PageResult.PREPEND
- && pageResult.page.size() == 0;
- boolean deferEnd = !deferEmpty
- && resultType == PageResult.APPEND
- && pageResult.page.size() == 0;
- deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
- }
}
- @Override
- public void onPageError(@PageResult.ResultType int resultType,
- @NonNull Throwable error, boolean retryable) {
- LoadState errorState = retryable ? LoadState.RETRYABLE_ERROR : LoadState.ERROR;
+ triggerBoundaryCallback(type, page);
+ return continueLoading;
+ }
- if (resultType == PageResult.PREPEND) {
- mLoadStateManager.setState(LoadType.START, errorState, error);
- } else if (resultType == PageResult.APPEND) {
- mLoadStateManager.setState(LoadType.END, errorState, error);
- } else {
- // TODO: pass init signal through to *previous* list
- throw new IllegalStateException("TODO");
- }
+ @Override
+ public void onStateChanged(@NonNull LoadType type, @NonNull LoadState state,
+ @Nullable Throwable error) {
+ dispatchStateChange(type, state, error);
+ }
+
+ private void triggerBoundaryCallback(@NonNull LoadType type, @NonNull List<V> page) {
+ if (mBoundaryCallback != null) {
+ boolean deferEmpty = mStorage.size() == 0;
+ boolean deferBegin = !deferEmpty
+ && type == LoadType.START
+ && page.size() == 0;
+ boolean deferEnd = !deferEmpty
+ && type == LoadType.END
+ && page.size() == 0;
+ deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
}
- };
+ }
+
+ @NonNull
+ private final Pager mPager;
@Override
public void retry() {
super.retry();
- if (mLoadStateManager.getStart() == LoadState.RETRYABLE_ERROR) {
- schedulePrepend();
- }
- if (mLoadStateManager.getEnd() == LoadState.RETRYABLE_ERROR) {
- scheduleAppend();
+ mPager.retry();
+
+ if (mRefreshRetryCallback != null
+ && mPager.mLoadStateManager.getRefresh() == LoadState.RETRYABLE_ERROR) {
+ // Loading the next PagedList failed, signal the retry callback.
+ mRefreshRetryCallback.run();
}
}
static final int LAST_LOAD_UNSPECIFIED = -1;
ContiguousPagedList(
- @NonNull ContiguousDataSource<K, V> dataSource,
+ @NonNull DataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
- @Nullable K key,
+ @NonNull DataSource.BaseResult<V> initialResult,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
+ mPager = new Pager<>(config, dataSource, mainThreadExecutor, backgroundThreadExecutor,
+ this, mStorage, initialResult);
- if (mDataSource.isInvalid()) {
- detach();
+ if (config.enablePlaceholders) {
+ // Placeholders enabled, pass raw data to storage init
+ mStorage.init(initialResult.leadingNulls, initialResult.data,
+ initialResult.trailingNulls, initialResult.offset, this);
} else {
- mDataSource.dispatchLoadInitial(key,
- mConfig.initialLoadSizeHint,
- mConfig.pageSize,
- mConfig.enablePlaceholders,
- mMainThreadExecutor,
- mReceiver);
+ // If placeholder are disabled, avoid passing leading/trailing nulls,
+ // since DataSource may have passed them anyway
+ mStorage.init(0, initialResult.data,
+ 0, initialResult.offset + initialResult.leadingNulls, this);
}
+
mShouldTrim = mDataSource.supportsPageDropping()
&& mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
+
+ if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
+ // Because the ContiguousPagedList wasn't initialized with a last load position,
+ // initialize it to the middle of the initial load
+ mLastLoad = initialResult.leadingNulls + initialResult.offset
+ + initialResult.data.size() / 2;
+ }
+ triggerBoundaryCallback(LoadType.REFRESH, initialResult.data);
+ }
+
+ @Override
+ void dispatchCurrentLoadState(LoadStateListener listener) {
+ mPager.mLoadStateManager.dispatchCurrentLoadState(listener);
+ }
+
+ @Override
+ void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {
+ mPager.mLoadStateManager.setState(LoadType.REFRESH, loadState, error);
}
@MainThread
@@ -263,63 +267,24 @@
mStorage.getLeadingNullCount() + mStorage.getStorageCount());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
- if (mPrependItemsRequested > 0 && mLoadStateManager.getStart() == LoadState.IDLE) {
- schedulePrepend();
+ if (mPrependItemsRequested > 0) {
+ mPager.trySchedulePrepend();
}
mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
- if (mAppendItemsRequested > 0 && mLoadStateManager.getEnd() == LoadState.IDLE) {
- scheduleAppend();
+ if (mAppendItemsRequested > 0) {
+ mPager.tryScheduleAppend();
}
}
- @MainThread
- private void schedulePrepend() {
- mLoadStateManager.setState(LoadType.START, LoadState.LOADING, null);
-
- final int position = mStorage.getLeadingNullCount() + mStorage.getPositionOffset();
-
- // safe to access first item here - mStorage can't be empty if we're prepending
- final V item = mStorage.getFirstLoadedItem();
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (isDetached()) {
- return;
- }
- if (mDataSource.isInvalid()) {
- detach();
- } else {
- mDataSource.dispatchLoadBefore(position, item, mConfig.pageSize,
- mMainThreadExecutor, mReceiver);
- }
- }
- });
+ @Override
+ public boolean isDetached() {
+ return mPager.isDetached();
}
- @MainThread
- private void scheduleAppend() {
- mLoadStateManager.setState(LoadType.END, LoadState.LOADING, null);
-
- final int position = mStorage.getLeadingNullCount()
- + mStorage.getStorageCount() - 1 + mStorage.getPositionOffset();
-
- // safe to access first item here - mStorage can't be empty if we're appending
- final V item = mStorage.getLastLoadedItem();
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (isDetached()) {
- return;
- }
- if (mDataSource.isInvalid()) {
- detach();
- } else {
- mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
- mMainThreadExecutor, mReceiver);
- }
- }
- });
+ @Override
+ public void detach() {
+ mPager.detach();
}
@Override
@@ -351,14 +316,6 @@
@MainThread
@Override
public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
- // consider whether to post more work, now that a page is fully prepended
- mPrependItemsRequested = mPrependItemsRequested - changedCount - addedCount;
- if (mPrependItemsRequested > 0) {
- // not done prepending, keep going
- schedulePrepend();
- } else {
- mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
- }
// finally dispatch callbacks, after prepend may have already been scheduled
notifyChanged(leadingNulls, changedCount);
@@ -369,32 +326,12 @@
@MainThread
@Override
- public void onEmptyPrepend() {
- mLoadStateManager.setState(LoadType.START, LoadState.DONE, null);
- }
-
- @MainThread
- @Override
public void onPageAppended(int endPosition, int changedCount, int addedCount) {
- // consider whether to post more work, now that a page is fully appended
- mAppendItemsRequested = mAppendItemsRequested - changedCount - addedCount;
- if (mAppendItemsRequested > 0) {
- // not done appending, keep going
- scheduleAppend();
- } else {
- mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
- }
-
// finally dispatch callbacks, after append may have already been scheduled
notifyChanged(endPosition, changedCount);
notifyInserted(endPosition + changedCount, addedCount);
}
- @MainThread
- @Override
- public void onEmptyAppend() {
- mLoadStateManager.setState(LoadType.END, LoadState.DONE, null);
- }
@MainThread
@Override
diff --git a/paging/common/src/main/java/androidx/paging/DataSource.java b/paging/common/src/main/java/androidx/paging/DataSource.java
index fe4f126..2313653 100644
--- a/paging/common/src/main/java/androidx/paging/DataSource.java
+++ b/paging/common/src/main/java/androidx/paging/DataSource.java
@@ -17,13 +17,15 @@
package androidx.paging;
import androidx.annotation.AnyThread;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.arch.core.util.Function;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -36,7 +38,7 @@
* it loads more data, but the data loaded cannot be updated. If the underlying data set is
* modified, a new PagedList / DataSource pair must be created to represent the new data.
* <h4>Loading Pages</h4>
- * PagedList queries data from its DataSource in response to loading hints. {@link PagedListAdapter}
+ * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
* calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
* <p>
* To control how and when a PagedList queries data from its DataSource, see
@@ -125,7 +127,7 @@
* DataSource becomes invalid, the only way to query more data is to create a new DataSource
* from the Factory.
* <p>
- * {@link LivePagedListBuilder} for example will construct a new PagedList and DataSource
+ * {@link androidx.paging.LivePagedListBuilder} for example will construct a new PagedList and DataSource
* when the current DataSource is invalidated, and pass the new PagedList through the
* {@code LiveData<PagedList>} to observers.
*
@@ -206,10 +208,6 @@
return dest;
}
- // Since we currently rely on implementation details of two implementations,
- // prevent external subclassing, except through exposed subclasses
- DataSource() {
- }
/**
* Applies the given function to each value emitted by the DataSource.
@@ -227,8 +225,10 @@
* @see DataSource.Factory#mapByPage(Function)
*/
@NonNull
- public abstract <ToValue> DataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function);
+ public <ToValue> DataSource<Key, ToValue> mapByPage(
+ @NonNull Function<List<Value>, List<ToValue>> function) {
+ return new WrapperDataSource<>(this, function);
+ }
/**
* Applies the given function to each value emitted by the DataSource.
@@ -246,111 +246,21 @@
* @see DataSource.Factory#mapByPage(Function)
*/
@NonNull
- public abstract <ToValue> DataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function);
+ public <ToValue> DataSource<Key, ToValue> map(
+ @NonNull Function<Value, ToValue> function) {
+ return mapByPage(createListFunction(function));
+ }
/**
* Returns true if the data source guaranteed to produce a contiguous set of items,
* never producing gaps.
*/
- abstract boolean isContiguous();
+ boolean isContiguous() {
+ return true;
+ }
- static class LoadCallbackHelper<T> {
- static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
- if (position < 0) {
- throw new IllegalArgumentException("Position must be non-negative");
- }
- if (data.size() + position > totalCount) {
- throw new IllegalArgumentException(
- "List size + position too large, last item in list beyond totalCount.");
- }
- if (data.size() == 0 && totalCount > 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- }
-
- @PageResult.ResultType
- final int mResultType;
- private final DataSource mDataSource;
- final PageResult.Receiver<T> mReceiver;
-
- // mSignalLock protects mPostExecutor, and mHasSignalled
- private final Object mSignalLock = new Object();
- private Executor mPostExecutor = null;
-
- @GuardedBy("mSignalLock")
- private boolean mHasSignalled = false;
-
- LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
- @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
- mDataSource = dataSource;
- mResultType = resultType;
- mPostExecutor = mainThreadExecutor;
- mReceiver = receiver;
- }
-
- void setPostExecutor(Executor postExecutor) {
- synchronized (mSignalLock) {
- mPostExecutor = postExecutor;
- }
- }
-
- /**
- * Call before verifying args, or dispatching actul results
- *
- * @return true if DataSource was invalid, and invalid result dispatched
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean dispatchInvalidResultIfInvalid() {
- if (mDataSource.isInvalid()) {
- dispatchResultToReceiver(PageResult.<T>getInvalidResult());
- return true;
- }
- return false;
- }
-
- void dispatchResultToReceiver(@NonNull PageResult<T> result) {
- dispatchToReceiver(result, null, false);
- }
-
- void dispatchErrorToReceiver(@NonNull Throwable error, boolean retryable) {
- dispatchToReceiver(null, error, retryable);
- }
-
- private void dispatchToReceiver(final @Nullable PageResult<T> result,
- final @Nullable Throwable error, final boolean retryable) {
- Executor executor;
- synchronized (mSignalLock) {
- if (mHasSignalled) {
- throw new IllegalStateException(
- "callback.onResult/onError already called, cannot call again.");
- }
- mHasSignalled = true;
- executor = mPostExecutor;
- }
-
- if (executor != null) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- dispatchOnCurrentThread(result, error, retryable);
- }
- });
- } else {
- dispatchOnCurrentThread(result, error, retryable);
- }
- }
-
- @SuppressWarnings("ConstantConditions")
- void dispatchOnCurrentThread(@Nullable PageResult<T> result,
- @Nullable Throwable error, boolean retryable) {
- if (result != null) {
- mReceiver.onPageResult(mResultType, result);
- } else {
- mReceiver.onPageError(mResultType, error, retryable);
- }
- }
+ boolean supportsPageDropping() {
+ return true;
}
/**
@@ -385,12 +295,15 @@
* A data source will only invoke its callbacks once - the first time {@link #invalidate()}
* is called, on that thread.
*
- * @param onInvalidatedCallback The callback, will be invoked on thread that
- * {@link #invalidate()} is called on.
+ * @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
+ * DataSource.
*/
@AnyThread
- @SuppressWarnings("WeakerAccess")
public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ //noinspection ConstantConditions
+ if (onInvalidatedCallback == null) {
+ throw new IllegalArgumentException("onInvalidatedCallback must be non-null");
+ }
mOnInvalidatedCallbacks.add(onInvalidatedCallback);
}
@@ -400,7 +313,6 @@
* @param onInvalidatedCallback The previously added callback.
*/
@AnyThread
- @SuppressWarnings("WeakerAccess")
public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
}
@@ -428,4 +340,206 @@
public boolean isInvalid() {
return mInvalid.get();
}
+
+ enum LoadType {
+ INITIAL,
+ START,
+ END
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class Params<K> {
+ @NonNull
+ public final LoadType type;
+ /* can be NULL for init, otherwise non-null */
+ @Nullable
+ public final K key;
+ public final int initialLoadSize;
+ public final boolean placeholdersEnabled;
+ public final int pageSize;
+
+ Params(@NonNull LoadType type, @Nullable K key, int initialLoadSize,
+ boolean placeholdersEnabled, int pageSize) {
+ this.type = type;
+ this.key = key;
+ this.initialLoadSize = initialLoadSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ this.pageSize = pageSize;
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ static class BaseResult<Value> {
+ @SuppressWarnings("unchecked")
+ static <T> BaseResult<T> empty() {
+ return (BaseResult<T>) EMPTY;
+ }
+
+ private static final BaseResult<Object> EMPTY =
+ new BaseResult<>(Collections.emptyList(), null, null, 0, 0, 0, true);
+
+ public final List<Value> data;
+ public final Object prevKey;
+ public final Object nextKey;
+ public final int leadingNulls;
+ public final int trailingNulls;
+ public final int offset;
+ /**
+ * Set to true if the result is an initial load that is passed totalCount
+ */
+ public final boolean counted;
+
+ protected BaseResult(List<Value> data, Object prevKey, Object nextKey, int leadingNulls,
+ int trailingNulls, int offset, boolean counted) {
+ this.data = data;
+ this.prevKey = prevKey;
+ this.nextKey = nextKey;
+ this.leadingNulls = leadingNulls;
+ this.trailingNulls = trailingNulls;
+ this.offset = offset;
+ this.counted = counted;
+ validate();
+ }
+
+ <ToValue> BaseResult(@NonNull BaseResult<ToValue> result,
+ @NonNull Function<List<ToValue>, List<Value>> function) {
+ data = convert(function, result.data);
+ prevKey = result.prevKey;
+ nextKey = result.nextKey;
+ leadingNulls = result.leadingNulls;
+ trailingNulls = result.trailingNulls;
+ offset = result.offset;
+ counted = result.counted;
+ validate();
+ }
+
+ private int position() {
+ // only one of leadingNulls / offset may be used
+ return leadingNulls + offset;
+ }
+
+ static final int TOTAL_COUNT_UNKNOWN = -1;
+
+ int totalCount() {
+ // only one of leadingNulls / offset may be used
+ if (counted) {
+ return position() + data.size() + trailingNulls;
+ } else {
+ return TOTAL_COUNT_UNKNOWN;
+ }
+
+ }
+
+ void validate() {
+ if (leadingNulls < 0 || offset < 0) {
+ throw new IllegalArgumentException("Position must be non-negative");
+ }
+ if (data.isEmpty() && (leadingNulls != 0 || trailingNulls != 0)) {
+ throw new IllegalArgumentException("Initial result cannot be empty if items are"
+ + " present in data set.");
+ }
+ if (trailingNulls < 0) {
+ throw new IllegalArgumentException(
+ "List size + position too large, last item in list beyond totalCount.");
+ }
+ }
+
+ void validateForInitialTiling(int pageSize) {
+ if (!counted) {
+ throw new IllegalStateException("Placeholders requested, but totalCount not"
+ + " provided. Please call the three-parameter onResult method, or"
+ + " disable placeholders in the PagedList.Config");
+ }
+ if (trailingNulls != 0
+ && data.size() % pageSize != 0) {
+ int totalCount = leadingNulls + data.size() + trailingNulls;
+ throw new IllegalArgumentException("PositionalDataSource requires initial load size"
+ + " to be a multiple of page size to support internal tiling. loadSize "
+ + data.size() + ", position " + leadingNulls + ", totalCount " + totalCount
+ + ", pageSize " + pageSize);
+ }
+ if (position() % pageSize != 0) {
+ throw new IllegalArgumentException("Initial load must be pageSize aligned."
+ + "Position = " + position() + ", pageSize = " + pageSize);
+ }
+ }
+
+ @SuppressWarnings("EqualsHashCode")
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BaseResult)) {
+ return false;
+ }
+ BaseResult other = (BaseResult) o;
+ return data.equals(other.data)
+ && PagedList.equalsHelper(prevKey, other.prevKey)
+ && PagedList.equalsHelper(nextKey, other.nextKey)
+ && leadingNulls == other.leadingNulls
+ && trailingNulls == other.trailingNulls
+ && offset == other.offset
+ && counted == other.counted;
+ }
+ }
+
+ enum KeyType {
+ POSITIONAL,
+ PAGE_KEYED,
+ ITEM_KEYED,
+ }
+
+ @NonNull
+ final KeyType mType;
+
+ // Since we currently rely on implementation details of two implementations,
+ // prevent external subclassing, except through exposed subclasses
+ DataSource(@NonNull KeyType type) {
+ mType = type;
+ }
+
+ abstract ListenableFuture<? extends BaseResult<Value>> load(
+ @NonNull Params<Key> params);
+
+ @Nullable
+ abstract Key getKey(@NonNull Value item);
+
+ @Nullable
+ final Key getKey(int lastLoad, @Nullable Value item) {
+ if (mType == KeyType.POSITIONAL) {
+ //noinspection unchecked
+ return (Key) ((Integer) lastLoad);
+ }
+ if (item == null) {
+ return null;
+ }
+ return getKey(item);
+ }
+
+ /**
+ * Determine whether an error passed to a loading method is retryable.
+ *
+ * @param error Throwable returned from an attempted load from this DataSource.
+ * @return true if the error is retryable, otherwise false.
+ */
+ public boolean isRetryableError(@NonNull Throwable error) {
+ return false;
+ }
+
+ final void initExecutor(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ /**
+ * Null until loadInitial is called by PagedList construction
+ */
+ @Nullable
+ private Executor mExecutor;
+
+ @NonNull
+ Executor getExecutor() {
+ if (mExecutor == null) {
+ throw new IllegalStateException(
+ "This DataSource has not been passed to a PagedList, has no executor yet.");
+ }
+ return mExecutor;
+ }
}
diff --git a/paging/common/src/main/java/androidx/paging/InitialPagedList.java b/paging/common/src/main/java/androidx/paging/InitialPagedList.java
new file mode 100644
index 0000000..2589bdf
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/InitialPagedList.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.paging.futures.DirectExecutor;
+
+/**
+ * InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
+ *
+ * It's used solely for listening to {@link PagedList.LoadType#REFRESH} loading events, and retrying
+ * any errors that occur during initial load.
+ */
+class InitialPagedList<K, V> extends ContiguousPagedList<K, V> {
+ @Nullable
+ private K mInitialKey;
+
+ InitialPagedList(
+ @NonNull DataSource<K, V> dataSource,
+ @NonNull Config config,
+ @Nullable K initialKey) {
+ super(dataSource,
+ DirectExecutor.INSTANCE,
+ DirectExecutor.INSTANCE,
+ null,
+ config,
+ DataSource.BaseResult.<V>empty(),
+ /* no previous load, so pass 0 */ 0);
+ mInitialKey = initialKey;
+ }
+
+ @Nullable
+ @Override
+ public Object getLastKey() {
+ return mInitialKey;
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
index 493aba7..bba7477d 100644
--- a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
@@ -19,16 +19,18 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
+import androidx.concurrent.futures.ResolvableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
* <p>
* Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
- * to load item {@code N}. This is common, for example, in sorted database queries where
+ * to load item {@code N}. This is common, for example, in uniquely sorted database queries where
* attributes of the item such just before the next query define how to execute it.
* <p>
* The {@code InMemoryByItemRepository} in the
@@ -37,10 +39,13 @@
* <a href="https://square.github.io/retrofit/">Retrofit</a>, while
* handling swipe-to-refresh, network errors, and retry.
*
+ * @see ListenableItemKeyedDataSource
+ *
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
-public abstract class ItemKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
+public abstract class ItemKeyedDataSource<Key, Value> extends
+ ListenableItemKeyedDataSource<Key, Value> {
/**
* Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
@@ -48,36 +53,11 @@
* @param <Key> Type of data used to query Value types out of the DataSource.
*/
@SuppressWarnings("WeakerAccess")
- public static class LoadInitialParams<Key> {
- /**
- * Load items around this key, or at the beginning of the data set if {@code null} is
- * passed.
- * <p>
- * Note that this key is generally a hint, and may be ignored if you want to always load
- * from the beginning.
- */
- @Nullable
- public final Key requestedInitialKey;
-
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the total count passed to
- * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
- */
- public final boolean placeholdersEnabled;
-
-
+ public static class LoadInitialParams<Key> extends
+ ListenableItemKeyedDataSource.LoadInitialParams<Key> {
public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
boolean placeholdersEnabled) {
- this.requestedInitialKey = requestedInitialKey;
- this.requestedLoadSize = requestedLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
+ super(requestedInitialKey, requestedLoadSize, placeholdersEnabled);
}
}
@@ -88,25 +68,9 @@
* @param <Key> Type of data used to query Value types out of the DataSource.
*/
@SuppressWarnings("WeakerAccess")
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
+ public static class LoadParams<Key> extends ListenableItemKeyedDataSource.LoadParams<Key> {
public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
+ super(key, requestedLoadSize);
}
}
@@ -184,9 +148,9 @@
public abstract void onResult(@NonNull List<Value> data);
/**
- * Called to report a non-retryable error from a DataSource.
+ * Called to report an error from a DataSource.
* <p>
- * Call this method to report a non-retryable error from
+ * Call this method to report an error from
* {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
* {@link #loadBefore(LoadParams, LoadCallback)}, or
* {@link #loadAfter(LoadParams, LoadCallback)} methods.
@@ -198,132 +162,86 @@
throw new IllegalStateException(
"You must implement onError if implementing your own load callback");
}
-
- /**
- * Called to report a retryable error from a DataSource.
- * <p>
- * Call this method to report a retryable error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
- * {@link #loadBefore(LoadParams, LoadCallback)}, or
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onRetryableError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onRetryableError if implementing your own load callback");
- }
}
- static class LoadInitialCallbackImpl<Value> extends LoadInitialCallback<Value> {
- final LoadCallbackHelper<Value> mCallbackHelper;
- private final boolean mCountingEnabled;
- LoadInitialCallbackImpl(@NonNull ItemKeyedDataSource dataSource, boolean countingEnabled,
- @NonNull PageResult.Receiver<Value> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
- mCountingEnabled = countingEnabled;
- }
+ @NonNull
+ @Override
+ public final ListenableFuture<InitialResult<Value>> loadInitial(
+ final @NonNull ListenableItemKeyedDataSource.LoadInitialParams<Key> params) {
+ final ResolvableFuture<InitialResult<Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ LoadInitialCallback<Value> callback = new LoadInitialCallback<Value>() {
+ @Override
+ public void onResult(@NonNull List<Value> data, int position, int totalCount) {
+ future.set(new InitialResult<>(data, position, totalCount));
+ }
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
+ @Override
+ public void onResult(@NonNull List<Value> data) {
+ future.set(new InitialResult<>(data));
+ }
- int trailingUnloadedCount = totalCount - position - data.size();
- if (mCountingEnabled) {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
- data, position, trailingUnloadedCount, 0));
- } else {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
- }
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
+ }
+ };
+ loadInitial(new LoadInitialParams<>(
+ params.requestedInitialKey,
+ params.requestedLoadSize,
+ params.placeholdersEnabled),
+ callback);
}
- }
+ });
+ return future;
+ }
- @Override
- public void onResult(@NonNull List<Value> data) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ @SuppressWarnings("WeakerAccess")
+ LoadCallback<Value> getFutureAsCallback(
+ final @NonNull ResolvableFuture<Result<Value>> future) {
+ return new LoadCallback<Value>() {
+ @Override
+ public void onResult(@NonNull List<Value> data) {
+ future.set(new Result<>(data));
}
- }
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
- }
-
- static class LoadCallbackImpl<Value> extends LoadCallback<Value> {
- final LoadCallbackHelper<Value> mCallbackHelper;
-
- LoadCallbackImpl(@NonNull ItemKeyedDataSource dataSource, @PageResult.ResultType int type,
- @Nullable Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(
- dataSource, type, mainThreadExecutor, receiver);
- }
-
- @Override
- public void onResult(@NonNull List<Value> data) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
}
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
+ };
}
- @Nullable
+ @NonNull
@Override
- final Key getKey(int position, Value item) {
- if (item == null) {
- return null;
- }
-
- return getKey(item);
+ public final ListenableFuture<Result<Value>> loadBefore(
+ final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
+ final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ loadBefore(new LoadParams<>(params.key, params.requestedLoadSize),
+ getFutureAsCallback(future));
+ }
+ });
+ return future;
}
+ @NonNull
@Override
- final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
- boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- LoadInitialCallbackImpl<Value> callback =
- new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
- loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
- }
-
- @Override
- final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
- int pageSize, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),
- new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
- }
-
- @Override
- final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
- int pageSize, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),
- new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+ public final ListenableFuture<Result<Value>> loadAfter(
+ final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
+ final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ loadAfter(new LoadParams<>(params.key, params.requestedLoadSize),
+ getFutureAsCallback(future));
+ }
+ });
+ return future;
}
/**
@@ -338,7 +256,7 @@
* {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
* are hints, not requirements, so they may be altered or ignored. Note that ignoring the
* {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
- * initializing at the same location. If your data source never invalidates (for example,
+ * initializing at the same location. If your DataSource never invalidates (for example,
* loading from the network without the network ever signalling that old data must be reloaded),
* it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
* data set.
@@ -346,20 +264,22 @@
* @param params Parameters for initial load, including initial key and requested size.
* @param callback Callback that receives initial load data.
*/
- public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
+ public abstract void loadInitial(
+ @NonNull LoadInitialParams<Key> params,
@NonNull LoadInitialCallback<Value> callback);
/**
* Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
* <p>
* Data may be passed synchronously during the loadAfter method, or deferred and called at a
* later time. Further loads going down will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key to load after, and requested size.
@@ -372,7 +292,8 @@
* Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
* <p>
* <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
* passed, so if you vary size, ensure that the last item is adjacent to the passed key.
@@ -381,7 +302,7 @@
* later time. Further loads going up will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key to load before, and requested size.
@@ -408,6 +329,7 @@
* @return Key associated with given item.
*/
@NonNull
+ @Override
public abstract Key getKey(@NonNull Value item);
@NonNull
diff --git a/paging/common/src/main/java/androidx/paging/ListDataSource.java b/paging/common/src/main/java/androidx/paging/ListDataSource.java
index 481d5f0..3f0971f 100644
--- a/paging/common/src/main/java/androidx/paging/ListDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/ListDataSource.java
@@ -32,7 +32,6 @@
public void loadInitial(@NonNull LoadInitialParams params,
@NonNull LoadInitialCallback<T> callback) {
final int totalCount = mList.size();
-
final int position = computeInitialLoadPosition(params, totalCount);
final int loadSize = computeInitialLoadSize(params, position, totalCount);
@@ -46,6 +45,6 @@
public void loadRange(@NonNull LoadRangeParams params,
@NonNull LoadRangeCallback<T> callback) {
callback.onResult(mList.subList(params.startPosition,
- params.startPosition + params.loadSize));
+ Math.min(mList.size(), params.startPosition + params.loadSize)));
}
}
diff --git a/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
new file mode 100644
index 0000000..e81193d
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ * <p>
+ * Implement a DataSource using ListenableItemKeyedDataSource if you need to use data from item
+ * {@code N - 1} to load item {@code N}. This is common, for example, in uniquely sorted database
+ * queries where attributes of the item such just before the next query define how to execute it.
+ *
+ * @see ItemKeyedDataSource
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+public abstract class ListenableItemKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
+ public ListenableItemKeyedDataSource() {
+ super(KeyType.ITEM_KEYED);
+ }
+
+ @Override
+ final ListenableFuture<? extends BaseResult<Value>> load(@NonNull Params<Key> params) {
+ if (params.type == LoadType.INITIAL) {
+ ItemKeyedDataSource.LoadInitialParams<Key> initParams =
+ new ItemKeyedDataSource.LoadInitialParams<>(params.key,
+ params.initialLoadSize, params.placeholdersEnabled);
+ return loadInitial(initParams);
+ } else {
+ //noinspection ConstantConditions (key is known to be non-null for non-initial queries)
+ ItemKeyedDataSource.LoadParams<Key> loadParams =
+ new ItemKeyedDataSource.LoadParams<>(params.key, params.pageSize);
+
+ if (params.type == LoadType.START) {
+ return loadBefore(loadParams);
+ } else if (params.type == LoadType.END) {
+ return loadAfter(loadParams);
+ }
+ }
+ throw new IllegalArgumentException("Unsupported type " + params.type.toString());
+ }
+
+
+ /**
+ * Holder object for inputs to {@code loadInitial()}.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ */
+ public static class LoadInitialParams<Key> {
+ /**
+ * Load items around this key, or at the beginning of the data set if {@code null} is
+ * passed.
+ * <p>
+ * Note that this key is generally a hint, and may be ignored if you want to always load
+ * from the beginning.
+ */
+ @Nullable
+ public final Key requestedInitialKey;
+
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Note that this may be larger than available data.
+ */
+ public final int requestedLoadSize;
+
+ /**
+ * Defines whether placeholders are enabled, and whether the loaded total count will be
+ * ignored.
+ */
+ public final boolean placeholdersEnabled;
+
+ public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
+ boolean placeholdersEnabled) {
+ this.requestedInitialKey = requestedInitialKey;
+ this.requestedLoadSize = requestedLoadSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@code loadBefore()} and {@code loadAfter()}.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ */
+ public static class LoadParams<Key> {
+ /**
+ * Load items before/after this key.
+ * <p>
+ * Returned data must begin directly adjacent to this position.
+ */
+ @NonNull
+ public final Key key;
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Returned page can be of this size, but it may be altered if that is easier, e.g. a
+ * network data source where the backend defines page size.
+ */
+ public final int requestedLoadSize;
+
+ public LoadParams(@NonNull Key key, int requestedLoadSize) {
+ this.key = key;
+ this.requestedLoadSize = requestedLoadSize;
+ }
+ }
+
+ /**
+ * Load initial data.
+ * <p>
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass {@code totalCount}
+ * to the {@link InitialResult} constructor. This enables PagedLists presenting data from this
+ * source to display placeholders to represent unloaded items.
+ * <p>
+ * {@link ItemKeyedDataSource.LoadInitialParams#requestedInitialKey} and
+ * {@link ItemKeyedDataSource.LoadInitialParams#requestedLoadSize} are hints, not requirements,
+ * so they may be altered or ignored. Note that ignoring the {@code requestedInitialKey} can
+ * prevent subsequent PagedList/DataSource pairs from initializing at the same location. If your
+ * DataSource never invalidates (for example, loading from the network without the network ever
+ * signalling that old data must be reloaded), it's fine to ignore the {@code initialLoadKey}
+ * and always start from the beginning of the data set.
+ *
+ * @param params Parameters for initial load, including initial key and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<InitialResult<Value>> loadInitial(
+ @NonNull LoadInitialParams<Key> params);
+
+ /**
+ * Load list data after the key specified in
+ * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load after, and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<Result<Value>> loadAfter(@NonNull LoadParams<Key> params);
+
+
+ /**
+ * Load list data after the key specified in
+ * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ * <p>
+ * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
+ * passed, so if you don't return a page of the requested size, ensure that the last item is
+ * adjacent to the passed key.
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load before, and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<Result<Value>> loadBefore(@NonNull LoadParams<Key> params);
+
+
+ @Nullable
+ @Override
+ public abstract Key getKey(@NonNull Value item);
+
+ /**
+ * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
+ * initially loaded data.
+ *
+ * @param <V> The type of the data loaded.
+ */
+ public static class InitialResult<V> extends BaseResult<V> {
+ public InitialResult(@NonNull List<V> data, int position, int totalCount) {
+ super(data, null, null, position, totalCount - data.size() - position, position, true);
+ }
+
+ public InitialResult(@NonNull List<V> data) {
+ super(data, null, null, 0, 0, 0, false);
+ }
+ }
+
+ /**
+ * Type produced by {@link #loadBefore(LoadParams)} and
+ * {@link #loadAfter(LoadParams)} to represent a page of loaded data.
+ *
+ * @param <V> The type of the data loaded.
+ */
+ public static class Result<V> extends BaseResult<V> {
+ public Result(@NonNull List<V> data) {
+ super(data, null, null, 0, 0, 0, false);
+ }
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
new file mode 100644
index 0000000..f15075c
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.concurrent.futures.ResolvableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Incremental data loader for page-keyed content, where requests return keys for next/previous
+ * pages.
+ * <p>
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
+ * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ *
+ * @param <Key> Type of data used to query Value types out of the DataSource.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+public abstract class ListenablePageKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
+ public ListenablePageKeyedDataSource() {
+ super(KeyType.PAGE_KEYED);
+ }
+
+ @Override
+ final ListenableFuture<? extends BaseResult<Value>> load(
+ @NonNull Params<Key> params) {
+ if (params.type == LoadType.INITIAL) {
+ PageKeyedDataSource.LoadInitialParams<Key> initParams =
+ new PageKeyedDataSource.LoadInitialParams<>(
+ params.initialLoadSize, params.placeholdersEnabled);
+ return loadInitial(initParams);
+ } else {
+ if (params.key == null) {
+ // null key, immediately return empty data
+ ResolvableFuture<BaseResult<Value>> future = ResolvableFuture.create();
+ future.set(BaseResult.<Value>empty());
+ return future;
+ }
+
+ PageKeyedDataSource.LoadParams<Key> loadParams =
+ new PageKeyedDataSource.LoadParams<>(params.key, params.pageSize);
+
+ if (params.type == LoadType.START) {
+ return loadBefore(loadParams);
+ } else if (params.type == LoadType.END) {
+ return loadAfter(loadParams);
+ }
+ }
+ throw new IllegalArgumentException("Unsupported type " + params.type.toString());
+ }
+
+ /**
+ * Holder object for inputs to {@code loadInitial()}.
+ *
+ * @param <Key> Type of data used to query pages.
+ */
+ @SuppressWarnings("unused")
+ public static class LoadInitialParams<Key> {
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Note that this may be larger than available data.
+ */
+ public final int requestedLoadSize;
+
+ /**
+ * Defines whether placeholders are enabled, and whether the loaded total count will be
+ * ignored.
+ */
+ public final boolean placeholdersEnabled;
+
+
+ public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
+ this.requestedLoadSize = requestedLoadSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@code loadBefore()} and {@code loadAfter()}.
+ *
+ * @param <Key> Type of data used to query pages.
+ */
+ public static class LoadParams<Key> {
+ /**
+ * Load items before/after this key.
+ * <p>
+ * Returned data must begin directly adjacent to this position.
+ */
+ @NonNull
+ public final Key key;
+
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Returned page can be of this size, but it may be altered if that is easier, e.g. a
+ * network data source where the backend defines page size.
+ */
+ public final int requestedLoadSize;
+
+ public LoadParams(@NonNull Key key, int requestedLoadSize) {
+ this.key = key;
+ this.requestedLoadSize = requestedLoadSize;
+ }
+ }
+
+ /**
+ * Load initial data.
+ * <p>
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the position and
+ * count to the
+ * {@link InitialResult InitialResult constructor}. This
+ * enables PagedLists presenting data from this source to display placeholders to represent
+ * unloaded items.
+ * <p>
+ * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement,
+ * so it may be may be altered or ignored.
+ *
+ * @param params Parameters for initial load, including requested load size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<InitialResult<Key, Value>> loadInitial(
+ @NonNull LoadInitialParams<Key> params);
+
+ /**
+ * Prepend page with the key specified by
+ * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<Result<Key, Value>> loadBefore(
+ @NonNull LoadParams<Key> params);
+ /**
+ * Append page with the key specified by
+ * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
+ * <p>
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ * <p>
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<Result<Key, Value>> loadAfter(
+ @NonNull LoadParams<Key> params);
+
+ @Nullable
+ @Override
+ Key getKey(@NonNull Value item) {
+ return null;
+ }
+
+ @Override
+ boolean supportsPageDropping() {
+ /* To support page dropping when PageKeyed, we'll need to:
+ * - Stash keys for every page we have loaded (can id by index relative to loadInitial)
+ * - Drop keys for any page not adjacent to loaded content
+ * - And either:
+ * - Allow impl to signal previous page key: onResult(data, nextPageKey, prevPageKey)
+ * - Re-trigger loadInitial, and break assumption it will only occur once.
+ */
+ return false;
+ }
+
+ /**
+ * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
+ * initially loaded data.
+ *
+ * @param <Key> Type of key used to identify pages.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+ public static class InitialResult<Key, Value> extends BaseResult<Value> {
+ public InitialResult(@NonNull List<Value> data, int position, int totalCount,
+ @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
+ super(data, previousPageKey, nextPageKey,
+ position, totalCount - data.size() - position, position, true);
+ }
+
+ public InitialResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
+ @Nullable Key nextPageKey) {
+ super(data, previousPageKey, nextPageKey, 0, 0, 0, false);
+ }
+ }
+
+ /**
+ * Type produced by {@link #loadBefore(LoadParams)} and {@link #loadAfter(LoadParams)} to
+ * represent a page of loaded data.
+ *
+ * @param <Key> Type of key used to identify pages.
+ * @param <Value> Type of items being loaded by the DataSource.
+ */
+ public static class Result<Key, Value> extends BaseResult<Value> {
+ public Result(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
+ super(data, adjacentPageKey, adjacentPageKey, 0, 0, 0, false);
+ }
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
new file mode 100644
index 0000000..25c47ab
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
+ * <p>
+ * Extend ListenablePositionalDataSource if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. If your data source can't support loading arbitrary
+ * requested page sizes (e.g. when network page size constraints are only known at runtime), either
+ * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
+ * the two parameter {@link InitialResult InitialResult constructor}.
+ *
+ * @see PositionalDataSource
+ *
+ * @param <T> Type of items being loaded by the PositionalDataSource.
+ */
+public abstract class ListenablePositionalDataSource<T> extends DataSource<Integer, T> {
+ public ListenablePositionalDataSource() {
+ super(KeyType.POSITIONAL);
+ }
+
+ @Override
+ final ListenableFuture<? extends BaseResult<T>> load(@NonNull Params<Integer> params) {
+ if (params.type == LoadType.INITIAL) {
+ int initialPosition = 0;
+ int initialLoadSize = params.initialLoadSize;
+ if (params.key != null) {
+ initialPosition = params.key;
+
+ if (params.placeholdersEnabled) {
+ // snap load size to page multiple (minimum two)
+ initialLoadSize = Math.max(initialLoadSize / params.pageSize, 2)
+ * params.pageSize;
+
+ // move start so the load is centered around the key, not starting at it
+ final int idealStart = initialPosition - initialLoadSize / 2;
+ initialPosition = Math.max(0, idealStart / params.pageSize * params.pageSize);
+ } else {
+ // not tiled, so don't try to snap or force multiple of a page size
+ initialPosition = initialPosition - initialLoadSize / 2;
+ }
+
+ }
+ PositionalDataSource.LoadInitialParams initParams =
+ new PositionalDataSource.LoadInitialParams(
+ initialPosition,
+ initialLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled);
+ return loadInitial(initParams);
+ } else {
+ int startIndex = params.key;
+ int loadSize = params.pageSize;
+ if (params.type == LoadType.START) {
+ loadSize = Math.min(loadSize, startIndex + 1);
+ startIndex = startIndex - loadSize + 1;
+ }
+ return loadRange(new PositionalDataSource.LoadRangeParams(startIndex, loadSize));
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@code loadInitial()}.
+ */
+ public static class LoadInitialParams {
+ /**
+ * Initial load position requested.
+ * <p>
+ * Note that this may not be within the bounds of your data set, it may need to be adjusted
+ * before you execute your load.
+ */
+ public final int requestedStartPosition;
+
+ /**
+ * Requested number of items to load.
+ * <p>
+ * Note that this may be larger than available data.
+ */
+ public final int requestedLoadSize;
+
+ /**
+ * Defines page size acceptable for return values.
+ * <p>
+ * List of items passed to the callback must be an integer multiple of page size.
+ */
+ public final int pageSize;
+
+ /**
+ * Defines whether placeholders are enabled, and whether the loaded total count will be
+ * ignored.
+ */
+ public final boolean placeholdersEnabled;
+
+ public LoadInitialParams(
+ int requestedStartPosition,
+ int requestedLoadSize,
+ int pageSize,
+ boolean placeholdersEnabled) {
+ this.requestedStartPosition = requestedStartPosition;
+ this.requestedLoadSize = requestedLoadSize;
+ this.pageSize = pageSize;
+ this.placeholdersEnabled = placeholdersEnabled;
+ }
+ }
+
+ /**
+ * Holder object for inputs to {@code loadRange()}.
+ */
+ public static class LoadRangeParams {
+ /**
+ * START position of data to load.
+ * <p>
+ * Returned data must start at this position.
+ */
+ public final int startPosition;
+ /**
+ * Number of items to load.
+ * <p>
+ * Returned data must be of this size, unless at end of the list.
+ */
+ public final int loadSize;
+
+ public LoadRangeParams(int startPosition, int loadSize) {
+ this.startPosition = startPosition;
+ this.loadSize = loadSize;
+ }
+ }
+
+ /**
+ * Load initial list data.
+ * <p>
+ * This method is called to load the initial page(s) from the DataSource.
+ * <p>
+ * Result list must be a multiple of pageSize to enable efficient tiling.
+ *
+ * @param params Parameters for initial load, including requested start position, load size, and
+ * page size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<InitialResult<T>> loadInitial(
+ @NonNull LoadInitialParams params);
+
+ /**
+ * Called to load a range of data from the DataSource.
+ * <p>
+ * This method is called to load additional pages from the DataSource after the
+ * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
+ * <p>
+ * Unlike {@link #loadInitial(LoadInitialParams)}, this method must return
+ * the number of items requested, at the position requested.
+ *
+ * @param params Parameters for load, including start position and load size.
+ * @return ListenableFuture of the loaded data.
+ */
+ @NonNull
+ public abstract ListenableFuture<RangeResult<T>> loadRange(@NonNull LoadRangeParams params);
+
+ @Nullable
+ @Override
+ final Integer getKey(@NonNull T item) {
+ return null;
+ }
+
+ /**
+ * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
+ * initially loaded data.
+ *
+ * @param <V> The type of the data loaded.
+ */
+ public static class InitialResult<V> extends BaseResult<V> {
+ public InitialResult(@NonNull List<V> data, int position, int totalCount) {
+ super(data, null, null, position, totalCount - data.size() - position, 0, true);
+ if (data.isEmpty() && position != 0) {
+ throw new IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set.");
+ }
+ }
+
+ public InitialResult(@NonNull List<V> data, int position) {
+ super(data, null, null, 0, 0, position, false);
+ if (data.isEmpty() && position != 0) {
+ throw new IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set.");
+ }
+ }
+ }
+
+ /**
+ * Type produced by {@link #loadRange(LoadRangeParams)} to represent a page
+ * of loaded data.
+ *
+ * @param <V> The type of the data loaded.
+ */
+ public static class RangeResult<V> extends BaseResult<V> {
+ public RangeResult(@NonNull List<V> data) {
+ super(data, null, null, 0, 0, 0, false);
+ }
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
index 81e8626..65f9c0d 100644
--- a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
@@ -16,13 +16,14 @@
package androidx.paging;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.util.Function;
+import androidx.concurrent.futures.ResolvableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Incremental data loader for page-keyed content, where requests return keys for next/previous
@@ -41,75 +42,17 @@
* @param <Key> Type of data used to query Value types out of the DataSource.
* @param <Value> Type of items being loaded by the DataSource.
*/
-public abstract class PageKeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
- private final Object mKeyLock = new Object();
-
- @Nullable
- @GuardedBy("mKeyLock")
- private Key mNextKey = null;
-
- @Nullable
- @GuardedBy("mKeyLock")
- private Key mPreviousKey = null;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void initKeys(@Nullable Key previousKey, @Nullable Key nextKey) {
- synchronized (mKeyLock) {
- mPreviousKey = previousKey;
- mNextKey = nextKey;
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void setPreviousKey(@Nullable Key previousKey) {
- synchronized (mKeyLock) {
- mPreviousKey = previousKey;
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void setNextKey(@Nullable Key nextKey) {
- synchronized (mKeyLock) {
- mNextKey = nextKey;
- }
- }
-
- private @Nullable Key getPreviousKey() {
- synchronized (mKeyLock) {
- return mPreviousKey;
- }
- }
-
- private @Nullable Key getNextKey() {
- synchronized (mKeyLock) {
- return mNextKey;
- }
- }
-
+public abstract class PageKeyedDataSource<Key, Value>
+ extends ListenablePageKeyedDataSource<Key, Value> {
/**
* Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
*
* @param <Key> Type of data used to query pages.
*/
- @SuppressWarnings("WeakerAccess")
- public static class LoadInitialParams<Key> {
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the total count passed to
- * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)} will be ignored.
- */
- public final boolean placeholdersEnabled;
-
-
+ public static class LoadInitialParams<Key> extends
+ ListenablePageKeyedDataSource.LoadInitialParams<Key> {
public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
- this.requestedLoadSize = requestedLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
+ super(requestedLoadSize, placeholdersEnabled);
}
}
@@ -119,27 +62,9 @@
*
* @param <Key> Type of data used to query pages.
*/
- @SuppressWarnings("WeakerAccess")
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
-
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
+ public static class LoadParams<Key> extends ListenablePageKeyedDataSource.LoadParams<Key> {
public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
+ super(key, requestedLoadSize);
}
}
@@ -150,9 +75,9 @@
* A callback can be called only once, and will throw if called again.
* <p>
* If you can compute the number of items in the data set before and after the loaded range,
- * call the five parameter {@link #onResult(List, int, int, Object, Object)} to pass that
+ * call the five parameter {@link #onResult(List, int, int, Key, Key)} to pass that
* information. You can skip passing this information by calling the three parameter
- * {@link #onResult(List, Object, Object)}, either if it's difficult to compute, or if
+ * {@link #onResult(List, Key, Key)}, either if it's difficult to compute, or if
* {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
* information will be ignored.
* <p>
@@ -207,9 +132,9 @@
@Nullable Key nextPageKey);
/**
- * Called to report a non-retryable error from a DataSource.
+ * Called to report an error from a DataSource.
* <p>
- * Call this method to report a non-retryable error from
+ * Call this method to report an error from
* {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
*
* @param error The error that occurred during loading.
@@ -219,20 +144,6 @@
throw new IllegalStateException(
"You must implement onError if implementing your own load callback");
}
-
- /**
- * Called to report a retryable error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onRetryableError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onRetryableError if implementing your own load callback");
- }
}
/**
@@ -249,7 +160,6 @@
* @param <Value> Type of items being loaded.
*/
public abstract static class LoadCallback<Key, Value> {
-
/**
* Called to pass loaded data from a DataSource.
* <p>
@@ -274,9 +184,9 @@
public abstract void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey);
/**
- * Called to report a non-retryable error from a DataSource.
+ * Called to report an error from a DataSource.
* <p>
- * Call this method to report a non-retryable error from your PageKeyedDataSource's
+ * Call this method to report an error from your PageKeyedDataSource's
* {@link #loadBefore(LoadParams, LoadCallback)} and
* {@link #loadAfter(LoadParams, LoadCallback)} methods.
*
@@ -287,166 +197,91 @@
throw new IllegalStateException(
"You must implement onError if implementing your own load callback");
}
-
- /**
- * Called to report a retryable error from a DataSource.
- * <p>
- * Call this method to report an error from your PageKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onRetryableError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onRetryableError if implementing your own load callback");
- }
}
- static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
- final LoadCallbackHelper<Value> mCallbackHelper;
- private final PageKeyedDataSource<Key, Value> mDataSource;
- private final boolean mCountingEnabled;
- LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
- boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(
- dataSource, PageResult.INIT, null, receiver);
- mDataSource = dataSource;
- mCountingEnabled = countingEnabled;
- }
+ @NonNull
+ @Override
+ public final ListenableFuture<InitialResult<Key, Value>> loadInitial(
+ final @NonNull ListenablePageKeyedDataSource.LoadInitialParams<Key> params) {
+ final ResolvableFuture<InitialResult<Key, Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ LoadInitialCallback<Key, Value> callback = new LoadInitialCallback<Key, Value>() {
+ @Override
+ public void onResult(@NonNull List<Value> data, int position, int totalCount,
+ @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
+ future.set(new InitialResult<>(data, position, totalCount, previousPageKey,
+ nextPageKey));
+ }
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
+ @Override
+ public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
+ @Nullable Key nextPageKey) {
+ future.set(new InitialResult<>(data, previousPageKey, nextPageKey));
+ }
- // setup keys before dispatching data, so guaranteed to be ready
- mDataSource.initKeys(previousPageKey, nextPageKey);
-
- int trailingUnloadedCount = totalCount - position - data.size();
- if (mCountingEnabled) {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
- data, position, trailingUnloadedCount, 0));
- } else {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
- }
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
+ }
+ };
+ loadInitial(new LoadInitialParams<Key>(
+ params.requestedLoadSize,
+ params.placeholdersEnabled),
+ callback);
}
- }
+ });
+ return future;
+ }
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- mDataSource.initKeys(previousPageKey, nextPageKey);
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ @SuppressWarnings("WeakerAccess")
+ LoadCallback<Key, Value> getFutureAsCallback(
+ final @NonNull ResolvableFuture<Result<Key, Value>> future) {
+ return new LoadCallback<Key, Value>() {
+ @Override
+ public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
+ future.set(new Result<>(data, adjacentPageKey));
}
- }
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
- }
-
- static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
- final LoadCallbackHelper<Value> mCallbackHelper;
- private final PageKeyedDataSource<Key, Value> mDataSource;
- LoadCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
- @PageResult.ResultType int type, @Nullable Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(
- dataSource, type, mainThreadExecutor, receiver);
- mDataSource = dataSource;
- }
-
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- if (mCallbackHelper.mResultType == PageResult.APPEND) {
- mDataSource.setNextKey(adjacentPageKey);
- } else {
- mDataSource.setPreviousKey(adjacentPageKey);
- }
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
}
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
+ };
}
- @Nullable
+ @NonNull
@Override
- final Key getKey(int position, Value item) {
- // don't attempt to persist keys, since we currently don't pass them to initial load
- return null;
+ public final ListenableFuture<Result<Key, Value>> loadBefore(
+ final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
+ final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ loadBefore(new LoadParams<>(
+ params.key,
+ params.requestedLoadSize),
+ getFutureAsCallback(future));
+ }
+ });
+ return future;
}
+ @NonNull
@Override
- boolean supportsPageDropping() {
- /* To support page dropping when PageKeyed, we'll need to:
- * - Stash keys for every page we have loaded (can id by index relative to loadInitial)
- * - Drop keys for any page not adjacent to loaded content
- * - And either:
- * - Allow impl to signal previous page key: onResult(data, nextPageKey, prevPageKey)
- * - Re-trigger loadInitial, and break assumption it will only occur once.
- */
- return false;
- }
-
- @Override
- final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
- boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- LoadInitialCallbackImpl<Key, Value> callback =
- new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
- loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
- }
-
-
- @Override
- final void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem,
- int pageSize, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- @Nullable Key key = getNextKey();
- if (key != null) {
- loadAfter(new LoadParams<>(key, pageSize),
- new LoadCallbackImpl<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
- } else {
- receiver.onPageResult(PageResult.APPEND, PageResult.<Value>getEmptyResult());
- }
- }
-
- @Override
- final void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
- int pageSize, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- @Nullable Key key = getPreviousKey();
- if (key != null) {
- loadBefore(new LoadParams<>(key, pageSize),
- new LoadCallbackImpl<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
- } else {
- receiver.onPageResult(PageResult.PREPEND, PageResult.<Value>getEmptyResult());
- }
+ public final ListenableFuture<Result<Key, Value>> loadAfter(
+ final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
+ final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ loadAfter(new LoadParams<>(
+ params.key,
+ params.requestedLoadSize), getFutureAsCallback(future));
+ }
+ });
+ return future;
}
/**
@@ -455,7 +290,7 @@
* This method is called first to initialize a PagedList with data. If it's possible to count
* the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
* the callback via the three-parameter
- * {@link LoadInitialCallback#onResult(List, int, int, Object, Object)}. This enables PagedLists
+ * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)}. This enables PagedLists
* presenting data from this source to display placeholders to represent unloaded items.
* <p>
* {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
@@ -471,13 +306,14 @@
* Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
* <p>
* Data may be passed synchronously during the load method, or deferred and called at a
* later time. Further loads going down will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key for the new page, and requested load
@@ -491,13 +327,14 @@
* Append page with the key specified by {@link LoadParams#key LoadParams.key}.
* <p>
* It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally safer to increase the number loaded than reduce.
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
* <p>
* Data may be passed synchronously during the load method, or deferred and called at a
* later time. Further loads going down will be blocked until the callback is called.
* <p>
* If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent, it is valid to call {@link #invalidate()} to invalidate the data source,
+ * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
* and prevent further loading.
*
* @param params Parameters for the load, including the key for the new page, and requested load
diff --git a/paging/common/src/main/java/androidx/paging/PageResult.java b/paging/common/src/main/java/androidx/paging/PageResult.java
deleted file mode 100644
index d231d2b..0000000
--- a/paging/common/src/main/java/androidx/paging/PageResult.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-
-import java.lang.annotation.Retention;
-import java.util.Collections;
-import java.util.List;
-
-class PageResult<T> {
- /**
- * Single empty instance to avoid allocations.
- * <p>
- * Note, distinct from {@link #INVALID_RESULT} because {@link #isInvalid()} checks instance.
- */
- @SuppressWarnings("unchecked")
- private static final PageResult EMPTY_RESULT =
- new PageResult(Collections.emptyList(), 0);
-
- @SuppressWarnings("unchecked")
- private static final PageResult INVALID_RESULT =
- new PageResult(Collections.emptyList(), 0);
-
- @SuppressWarnings("unchecked")
- static <T> PageResult<T> getEmptyResult() {
- return EMPTY_RESULT;
- }
-
- @SuppressWarnings("unchecked")
- static <T> PageResult<T> getInvalidResult() {
- return INVALID_RESULT;
- }
-
- @Retention(SOURCE)
- @IntDef({INIT, APPEND, PREPEND, TILE})
- @interface ResultType {}
-
- static final int INIT = 0;
-
- // contiguous results
- static final int APPEND = 1;
- static final int PREPEND = 2;
-
- // non-contiguous, tile result
- static final int TILE = 3;
-
- @NonNull
- public final List<T> page;
- @SuppressWarnings("WeakerAccess")
- public final int leadingNulls;
- @SuppressWarnings("WeakerAccess")
- public final int trailingNulls;
- @SuppressWarnings("WeakerAccess")
- public final int positionOffset;
-
- PageResult(@NonNull List<T> list, int leadingNulls, int trailingNulls, int positionOffset) {
- this.page = list;
- this.leadingNulls = leadingNulls;
- this.trailingNulls = trailingNulls;
- this.positionOffset = positionOffset;
- }
-
- PageResult(@NonNull List<T> list, int positionOffset) {
- this.page = list;
- this.leadingNulls = 0;
- this.trailingNulls = 0;
- this.positionOffset = positionOffset;
- }
-
- @Override
- public String toString() {
- return "Result " + leadingNulls
- + ", " + page
- + ", " + trailingNulls
- + ", offset " + positionOffset;
- }
-
- public boolean isInvalid() {
- return this == INVALID_RESULT;
- }
-
- abstract static class Receiver<T> {
- @MainThread
- public abstract void onPageResult(@ResultType int type, @NonNull PageResult<T> pageResult);
-
- @MainThread
- public abstract void onPageError(@ResultType int type,
- @NonNull Throwable error, boolean retryable);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PagedList.java b/paging/common/src/main/java/androidx/paging/PagedList.java
index be9079c..2339f2b 100644
--- a/paging/common/src/main/java/androidx/paging/PagedList.java
+++ b/paging/common/src/main/java/androidx/paging/PagedList.java
@@ -23,20 +23,25 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.WorkerThread;
+import androidx.arch.core.util.Function;
+import androidx.paging.futures.DirectExecutor;
+import androidx.paging.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
import java.lang.ref.WeakReference;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Lazy loading list that pages in immutable content from a {@link DataSource}.
* <p>
* A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}.
* Items can be accessed with {@link #get(int)}, and further loading can be triggered with
- * {@link #loadAround(int)}. To display a PagedList, see {@link PagedListAdapter}, which enables the
+ * {@link #loadAround(int)}. To display a PagedList, see {@link androidx.paging.PagedListAdapter}, which enables the
* binding of a PagedList to a {@link androidx.recyclerview.widget.RecyclerView}.
* <h4>Loading Data</h4>
* <p>
@@ -55,8 +60,8 @@
* PagedList can present data for an unbounded, infinite scrolling list, or a very large but
* countable list. Use {@link Config} to control how many items a PagedList loads, and when.
* <p>
- * If you use {@link LivePagedListBuilder} to get a
- * {@link androidx.lifecycle.LiveData}<PagedList>, it will initialize PagedLists on a
+ * If you use {@link androidx.paging.LivePagedListBuilder} to get a
+ * {@link androidx.lifecycle.LiveData}, it will initialize PagedLists on a
* background thread for you.
* <h4>Placeholders</h4>
* <p>
@@ -74,7 +79,7 @@
* Placeholders have several benefits:
* <ul>
* <li>They express the full sized list to the presentation layer (often a
- * {@link PagedListAdapter}), and so can support scrollbars (without jumping as pages are
+ * {@link androidx.paging.PagedListAdapter}), and so can support scrollbars (without jumping as pages are
* loaded or dropped) and fast-scrolling to any position, loaded or not.
* <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
* is always full sized.
@@ -96,13 +101,13 @@
* <h4>Mutability and Snapshots</h4>
* A PagedList is <em>mutable</em> while loading, or ready to load from its DataSource.
* As loads succeed, a mutable PagedList will be updated via Runnables on the main thread. You can
- * listen to these updates with a {@link Callback}. (Note that {@link PagedListAdapter} will listen
+ * listen to these updates with a {@link Callback}. (Note that {@link androidx.paging.PagedListAdapter} will listen
* to these to signal RecyclerView about the updates/changes).
* <p>
* If a PagedList attempts to load from an invalid DataSource, it will {@link #detach()}
* from the DataSource, meaning that it will no longer attempt to load data. It will return true
* from {@link #isImmutable()}, and a new DataSource / PagedList pair must be created to load
- * further data. See {@link DataSource} and {@link LivePagedListBuilder} for how new PagedLists are
+ * further data. See {@link DataSource} and {@link androidx.paging.LivePagedListBuilder} for how new PagedLists are
* created to represent changed data.
* <p>
* A PagedList snapshot is simply an immutable shallow copy of the current state of the PagedList as
@@ -177,9 +182,6 @@
RETRYABLE_ERROR,
}
-
-
-
/**
* Listener for changes to loading state - whether the refresh, prepend, or append is idle,
* loading, or has an error.
@@ -214,7 +216,8 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
static boolean equalsHelper(@Nullable Object a, @Nullable Object b) {
- // Because Objects.equals() is API 19+
+ // (Because Objects.equals() is API 19+)
+ //noinspection EqualsReplaceableByObjectsCall
return a == b || (a != null && a.equals(b));
}
@@ -247,21 +250,6 @@
return mEnd;
}
- @Nullable
- public Throwable getRefreshError() {
- return mRefreshError;
- }
-
- @Nullable
- public Throwable getStartError() {
- return mStartError;
- }
-
- @Nullable
- public Throwable getEndError() {
- return mEndError;
- }
-
void setState(@NonNull LoadType type, @NonNull LoadState state, @Nullable Throwable error) {
boolean expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR;
boolean hasError = error != null;
@@ -293,8 +281,16 @@
protected abstract void onStateChanged(@NonNull LoadType type,
@NonNull LoadState state, @Nullable Throwable error);
+
+ void dispatchCurrentLoadState(LoadStateListener listener) {
+ listener.onLoadStateChanged(PagedList.LoadType.REFRESH, mRefresh, mRefreshError);
+ listener.onLoadStateChanged(PagedList.LoadType.START, mStart, mStartError);
+ listener.onLoadStateChanged(PagedList.LoadType.END, mEnd, mEndError);
+ }
}
+ void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {}
+
/**
* Retry any retryable errors associated with this PagedList.
* <p>
@@ -303,27 +299,14 @@
* {@code onError()} to signify the error as retryable.
* <p>
* You can observe loading state via {@link #addWeakLoadStateListener(LoadStateListener)},
- * though generally this is done through the {@link PagedListAdapter} or
- * {@link AsyncPagedListDiffer}.
+ * though generally this is done through the {@link androidx.paging.PagedListAdapter} or
+ * {@link androidx.paging.AsyncPagedListDiffer}.
*
* @see #addWeakLoadStateListener(LoadStateListener)
* @see #removeWeakLoadStateListener(LoadStateListener)
*/
public void retry() {}
- // Notes on threading:
- //
- // PagedList and its subclasses are passed and accessed on multiple threads, but are always
- // owned by a single thread. During initialization, this is the creation thread, generally the
- // fetchExecutor/fetchScheduler when using LiveData/RxJava. After initialization, the PagedList
- // is owned by the main thread (or notifyScheduler, if overridden in RxJava).
- //
- // The only exception is detach()/isDetached(), which can be called from the fetch thread.
- // However these methods simply wrap a atomic boolean, so are safe.
- //
- // The PageResult.Receiver that receives new data from the DataSource is always run on the
- // owning thread.
-
@NonNull
final Executor mMainThreadExecutor;
@NonNull
@@ -334,6 +317,12 @@
final Config mConfig;
@NonNull
final PagedStorage<T> mStorage;
+ @Nullable
+ Runnable mRefreshRetryCallback = null;
+
+ void setRetryCallback(@Nullable Runnable refreshRetryCallback) {
+ mRefreshRetryCallback = refreshRetryCallback;
+ }
/**
* Last access location, in total position space (including offset).
@@ -358,34 +347,22 @@
private int mLowestIndexAccessed = Integer.MAX_VALUE;
private int mHighestIndexAccessed = Integer.MIN_VALUE;
- private final AtomicBoolean mDetached = new AtomicBoolean(false);
-
private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@SuppressWarnings("WeakerAccess") /* synthetic access */
final ArrayList<WeakReference<LoadStateListener>> mListeners = new ArrayList<>();
- final LoadStateManager mLoadStateManager = new LoadStateManager() {
- @Override
- protected void onStateChanged(@NonNull final LoadType type, @NonNull final LoadState state,
- @Nullable final Throwable error) {
- // new state, dispatch to listeners
- // Post, since UI will want to react immediately
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null) {
- mListeners.remove(i);
- } else {
- currentListener.onLoadStateChanged(type, state, error);
- }
- }
- }
- });
+ void dispatchStateChange(@NonNull LoadType type, @NonNull LoadState state,
+ @Nullable Throwable error) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ final LoadStateListener currentListener = mListeners.get(i).get();
+ if (currentListener == null) {
+ mListeners.remove(i);
+ } else {
+ currentListener.onLoadStateChanged(type, state, error);
+ }
}
- };
+ }
PagedList(@NonNull PagedStorage<T> storage,
@NonNull Executor mainThreadExecutor,
@@ -406,7 +383,7 @@
*
*
* @param dataSource DataSource providing data to the PagedList
- * @param notifyExecutor Thread that will use and consume data from the PagedList.
+ * @param notifyExecutor Thread tat will use and consume data from the PagedList.
* Generally, this is the UI/main thread.
* @param fetchExecutor Data loading will be done via this executor -
* should be a background thread.
@@ -415,42 +392,45 @@
* @param <K> Key type that indicates to the DataSource what data to load.
* @param <T> Type of items to be held and loaded by the PagedList.
*
- * @return Newly created PagedList, which will page in data from the DataSource as needed.
+ * @return ListenableFuture for newly created PagedList, which will page in data from the
+ * DataSource as needed.
*/
@NonNull
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
- @NonNull Executor notifyExecutor,
- @NonNull Executor fetchExecutor,
- @Nullable BoundaryCallback<T> boundaryCallback,
- @NonNull Config config,
+ static <K, T> ListenableFuture<PagedList<T>> create(
+ @NonNull final DataSource<K, T> dataSource,
+ @NonNull final Executor notifyExecutor,
+ @NonNull final Executor fetchExecutor,
+ @NonNull final Executor initialLoadExecutor,
+ @Nullable final BoundaryCallback<T> boundaryCallback,
+ @NonNull final Config config,
@Nullable K key) {
- if (dataSource.isContiguous() || !config.enablePlaceholders) {
- int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
- if (!dataSource.isContiguous()) {
- //noinspection unchecked
- dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
- .wrapAsContiguousWithoutPlaceholders();
- if (key != null) {
- lastLoad = (Integer) key;
- }
- }
- ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
- return new ContiguousPagedList<>(contigDataSource,
- notifyExecutor,
- fetchExecutor,
- boundaryCallback,
- config,
- key,
- lastLoad);
- } else {
- return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
- notifyExecutor,
- fetchExecutor,
- boundaryCallback,
- config,
- (key != null) ? (Integer) key : 0);
- }
+ dataSource.initExecutor(initialLoadExecutor);
+
+ final int lastLoad = (dataSource.mType == DataSource.KeyType.POSITIONAL && key != null)
+ ? (Integer) key : ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
+
+ return Futures.transform(
+ dataSource.load(
+ new DataSource.Params<>(
+ DataSource.LoadType.INITIAL,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize)),
+ new Function<DataSource.BaseResult<T>, PagedList<T>>() {
+ @Override
+ public PagedList<T> apply(DataSource.BaseResult<T> initialResult) {
+ dataSource.initExecutor(fetchExecutor);
+ return new ContiguousPagedList<>(dataSource,
+ notifyExecutor,
+ fetchExecutor,
+ boundaryCallback,
+ config,
+ initialResult,
+ lastLoad);
+ }
+ },
+ DirectExecutor.INSTANCE);
}
/**
@@ -463,7 +443,7 @@
* so that the UI doesn't show an empty list, or placeholders for a few frames, just before
* showing initial content.
* <p>
- * {@link LivePagedListBuilder} does this creation on a background thread automatically, if you
+ * {@link androidx.paging.LivePagedListBuilder} does this creation on a background thread automatically, if you
* want to receive a {@code LiveData<PagedList<...>>}.
*
* @param <Key> Type of key used to load data from the DataSource.
@@ -475,7 +455,7 @@
private final Config mConfig;
private Executor mNotifyExecutor;
private Executor mFetchExecutor;
- private BoundaryCallback mBoundaryCallback;
+ private BoundaryCallback<Value> mBoundaryCallback;
private Key mInitialKey;
/**
@@ -552,7 +532,7 @@
@SuppressWarnings("unused")
@NonNull
public Builder<Key, Value> setBoundaryCallback(
- @Nullable BoundaryCallback boundaryCallback) {
+ @Nullable BoundaryCallback<Value> boundaryCallback) {
mBoundaryCallback = boundaryCallback;
return this;
}
@@ -572,19 +552,14 @@
/**
* Creates a {@link PagedList} with the given parameters.
* <p>
- * This call will dispatch the {@link DataSource}'s loadInitial method immediately. If a
- * DataSource posts all of its work (e.g. to a network thread), the PagedList will
- * be immediately created as empty, and grow to its initial size when the initial load
- * completes.
- * <p>
- * If the DataSource implements its load synchronously, doing the load work immediately in
- * the loadInitial method, the PagedList will block on that load before completing
- * construction. In this case, use a background thread to create a PagedList.
+ * This call will dispatch the {@link androidx.paging.DataSource}'s loadInitial method immediately on the
+ * current thread, and block the current on the result. This method should always be called
+ * on a worker thread to prevent blocking the main thread.
* <p>
* It's fine to create a PagedList with an async DataSource on the main thread, such as in
* the constructor of a ViewModel. An async network load won't block the initialLoad
* function. For a synchronous DataSource such as one created from a Room database, a
- * {@code LiveData<PagedList>} can be safely constructed with {@link LivePagedListBuilder}
+ * {@code LiveData<PagedList>} can be safely constructed with {@link androidx.paging.LivePagedListBuilder}
* on the main thread, since actual construction work is deferred, and done on a background
* thread.
* <p>
@@ -594,8 +569,12 @@
* will be immediately {@link PagedList#isDetached() detached}, and you can retry
* construction (including setting a new DataSource).
*
+ * @deprecated This method has no means of handling errors encountered during initial load,
+ * and blocks on the initial load result. Use {@link #buildAsync()} instead.
+ *
* @return The newly constructed PagedList
*/
+ @Deprecated
@WorkerThread
@NonNull
public PagedList<Value> build() {
@@ -607,11 +586,44 @@
throw new IllegalArgumentException("BackgroundThreadExecutor required");
}
- //noinspection unchecked
+ try {
+ return create(DirectExecutor.INSTANCE).get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates a {@link PagedList} asynchronously with the given parameters.
+ * <p>
+ * This call will dispatch the {@link DataSource}'s loadInitial method immediately, and
+ * return a {@code ListenableFuture<PagedList<T>>} that will resolve (triggering listeners)
+ * once the initial load is completed (success or failure).
+ *
+ * @return The newly constructed PagedList
+ */
+ @SuppressWarnings("unused")
+ @NonNull
+ public ListenableFuture<PagedList<Value>> buildAsync() {
+ // TODO: define defaults, once they can be used in module without android dependency
+ if (mNotifyExecutor == null) {
+ throw new IllegalArgumentException("MainThreadExecutor required");
+ }
+ if (mFetchExecutor == null) {
+ throw new IllegalArgumentException("BackgroundThreadExecutor required");
+ }
+
+ return create(mFetchExecutor);
+ }
+
+ private ListenableFuture<PagedList<Value>> create(@NonNull Executor initialFetchExecutor) {
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
+ initialFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
@@ -857,7 +869,7 @@
* <p>
* When a PagedList is invalidated, you can pass the key returned by this function to initialize
* the next PagedList. This ensures (depending on load times) that the next PagedList that
- * arrives will have data that overlaps. If you use {@link LivePagedListBuilder}, it will do
+ * arrives will have data that overlaps. If you use androidx.paging.LivePagedListBuilder, it will do
* this for you.
*
* @return Key of position most recently passed to {@link #loadAround(int)}.
@@ -873,22 +885,17 @@
*
* @return True if the data source is detached.
*/
- @SuppressWarnings("WeakerAccess")
- public boolean isDetached() {
- return mDetached.get();
- }
+ public abstract boolean isDetached();
/**
* Detach the PagedList from its DataSource, and attempt to load no more data.
* <p>
- * This is called automatically when a DataSource load returns <code>null</code>, which is a
+ * This is called automatically when a DataSource is observed to be invalid, which is a
* signal to stop loading. The PagedList will continue to present existing data, but will not
* initiate new loads.
*/
- @SuppressWarnings("WeakerAccess")
- public void detach() {
- mDetached.set(true);
- }
+ @SuppressWarnings("unused")
+ public abstract void detach();
/**
* Position offset of the data in the list.
@@ -903,7 +910,6 @@
return mStorage.getPositionOffset();
}
-
/**
* Add a LoadStateListener to observe the loading state of the PagedList.
*
@@ -922,12 +928,7 @@
// then add the new one
mListeners.add(new WeakReference<>(listener));
- listener.onLoadStateChanged(PagedList.LoadType.REFRESH, mLoadStateManager.getRefresh(),
- mLoadStateManager.getRefreshError());
- listener.onLoadStateChanged(PagedList.LoadType.START, mLoadStateManager.getStart(),
- mLoadStateManager.getStartError());
- listener.onLoadStateChanged(PagedList.LoadType.END, mLoadStateManager.getEnd(),
- mLoadStateManager.getEndError());
+ dispatchCurrentLoadState(listener);
}
/**
@@ -946,6 +947,8 @@
}
}
+ abstract void dispatchCurrentLoadState(LoadStateListener listener);
+
/**
* Adds a callback, and issues updates since the previousSnapshot was created.
* <p>
@@ -959,7 +962,7 @@
* version, including any changes that may have been made.
* <p>
* The callback is internally held as weak reference, so PagedList doesn't hold a strong
- * reference to its observer, such as a {@link PagedListAdapter}. If an adapter were held with a
+ * reference to its observer, such as a {@link androidx.paging.PagedListAdapter}. If an adapter were held with a
* strong reference, it would be necessary to clear its PagedList observer before it could be
* GC'd.
*
@@ -1239,13 +1242,13 @@
* 2) placeholders are not disabled on the Config.
* <p>
* Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
- * (often a {@link PagedListAdapter}) doesn't need to account for null items.
+ * (often a {@link androidx.paging.PagedListAdapter}) doesn't need to account for null items.
* <p>
* If placeholders are disabled, not-yet-loaded content will not be present in the list.
* Paging will still occur, but as items are loaded or removed, they will be signaled
* as inserts to the {@link PagedList.Callback}.
* {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading,
- * though a {@link PagedListAdapter} may still receive change events as a result of
+ * though a {@link androidx.paging.PagedListAdapter} may still receive change events as a result of
* PagedList diffing.
*
* @param enablePlaceholders False if null placeholders should be disabled.
@@ -1367,7 +1370,7 @@
* account for the new items.
* <p>
* Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
- * {@link LivePagedListBuilder#setBoundaryCallback}), the callbacks may be issued multiple
+ * {@link androidx.paging.LivePagedListBuilder#setBoundaryCallback}, the callbacks may be issued multiple
* times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
* avoid triggering it again while the load is ongoing.
* <p>
diff --git a/paging/common/src/main/java/androidx/paging/PagedStorage.java b/paging/common/src/main/java/androidx/paging/PagedStorage.java
index c644234..232302f 100644
--- a/paging/common/src/main/java/androidx/paging/PagedStorage.java
+++ b/paging/common/src/main/java/androidx/paging/PagedStorage.java
@@ -29,7 +29,7 @@
* It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds
* data, and does not have any notion of the ideas of async loads, or prefetching.
*/
-final class PagedStorage<T> extends AbstractList<T> {
+final class PagedStorage<T> extends AbstractList<T> implements Pager.AdjacentProvider<T> {
/**
* Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
* in that position is already loading. We use a singleton placeholder list that is distinct
@@ -38,7 +38,6 @@
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private static final List PLACEHOLDER_LIST = new ArrayList();
- // Always set
private int mLeadingNullCount;
/**
* List of pages in storage.
@@ -217,8 +216,6 @@
void onPageInserted(int start, int count);
void onPagesRemoved(int startOfDrops, int count);
void onPagesSwappedToPlaceholder(int startOfDrops, int count);
- void onEmptyPrepend();
- void onEmptyAppend();
}
int getPositionOffset() {
@@ -344,24 +341,10 @@
// ---------------- Contiguous API -------------------
- T getFirstLoadedItem() {
- // safe to access first page's first item here:
- // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- return mPages.get(0).get(0);
- }
-
- T getLastLoadedItem() {
- // safe to access last page's last item here:
- // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- List<T> page = mPages.get(mPages.size() - 1);
- return page.get(page.size() - 1);
- }
-
void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
final int count = page.size();
if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- callback.onEmptyPrepend();
+ // Nothing returned from source, nothing to do
return;
}
if (mPageSize > 0 && count != mPageSize) {
@@ -393,8 +376,7 @@
void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
final int count = page.size();
if (count == 0) {
- // Nothing returned from source, stop loading in this direction
- callback.onEmptyAppend();
+ // Nothing returned from source, nothing to do
return;
}
@@ -422,6 +404,39 @@
changedCount, addedCount);
}
+ // ------------- Adjacent Provider interface (contiguous-only) ------------------
+
+ @Override
+ public T getFirstLoadedItem() {
+ // safe to access first page's first item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ return mPages.get(0).get(0);
+ }
+
+ @Override
+ public T getLastLoadedItem() {
+ // safe to access last page's last item here:
+ // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
+ List<T> page = mPages.get(mPages.size() - 1);
+ return page.get(page.size() - 1);
+ }
+
+ @Override
+ public int getFirstLoadedItemIndex() {
+ return getLeadingNullCount() + getPositionOffset();
+ }
+
+ @Override
+ public int getLastLoadedItemIndex() {
+ return getLeadingNullCount() + getStorageCount() - 1 + getPositionOffset();
+ }
+
+ @Override
+ public void onPageResultResolution(@NonNull PagedList.LoadType type,
+ @NonNull DataSource.BaseResult<T> pageResult) {
+ // ignored
+ }
+
// ------------------ Non-Contiguous API (tiling required) ----------------------
/**
@@ -466,7 +481,6 @@
void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
-
int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
for (int i = 0; i < pageCount; i++) {
int beginInclusive = i * pageSize;
diff --git a/paging/common/src/main/java/androidx/paging/Pager.java b/paging/common/src/main/java/androidx/paging/Pager.java
new file mode 100644
index 0000000..92ae848
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/Pager.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.paging.futures.FutureCallback;
+import androidx.paging.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+class Pager<K, V> {
+ @SuppressWarnings("unchecked")
+ Pager(@NonNull PagedList.Config config,
+ @NonNull DataSource<K, V> source,
+ @NonNull Executor notifyExecutor,
+ @NonNull Executor fetchExecutor,
+ @NonNull PageConsumer<V> pageConsumer,
+ @Nullable AdjacentProvider<V> adjacentProvider,
+ @NonNull DataSource.BaseResult<V> result) {
+ mConfig = config;
+ mSource = source;
+ mNotifyExecutor = notifyExecutor;
+ mFetchExecutor = fetchExecutor;
+ mPageConsumer = pageConsumer;
+ if (adjacentProvider == null) {
+ adjacentProvider = new SimpleAdjacentProvider<>();
+ }
+ mAdjacentProvider = adjacentProvider;
+ mPrevKey = (K) result.prevKey;
+ mNextKey = (K) result.nextKey;
+ adjacentProvider.onPageResultResolution(PagedList.LoadType.REFRESH, result);
+ mTotalCount = result.totalCount();
+
+ // TODO: move this validation to tiled paging impl, once that's added back
+ if (mSource.mType == DataSource.KeyType.POSITIONAL && mConfig.enablePlaceholders) {
+ result.validateForInitialTiling(mConfig.pageSize);
+ }
+ }
+
+ private final int mTotalCount;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ final PagedList.Config mConfig;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ final DataSource<K, V> mSource;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ final Executor mNotifyExecutor;
+
+ @NonNull
+ private final Executor mFetchExecutor;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ @NonNull
+ final PageConsumer<V> mPageConsumer;
+
+ @NonNull
+ private final AdjacentProvider<V> mAdjacentProvider;
+
+ @Nullable
+ private K mPrevKey;
+
+ @Nullable
+ private K mNextKey;
+
+ private final AtomicBoolean mDetached = new AtomicBoolean(false);
+
+ PagedList.LoadStateManager mLoadStateManager = new PagedList.LoadStateManager() {
+ @Override
+ protected void onStateChanged(@NonNull PagedList.LoadType type,
+ @NonNull PagedList.LoadState state, @Nullable Throwable error) {
+ mPageConsumer.onStateChanged(type, state, error);
+ }
+ };
+
+ private void listenTo(@NonNull final PagedList.LoadType type,
+ @NonNull final ListenableFuture<? extends DataSource.BaseResult<V>> future) {
+ // First listen on the BG thread if the DataSource is invalid, since it can be expensive
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ // if invalid, drop result on the floor
+ if (mSource.isInvalid()) {
+ detach();
+ return;
+ }
+
+ // Source has been verified to be valid after producing data, so sent data to UI
+ Futures.addCallback(future, new FutureCallback<DataSource.BaseResult<V>>() {
+ @Override
+ public void onSuccess(DataSource.BaseResult<V> value) {
+ onLoadSuccess(type, value);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable throwable) {
+ onLoadError(type, throwable);
+ }
+ }, mNotifyExecutor);
+ }
+ }, mFetchExecutor);
+ }
+
+ interface PageConsumer<V> {
+ // return true if we need to fetch more
+ boolean onPageResult(
+ @NonNull PagedList.LoadType type,
+ @NonNull DataSource.BaseResult<V> pageResult);
+
+ void onStateChanged(@NonNull PagedList.LoadType type,
+ @NonNull PagedList.LoadState state, @Nullable Throwable error);
+ }
+
+ interface AdjacentProvider<V> {
+ V getFirstLoadedItem();
+
+ V getLastLoadedItem();
+
+ int getFirstLoadedItemIndex();
+
+ int getLastLoadedItemIndex();
+
+ /**
+ * Notify the AdjacentProvider of new loaded data, to update first/last item/index.
+ *
+ * NOTE: this data may not be committed (e.g. it may be dropped due to max size). Up to the
+ * implementation of the AdjacentProvider to handle this (generally by ignoring this
+ * call if dropping is supported).
+ */
+ void onPageResultResolution(
+ @NonNull PagedList.LoadType type,
+ @NonNull DataSource.BaseResult<V> result);
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ void onLoadSuccess(PagedList.LoadType type, DataSource.BaseResult<V> value) {
+ if (isDetached()) {
+ // abort!
+ return;
+ }
+
+ mAdjacentProvider.onPageResultResolution(type, value);
+
+ if (mPageConsumer.onPageResult(type, value)) {
+ if (type.equals(PagedList.LoadType.START)) {
+ //noinspection unchecked
+ mPrevKey = (K) value.prevKey;
+ schedulePrepend();
+ } else if (type.equals(PagedList.LoadType.END)) {
+ //noinspection unchecked
+ mNextKey = (K) value.nextKey;
+ scheduleAppend();
+ } else {
+ throw new IllegalStateException("Can only fetch more during append/prepend");
+ }
+ } else {
+ PagedList.LoadState state =
+ value.data.isEmpty() ? PagedList.LoadState.DONE : PagedList.LoadState.IDLE;
+ mLoadStateManager.setState(type, state, null);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic accessor */
+ void onLoadError(PagedList.LoadType type, Throwable throwable) {
+ if (isDetached()) {
+ // abort!
+ return;
+ }
+ // TODO: handle nesting
+ PagedList.LoadState state = mSource.isRetryableError(throwable)
+ ? PagedList.LoadState.RETRYABLE_ERROR : PagedList.LoadState.ERROR;
+ mLoadStateManager.setState(type, state, throwable);
+ }
+
+ public void trySchedulePrepend() {
+ if (mLoadStateManager.getStart().equals(PagedList.LoadState.IDLE)) {
+ schedulePrepend();
+ }
+ }
+
+ public void tryScheduleAppend() {
+ if (mLoadStateManager.getEnd().equals(PagedList.LoadState.IDLE)) {
+ scheduleAppend();
+ }
+ }
+
+ private boolean canPrepend() {
+ if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
+ // don't know count / position from initial load, so be conservative, return true
+ return true;
+ }
+
+ // position is known, do we have space left?
+ return mAdjacentProvider.getFirstLoadedItemIndex() > 0;
+ }
+
+ private boolean canAppend() {
+ if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
+ // don't know count / position from initial load, so be conservative, return true
+ return true;
+ }
+
+ // count is known, do we have space left?
+ return mAdjacentProvider.getLastLoadedItemIndex() < mTotalCount - 1;
+ }
+
+ private void schedulePrepend() {
+ if (!canPrepend()) {
+ onLoadSuccess(PagedList.LoadType.START, DataSource.BaseResult.<V>empty());
+ return;
+ }
+ K key;
+ switch(mSource.mType) {
+ case POSITIONAL:
+ //noinspection unchecked
+ key = (K) ((Integer) (mAdjacentProvider.getFirstLoadedItemIndex() - 1));
+ break;
+ case PAGE_KEYED:
+ key = mPrevKey;
+ break;
+ case ITEM_KEYED:
+ key = mSource.getKey(mAdjacentProvider.getFirstLoadedItem());
+ break;
+ default:
+ throw new IllegalArgumentException("unknown source type");
+ }
+ mLoadStateManager.setState(
+ PagedList.LoadType.START, PagedList.LoadState.LOADING, null);
+ listenTo(PagedList.LoadType.START, mSource.load(new DataSource.Params<>(
+ DataSource.LoadType.START,
+ key,
+ mConfig.initialLoadSizeHint,
+ mConfig.enablePlaceholders,
+ mConfig.pageSize)));
+ }
+
+ private void scheduleAppend() {
+ if (!canAppend()) {
+ onLoadSuccess(PagedList.LoadType.END, DataSource.BaseResult.<V>empty());
+ return;
+ }
+
+ K key;
+ switch(mSource.mType) {
+ case POSITIONAL:
+ //noinspection unchecked
+ key = (K) ((Integer) (mAdjacentProvider.getLastLoadedItemIndex() + 1));
+ break;
+ case PAGE_KEYED:
+ key = mNextKey;
+ break;
+ case ITEM_KEYED:
+ key = mSource.getKey(mAdjacentProvider.getLastLoadedItem());
+ break;
+ default:
+ throw new IllegalArgumentException("unknown source type");
+ }
+
+ mLoadStateManager.setState(PagedList.LoadType.END, PagedList.LoadState.LOADING, null);
+ listenTo(PagedList.LoadType.END, mSource.load(new DataSource.Params<>(
+ DataSource.LoadType.END,
+ key,
+ mConfig.initialLoadSizeHint,
+ mConfig.enablePlaceholders,
+ mConfig.pageSize)));
+ }
+
+ void retry() {
+ if (mLoadStateManager.getStart().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
+ schedulePrepend();
+ }
+ if (mLoadStateManager.getEnd().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
+ scheduleAppend();
+ }
+ }
+
+ boolean isDetached() {
+ return mDetached.get();
+ }
+
+ void detach() {
+ mDetached.set(true);
+ }
+
+ static class SimpleAdjacentProvider<V> implements AdjacentProvider<V> {
+ private int mFirstIndex;
+ private int mLastIndex;
+
+ private V mFirstItem;
+ private V mLastItem;
+
+ boolean mCounted;
+ int mLeadingUnloadedCount;
+ int mTrailingUnloadedCount;
+
+ @Override
+ public V getFirstLoadedItem() {
+ return mFirstItem;
+ }
+
+ @Override
+ public V getLastLoadedItem() {
+ return mLastItem;
+ }
+
+ @Override
+ public int getFirstLoadedItemIndex() {
+ return mFirstIndex;
+ }
+
+ @Override
+ public int getLastLoadedItemIndex() {
+ return mLastIndex;
+ }
+
+ @Override
+ public void onPageResultResolution(@NonNull PagedList.LoadType type,
+ @NonNull DataSource.BaseResult<V> result) {
+ if (result.data.isEmpty()) {
+ return;
+ }
+ if (type == PagedList.LoadType.START) {
+ mFirstIndex -= result.data.size();
+ mFirstItem = result.data.get(0);
+ if (mCounted) {
+ mLeadingUnloadedCount -= result.data.size();
+ }
+ } else if (type == PagedList.LoadType.END) {
+ mLastIndex += result.data.size();
+ mLastItem = result.data.get(result.data.size() - 1);
+ if (mCounted) {
+ mTrailingUnloadedCount -= result.data.size();
+ }
+ } else {
+ mFirstIndex = result.leadingNulls + result.offset;
+ mLastIndex = mFirstIndex + result.data.size() - 1;
+ mFirstItem = result.data.get(0);
+ mLastItem = result.data.get(result.data.size() - 1);
+
+ if (result.counted) {
+ mCounted = true;
+ mLeadingUnloadedCount = result.leadingNulls;
+ mTrailingUnloadedCount = result.trailingNulls;
+ }
+ }
+ }
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
index 158c950..98f95b6 100644
--- a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
@@ -17,13 +17,14 @@
package androidx.paging;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.arch.core.util.Function;
+import androidx.concurrent.futures.ResolvableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
@@ -31,14 +32,9 @@
* <p>
* Extend PositionalDataSource if you can load pages of a requested size at arbitrary
* positions, and provide a fixed item count. If your data source can't support loading arbitrary
- * requested page sizes (e.g. when network page size constraints are only known at runtime), use
- * either {@link PageKeyedDataSource} or {@link ItemKeyedDataSource} instead.
- * <p>
- * Note that unless {@link PagedList.Config#enablePlaceholders placeholders are disabled}
- * PositionalDataSource requires counting the size of the data set. This allows pages to be tiled in
- * at arbitrary, non-contiguous locations based upon what the user observes in a {@link PagedList}.
- * If placeholders are disabled, initialize with the two parameter
- * {@link LoadInitialCallback#onResult(List, int)}.
+ * requested page sizes (e.g. when network page size constraints are only known at runtime), either
+ * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
+ * the two parameter {@link LoadInitialCallback#onResult(List, int)}.
* <p>
* Room can generate a Factory of PositionalDataSources for you:
* <pre>
@@ -48,76 +44,31 @@
* public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
* }</pre>
*
+ * @see ListenablePositionalDataSource
+ *
* @param <T> Type of items being loaded by the PositionalDataSource.
*/
-public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
+public abstract class PositionalDataSource<T> extends ListenablePositionalDataSource<T> {
/**
* Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
*/
- @SuppressWarnings("WeakerAccess")
- public static class LoadInitialParams {
- /**
- * Initial load position requested.
- * <p>
- * Note that this may not be within the bounds of your data set, it may need to be adjusted
- * before you execute your load.
- */
- public final int requestedStartPosition;
-
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines page size acceptable for return values.
- * <p>
- * List of items passed to the callback must be an integer multiple of page size.
- */
- public final int pageSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the total count passed to
- * {@link LoadInitialCallback#onResult(List, int, int)} will be ignored.
- */
- public final boolean placeholdersEnabled;
-
+ public static class LoadInitialParams extends ListenablePositionalDataSource.LoadInitialParams {
public LoadInitialParams(
int requestedStartPosition,
int requestedLoadSize,
int pageSize,
boolean placeholdersEnabled) {
- this.requestedStartPosition = requestedStartPosition;
- this.requestedLoadSize = requestedLoadSize;
- this.pageSize = pageSize;
- this.placeholdersEnabled = placeholdersEnabled;
+ super(requestedStartPosition, requestedLoadSize, pageSize, placeholdersEnabled);
}
}
/**
* Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
*/
- @SuppressWarnings("WeakerAccess")
- public static class LoadRangeParams {
- /**
- * START position of data to load.
- * <p>
- * Returned data must start at this position.
- */
- public final int startPosition;
- /**
- * Number of items to load.
- * <p>
- * Returned data must be of this size, unless at end of the list.
- */
- public final int loadSize;
-
+ public static class LoadRangeParams extends ListenablePositionalDataSource.LoadRangeParams {
public LoadRangeParams(int startPosition, int loadSize) {
- this.startPosition = startPosition;
- this.loadSize = loadSize;
+ super(startPosition, loadSize);
}
}
@@ -175,9 +126,9 @@
public abstract void onResult(@NonNull List<T> data, int position);
/**
- * Called to report a non-retryable error from a DataSource.
+ * Called to report an error from a DataSource.
* <p>
- * Call this method to report a non-retryable error from
+ * Call this method to report an error from
* {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
*
* @param error The error that occurred during loading.
@@ -187,20 +138,6 @@
throw new IllegalStateException(
"You must implement onError if implementing your own load callback");
}
-
- /**
- * Called to report a retryable error from a DataSource.
- * <p>
- * Call this method to report a retryable error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onRetryableError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onRetryableError if implementing your own load callback");
- }
}
/**
@@ -225,9 +162,9 @@
public abstract void onResult(@NonNull List<T> data);
/**
- * Called to report a non-retryable error from a DataSource.
+ * Called to report an error from a DataSource.
* <p>
- * Call this method to report a non-retryable error from
+ * Call this method to report an error from
* {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
*
* @param error The error that occurred during loading.
@@ -237,146 +174,96 @@
throw new IllegalStateException(
"You must implement onError if implementing your own load callback");
}
-
- /**
- * Called to report a retryable error from a DataSource.
- * <p>
- * Call this method to report a retryable error from
- * {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onRetryableError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onRetryableError if implementing your own load callback");
- }
}
- static class LoadInitialCallbackImpl<T> extends LoadInitialCallback<T> {
- final LoadCallbackHelper<T> mCallbackHelper;
- private final boolean mCountingEnabled;
- private final int mPageSize;
+ @NonNull
+ @Override
+ public final ListenableFuture<InitialResult<T>> loadInitial(
+ @NonNull final ListenablePositionalDataSource.LoadInitialParams params) {
+ final ResolvableFuture<InitialResult<T>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ final LoadInitialParams newParams = new LoadInitialParams(
+ params.requestedStartPosition,
+ params.requestedLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled);
+ LoadInitialCallback<T> callback = new LoadInitialCallback<T>() {
+ @Override
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ if (isInvalid()) {
+ // NOTE: this isInvalid() check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(new InitialResult<>(Collections.<T>emptyList(), 0, 0));
+ } else {
+ setFuture(newParams, new InitialResult<>(data, position, totalCount));
+ }
+ }
- LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
- int pageSize, PageResult.Receiver<T> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(dataSource, PageResult.INIT, null, receiver);
- mCountingEnabled = countingEnabled;
- mPageSize = pageSize;
- if (mPageSize < 1) {
- throw new IllegalArgumentException("Page size must be non-negative");
+ @Override
+ public void onResult(@NonNull List<T> data, int position) {
+ if (isInvalid()) {
+ // NOTE: this isInvalid() check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(new InitialResult<>(Collections.<T>emptyList(), 0));
+ } else {
+ setFuture(newParams, new InitialResult<>(data, position));
+ }
+ }
+
+ private void setFuture(
+ @NonNull ListenablePositionalDataSource.LoadInitialParams params,
+ @NonNull InitialResult<T> result) {
+ if (params.placeholdersEnabled) {
+ result.validateForInitialTiling(params.pageSize);
+ }
+ future.set(result);
+ }
+
+
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
+ }
+ };
+ loadInitial(newParams,
+ callback);
}
- }
-
- @Override
- public void onResult(@NonNull List<T> data, int position, int totalCount) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
- if (position + data.size() != totalCount
- && data.size() % mPageSize != 0) {
- throw new IllegalArgumentException("PositionalDataSource requires initial load"
- + " size to be a multiple of page size to support internal tiling."
- + " loadSize " + data.size() + ", position " + position
- + ", totalCount " + totalCount + ", pageSize " + mPageSize);
- }
-
- if (mCountingEnabled) {
- int trailingUnloadedCount = totalCount - position - data.size();
- mCallbackHelper.dispatchResultToReceiver(
- new PageResult<>(data, position, trailingUnloadedCount, 0));
- } else {
- // Only occurs when wrapped as contiguous
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
- }
- }
- }
-
- @Override
- public void onResult(@NonNull List<T> data, int position) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- if (position < 0) {
- throw new IllegalArgumentException("Position must be non-negative");
- }
- if (data.isEmpty() && position != 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- if (mCountingEnabled) {
- throw new IllegalStateException("Placeholders requested, but totalCount not"
- + " provided. Please call the three-parameter onResult method, or"
- + " disable placeholders in the PagedList.Config");
- }
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
- }
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
+ });
+ return future;
}
- static class LoadRangeCallbackImpl<T> extends LoadRangeCallback<T> {
- private LoadCallbackHelper<T> mCallbackHelper;
- private final int mPositionOffset;
- LoadRangeCallbackImpl(@NonNull PositionalDataSource dataSource,
- @PageResult.ResultType int resultType, int positionOffset,
- Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
- mCallbackHelper = new LoadCallbackHelper<>(
- dataSource, resultType, mainThreadExecutor, receiver);
- mPositionOffset = positionOffset;
- }
+ @NonNull
+ @Override
+ public final ListenableFuture<RangeResult<T>> loadRange(
+ final @NonNull ListenablePositionalDataSource.LoadRangeParams params) {
+ final ResolvableFuture<RangeResult<T>> future = ResolvableFuture.create();
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ LoadRangeCallback<T> callback = new LoadRangeCallback<T>() {
+ @Override
+ public void onResult(@NonNull List<T> data) {
+ if (isInvalid()) {
+ future.set(new RangeResult<>(Collections.<T>emptyList()));
+ } else {
+ future.set(new RangeResult<>(data));
+ }
+ }
- @Override
- public void onResult(@NonNull List<T> data) {
- if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
- mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
- data, 0, 0, mPositionOffset));
+ @Override
+ public void onError(@NonNull Throwable error) {
+ future.setException(error);
+ }
+ };
+ loadRange(new LoadRangeParams(
+ params.startPosition,
+ params.loadSize),
+ callback);
}
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, false);
- }
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- mCallbackHelper.dispatchErrorToReceiver(error, true);
- }
- }
-
- final void dispatchLoadInitial(boolean acceptCount,
- int requestedStartPosition, int requestedLoadSize, int pageSize,
- @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
- LoadInitialCallbackImpl<T> callback =
- new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
-
- LoadInitialParams params = new LoadInitialParams(
- requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
- loadInitial(params, callback);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
- }
-
- final void dispatchLoadRange(@PageResult.ResultType int resultType, int startPosition,
- int count, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<T> receiver) {
- LoadRangeCallback<T> callback = new LoadRangeCallbackImpl<>(
- this, resultType, startPosition, mainThreadExecutor, receiver);
- if (count == 0) {
- callback.onResult(Collections.<T>emptyList());
- } else {
- loadRange(new LoadRangeParams(startPosition, count), callback);
- }
+ });
+ return future;
}
/**
@@ -417,11 +304,6 @@
return false;
}
- @NonNull
- ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
- return new ContiguousWithoutPlaceholdersWrapper<>(this);
- }
-
/**
* Helper for computing an initial position in
* {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
@@ -462,9 +344,11 @@
* @param totalCount Total size of the data set.
* @return Position to start loading at.
*
- * @see #computeInitialLoadSize(LoadInitialParams, int, int)
+ *
+ * @see #computeInitialLoadSize(ListenablePositionalDataSource.LoadInitialParams, int, int)
*/
- public static int computeInitialLoadPosition(@NonNull LoadInitialParams params,
+ public static int computeInitialLoadPosition(
+ @NonNull ListenablePositionalDataSource.LoadInitialParams params,
int totalCount) {
int position = params.requestedStartPosition;
int initialLoadSize = params.requestedLoadSize;
@@ -488,7 +372,8 @@
* computed ahead of loading.
* <p>
* This function takes the requested load size, and bounds checks it against the value returned
- * by {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
+ * by
+ * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}.
* <p>
* Example usage in a PositionalDataSource subclass:
* <pre>
@@ -520,123 +405,18 @@
* @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
* including page size, and requested start/loadSize.
* @param initialLoadPosition Value returned by
- * {@link #computeInitialLoadPosition(LoadInitialParams, int)}
+ * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}
* @param totalCount Total size of the data set.
* @return Number of items to load.
*
- * @see #computeInitialLoadPosition(LoadInitialParams, int)
+ * @see #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)
*/
- @SuppressWarnings("WeakerAccess")
- public static int computeInitialLoadSize(@NonNull LoadInitialParams params,
+ public static int computeInitialLoadSize(@NonNull
+ ListenablePositionalDataSource.LoadInitialParams params,
int initialLoadPosition, int totalCount) {
return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
}
- @SuppressWarnings("deprecation")
- static class ContiguousWithoutPlaceholdersWrapper<Value>
- extends ContiguousDataSource<Integer, Value> {
- @NonNull
- final PositionalDataSource<Value> mSource;
-
- ContiguousWithoutPlaceholdersWrapper(
- @NonNull PositionalDataSource<Value> source) {
- mSource = source;
- }
-
- @Override
- public void addInvalidatedCallback(
- @NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.addInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void removeInvalidatedCallback(
- @NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.removeInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void invalidate() {
- mSource.invalidate();
- }
-
- @Override
- public boolean isInvalid() {
- return mSource.isInvalid();
- }
-
- @NonNull
- @Override
- public <ToValue> DataSource<Integer, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- throw new UnsupportedOperationException(
- "Inaccessible inner type doesn't support map op");
- }
-
- @NonNull
- @Override
- public <ToValue> DataSource<Integer, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- throw new UnsupportedOperationException(
- "Inaccessible inner type doesn't support map op");
- }
-
- @Override
- void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
- boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
-
- if (position == null) {
- position = 0;
- } else {
- // snap load size to page multiple (minimum two)
- initialLoadSize = (Math.max(initialLoadSize / pageSize, 2)) * pageSize;
-
- // move start pos so that the load is centered around the key, not starting at it
- final int idealStart = position - initialLoadSize / 2;
- position = Math.max(0, idealStart / pageSize * pageSize);
- }
-
- // Note enablePlaceholders will be false here, but we don't have a way to communicate
- // this to PositionalDataSource. This is fine, because only the list and its position
- // offset will be consumed by the LoadInitialCallback.
- mSource.dispatchLoadInitial(false, position, initialLoadSize,
- pageSize, mainThreadExecutor, receiver);
- }
-
- @Override
- void dispatchLoadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- int startIndex = currentEndIndex + 1;
- mSource.dispatchLoadRange(
- PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver);
- }
-
- @Override
- void dispatchLoadBefore(int currentBeginIndex, @NonNull Value currentBeginItem,
- int pageSize, @NonNull Executor mainThreadExecutor,
- @NonNull PageResult.Receiver<Value> receiver) {
- int startIndex = currentBeginIndex - 1;
- if (startIndex < 0) {
- // trigger empty list load
- mSource.dispatchLoadRange(
- PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver);
- } else {
- int loadSize = Math.min(pageSize, startIndex + 1);
- startIndex = startIndex - loadSize + 1;
- mSource.dispatchLoadRange(
- PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
- }
- }
-
- @Override
- Integer getKey(int position, Value item) {
- return position;
- }
-
- }
-
@NonNull
@Override
public final <V> PositionalDataSource<V> mapByPage(
diff --git a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
index 42d8cfe..a3e5a12 100644
--- a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
+++ b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
@@ -42,6 +42,9 @@
}
@Override
+ public void detach() {}
+
+ @Override
public boolean isDetached() {
return true;
}
@@ -69,6 +72,10 @@
}
@Override
+ void dispatchCurrentLoadState(LoadStateListener listener) {
+ }
+
+ @Override
void loadAroundInternal(int index) {
}
}
diff --git a/paging/common/src/main/java/androidx/paging/TiledPagedList.java b/paging/common/src/main/java/androidx/paging/TiledPagedList.java
deleted file mode 100644
index c01fa83..0000000
--- a/paging/common/src/main/java/androidx/paging/TiledPagedList.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-class TiledPagedList<T> extends PagedList<T>
- implements PagedStorage.Callback {
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final PositionalDataSource<T> mDataSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- PageResult.Receiver<T> mReceiver = new PageResult.Receiver<T>() {
- // Creation thread for initial synchronous load, otherwise main thread
- // Safe to access main thread only state - no other thread has reference during construction
- @AnyThread
- @Override
- public void onPageResult(@PageResult.ResultType int type,
- @NonNull PageResult<T> pageResult) {
- if (pageResult.isInvalid()) {
- detach();
- return;
- }
-
- if (isDetached()) {
- // No op, have detached
- return;
- }
-
- if (type != PageResult.INIT && type != PageResult.TILE) {
- throw new IllegalArgumentException("unexpected resultType" + type);
- }
-
- List<T> page = pageResult.page;
- if (mStorage.getPageCount() == 0) {
- mStorage.initAndSplit(
- pageResult.leadingNulls, page, pageResult.trailingNulls,
- pageResult.positionOffset, mConfig.pageSize, TiledPagedList.this);
- } else {
- mStorage.tryInsertPageAndTrim(
- pageResult.positionOffset,
- page,
- mLastLoad,
- mConfig.maxSize,
- mRequiredRemainder,
- TiledPagedList.this);
- }
-
- if (mBoundaryCallback != null) {
- boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty
- && pageResult.leadingNulls == 0
- && pageResult.positionOffset == 0;
- int size = size();
- boolean deferEnd = !deferEmpty
- && ((type == PageResult.INIT && pageResult.trailingNulls == 0)
- || (type == PageResult.TILE
- && (pageResult.positionOffset + mConfig.pageSize >= size)));
- deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
- }
- }
-
- @Override
- public void onPageError(int type, @NonNull Throwable error, boolean retryable) {
- throw new IllegalStateException("Tiled error handling not yet implemented");
- }
- };
-
- @WorkerThread
- TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @Nullable BoundaryCallback<T> boundaryCallback,
- @NonNull Config config,
- int position) {
- super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
- boundaryCallback, config);
- mDataSource = dataSource;
-
- final int pageSize = mConfig.pageSize;
- mLastLoad = position;
-
- if (mDataSource.isInvalid()) {
- detach();
- } else {
- final int firstLoadSize =
- (Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize;
-
- final int idealStart = position - firstLoadSize / 2;
- final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);
-
- mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
- pageSize, mMainThreadExecutor, mReceiver);
- }
- }
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- @NonNull
- @Override
- public DataSource<?, T> getDataSource() {
- return mDataSource;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mLastLoad;
- }
-
- @Override
- protected void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> pagedListSnapshot,
- @NonNull Callback callback) {
- //noinspection UnnecessaryLocalVariable
- final PagedStorage<T> snapshot = pagedListSnapshot.mStorage;
-
- if (snapshot.isEmpty()
- || mStorage.size() != snapshot.size()) {
- throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
- + " to be a snapshot of this PagedList");
- }
-
- // loop through each page and signal the callback for any pages that are present now,
- // but not in the snapshot.
- final int pageSize = mConfig.pageSize;
- final int leadingNullPages = mStorage.getLeadingNullCount() / pageSize;
- final int pageCount = mStorage.getPageCount();
- for (int i = 0; i < pageCount; i++) {
- int pageIndex = i + leadingNullPages;
- int updatedPages = 0;
- // count number of consecutive pages that were added since the snapshot...
- while (updatedPages < mStorage.getPageCount()
- && mStorage.hasPage(pageSize, pageIndex + updatedPages)
- && !snapshot.hasPage(pageSize, pageIndex + updatedPages)) {
- updatedPages++;
- }
- // and signal them all at once to the callback
- if (updatedPages > 0) {
- callback.onChanged(pageIndex * pageSize, pageSize * updatedPages);
- i += updatedPages - 1;
- }
- }
- }
-
- @Override
- protected void loadAroundInternal(int index) {
- mStorage.allocatePlaceholders(index, mConfig.prefetchDistance, mConfig.pageSize, this);
- }
-
- @Override
- public void onInitialized(int count) {
- notifyInserted(0, count);
- }
-
- @Override
- public void onPagePrepended(int leadingNulls, int changed, int added) {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
- }
-
- @Override
- public void onPageAppended(int endPosition, int changed, int added) {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
- }
-
- @Override
- public void onEmptyPrepend() {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
- }
-
- @Override
- public void onEmptyAppend() {
- throw new IllegalStateException("Contiguous callback on TiledPagedList");
- }
-
- @Override
- public void onPagePlaceholderInserted(final int pageIndex) {
- // placeholder means initialize a load
- mBackgroundThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- if (isDetached()) {
- return;
- }
- final int pageSize = mConfig.pageSize;
-
- if (mDataSource.isInvalid()) {
- detach();
- } else {
- int startPosition = pageIndex * pageSize;
- int count = Math.min(pageSize, mStorage.size() - startPosition);
- mDataSource.dispatchLoadRange(
- PageResult.TILE, startPosition, count, mMainThreadExecutor, mReceiver);
- }
- }
- });
- }
-
- @Override
- public void onPageInserted(int start, int count) {
- notifyChanged(start, count);
- }
-
- @Override
- public void onPagesRemoved(int startOfDrops, int count) {
- notifyRemoved(startOfDrops, count);
- }
-
- @Override
- public void onPagesSwappedToPlaceholder(int startOfDrops, int count) {
- notifyChanged(startOfDrops, count);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
new file mode 100644
index 0000000..d8c4df4
--- /dev/null
+++ b/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.util.Function;
+import androidx.paging.futures.DirectExecutor;
+import androidx.paging.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.IdentityHashMap;
+import java.util.List;
+
+/**
+ * @param <Key> DataSource key type, same for original and wrapped.
+ * @param <ValueFrom> Value type of original DataSource.
+ * @param <ValueTo> Value type of new DataSource.
+ */
+class WrapperDataSource<Key, ValueFrom, ValueTo> extends DataSource<Key, ValueTo> {
+ private final DataSource<Key, ValueFrom> mSource;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Function<List<ValueFrom>, List<ValueTo>> mListFunction;
+
+
+ private final IdentityHashMap<ValueTo, Key> mKeyMap;
+
+ WrapperDataSource(@NonNull DataSource<Key, ValueFrom> source,
+ @NonNull Function<List<ValueFrom>, List<ValueTo>> listFunction) {
+ super(source.mType);
+ mSource = source;
+ mListFunction = listFunction;
+ mKeyMap = source.mType == KeyType.ITEM_KEYED ? new IdentityHashMap<ValueTo, Key>() : null;
+ }
+
+ @Override
+ public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ mSource.addInvalidatedCallback(onInvalidatedCallback);
+ }
+
+ @Override
+ public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
+ mSource.removeInvalidatedCallback(onInvalidatedCallback);
+ }
+
+ @Override
+ public void invalidate() {
+ mSource.invalidate();
+ }
+
+ @Override
+ public boolean isInvalid() {
+ return mSource.isInvalid();
+ }
+
+ @Nullable
+ @Override
+ Key getKey(@NonNull ValueTo item) {
+ if (mKeyMap != null) {
+ synchronized (mKeyMap) {
+ return mKeyMap.get(item);
+ }
+ }
+ // positional / page-keyed
+ return null;
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void stashKeysIfNeeded(@NonNull List<ValueFrom> source, @NonNull List<ValueTo> dest) {
+ if (mKeyMap != null) {
+ synchronized (mKeyMap) {
+ for (int i = 0; i < dest.size(); i++) {
+ mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
+ }
+ }
+ }
+ }
+
+ @Override
+ final ListenableFuture<? extends BaseResult> load(@NonNull Params params) {
+ //noinspection unchecked
+ return Futures.transform(
+ mSource.load(params),
+ new Function<BaseResult<ValueFrom>, BaseResult<ValueTo>>() {
+ @Override
+ public BaseResult<ValueTo> apply(BaseResult<ValueFrom> input) {
+ BaseResult<ValueTo> result = new BaseResult<>(input, mListFunction);
+ stashKeysIfNeeded(input.data, result.data);
+ return result;
+ }
+ },
+ DirectExecutor.INSTANCE);
+ }
+}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
index dc9e3b7..3a1b5c9 100644
--- a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
@@ -86,11 +86,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
@@ -107,11 +102,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
@@ -128,11 +118,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
index 0010df5..3ffc79d 100644
--- a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
@@ -74,11 +74,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
@@ -95,11 +90,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
@@ -116,11 +106,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
index 012af75..fdc2b9a 100644
--- a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
+++ b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
@@ -70,11 +70,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
@@ -91,11 +86,6 @@
public void onError(@NonNull Throwable error) {
callback.onError(error);
}
-
- @Override
- public void onRetryableError(@NonNull Throwable error) {
- callback.onRetryableError(error);
- }
});
}
}
diff --git a/paging/common/src/main/java/androidx/paging/futures/Futures.java b/paging/common/src/main/java/androidx/paging/futures/Futures.java
index 272512b..4ad52f8 100644
--- a/paging/common/src/main/java/androidx/paging/futures/Futures.java
+++ b/paging/common/src/main/java/androidx/paging/futures/Futures.java
@@ -33,8 +33,43 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class Futures {
private Futures() {}
-
- private static <V> void addCallback(@NonNull final ListenableFuture<V> future,
+ /**
+ * Registers separate success and failure callbacks to be run when the {@code Future}'s
+ * computation is complete or, if the computation is already complete, immediately.
+ *
+ * <p>The callback is run on {@code executor}. There is no guaranteed ordering of execution of
+ * callbacks, but any callback added through this method is guaranteed to be called once the
+ * computation is complete.
+ *
+ * <p>Example:
+ *
+ * <pre>{@code
+ * ListenableFuture<QueryResult> future = ...;
+ * Executor e = ...
+ * addCallback(future,
+ * new FutureCallback<QueryResult>() {
+ * public void onSuccess(QueryResult result) {
+ * storeInCache(result);
+ * }
+ * public void onFailure(Throwable t) {
+ * reportError(t);
+ * }
+ * }, e);
+ * }</pre>
+ *
+ * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
+ * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
+ * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
+ * callbacks passed to this method.
+ *
+ * <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
+ * ListenableFuture#addListener addListener}.
+ *
+ * @param future The future attach the callback to.
+ * @param callback The callback to invoke when {@code future} is completed.
+ * @param executor The executor to run {@code callback} when the future completes.
+ */
+ public static <V> void addCallback(@NonNull final ListenableFuture<V> future,
@NonNull final FutureCallback<? super V> callback, @NonNull Executor executor) {
future.addListener(new Runnable() {
@Override
diff --git a/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt b/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt
deleted file mode 100644
index 6a4ff37..0000000
--- a/paging/common/src/test/java/androidx/paging/AsyncListDataSource.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging
-
-class AsyncListDataSource<T>(list: List<T>)
- : PositionalDataSource<T>() {
- private val workItems: MutableList<() -> Unit> = ArrayList()
- private val listDataSource = ListDataSource(list)
-
- override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
- workItems.add {
- listDataSource.loadInitial(params, callback)
- }
- }
-
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
- workItems.add {
- listDataSource.loadRange(params, callback)
- }
- }
-
- fun flush() {
- workItems.map { it() }
- workItems.clear()
- }
-}
diff --git a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
index 5099839..98161da 100644
--- a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
@@ -16,13 +16,11 @@
package androidx.paging
-import androidx.arch.core.util.Function
-import androidx.paging.PagedList.LoadState.LOADING
import androidx.paging.PagedList.LoadState.IDLE
+import androidx.paging.PagedList.LoadState.LOADING
import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
-import org.junit.Assert.assertArrayEquals
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -33,7 +31,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
-import java.util.concurrent.Executor
@RunWith(Parameterized::class)
class ContiguousPagedListTest(private val placeholdersEnabled: Boolean) {
@@ -41,6 +38,7 @@
private val mBackgroundThread = TestExecutor()
private class Item(position: Int) {
+ val pos: Int = position
val name: String = "Item $position"
override fun toString(): String {
@@ -48,82 +46,52 @@
}
}
+ /**
+ * Note: we use a non-positional dataSource here because we want to avoid the initial load size
+ * and alignment restrictions. These tests were written before positional+contiguous enforced
+ * these behaviors.
+ */
private inner class TestSource(val listData: List<Item> = ITEMS)
- : ContiguousDataSource<Int, Item>() {
- override fun dispatchLoadInitial(
- key: Int?,
- initialLoadSize: Int,
- pageSize: Int,
- enablePlaceholders: Boolean,
- mainThreadExecutor: Executor,
- receiver: PageResult.Receiver<Item>
+ : ItemKeyedDataSource<Int, Item>() {
+ override fun loadInitial(
+ params: LoadInitialParams<Int>,
+ callback: LoadInitialCallback<Item>
) {
- val convertPosition = key ?: 0
- val position = Math.max(0, (convertPosition - initialLoadSize / 2))
- val data = getClampedRange(position, position + initialLoadSize)
- if (data != null) {
- val trailingUnloadedCount = listData.size - position - data.size
+ val initPos = params.requestedInitialKey ?: 0
+ val start = Math.max(initPos - params.requestedLoadSize / 2, 0)
- if (enablePlaceholders && placeholdersEnabled) {
- receiver.onPageResult(
- PageResult.INIT,
- PageResult(data, position, trailingUnloadedCount, 0)
- )
- } else {
- // still must pass offset, even if not counted
- receiver.onPageResult(
- PageResult.INIT,
- PageResult(data, position)
- )
- }
+ val result = getClampedRange(start, start + params.requestedLoadSize)
+ if (result == null) {
+ callback.onError(Exception())
} else {
- receiver.onPageError(PageResult.INIT, Exception(), true)
- }
- }
-
- override fun dispatchLoadAfter(
- currentEndIndex: Int,
- currentEndItem: Item,
- pageSize: Int,
- mainThreadExecutor: Executor,
- receiver: PageResult.Receiver<Item>
- ) {
- val startIndex = currentEndIndex + 1
- val data = getClampedRange(startIndex, startIndex + pageSize)
-
- mainThreadExecutor.execute {
- if (data != null) {
- receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
+ if (placeholdersEnabled) {
+ callback.onResult(result, start, listData.size)
} else {
- receiver.onPageError(PageResult.APPEND, Exception(), true)
+ callback.onResult(result)
}
}
}
- override fun dispatchLoadBefore(
- currentBeginIndex: Int,
- currentBeginItem: Item,
- pageSize: Int,
- mainThreadExecutor: Executor,
- receiver: PageResult.Receiver<Item>
- ) {
-
- val startIndex = currentBeginIndex - 1
- val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
-
- mainThreadExecutor.execute {
- if (data != null) {
- receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
- } else {
- receiver.onPageError(PageResult.PREPEND, Exception(), true)
- }
+ override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Item>) {
+ val result = getClampedRange(params.key + 1, params.key + 1 + params.requestedLoadSize)
+ if (result == null) {
+ callback.onError(Exception())
+ } else {
+ callback.onResult(result)
}
}
- override fun getKey(position: Int, item: Item?): Int {
- return 0
+ override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Item>) {
+ val result = getClampedRange(params.key - params.requestedLoadSize, params.key)
+ if (result == null) {
+ callback.onError(Exception())
+ } else {
+ callback.onResult(result)
+ }
}
+ override fun getKey(item: Item): Int = item.pos
+
private fun getClampedRange(startInc: Int, endExc: Int): List<Item>? {
val matching = errorIndices.filter { it in startInc..(endExc - 1) }
if (matching.isNotEmpty()) {
@@ -134,21 +102,15 @@
return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
}
- override fun <ToValue : Any?> mapByPage(function: Function<List<Item>, List<ToValue>>):
- DataSource<Int, ToValue> {
- throw UnsupportedOperationException()
- }
-
- override fun <ToValue : Any?> map(function: Function<Item, ToValue>):
- DataSource<Int, ToValue> {
- throw UnsupportedOperationException()
- }
-
fun enqueueErrorForIndex(index: Int) {
errorIndices.add(index)
}
val errorIndices = mutableListOf<Int>()
+
+ override fun isRetryableError(error: Throwable): Boolean {
+ return true
+ }
}
private fun DataSource<*, Item>.enqueueErrorForIndex(index: Int) {
@@ -177,7 +139,7 @@
// assert nulls + content
val expected = arrayOfNulls<Item>(ITEMS.size)
System.arraycopy(ITEMS.toTypedArray(), start, expected, start, count)
- assertArrayEquals(expected, actual.toTypedArray())
+ assertEquals(expected.toList(), actual)
val expectedTrailing = ITEMS.size - start - count
assertEquals(ITEMS.size, actual.size)
@@ -202,28 +164,32 @@
}
private fun createCountedPagedList(
- initialPosition: Int,
+ initialPosition: Int?,
pageSize: Int = 20,
initLoadSize: Int = 40,
prefetchDistance: Int = 20,
listData: List<Item> = ITEMS,
boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
- lastLoad: Int = ContiguousPagedList.LAST_LOAD_UNSPECIFIED,
- maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED
+ maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED,
+ dataSource: DataSource<Int, Item> = TestSource(listData)
): ContiguousPagedList<Int, Item> {
- return ContiguousPagedList(
- TestSource(listData),
+ val ret = PagedList.create(
+ dataSource,
mMainThread,
mBackgroundThread,
+ DirectExecutor.INSTANCE,
boundaryCallback,
PagedList.Config.Builder()
.setPageSize(pageSize)
.setInitialLoadSizeHint(initLoadSize)
.setPrefetchDistance(prefetchDistance)
.setMaxSize(maxSize)
+ .setEnablePlaceholders(placeholdersEnabled)
.build(),
- initialPosition,
- lastLoad)
+ initialPosition
+ ).get()
+ @Suppress("UNCHECKED_CAST")
+ return ret as ContiguousPagedList<Int, Item>
}
@Test
@@ -320,24 +286,24 @@
@Test
fun outwards() {
- val pagedList = createCountedPagedList(50)
+ val pagedList = createCountedPagedList(40)
val callback = mock(PagedList.Callback::class.java)
pagedList.addWeakCallback(null, callback)
- verifyRange(30, 40, pagedList)
+ verifyRange(20, 40, pagedList)
verifyZeroInteractions(callback)
- pagedList.loadAround(if (placeholdersEnabled) 65 else 35)
+ pagedList.loadAround(if (placeholdersEnabled) 55 else 35)
drain()
- verifyRange(30, 60, pagedList)
- verifyCallback(callback, 70, 40)
+ verifyRange(20, 60, pagedList)
+ verifyCallback(callback, 60, 40)
verifyNoMoreInteractions(callback)
- pagedList.loadAround(if (placeholdersEnabled) 35 else 5)
+ pagedList.loadAround(if (placeholdersEnabled) 25 else 5)
drain()
- verifyRange(10, 80, pagedList)
- verifyCallback(callback, 10, 0)
+ verifyRange(0, 80, pagedList)
+ verifyCallback(callback, 0, 0)
verifyNoMoreInteractions(callback)
}
@@ -752,10 +718,11 @@
@Test
fun initialLoad_lastLoad() {
val pagedList = createCountedPagedList(
- initialPosition = 0,
+ initialPosition = 4,
initLoadSize = 20,
- lastLoad = 4)
- // last load is param passed
+ pageSize = 10,
+ dataSource = ListDataSource(ITEMS))
+ // With positional DataSource, last load is param passed
assertEquals(4, pagedList.mLastLoad)
verifyRange(0, 20, pagedList)
}
@@ -763,72 +730,35 @@
@Test
fun initialLoad_lastLoadComputed() {
val pagedList = createCountedPagedList(
- initialPosition = 0,
- initLoadSize = 20,
- lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+ initialPosition = null,
+ initLoadSize = 20)
// last load is middle of initial load
assertEquals(10, pagedList.mLastLoad)
verifyRange(0, 20, pagedList)
}
@Test
- fun initialLoadAsync() {
- // Note: ignores Parameterized param
- val asyncDataSource = AsyncListDataSource(ITEMS)
- val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
- val pagedList = ContiguousPagedList(
- dataSource, mMainThread, mBackgroundThread, null,
- PagedList.Config.Builder().setPageSize(10).build(), null,
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
-
- assertTrue(pagedList.isEmpty())
- drain()
- assertTrue(pagedList.isEmpty())
- asyncDataSource.flush()
- assertTrue(pagedList.isEmpty())
- mBackgroundThread.executeAll()
- assertTrue(pagedList.isEmpty())
- verifyZeroInteractions(callback)
-
- // Data source defers callbacks until flush, which posts result to main thread
- mMainThread.executeAll()
- assertFalse(pagedList.isEmpty())
- // callback onInsert called once with initial size
- verify(callback).onInserted(0, pagedList.size)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
fun addWeakCallbackEmpty() {
- // Note: ignores Parameterized param
- val asyncDataSource = AsyncListDataSource(ITEMS)
- val dataSource = asyncDataSource.wrapAsContiguousWithoutPlaceholders()
- val pagedList = ContiguousPagedList(
- dataSource, mMainThread, mBackgroundThread, null,
- PagedList.Config.Builder().setPageSize(10).build(), null,
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+ val pagedList = createCountedPagedList(0)
val callback = mock(PagedList.Callback::class.java)
+ verifyRange(0, 40, pagedList)
// capture empty snapshot
- val emptySnapshot = pagedList.snapshot()
- assertTrue(pagedList.isEmpty())
- assertTrue(emptySnapshot.isEmpty())
+ val initSnapshot = pagedList.snapshot()
+ assertEquals(pagedList, initSnapshot)
// verify that adding callback notifies nothing going from empty -> empty
- pagedList.addWeakCallback(emptySnapshot, callback)
+ pagedList.addWeakCallback(initSnapshot, callback)
verifyZeroInteractions(callback)
pagedList.removeWeakCallback(callback)
- // data added in asynchronously
- asyncDataSource.flush()
+ pagedList.loadAround(35)
drain()
- assertFalse(pagedList.isEmpty())
+ verifyRange(0, 60, pagedList)
// verify that adding callback notifies insert going from empty -> content
- pagedList.addWeakCallback(emptySnapshot, callback)
- verify(callback).onInserted(0, pagedList.size)
+ pagedList.addWeakCallback(initSnapshot, callback)
+ verifyCallback(callback, 40)
verifyNoMoreInteractions(callback)
}
diff --git a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
index 3f6efdbf..fb72c74 100644
--- a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -16,15 +16,14 @@
package androidx.paging
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -39,20 +38,10 @@
key: Key?,
initialLoadSize: Int,
enablePlaceholders: Boolean
- ): PageResult<Item> {
- @Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<Item>
- @Suppress("UNCHECKED_CAST")
- val captor = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<Item>>
-
- dataSource.dispatchLoadInitial(key, initialLoadSize,
- /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
-
- verify(receiver).onPageResult(anyInt(), captor.capture())
- verifyNoMoreInteractions(receiver)
- assertNotNull(captor.value)
- return captor.value
+ ): DataSource.BaseResult<Item> {
+ dataSource.initExecutor(DirectExecutor.INSTANCE)
+ return dataSource.loadInitial(
+ ItemKeyedDataSource.LoadInitialParams(key, initialLoadSize, enablePlaceholders)).get()
}
@Test
@@ -61,7 +50,7 @@
val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[49]), 10, true)
assertEquals(45, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
assertEquals(45, result.trailingNulls)
}
@@ -73,7 +62,7 @@
val result = loadInitial(dataSource, dataSource.getKey(ITEMS_BY_NAME_ID[0]), 20, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 1), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -86,7 +75,7 @@
val result = loadInitial(dataSource, key, 20, true)
assertEquals(90, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(90, 100), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -98,7 +87,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.data)
assertEquals(90, result.trailingNulls)
}
@@ -114,7 +103,7 @@
// do: load after was empty, so pass full size to load before, since this can incur larger
// loads than requested (see keyMatchesLastItem test)
assertEquals(95, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(95, 100), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -129,7 +118,7 @@
val result = loadInitial(dataSource, key, 10, false)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -142,7 +131,7 @@
val result = loadInitial(dataSource, key, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -154,7 +143,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.page)
+ assertEquals(ITEMS_BY_NAME_ID.subList(0, 10), result.data)
assertEquals(0, result.trailingNulls)
}
@@ -169,7 +158,7 @@
val result = loadInitial(dataSource, key, 10, true)
assertEquals(0, result.leadingNulls)
- assertTrue(result.page.isEmpty())
+ assertTrue(result.data.isEmpty())
assertEquals(0, result.trailingNulls)
}
@@ -179,7 +168,7 @@
val result = loadInitial(dataSource, null, 10, true)
assertEquals(0, result.leadingNulls)
- assertTrue(result.page.isEmpty())
+ assertTrue(result.data.isEmpty())
assertEquals(0, result.trailingNulls)
}
@@ -318,13 +307,14 @@
}
}
- ContiguousPagedList<String, String>(
- dataSource, FailExecutor(), FailExecutor(), null,
- PagedList.Config.Builder()
- .setPageSize(10)
- .build(),
- "",
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+ PagedList.create(dataSource, FailExecutor(),
+ DirectExecutor.INSTANCE,
+ DirectExecutor.INSTANCE,
+ null,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "").get()
}
@Test
@@ -364,12 +354,6 @@
it.onResult(emptyList(), 0, 2)
}
- @Test
- fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
- // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
- it.onResult(emptyList(), 0, 1)
- }
-
private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
: ItemKeyedDataSource<K, B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
@@ -401,10 +385,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -417,10 +397,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -433,10 +409,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
diff --git a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
index b7cafe7..54a5a6a 100644
--- a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -48,7 +49,7 @@
callback: LoadInitialCallback<String, Item>
) {
if (error) {
- callback.onRetryableError(EXCEPTION)
+ callback.onError(EXCEPTION)
error = false
return
}
@@ -59,7 +60,7 @@
override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
if (error) {
- callback.onRetryableError(EXCEPTION)
+ callback.onError(EXCEPTION)
error = false
return
}
@@ -70,7 +71,7 @@
override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<String, Item>) {
if (error) {
- callback.onRetryableError(EXCEPTION)
+ callback.onError(EXCEPTION)
error = false
return
}
@@ -87,10 +88,17 @@
@Test
fun loadFullVerify() {
// validate paging entire ItemDataSource results in full, correctly ordered data
- val pagedList = ContiguousPagedList<String, Item>(ItemDataSource(),
- mMainThread, mBackgroundThread,
- null, PagedList.Config.Builder().setPageSize(100).build(), null,
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+ val pagedListFuture = PagedList.create(
+ ItemDataSource(),
+ mMainThread,
+ mBackgroundThread,
+ mBackgroundThread,
+ null,
+ PagedList.Config.Builder().setPageSize(100).build(),
+ null
+ )
+ mBackgroundThread.executeAll()
+ val pagedList = pagedListFuture.get()
// validate initial load
assertEquals(PAGE_MAP[INIT_KEY]!!.data, pagedList)
@@ -138,13 +146,14 @@
}
}
- ContiguousPagedList<String, String>(
- dataSource, FailExecutor(), FailExecutor(), null,
- PagedList.Config.Builder()
- .setPageSize(10)
- .build(),
- "",
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+ PagedList.create(dataSource, FailExecutor(),
+ DirectExecutor.INSTANCE,
+ DirectExecutor.INSTANCE,
+ null,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "").get()
}
@Test
@@ -185,12 +194,6 @@
}
@Test
- fun initialLoadCallbackInvalidThreeArg() = performLoadInitial(invalidateDataSource = true) {
- // LoadInitialCallback doesn't throw on invalid args if DataSource is invalid
- it.onResult(emptyList(), 0, 1, null, null)
- }
-
- @Test
fun pageDroppingNotSupported() {
assertFalse(ItemDataSource().supportsPageDropping())
}
@@ -226,16 +229,17 @@
val boundaryCallback =
mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = ContiguousPagedList<String, String>(
- dataSource,
- executor,
- executor,
- boundaryCallback,
- PagedList.Config.Builder()
- .setPageSize(10)
- .build(),
- "",
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+
+ val pagedList = PagedList.create(dataSource,
+ executor,
+ executor,
+ executor,
+ boundaryCallback,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "").apply { executor.executeAll() }.get()
+
pagedList.loadAround(0)
verifyZeroInteractions(boundaryCallback)
@@ -278,16 +282,17 @@
val boundaryCallback =
mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = ContiguousPagedList<String, String>(
- dataSource,
- executor,
- executor,
- boundaryCallback,
- PagedList.Config.Builder()
- .setPageSize(10)
- .build(),
- "",
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
+
+ val pagedList = PagedList.create(dataSource,
+ executor,
+ executor,
+ executor,
+ boundaryCallback,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "").apply { executor.executeAll() }.get()
+
pagedList.loadAround(0)
verifyZeroInteractions(boundaryCallback)
@@ -341,10 +346,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -357,10 +358,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -373,10 +370,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -419,7 +412,7 @@
// load after - error
orig.enqueueError()
wrapper.loadAfter(PageKeyedDataSource.LoadParams(expectedInitial.next, 4), loadCallback)
- verify(loadCallback).onRetryableError(EXCEPTION)
+ verify(loadCallback).onError(EXCEPTION)
verifyNoMoreInteractions(loadCallback)
// load before
@@ -433,7 +426,7 @@
// load before - error
orig.enqueueError()
wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev, 4), loadCallback)
- verify(loadCallback).onRetryableError(EXCEPTION)
+ verify(loadCallback).onError(EXCEPTION)
verifyNoMoreInteractions(loadCallback)
// verify invalidation
diff --git a/paging/common/src/test/java/androidx/paging/PagedListTest.kt b/paging/common/src/test/java/androidx/paging/PagedListTest.kt
new file mode 100644
index 0000000..3128e85
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/PagedListTest.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PagedListTest {
+ private val mainThread = TestExecutor()
+ private val backgroundThread = TestExecutor()
+
+ @Test
+ fun createLegacy() {
+ @Suppress("DEPRECATION")
+ val pagedList = PagedList.Builder(ListDataSource(ITEMS), 100)
+ .setNotifyExecutor(mainThread)
+ .setFetchExecutor(backgroundThread)
+ .build()
+ // if build succeeds without flushing an executor, success!
+ assertEquals(ITEMS, pagedList)
+ }
+
+ @Test
+ fun createAsync() {
+ val config = PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build()
+ val success = mutableListOf(false)
+ val future = PagedList.create(
+ ListDataSource(ITEMS),
+ mainThread,
+ backgroundThread,
+ backgroundThread,
+ null,
+ config,
+ 0)
+ future.addListener(Runnable {
+ assertEquals(ITEMS.subList(0, 30), future.get())
+ success[0] = true
+ }, backgroundThread)
+ backgroundThread.executeAll()
+ assertTrue(success[0])
+ }
+
+ @Test
+ fun createAsyncThrow() {
+ val dataSource = object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ callback.onError(Exception())
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ fail("no load range expected")
+ }
+ }
+
+ val config = PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build()
+ val success = mutableListOf(false)
+ val future = PagedList.create(
+ dataSource,
+ mainThread,
+ backgroundThread,
+ backgroundThread,
+ null,
+ config,
+ 0)
+ future.addListener(Runnable {
+ try {
+ future.get()
+ } catch (e: Exception) {
+ success[0] = true
+ }
+ }, backgroundThread)
+ backgroundThread.executeAll()
+ assertTrue(success[0])
+ }
+
+ private val ITEMS = List(100) { "$it" }
+}
diff --git a/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt b/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
index d99b401..44c5f48 100644
--- a/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
+++ b/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
@@ -619,8 +619,6 @@
override fun onPageInserted(start: Int, count: Int) {}
override fun onPagesRemoved(startOfDrops: Int, count: Int) {}
override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {}
- override fun onEmptyPrepend() {}
- override fun onEmptyAppend() {}
}
}
}
diff --git a/paging/common/src/test/java/androidx/paging/PagerTest.kt b/paging/common/src/test/java/androidx/paging/PagerTest.kt
new file mode 100644
index 0000000..167d304f
--- /dev/null
+++ b/paging/common/src/test/java/androidx/paging/PagerTest.kt
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging
+
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.paging.PagedList.LoadState.DONE
+import androidx.paging.PagedList.LoadState.IDLE
+import androidx.paging.PagedList.LoadState.LOADING
+import androidx.paging.PagedList.LoadType.END
+import androidx.paging.PagedList.LoadType.START
+import androidx.paging.PositionalDataSource.computeInitialLoadPosition
+import androidx.paging.PositionalDataSource.computeInitialLoadSize
+import androidx.paging.futures.DirectExecutor
+import com.google.common.util.concurrent.ListenableFuture
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PagerTest {
+ val testExecutor = TestExecutor()
+
+ inner class ImmediateListDataSource(private val data: List<String>) :
+ ListenablePositionalDataSource<String>() {
+ init {
+ initExecutor(testExecutor)
+ }
+
+ override fun loadInitial(params: LoadInitialParams):
+ ListenableFuture<InitialResult<String>> {
+ val future = ResolvableFuture.create<InitialResult<String>>()
+ executor.execute {
+ val totalCount = data.size
+
+ val position = computeInitialLoadPosition(params, totalCount)
+ val loadSize = computeInitialLoadSize(params, position, totalCount)
+
+ val sublist = data.subList(position, position + loadSize)
+ future.set(InitialResult(sublist, position, totalCount))
+ }
+ return future
+ }
+
+ override fun loadRange(params: LoadRangeParams):
+ ListenableFuture<RangeResult<String>> {
+ val future = ResolvableFuture.create<RangeResult<String>>()
+ executor.execute {
+
+ val position = params.startPosition
+ val end = Math.min(position + params.loadSize, data.size)
+ future.set(RangeResult(position, end))
+ }
+
+ return future
+ }
+ }
+
+ val data = List(9) { "$it" }
+
+ private fun RangeResult(start: Int, end: Int):
+ ListenablePositionalDataSource.RangeResult<String> {
+ return ListenablePositionalDataSource.RangeResult(data.subList(start, end))
+ }
+
+ private data class Result(
+ val type: PagedList.LoadType,
+ val pageResult: DataSource.BaseResult<String>
+ )
+
+ private data class StateChange(
+ val type: PagedList.LoadType,
+ val state: PagedList.LoadState,
+ val error: Throwable? = null
+ )
+
+ private class MockConsumer : Pager.PageConsumer<String> {
+
+ private val results: MutableList<Result> = arrayListOf()
+ private val stateChanges: MutableList<StateChange> = arrayListOf()
+
+ fun takeResults(): List<Result> {
+ val ret = results.map { it }
+ results.clear()
+ return ret
+ }
+
+ fun takeStateChanges(): List<StateChange> {
+ val ret = stateChanges.map { it }
+ stateChanges.clear()
+ return ret
+ }
+
+ override fun onPageResult(
+ type: PagedList.LoadType,
+ pageResult: DataSource.BaseResult<String>
+ ): Boolean {
+ results.add(Result(type, pageResult))
+ return false
+ }
+
+ override fun onStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ stateChanges.add(StateChange(type, state, error))
+ }
+ }
+
+ private fun createPager(consumer: MockConsumer, start: Int = 0, end: Int = 10) = Pager(
+ PagedList.Config(2, 2, true, 10, PagedList.Config.MAX_SIZE_UNBOUNDED),
+ ImmediateListDataSource(data),
+ DirectExecutor.INSTANCE,
+ DirectExecutor.INSTANCE,
+ consumer,
+ null,
+ ListenablePositionalDataSource.InitialResult(data.subList(start, end), start, data.size))
+
+ @Test
+ fun simplePagerAppend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 2, 6)
+
+ assertTrue(consumer.takeResults().isEmpty())
+ assertTrue(consumer.takeStateChanges().isEmpty())
+
+ pager.tryScheduleAppend()
+
+ assertTrue(consumer.takeResults().isEmpty())
+ assertEquals(consumer.takeStateChanges(), listOf(
+ StateChange(END, PagedList.LoadState.LOADING)))
+
+ testExecutor.executeAll()
+
+ assertEquals(listOf(Result(END, RangeResult(6, 8))),
+ consumer.takeResults())
+ assertEquals(listOf(StateChange(END, IDLE)),
+ consumer.takeStateChanges())
+ }
+
+ @Test
+ fun simplePagerPrepend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 4, 8)
+
+ pager.trySchedulePrepend()
+
+ assertTrue(consumer.takeResults().isEmpty())
+ assertEquals(consumer.takeStateChanges(), listOf(
+ StateChange(START, PagedList.LoadState.LOADING)))
+
+ testExecutor.executeAll()
+
+ assertEquals(listOf(Result(START, RangeResult(2, 4))),
+ consumer.takeResults())
+ assertEquals(listOf(StateChange(START, IDLE)),
+ consumer.takeStateChanges())
+ }
+
+ @Test
+ fun doubleAppend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 2, 6)
+
+ pager.tryScheduleAppend()
+ testExecutor.executeAll()
+ pager.tryScheduleAppend()
+ testExecutor.executeAll()
+
+ assertEquals(listOf(
+ Result(END, RangeResult(6, 8)),
+ Result(END, RangeResult(8, 9))
+ ), consumer.takeResults())
+ assertEquals(listOf(
+ StateChange(END, LOADING),
+ StateChange(END, IDLE),
+ StateChange(END, LOADING),
+ StateChange(END, IDLE)
+ ), consumer.takeStateChanges())
+ }
+
+ @Test
+ fun doublePrepend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 4, 8)
+
+ pager.trySchedulePrepend()
+ testExecutor.executeAll()
+ pager.trySchedulePrepend()
+ testExecutor.executeAll()
+
+ assertEquals(listOf(
+ Result(START, RangeResult(2, 4)),
+ Result(START, RangeResult(0, 2))
+ ), consumer.takeResults())
+ assertEquals(listOf(
+ StateChange(START, LOADING),
+ StateChange(START, IDLE),
+ StateChange(START, LOADING),
+ StateChange(START, IDLE)
+ ), consumer.takeStateChanges())
+ }
+
+ @Test
+ fun emptyAppend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 0, 9)
+
+ pager.tryScheduleAppend()
+
+ // Pager triggers an immediate empty response here, so we don't need to flush the executor
+ assertEquals(listOf(
+ Result(END, DataSource.BaseResult.empty())
+ ), consumer.takeResults())
+ assertEquals(listOf(
+ StateChange(END, DONE)
+ ), consumer.takeStateChanges())
+ }
+
+ @Test
+ fun emptyPrepend() {
+ val consumer = MockConsumer()
+ val pager = createPager(consumer, 0, 9)
+
+ pager.trySchedulePrepend()
+
+ // Pager triggers an immediate empty response here, so we don't need to flush the executor
+ assertEquals(listOf(
+ Result(START, DataSource.BaseResult.empty())
+ ), consumer.takeResults())
+ assertEquals(listOf(
+ StateChange(START, DONE)
+ ), consumer.takeStateChanges())
+ }
+}
diff --git a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
index 4993dbf..08cd3fe 100644
--- a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
@@ -85,35 +86,6 @@
totalCount = 100))
}
- @Test
- fun fullLoadWrappedAsContiguous() {
- // verify that prepend / append work correctly with a PositionalDataSource, made contiguous
- val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setInitialLoadSizeHint(20)
- .setEnablePlaceholders(true)
- .build()
- val dataSource: PositionalDataSource<Int> = ListDataSource((0..99).toList())
- val testExecutor = TestExecutor()
- val pagedList = ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
- testExecutor, testExecutor, null, config, 25,
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
-
- assertEquals((10..29).toList(), pagedList)
-
- // prepend works correctly
- pagedList.loadAround(5)
- testExecutor.executeAll()
- assertEquals((0..29).toList(), pagedList)
-
- // and load the rest of the data to be sure further appends work
- for (i in (3..9)) {
- pagedList.loadAround(i * 10 - 5)
- testExecutor.executeAll()
- assertEquals((0..i * 10 + 9).toList(), pagedList)
- }
- }
-
private fun validatePositionOffset(enablePlaceholders: Boolean) {
val config = PagedList.Config.Builder()
.setPageSize(10)
@@ -129,7 +101,11 @@
// 36 - ((10 * 3) / 2) = 21, round down to 20
assertEquals(20, params.requestedStartPosition)
} else {
+ // 36 - ((10 * 3) / 2) = 21, no rounding
+ assertEquals(21, params.requestedStartPosition)
}
+
+ callback.onResult(listOf("a", "b"), 0, 2)
success[0] = true
}
@@ -138,6 +114,7 @@
}
}
+ @Suppress("DEPRECATION")
PagedList.Builder(dataSource, config)
.setFetchExecutor { it.run() }
.setNotifyExecutor { it.run() }
@@ -182,13 +159,11 @@
.setPageSize(10)
.setEnablePlaceholders(enablePlaceholders)
.build()
- if (enablePlaceholders) {
- TiledPagedList(dataSource, FailExecutor(), FailExecutor(), null, config, 0)
- } else {
- ContiguousPagedList(dataSource.wrapAsContiguousWithoutPlaceholders(),
- FailExecutor(), FailExecutor(), null, config, null,
- ContiguousPagedList.LAST_LOAD_UNSPECIFIED)
- }
+
+ dataSource.initExecutor(DirectExecutor.INSTANCE)
+
+ dataSource.loadInitial(PositionalDataSource.LoadInitialParams(
+ 0, config.initialLoadSizeHint, config.pageSize, config.enablePlaceholders)).get()
}
@Test
@@ -197,6 +172,12 @@
it.onResult(listOf("a", "b"), 0, 2)
}
+ @Test(expected = IllegalStateException::class)
+ fun initialLoadCallbackRequireTotalCount() = performLoadInitial(enablePlaceholders = true) {
+ // LoadInitialCallback requires 3 args when placeholders enabled
+ it.onResult(listOf("a", "b"), 0)
+ }
+
@Test(expected = IllegalArgumentException::class)
fun initialLoadCallbackNotPageSizeMultiple() = performLoadInitial {
// Positional LoadInitialCallback can't accept result that's not a multiple of page size
@@ -228,12 +209,6 @@
it.onResult(emptyList(), 0, 2)
}
- @Test(expected = IllegalStateException::class)
- fun initialLoadCallbackRequireTotalCount() = performLoadInitial(enablePlaceholders = true) {
- // LoadInitialCallback requires 3 args when placeholders enabled
- it.onResult(listOf("a", "b"), 0)
- }
-
@Test
fun initialLoadCallbackSuccessTwoArg() = performLoadInitial(enablePlaceholders = false) {
// LoadInitialCallback correct 2 arg usage
@@ -295,10 +270,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -311,10 +282,6 @@
override fun onError(error: Throwable) {
callback.onError(error)
}
-
- override fun onRetryableError(error: Throwable) {
- callback.onRetryableError(error)
- }
})
}
@@ -432,6 +399,14 @@
}
@Test
+ fun testGetKey() {
+ val source = ListDataSource(listOf("a", "b"))
+ assertEquals(null, source.getKey("a"))
+ assertEquals(1, source.getKey(1, "a"))
+ assertEquals(1, source.getKey(1, null))
+ }
+
+ @Test
fun testInvalidateToWrapper() {
val orig = ListDataSource(listOf(0, 1, 2))
val wrapper = orig.map { it.toString() }
@@ -449,24 +424,6 @@
assertTrue(orig.isInvalid)
}
- @Test
- fun testInvalidateToWrapper_contiguous() {
- val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.wrapAsContiguousWithoutPlaceholders()
-
- orig.invalidate()
- assertTrue(wrapper.isInvalid)
- }
-
- @Test
- fun testInvalidateFromWrapper_contiguous() {
- val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.wrapAsContiguousWithoutPlaceholders()
-
- wrapper.invalidate()
- assertTrue(orig.isInvalid)
- }
-
companion object {
private val ERROR = Exception()
}
diff --git a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
index b310fa8..2cae585 100644
--- a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
+++ b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
@@ -16,36 +16,24 @@
package androidx.paging
+import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
@Suppress("DEPRECATION")
@RunWith(JUnit4::class)
class TiledDataSourceTest {
fun TiledDataSource<String>.loadInitial(
- startPosition: Int, count: Int, pageSize: Int): List<String> {
- @Suppress("UNCHECKED_CAST")
- val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
-
- this.dispatchLoadInitial(true, startPosition, count, pageSize, FailExecutor(), receiver)
-
- @Suppress("UNCHECKED_CAST")
- val argument = ArgumentCaptor.forClass(PageResult::class.java)
- as ArgumentCaptor<PageResult<String>>
- verify(receiver).onPageResult(eq(PageResult.INIT), argument.capture())
- verifyNoMoreInteractions(receiver)
-
- val observed = argument.value
-
- return observed.page
+ startPosition: Int,
+ count: Int,
+ pageSize: Int
+ ): List<String> {
+ initExecutor(DirectExecutor.INSTANCE)
+ return loadInitial(PositionalDataSource.LoadInitialParams(
+ startPosition, count, pageSize, true)).get().data
}
@Test
diff --git a/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt b/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt
deleted file mode 100644
index 4b03112..0000000
--- a/paging/common/src/test/java/androidx/paging/TiledPagedListTest.kt
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging
-
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertNull
-import org.junit.Assert.assertSame
-import org.junit.Assert.assertTrue
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
-
-@RunWith(JUnit4::class)
-class TiledPagedListTest {
- private val mMainThread = TestExecutor()
- private val mBackgroundThread = TestExecutor()
-
- private class Item(position: Int) {
- val name: String = "Item $position"
-
- override fun toString(): String {
- return name
- }
- }
-
- private fun verifyLoadedPages(list: List<Item>, vararg loadedPages: Int) {
- val loadedPageList = loadedPages.asList()
- assertEquals(ITEMS.size, list.size)
- var totalCount = 0
- for (i in list.indices) {
- if (loadedPageList.contains(i / PAGE_SIZE)) {
- assertSame("Index $i", ITEMS[i], list[i])
- totalCount += 1
- } else {
- assertNull("Index $i", list[i])
- }
- }
- if (list is PagedList<Item>) {
- assertEquals(totalCount, list.loadedCount)
- }
- }
-
- private fun createTiledPagedList(
- loadPosition: Int,
- initPageCount: Int,
- pageSize: Int = PAGE_SIZE,
- prefetchDistance: Int = pageSize,
- listData: List<Item> = ITEMS,
- boundaryCallback: PagedList.BoundaryCallback<Item>? = null,
- maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED
- ): TiledPagedList<Item> {
- return TiledPagedList(
- ListDataSource(listData), mMainThread, mBackgroundThread, boundaryCallback,
- PagedList.Config.Builder()
- .setPageSize(pageSize)
- .setInitialLoadSizeHint(pageSize * initPageCount)
- .setPrefetchDistance(prefetchDistance)
- .setMaxSize(maxSize)
- .build(),
- loadPosition)
- }
-
- @Test
- fun getDataSource() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- assertTrue(pagedList.dataSource is ListDataSource<Item>)
-
- // snapshot keeps same DataSource
- assertSame(pagedList.dataSource,
- (pagedList.snapshot() as SnapshotPagedList<Item>).dataSource)
- }
-
- @Test(expected = IndexOutOfBoundsException::class)
- fun loadAroundNegative() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- pagedList.loadAround(-1)
- }
-
- @Test(expected = IndexOutOfBoundsException::class)
- fun loadAroundTooLarge() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- pagedList.loadAround(pagedList.size)
- }
-
- @Test
- fun initialLoad_onePage() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- verifyLoadedPages(pagedList, 0, 1)
- }
-
- @Test
- fun initialLoad_onePageOffset() {
- val pagedList = createTiledPagedList(loadPosition = 10, initPageCount = 1)
- verifyLoadedPages(pagedList, 0, 1)
- }
-
- @Test
- fun initialLoad_full() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 100)
- verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
- }
-
- @Test
- fun initialLoad_end() {
- val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
- verifyLoadedPages(pagedList, 3, 4)
- }
-
- @Test
- fun initialLoad_multiple() {
- val pagedList = createTiledPagedList(loadPosition = 9, initPageCount = 2)
- verifyLoadedPages(pagedList, 0, 1)
- }
-
- @Test
- fun initialLoad_offset() {
- val pagedList = createTiledPagedList(loadPosition = 41, initPageCount = 2)
- verifyLoadedPages(pagedList, 3, 4)
- }
-
- @Test
- fun initialLoad_initializesLastKey() {
- val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
- assertEquals(44, pagedList.lastKey)
- }
-
- @Test
- fun initialLoadAsync() {
- val dataSource = AsyncListDataSource(ITEMS)
- val pagedList = TiledPagedList(
- dataSource, mMainThread, mBackgroundThread, null,
- PagedList.Config.Builder().setPageSize(10).build(), 0)
-
- assertTrue(pagedList.isEmpty())
- drain()
- assertTrue(pagedList.isEmpty())
- dataSource.flush()
- assertTrue(pagedList.isEmpty())
- mBackgroundThread.executeAll()
- assertTrue(pagedList.isEmpty())
-
- // Data source defers callbacks until flush, which posts result to main thread
- mMainThread.executeAll()
- assertFalse(pagedList.isEmpty())
- }
-
- @Test
- fun addWeakCallbackEmpty() {
- val dataSource = AsyncListDataSource(ITEMS)
- val pagedList = TiledPagedList(
- dataSource, mMainThread, mBackgroundThread, null,
- PagedList.Config.Builder().setPageSize(10).build(), 0)
-
- // capture empty snapshot
- val emptySnapshot = pagedList.snapshot()
- assertTrue(pagedList.isEmpty())
- assertTrue(emptySnapshot.isEmpty())
-
- // data added in asynchronously
- dataSource.flush()
- drain()
- assertFalse(pagedList.isEmpty())
-
- // verify that adding callback works with empty start point
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(emptySnapshot, callback)
- verify(callback).onInserted(0, pagedList.size)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun append() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 0, 1)
- verifyZeroInteractions(callback)
-
- pagedList.loadAround(15)
-
- verifyLoadedPages(pagedList, 0, 1)
-
- drain()
-
- verifyLoadedPages(pagedList, 0, 1, 2)
- verify(callback).onChanged(20, 10)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun prepend() {
- val pagedList = createTiledPagedList(loadPosition = 44, initPageCount = 2)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 3, 4)
- verifyZeroInteractions(callback)
-
- pagedList.loadAround(35)
- drain()
-
- verifyLoadedPages(pagedList, 2, 3, 4)
- verify<PagedList.Callback>(callback).onChanged(20, 10)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun loadWithGap() {
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 0, 1)
- verifyZeroInteractions(callback)
-
- pagedList.loadAround(44)
- drain()
-
- verifyLoadedPages(pagedList, 0, 1, 3, 4)
- verify(callback).onChanged(30, 10)
- verify(callback).onChanged(40, 5)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun tinyPrefetchTest() {
- val pagedList = createTiledPagedList(
- loadPosition = 0, initPageCount = 1, prefetchDistance = 1)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 0, 1)
- verifyZeroInteractions(callback)
-
- pagedList.loadAround(33)
- drain()
-
- verifyLoadedPages(pagedList, 0, 1, 3)
- verify(callback).onChanged(30, 10)
- verifyNoMoreInteractions(callback)
-
- pagedList.loadAround(44)
- drain()
-
- verifyLoadedPages(pagedList, 0, 1, 3, 4)
- verify(callback).onChanged(40, 5)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun pageDropEnd() {
- val pagedList = createTiledPagedList(
- loadPosition = 0,
- initPageCount = 2,
- prefetchDistance = 1,
- maxSize = 40)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 0, 1)
- verifyZeroInteractions(callback)
-
- // load 3rd page
- pagedList.loadAround(19)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2)
- verify(callback).onChanged(20, 10)
- verifyNoMoreInteractions(callback)
-
- // load 4th page
- pagedList.loadAround(29)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2, 3)
- verify(callback).onChanged(30, 10)
- verifyNoMoreInteractions(callback)
-
- // load 5th page
- pagedList.loadAround(39)
- drain()
- verifyLoadedPages(pagedList, 1, 2, 3, 4)
- verify(callback).onChanged(40, 5)
- verify(callback).onChanged(0, 10)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun pageDropFront() {
- val pagedList = createTiledPagedList(
- loadPosition = 40,
- initPageCount = 2,
- prefetchDistance = 1,
- maxSize = 40)
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
- verifyLoadedPages(pagedList, 3, 4)
- verifyZeroInteractions(callback)
-
- // load 3rd page
- pagedList.loadAround(30)
- drain()
- verifyLoadedPages(pagedList, 2, 3, 4)
- verify(callback).onChanged(20, 10)
- verifyNoMoreInteractions(callback)
-
- // load 2nd page
- pagedList.loadAround(20)
- drain()
- verifyLoadedPages(pagedList, 1, 2, 3, 4)
- verify(callback).onChanged(10, 10)
- verifyNoMoreInteractions(callback)
-
- // load 1st page
- pagedList.loadAround(10)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2, 3)
- verify(callback).onChanged(0, 10)
- verify(callback).onChanged(40, 5)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun pageDropCancelPrepend() {
- val pagedList = createTiledPagedList(
- loadPosition = 25,
- initPageCount = 3,
- prefetchDistance = 1,
- maxSize = 30)
- verifyLoadedPages(pagedList, 1, 2, 3)
-
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
-
- // start a load at the beginning...
- pagedList.loadAround(10)
-
- mBackgroundThread.executeAll()
-
- // but before page received, access near end of list
- pagedList.loadAround(39)
- verifyZeroInteractions(callback)
- mMainThread.executeAll()
- // and the load at the end is dropped without signaling callback
- verifyNoMoreInteractions(callback)
- verifyLoadedPages(pagedList, 1, 2, 3)
-
- drain()
- verifyLoadedPages(pagedList, 2, 3, 4)
- verify(callback).onChanged(40, 5)
- verify(callback).onChanged(10, 10)
- }
-
- @Test
- fun pageDropCancelAppend() {
- val pagedList = createTiledPagedList(
- loadPosition = 25,
- initPageCount = 3,
- prefetchDistance = 1,
- maxSize = 30)
- verifyLoadedPages(pagedList, 1, 2, 3)
-
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(null, callback)
-
- // start a load at the end...
- pagedList.loadAround(39)
-
- mBackgroundThread.executeAll()
-
- // but before page received, access near front of list
- pagedList.loadAround(10)
- verifyZeroInteractions(callback)
- mMainThread.executeAll()
- // and the load at the end is dropped without signaling callback
- verifyNoMoreInteractions(callback)
- verifyLoadedPages(pagedList, 1, 2, 3)
-
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2)
- verify(callback).onChanged(0, 10)
- verify(callback).onChanged(30, 10)
- }
-
- @Test
- fun appendCallbackAddedLate() {
- val pagedList = createTiledPagedList(
- loadPosition = 0, initPageCount = 1, prefetchDistance = 0)
- verifyLoadedPages(pagedList, 0, 1)
-
- pagedList.loadAround(25)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2)
-
- // snapshot at 30 items
- val snapshot = pagedList.snapshot()
- verifyLoadedPages(snapshot, 0, 1, 2)
-
- pagedList.loadAround(35)
- pagedList.loadAround(44)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
- verifyLoadedPages(snapshot, 0, 1, 2)
-
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(snapshot, callback)
- verify(callback).onChanged(30, 20)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun prependCallbackAddedLate() {
- val pagedList = createTiledPagedList(
- loadPosition = 44, initPageCount = 2, prefetchDistance = 0)
- verifyLoadedPages(pagedList, 3, 4)
-
- pagedList.loadAround(25)
- drain()
- verifyLoadedPages(pagedList, 2, 3, 4)
-
- // snapshot at 30 items
- val snapshot = pagedList.snapshot()
- verifyLoadedPages(snapshot, 2, 3, 4)
-
- pagedList.loadAround(15)
- pagedList.loadAround(5)
- drain()
- verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
- verifyLoadedPages(snapshot, 2, 3, 4)
-
- val callback = mock(PagedList.Callback::class.java)
- pagedList.addWeakCallback(snapshot, callback)
- verify(callback).onChanged(0, 20)
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun placeholdersDisabled() {
- // disable placeholders with config, so we create a contiguous version of the pagedlist
- val config = PagedList.Config.Builder()
- .setPageSize(PAGE_SIZE)
- .setPrefetchDistance(PAGE_SIZE)
- .setInitialLoadSizeHint(2 * PAGE_SIZE)
- .setEnablePlaceholders(false)
- .build()
- val pagedList = PagedList.Builder<Int, Item>(ListDataSource(ITEMS), config)
- .setNotifyExecutor(mMainThread)
- .setFetchExecutor(mBackgroundThread)
- .setInitialKey(20)
- .build()
-
- assertTrue(pagedList.isContiguous)
-
- @Suppress("UNCHECKED_CAST")
- val contiguousPagedList = pagedList as ContiguousPagedList<Int, Item>
- assertEquals(0, contiguousPagedList.mStorage.leadingNullCount)
- assertEquals(2 * PAGE_SIZE, contiguousPagedList.mStorage.storageCount)
- assertEquals(0, contiguousPagedList.mStorage.trailingNullCount)
- }
-
- @Test
- fun boundaryCallback_empty() {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
- listData = ArrayList(), boundaryCallback = boundaryCallback)
- assertEquals(0, pagedList.size)
-
- // nothing yet
- verifyNoMoreInteractions(boundaryCallback)
-
- // onZeroItemsLoaded posted, since creation often happens on BG thread
- drain()
- verify(boundaryCallback).onZeroItemsLoaded()
- verifyNoMoreInteractions(boundaryCallback)
- }
-
- @Test
- fun boundaryCallback_immediate() {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 1,
- listData = ITEMS.subList(0, 2), boundaryCallback = boundaryCallback)
- assertEquals(2, pagedList.size)
-
- // nothing yet
- verifyZeroInteractions(boundaryCallback)
-
- // callbacks posted, since creation often happens on BG thread
- drain()
- verify(boundaryCallback).onItemAtFrontLoaded(ITEMS[0])
- verify(boundaryCallback).onItemAtEndLoaded(ITEMS[1])
- verifyNoMoreInteractions(boundaryCallback)
- }
-
- @Test
- fun boundaryCallback_delayedUntilLoaded() {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
- val pagedList = createTiledPagedList(loadPosition = 20, initPageCount = 1,
- boundaryCallback = boundaryCallback)
- verifyLoadedPages(pagedList, 1, 2) // 0, 3, and 4 not loaded yet
-
- // nothing yet, even after drain
- verifyZeroInteractions(boundaryCallback)
- drain()
- verifyZeroInteractions(boundaryCallback)
-
- pagedList.loadAround(0)
- pagedList.loadAround(44)
-
- // still nothing, since items aren't loaded...
- verifyZeroInteractions(boundaryCallback)
-
- drain()
- // first/last items loaded now, so callbacks dispatched
- verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
- verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
- verifyNoMoreInteractions(boundaryCallback)
- }
-
- @Test
- fun boundaryCallback_delayedUntilNearbyAccess() {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
- val pagedList = createTiledPagedList(loadPosition = 0, initPageCount = 5,
- prefetchDistance = 2, boundaryCallback = boundaryCallback)
- verifyLoadedPages(pagedList, 0, 1, 2, 3, 4)
-
- // all items loaded, but no access near ends, so no callbacks
- verifyZeroInteractions(boundaryCallback)
- drain()
- verifyZeroInteractions(boundaryCallback)
-
- pagedList.loadAround(0)
- pagedList.loadAround(44)
-
- // callbacks not posted immediately
- verifyZeroInteractions(boundaryCallback)
-
- drain()
-
- // items accessed, so now posted callbacks are run
- verify(boundaryCallback).onItemAtFrontLoaded(ITEMS.first())
- verify(boundaryCallback).onItemAtEndLoaded(ITEMS.last())
- verifyNoMoreInteractions(boundaryCallback)
- }
-
- private fun validateCallbackForSize(initPageCount: Int, itemCount: Int) {
- @Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
- val listData = ITEMS.subList(0, itemCount)
- val pagedList = createTiledPagedList(
- loadPosition = 0,
- initPageCount = initPageCount,
- prefetchDistance = 0,
- boundaryCallback = boundaryCallback,
- listData = listData)
- assertNotNull(pagedList[pagedList.size - 1 - PAGE_SIZE])
- assertNull(pagedList.last()) // not completed loading
-
- // no access near list beginning, so no callbacks yet
- verifyNoMoreInteractions(boundaryCallback)
- drain()
- verifyNoMoreInteractions(boundaryCallback)
-
- // trigger front boundary callback (via access)
- pagedList.loadAround(0)
- drain()
- verify(boundaryCallback).onItemAtFrontLoaded(listData.first())
- verifyNoMoreInteractions(boundaryCallback)
-
- // trigger end boundary callback (via load)
- pagedList.loadAround(pagedList.size - 1)
- drain()
- verify(boundaryCallback).onItemAtEndLoaded(listData.last())
- verifyNoMoreInteractions(boundaryCallback)
- }
-
- @Test
- fun boundaryCallbackPageSize1() {
- // verify different alignments of last page still trigger boundaryCallback correctly
- validateCallbackForSize(2, 3 * PAGE_SIZE - 2)
- validateCallbackForSize(2, 3 * PAGE_SIZE - 1)
- validateCallbackForSize(2, 3 * PAGE_SIZE)
- validateCallbackForSize(3, 3 * PAGE_SIZE + 1)
- validateCallbackForSize(3, 3 * PAGE_SIZE + 2)
- }
-
- private fun drain() {
- var executed: Boolean
- do {
- executed = mBackgroundThread.executeAll()
- executed = mMainThread.executeAll() || executed
- } while (executed)
- }
-
- companion object {
- // use a page size that's not an even divisor of ITEMS.size() to test end conditions
- private val PAGE_SIZE = 10
-
- private val ITEMS = List(45) { Item(it) }
- }
-}
diff --git a/paging/integration-tests/testapp/build.gradle b/paging/integration-tests/testapp/build.gradle
index 2029407..0c30abe 100644
--- a/paging/integration-tests/testapp/build.gradle
+++ b/paging/integration-tests/testapp/build.gradle
@@ -20,14 +20,17 @@
plugins {
id("AndroidXPlugin")
id("com.android.application")
+ id("kotlin-android")
+ id("kotlin-kapt")
}
dependencies {
implementation(ARCH_CORE_RUNTIME)
- implementation(project(":paging:paging-common"))
implementation(ARCH_ROOM_RUNTIME)
implementation(ARCH_ROOM_RXJAVA)
implementation(ARCH_LIFECYCLE_EXTENSIONS)
+
+ implementation(project(":paging:paging-common-ktx"))
implementation(project(":paging:paging-runtime"))
implementation(project(":paging:paging-rxjava2"))
@@ -36,6 +39,7 @@
implementation(MULTIDEX)
implementation(SUPPORT_RECYCLERVIEW, libs.support_exclude_config)
implementation(SUPPORT_APPCOMPAT, libs.support_exclude_config)
+ implementation(KOTLIN_STDLIB)
}
tasks['check'].dependsOn(tasks['connectedCheck'])
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.java
deleted file mode 100644
index 4bfe3cc..0000000
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging.integration.testapp.custom;
-
-import android.graphics.Color;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.paging.PositionalDataSource;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Sample data source with artificial data.
- */
-class ItemDataSource extends PositionalDataSource<Item> {
- private static final int COUNT = 500;
-
- @ColorInt
- private static final int[] COLORS = new int[] {
- Color.RED,
- Color.BLUE,
- Color.BLACK,
- };
-
- private static int sGenerationId;
- private final int mGenerationId = sGenerationId++;
-
- private List<Item> loadRangeInternal(int startPosition, int loadCount) {
- List<Item> items = new ArrayList<>();
- int end = Math.min(COUNT, startPosition + loadCount);
- int bgColor = COLORS[mGenerationId % COLORS.length];
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- for (int i = startPosition; i != end; i++) {
- items.add(new Item(i, "item " + i, bgColor));
- }
- return items;
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<Item> callback) {
- int position = computeInitialLoadPosition(params, COUNT);
- int loadSize = computeInitialLoadSize(params, position, COUNT);
- List<Item> data = loadRangeInternal(position, loadSize);
- callback.onResult(data, position, COUNT);
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback<Item> callback) {
- List<Item> data = loadRangeInternal(params.startPosition, params.loadSize);
- callback.onResult(data);
- }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
new file mode 100644
index 0000000..e10301a
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging.integration.testapp.custom
+
+import android.graphics.Color
+import androidx.annotation.ColorInt
+import androidx.paging.PositionalDataSource
+import java.util.ArrayList
+import java.util.concurrent.atomic.AtomicBoolean
+
+val dataSourceError = AtomicBoolean(false)
+/**
+ * Sample data source with artificial data.
+ */
+internal class ItemDataSource : PositionalDataSource<Item>() {
+ class RetryableItemError : Exception()
+
+ private val mGenerationId = sGenerationId++
+
+ private fun loadRangeInternal(startPosition: Int, loadCount: Int): List<Item>? {
+ val items = ArrayList<Item>()
+ val end = Math.min(COUNT, startPosition + loadCount)
+ val bgColor = COLORS[mGenerationId % COLORS.size]
+
+ Thread.sleep(1000)
+
+ if (end < startPosition) {
+ throw IllegalStateException()
+ }
+ for (i in startPosition until end) {
+ items.add(Item(i, "item $i", bgColor))
+ }
+ if (dataSourceError.compareAndSet(true, false)) {
+ return null
+ }
+ return items
+ }
+
+ companion object {
+ private const val COUNT = 60
+
+ @ColorInt
+ private val COLORS = intArrayOf(Color.RED, Color.BLUE, Color.BLACK)
+
+ private var sGenerationId: Int = 0
+ }
+
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
+ val position = PositionalDataSource.computeInitialLoadPosition(params, COUNT)
+ val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, COUNT)
+ val data = loadRangeInternal(position, loadSize)
+ if (data == null) {
+ callback.onError(RetryableItemError())
+ } else {
+ callback.onResult(data, position, COUNT)
+ }
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Item>) {
+ val data = loadRangeInternal(params.startPosition, params.loadSize)
+ if (data == null) {
+ callback.onError(RetryableItemError())
+ } else {
+ callback.onResult(data)
+ }
+ }
+
+ override fun isRetryableError(error: Throwable): Boolean {
+ return error is RetryableItemError
+ }
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemAdapter.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemAdapter.java
index 62337e0..33ed530 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemAdapter.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemAdapter.java
@@ -40,6 +40,9 @@
RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(
new TextView(parent.getContext())) {};
holder.itemView.setMinimumHeight(400);
+ holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
return holder;
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
index bc735cd..208b407 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListItemViewModel.java
@@ -43,7 +43,8 @@
};
private LiveData<PagedList<Item>> mLivePagedList =
- new LivePagedListBuilder<>(mFactory, 20).build();
+ new LivePagedListBuilder<>(mFactory, 10)
+ .build();
void invalidateList() {
synchronized (mDataSourceLock) {
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.java
deleted file mode 100644
index da88cf7..0000000
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.paging.integration.testapp.custom;
-
-import android.os.Bundle;
-import android.widget.Button;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.lifecycle.ViewModelProviders;
-import androidx.paging.integration.testapp.R;
-import androidx.recyclerview.widget.RecyclerView;
-
-/**
- * Sample PagedList activity with artificial data source.
- */
-public class PagedListSampleActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_recycler_view);
- final PagedListItemViewModel viewModel = ViewModelProviders.of(this)
- .get(PagedListItemViewModel.class);
-
- final PagedListItemAdapter adapter = new PagedListItemAdapter();
- final RecyclerView recyclerView = findViewById(R.id.recyclerview);
- recyclerView.setAdapter(adapter);
- viewModel.getLivePagedList().observe(this, adapter::submitList);
- final Button button = findViewById(R.id.button);
- button.setOnClickListener(v -> viewModel.invalidateList());
- }
-}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
new file mode 100644
index 0000000..474ac25
--- /dev/null
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging.integration.testapp.custom
+
+import android.os.Bundle
+import android.widget.Button
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProviders
+import androidx.paging.PagedList
+import androidx.paging.PagedListAdapter
+import androidx.paging.integration.testapp.R
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * Sample PagedList activity with artificial data source.
+ */
+class PagedListSampleActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_recycler_view)
+ val viewModel = ViewModelProviders.of(this)
+ .get(PagedListItemViewModel::class.java)
+
+ val adapter = PagedListItemAdapter()
+ val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
+ recyclerView.adapter = adapter
+
+ viewModel.livePagedList.observe(this,
+ Observer<PagedList<Item>> { adapter.submitList(it) })
+
+ setupLoadStateButtons(viewModel, adapter)
+
+ findViewById<Button>(R.id.button_error).setOnClickListener {
+ dataSourceError.set(true)
+ }
+ }
+
+ private fun setupLoadStateButtons(
+ viewModel: PagedListItemViewModel,
+ adapter: PagedListAdapter<Item, RecyclerView.ViewHolder>
+ ) {
+ val buttonStart = findViewById<Button>(R.id.button_start)
+ val buttonRefresh = findViewById<Button>(R.id.button_refresh)
+ val buttonEnd = findViewById<Button>(R.id.button_end)
+
+ buttonRefresh.setOnClickListener {
+ viewModel.invalidateList()
+ }
+ buttonStart.setOnClickListener {
+ adapter.currentList?.retry()
+ }
+ buttonEnd.setOnClickListener {
+ adapter.currentList?.retry()
+ }
+
+ adapter.addLoadStateListener { type, state, _ ->
+ val button = when (type) {
+ PagedList.LoadType.REFRESH -> buttonRefresh
+ PagedList.LoadType.START -> buttonStart
+ PagedList.LoadType.END -> buttonEnd
+ }
+ when (state) {
+ PagedList.LoadState.IDLE -> {
+ button.text = "Idle"
+ button.isEnabled = type == PagedList.LoadType.REFRESH
+ }
+ PagedList.LoadState.LOADING -> {
+ button.text = "Loading"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.DONE -> {
+ button.text = "Done"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.ERROR -> {
+ button.text = "Error"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.RETRYABLE_ERROR -> {
+ button.text = "Error"
+ button.isEnabled = true
+ }
+ }
+ }
+ }
+}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
index ca01a9a..324578e 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/room/RoomPagedListRxActivity.java
@@ -59,7 +59,7 @@
super.onStart();
mDisposable.add(mViewModel.getPagedListFlowable()
- .subscribe(list -> mAdapter.submitList(list)));
+ .subscribe(mAdapter::submitList));
}
@Override
diff --git a/paging/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml b/paging/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
index 61f48df..3822b72 100644
--- a/paging/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
+++ b/paging/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
@@ -31,14 +31,35 @@
android:clipToPadding="false"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
/>
<Button
- android:id="@+id/button"
+ android:id="@+id/button_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
- android:text="Update"/>
+ android:layout_alignParentTop="true"
+ android:text="Enqueue Error"/>
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentBottom="true">
+ <Button
+ android:id="@+id/button_start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Top"/>
+ <Button
+ android:id="@+id/button_refresh"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Refresh"/>
+ <Button
+ android:id="@+id/button_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Bottom"/>
+ </LinearLayout>
</RelativeLayout>
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 61f7c9a6..1e5be32 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -192,9 +192,11 @@
differ.getItem(12)
verifyNoMoreInteractions(callback)
drain()
- verify(callback).onChanged(10, 2, null)
- verify(callback).onChanged(12, 2, null)
- verify(callback).onChanged(14, 2, null)
+
+ // NOTE: tiling is currently disabled, so tiles at 6 and 8 are required to load around 12
+ for (pos in 6..14 step 2) {
+ verify(callback).onChanged(pos, 2, null)
+ }
verifyNoMoreInteractions(callback)
// finally, clear
@@ -346,12 +348,12 @@
differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(10, 20), 0))
differ.currentList!!.loadAround(0)
drain()
- assertEquals(differ.currentList!!.lastKey, 0)
+ assertEquals(0, differ.currentList!!.lastKey)
// if 10 items are prepended, lastKey should be updated to point to same item
differ.submitList(createPagedListFromListAndPos(config, ALPHABET_LIST.subList(0, 20), 0))
drain()
- assertEquals(differ.currentList!!.lastKey, 10)
+ assertEquals(10, differ.currentList!!.lastKey)
}
@Test
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 99edf8b..bf5c6e2 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -16,17 +16,23 @@
package androidx.paging
-import androidx.test.filters.SmallTest
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.Observer
+import androidx.paging.PagedList.LoadState.IDLE
+import androidx.paging.PagedList.LoadState.LOADING
+import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
+import androidx.paging.PagedList.LoadType.REFRESH
+import androidx.test.filters.SmallTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertNull
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
@@ -73,19 +79,39 @@
ArchTaskExecutor.getInstance().setDelegate(null)
}
- private class MockDataSource : PositionalDataSource<String>() {
- override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<String>) {
- assertEquals(2, params.pageSize)
- callback.onResult(listOf("a", "b"), 0, 4)
+ class MockDataSourceFactory : DataSource.Factory<Int, String>() {
+ override fun create(): DataSource<Int, String> {
+ return MockDataSource()
}
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- callback.onResult(listOf("c", "d"))
+ var throwable: Throwable? = null
+
+ fun enqueueRetryableError() {
+ throwable = RETRYABLE_EXCEPTION
}
- class Factory : DataSource.Factory<Int, String>() {
- override fun create(): DataSource<Int, String> {
- return MockDataSource()
+ private inner class MockDataSource : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ assertEquals(2, params.pageSize)
+
+ if (throwable != null) {
+
+ callback.onError(throwable!!)
+ throwable = null
+ } else {
+ callback.onResult(listOf("a", "b"), 0, 4)
+ }
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ callback.onResult(listOf("c", "d"))
+ }
+
+ override fun isRetryableError(error: Throwable): Boolean {
+ return error === RETRYABLE_EXCEPTION
}
}
}
@@ -95,7 +121,7 @@
// specify a background executor via builder, and verify it gets used for all loads,
// overriding default arch IO executor
val livePagedList = LivePagedListBuilder(
- MockDataSource.Factory(), 2)
+ MockDataSourceFactory(), 2)
.setFetchExecutor(backgroundExecutor)
.build()
@@ -105,8 +131,9 @@
pagedListHolder[0] = newList
})
- // won't compute until we flush...
- assertNull(pagedListHolder[0])
+ // initially, immediately get passed empty initial list
+ assertNotNull(pagedListHolder[0])
+ assertTrue(pagedListHolder[0] is InitialPagedList<*, *>)
// flush loadInitial, done with passed executor
backgroundExecutor.executeAll()
@@ -121,4 +148,78 @@
assertEquals(listOf("a", "b", "c", "d"), pagedList)
}
+
+ data class LoadState(
+ val type: PagedList.LoadType,
+ val state: PagedList.LoadState,
+ val error: Throwable?
+ )
+
+ @Test
+ fun failedLoad() {
+ val factory = MockDataSourceFactory()
+ factory.enqueueRetryableError()
+
+ val livePagedList = LivePagedListBuilder(
+ factory, 2)
+ .setFetchExecutor(backgroundExecutor)
+ .build()
+
+ val pagedListHolder: Array<PagedList<String>?> = arrayOfNulls(1)
+
+ livePagedList.observe(lifecycleOwner, Observer<PagedList<String>> { newList ->
+ pagedListHolder[0] = newList
+ })
+
+ val loadStates = mutableListOf<LoadState>()
+
+ // initially, immediately get passed empty initial list
+ val initPagedList = pagedListHolder[0]
+ assertNotNull(initPagedList!!)
+ assertTrue(initPagedList is InitialPagedList<*, *>)
+
+ val loadStateListener = PagedList.LoadStateListener { type, state, error ->
+ if (type == REFRESH) {
+ loadStates.add(LoadState(type, state, error))
+ }
+ }
+ initPagedList.addWeakLoadStateListener(loadStateListener)
+
+ // flush loadInitial, done with passed executor
+ backgroundExecutor.executeAll()
+
+ assertSame(initPagedList, pagedListHolder[0])
+ assertEquals(listOf(
+ LoadState(REFRESH, LOADING, null),
+ LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION)
+ ), loadStates)
+
+ initPagedList.retry()
+ assertSame(initPagedList, pagedListHolder[0])
+
+ // flush loadInitial, should succeed now
+ backgroundExecutor.executeAll()
+ assertNotSame(initPagedList, pagedListHolder[0])
+ assertEquals(listOf("a", "b", null, null), pagedListHolder[0])
+
+ assertEquals(listOf(
+ LoadState(REFRESH, LOADING, null),
+ LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION),
+ LoadState(REFRESH, LOADING, null)
+ ), loadStates)
+
+ // the IDLE result shows up on the next PagedList
+ initPagedList.removeWeakLoadStateListener(loadStateListener)
+ pagedListHolder[0]!!.addWeakLoadStateListener(loadStateListener)
+ assertEquals(listOf(
+ LoadState(REFRESH, LOADING, null),
+ LoadState(REFRESH, RETRYABLE_ERROR, RETRYABLE_EXCEPTION),
+ LoadState(REFRESH, LOADING, null),
+ LoadState(REFRESH, IDLE, null)
+ ), loadStates)
+ }
+
+ companion object {
+ val RETRYABLE_EXCEPTION = Exception("retryable")
+ }
}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index 73448df..7b0a456 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -28,6 +28,7 @@
PagedList.Config.Builder().setPageSize(1).build()
), PagedStorage.Callback {
val list = items.toList()
+ var detached = false
init {
@Suppress("UNCHECKED_CAST")
val keyedStorage = mStorage as PagedStorage<String>
@@ -46,12 +47,20 @@
return null
}
+ override fun isDetached(): Boolean = detached
+
+ override fun detach() {
+ detached = true
+ }
+
override fun dispatchUpdatesSinceSnapshot(
storageSnapshot: PagedList<String>,
callback: PagedList.Callback
) {
}
+ override fun dispatchCurrentLoadState(listener: LoadStateListener?) {}
+
override fun loadAroundInternal(index: Int) {}
override fun onInitialized(count: Int) {}
@@ -60,10 +69,6 @@
override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {}
- override fun onEmptyPrepend() {}
-
- override fun onEmptyAppend() {}
-
override fun onPagePlaceholderInserted(pageIndex: Int) {}
override fun onPageInserted(start: Int, count: Int) {}
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
index 54dbe04..0a02746 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
@@ -469,12 +469,7 @@
if (mPagedList != null) {
mPagedList.addWeakLoadStateListener(listener);
} else {
- listener.onLoadStateChanged(PagedList.LoadType.REFRESH, mLoadStateManager.getRefresh(),
- mLoadStateManager.getRefreshError());
- listener.onLoadStateChanged(PagedList.LoadType.START, mLoadStateManager.getStart(),
- mLoadStateManager.getStartError());
- listener.onLoadStateChanged(PagedList.LoadType.END, mLoadStateManager.getEnd(),
- mLoadStateManager.getEndError());
+ mLoadStateManager.dispatchCurrentLoadState(listener);
}
mLoadStateListeners.add(listener);
}
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.java b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
new file mode 100644
index 0000000..12ec08a
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.paging;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.paging.futures.FutureCallback;
+import androidx.paging.futures.Futures;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.Executor;
+
+class LivePagedList<Key, Value> extends LiveData<PagedList<Value>>
+ implements FutureCallback<PagedList<Value>> {
+ @NonNull
+ private final PagedList.Config mConfig;
+
+ @Nullable
+ private final PagedList.BoundaryCallback mBoundaryCallback;
+
+ @NonNull
+ private final DataSource.Factory<Key, Value> mDataSourceFactory;
+
+ @NonNull
+ private final Executor mNotifyExecutor;
+
+ @NonNull
+ private final Executor mFetchExecutor;
+
+ @NonNull
+ private PagedList<Value> mCurrentData;
+
+ @Nullable
+ private ListenableFuture<PagedList<Value>> mCurrentFuture = null;
+
+ @Override
+ protected void onActive() {
+ super.onActive();
+ invalidate(false);
+ }
+
+ private final DataSource.InvalidatedCallback mCallback =
+ new DataSource.InvalidatedCallback() {
+ @Override
+ public void onInvalidated() {
+ invalidate(true);
+ }
+ };
+
+ private final Runnable mRefreshRetryCallback = new Runnable() {
+ @Override
+ public void run() {
+ invalidate(true);
+ }
+ };
+
+ LivePagedList(
+ @Nullable Key initialKey,
+ @NonNull PagedList.Config config,
+ @Nullable PagedList.BoundaryCallback boundaryCallback,
+ @NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull Executor notifyExecutor,
+ @NonNull Executor fetchExecutor) {
+ mConfig = config;
+ mBoundaryCallback = boundaryCallback;
+ mDataSourceFactory = dataSourceFactory;
+ mNotifyExecutor = notifyExecutor;
+ mFetchExecutor = fetchExecutor;
+ mCurrentData = new InitialPagedList<>(dataSourceFactory.create(), config, initialKey);
+ onSuccess(mCurrentData);
+ }
+
+ private void onItemUpdate(@NonNull PagedList<Value> previous, @NonNull PagedList<Value> next) {
+ previous.setRetryCallback(null);
+ next.setRetryCallback(mRefreshRetryCallback);
+ }
+
+ @NonNull
+ private ListenableFuture<PagedList<Value>> getListenableFuture() {
+ DataSource<Key, Value> dataSource = mDataSourceFactory.create();
+ mCurrentData.getDataSource().removeInvalidatedCallback(mCallback);
+ dataSource.addInvalidatedCallback(mCallback);
+
+ mCurrentData.setInitialLoadState(PagedList.LoadState.LOADING, null);
+ //noinspection unchecked
+ return PagedList.create(
+ dataSource,
+ mNotifyExecutor,
+ mFetchExecutor,
+ mFetchExecutor,
+ mBoundaryCallback,
+ mConfig,
+ (Key) mCurrentData.getLastKey());
+ }
+
+ @Override
+ public void onError(@NonNull Throwable throwable) {
+ PagedList.LoadState loadState = mCurrentData.getDataSource()
+ .isRetryableError(throwable)
+ ? PagedList.LoadState.RETRYABLE_ERROR
+ : PagedList.LoadState.ERROR;
+ mCurrentData.setInitialLoadState(loadState, throwable);
+ }
+
+ @Override
+ public void onSuccess(@NonNull PagedList<Value> value) {
+ onItemUpdate(mCurrentData, value);
+ mCurrentData = value;
+ setValue(value);
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void invalidate(boolean force) {
+ if (mCurrentFuture != null) {
+ if (force) {
+ mCurrentFuture.cancel(false);
+ } else {
+ // work is already ongoing, not forcing, so skip invalidate
+ return;
+ }
+ }
+ mCurrentFuture = getListenableFuture();
+ Futures.addCallback(mCurrentFuture, this, mNotifyExecutor);
+ }
+}
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
index 44c8c92..d838fc3 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
@@ -17,11 +17,11 @@
package androidx.paging;
-import androidx.annotation.AnyThread;
+import android.annotation.SuppressLint;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.lifecycle.ComputableLiveData;
import androidx.lifecycle.LiveData;
import java.util.concurrent.Executor;
@@ -42,6 +42,7 @@
private PagedList.Config mConfig;
private DataSource.Factory<Key, Value> mDataSourceFactory;
private PagedList.BoundaryCallback mBoundaryCallback;
+ @SuppressLint("RestrictedApi")
private Executor mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor();
/**
@@ -149,60 +150,11 @@
*
* @return The LiveData of PagedLists
*/
+ @SuppressLint("RestrictedApi")
@NonNull
public LiveData<PagedList<Value>> build() {
- return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
+ return new LivePagedList<>(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
- }
- @AnyThread
- @NonNull
- private static <Key, Value> LiveData<PagedList<Value>> create(
- @Nullable final Key initialLoadKey,
- @NonNull final PagedList.Config config,
- @Nullable final PagedList.BoundaryCallback boundaryCallback,
- @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
- @NonNull final Executor notifyExecutor,
- @NonNull final Executor fetchExecutor) {
- return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
- @Nullable
- private PagedList<Value> mList;
- @Nullable
- private DataSource<Key, Value> mDataSource;
-
- private final DataSource.InvalidatedCallback mCallback =
- new DataSource.InvalidatedCallback() {
- @Override
- public void onInvalidated() {
- invalidate();
- }
- };
-
- @SuppressWarnings("unchecked") // for casting getLastKey to Key
- @Override
- protected PagedList<Value> compute() {
- @Nullable Key initializeKey = initialLoadKey;
- if (mList != null) {
- initializeKey = (Key) mList.getLastKey();
- }
-
- do {
- if (mDataSource != null) {
- mDataSource.removeInvalidatedCallback(mCallback);
- }
-
- mDataSource = dataSourceFactory.create();
- mDataSource.addInvalidatedCallback(mCallback);
-
- mList = new PagedList.Builder<>(mDataSource, config)
- .setNotifyExecutor(notifyExecutor)
- .setFetchExecutor(fetchExecutor)
- .setBoundaryCallback(boundaryCallback)
- .setInitialKey(initializeKey)
- .build();
- } while (mList.isDetached());
- return mList;
- }
- }.getLiveData();
}
}
diff --git a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
index a25f5ec..596e140 100644
--- a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
+++ b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
@@ -73,9 +73,11 @@
params: LoadInitialParams,
callback: LoadInitialCallback<String>
) {
+ callback.onResult(listOf<String>(), 0, 0)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ // never completes...
}
}
diff --git a/preference/api/1.1.0-alpha05.txt b/preference/api/1.1.0-alpha05.txt
new file mode 100644
index 0000000..0edd4f7b
--- /dev/null
+++ b/preference/api/1.1.0-alpha05.txt
@@ -0,0 +1,543 @@
+// Signature format: 3.0
+package androidx.preference {
+
+ public class CheckBoxPreference extends androidx.preference.TwoStatePreference {
+ ctor public CheckBoxPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public CheckBoxPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public CheckBoxPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public CheckBoxPreference(android.content.Context!);
+ }
+
+ public abstract class DialogPreference extends androidx.preference.Preference {
+ ctor public DialogPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public DialogPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public DialogPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public DialogPreference(android.content.Context!);
+ method public android.graphics.drawable.Drawable! getDialogIcon();
+ method public int getDialogLayoutResource();
+ method public CharSequence! getDialogMessage();
+ method public CharSequence! getDialogTitle();
+ method public CharSequence! getNegativeButtonText();
+ method public CharSequence! getPositiveButtonText();
+ method public void setDialogIcon(android.graphics.drawable.Drawable!);
+ method public void setDialogIcon(int);
+ method public void setDialogLayoutResource(int);
+ method public void setDialogMessage(CharSequence!);
+ method public void setDialogMessage(int);
+ method public void setDialogTitle(CharSequence!);
+ method public void setDialogTitle(int);
+ method public void setNegativeButtonText(CharSequence!);
+ method public void setNegativeButtonText(int);
+ method public void setPositiveButtonText(CharSequence!);
+ method public void setPositiveButtonText(int);
+ }
+
+ public static interface DialogPreference.TargetFragment {
+ method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
+ }
+
+ public class DropDownPreference extends androidx.preference.ListPreference {
+ ctor public DropDownPreference(android.content.Context!);
+ ctor public DropDownPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public DropDownPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public DropDownPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ method protected android.widget.ArrayAdapter! createAdapter();
+ }
+
+ public class EditTextPreference extends androidx.preference.DialogPreference {
+ ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public EditTextPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public EditTextPreference(android.content.Context!);
+ method public androidx.preference.EditTextPreference.OnBindEditTextListener? getOnBindEditTextListener();
+ method public String! getText();
+ method public void setOnBindEditTextListener(androidx.preference.EditTextPreference.OnBindEditTextListener?);
+ method public void setText(String!);
+ }
+
+ public static interface EditTextPreference.OnBindEditTextListener {
+ method public void onBindEditText(android.widget.EditText);
+ }
+
+ public static final class EditTextPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.EditTextPreference> {
+ method public static androidx.preference.EditTextPreference.SimpleSummaryProvider! getInstance();
+ method public CharSequence! provideSummary(androidx.preference.EditTextPreference!);
+ }
+
+ @Deprecated public class EditTextPreferenceDialogFragment extends androidx.preference.PreferenceDialogFragment {
+ ctor @Deprecated public EditTextPreferenceDialogFragment();
+ method @Deprecated public static androidx.preference.EditTextPreferenceDialogFragment! newInstance(String!);
+ method @Deprecated protected void onBindDialogView(android.view.View!);
+ method @Deprecated public void onDialogClosed(boolean);
+ }
+
+ public class EditTextPreferenceDialogFragmentCompat extends androidx.preference.PreferenceDialogFragmentCompat {
+ ctor public EditTextPreferenceDialogFragmentCompat();
+ method public static androidx.preference.EditTextPreferenceDialogFragmentCompat! newInstance(String!);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class ListPreference extends androidx.preference.DialogPreference {
+ ctor public ListPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public ListPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public ListPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public ListPreference(android.content.Context!);
+ method public int findIndexOfValue(String!);
+ method public CharSequence[]! getEntries();
+ method public CharSequence! getEntry();
+ method public CharSequence[]! getEntryValues();
+ method public String! getValue();
+ method public void setEntries(CharSequence[]!);
+ method public void setEntries(@ArrayRes int);
+ method public void setEntryValues(CharSequence[]!);
+ method public void setEntryValues(@ArrayRes int);
+ method public void setValue(String!);
+ method public void setValueIndex(int);
+ }
+
+ public static final class ListPreference.SimpleSummaryProvider implements androidx.preference.Preference.SummaryProvider<androidx.preference.ListPreference> {
+ method public static androidx.preference.ListPreference.SimpleSummaryProvider! getInstance();
+ method public CharSequence! provideSummary(androidx.preference.ListPreference!);
+ }
+
+ @Deprecated public class ListPreferenceDialogFragment extends androidx.preference.PreferenceDialogFragment {
+ ctor @Deprecated public ListPreferenceDialogFragment();
+ method @Deprecated public static androidx.preference.ListPreferenceDialogFragment! newInstance(String!);
+ method @Deprecated public void onDialogClosed(boolean);
+ method @Deprecated protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder!);
+ }
+
+ public class ListPreferenceDialogFragmentCompat extends androidx.preference.PreferenceDialogFragmentCompat {
+ ctor public ListPreferenceDialogFragmentCompat();
+ method public static androidx.preference.ListPreferenceDialogFragmentCompat! newInstance(String!);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class MultiSelectListPreference extends androidx.preference.DialogPreference {
+ ctor public MultiSelectListPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public MultiSelectListPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public MultiSelectListPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public MultiSelectListPreference(android.content.Context!);
+ method public int findIndexOfValue(String!);
+ method public CharSequence[]! getEntries();
+ method public CharSequence[]! getEntryValues();
+ method protected boolean[]! getSelectedItems();
+ method public java.util.Set<java.lang.String>! getValues();
+ method public void setEntries(CharSequence[]!);
+ method public void setEntries(@ArrayRes int);
+ method public void setEntryValues(CharSequence[]!);
+ method public void setEntryValues(@ArrayRes int);
+ method public void setValues(java.util.Set<java.lang.String>!);
+ }
+
+ @Deprecated public class MultiSelectListPreferenceDialogFragment extends androidx.preference.PreferenceDialogFragment {
+ ctor @Deprecated public MultiSelectListPreferenceDialogFragment();
+ method @Deprecated public static androidx.preference.MultiSelectListPreferenceDialogFragment! newInstance(String!);
+ method @Deprecated public void onDialogClosed(boolean);
+ method @Deprecated protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder!);
+ }
+
+ public class MultiSelectListPreferenceDialogFragmentCompat extends androidx.preference.PreferenceDialogFragmentCompat {
+ ctor public MultiSelectListPreferenceDialogFragmentCompat();
+ method public static androidx.preference.MultiSelectListPreferenceDialogFragmentCompat! newInstance(String!);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class Preference implements java.lang.Comparable<androidx.preference.Preference> {
+ ctor public Preference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public Preference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public Preference(android.content.Context!, android.util.AttributeSet!);
+ ctor public Preference(android.content.Context!);
+ method public boolean callChangeListener(Object!);
+ method public int compareTo(androidx.preference.Preference);
+ method protected <T extends androidx.preference.Preference> T? findPreferenceInHierarchy(String);
+ method public android.content.Context! getContext();
+ method public String! getDependency();
+ method public android.os.Bundle! getExtras();
+ method public String! getFragment();
+ method public android.graphics.drawable.Drawable! getIcon();
+ method public android.content.Intent! getIntent();
+ method public String! getKey();
+ method public final int getLayoutResource();
+ method public androidx.preference.Preference.OnPreferenceChangeListener! getOnPreferenceChangeListener();
+ method public androidx.preference.Preference.OnPreferenceClickListener! getOnPreferenceClickListener();
+ method public int getOrder();
+ method public androidx.preference.PreferenceGroup? getParent();
+ method protected boolean getPersistedBoolean(boolean);
+ method protected float getPersistedFloat(float);
+ method protected int getPersistedInt(int);
+ method protected long getPersistedLong(long);
+ method protected String! getPersistedString(String!);
+ method public java.util.Set<java.lang.String>! getPersistedStringSet(java.util.Set<java.lang.String>!);
+ method public androidx.preference.PreferenceDataStore? getPreferenceDataStore();
+ method public androidx.preference.PreferenceManager! getPreferenceManager();
+ method public android.content.SharedPreferences! getSharedPreferences();
+ method public boolean getShouldDisableView();
+ method public CharSequence! getSummary();
+ method public final androidx.preference.Preference.SummaryProvider? getSummaryProvider();
+ method public CharSequence! getTitle();
+ method public final int getWidgetLayoutResource();
+ method public boolean hasKey();
+ method public boolean isCopyingEnabled();
+ method public boolean isEnabled();
+ method public boolean isIconSpaceReserved();
+ method public boolean isPersistent();
+ method public boolean isSelectable();
+ method public final boolean isShown();
+ method public boolean isSingleLineTitle();
+ method public final boolean isVisible();
+ method protected void notifyChanged();
+ method public void notifyDependencyChange(boolean);
+ method protected void notifyHierarchyChanged();
+ method public void onAttached();
+ method protected void onAttachedToHierarchy(androidx.preference.PreferenceManager!);
+ method public void onBindViewHolder(androidx.preference.PreferenceViewHolder!);
+ method protected void onClick();
+ method public void onDependencyChanged(androidx.preference.Preference!, boolean);
+ method public void onDetached();
+ method protected Object! onGetDefaultValue(android.content.res.TypedArray!, int);
+ method @CallSuper public void onInitializeAccessibilityNodeInfo(androidx.core.view.accessibility.AccessibilityNodeInfoCompat!);
+ method public void onParentChanged(androidx.preference.Preference!, boolean);
+ method protected void onPrepareForRemoval();
+ method protected void onRestoreInstanceState(android.os.Parcelable!);
+ method protected android.os.Parcelable! onSaveInstanceState();
+ method @Deprecated protected void onSetInitialValue(boolean, Object!);
+ method protected void onSetInitialValue(Object?);
+ method public android.os.Bundle! peekExtras();
+ method protected boolean persistBoolean(boolean);
+ method protected boolean persistFloat(float);
+ method protected boolean persistInt(int);
+ method protected boolean persistLong(long);
+ method protected boolean persistString(String!);
+ method public boolean persistStringSet(java.util.Set<java.lang.String>!);
+ method public void restoreHierarchyState(android.os.Bundle!);
+ method public void saveHierarchyState(android.os.Bundle!);
+ method public void setCopyingEnabled(boolean);
+ method public void setDefaultValue(Object!);
+ method public void setDependency(String!);
+ method public void setEnabled(boolean);
+ method public void setFragment(String!);
+ method public void setIcon(android.graphics.drawable.Drawable!);
+ method public void setIcon(int);
+ method public void setIconSpaceReserved(boolean);
+ method public void setIntent(android.content.Intent!);
+ method public void setKey(String!);
+ method public void setLayoutResource(int);
+ method public void setOnPreferenceChangeListener(androidx.preference.Preference.OnPreferenceChangeListener!);
+ method public void setOnPreferenceClickListener(androidx.preference.Preference.OnPreferenceClickListener!);
+ method public void setOrder(int);
+ method public void setPersistent(boolean);
+ method public void setPreferenceDataStore(androidx.preference.PreferenceDataStore!);
+ method public void setSelectable(boolean);
+ method public void setShouldDisableView(boolean);
+ method public void setSingleLineTitle(boolean);
+ method public void setSummary(CharSequence!);
+ method public void setSummary(int);
+ method public final void setSummaryProvider(androidx.preference.Preference.SummaryProvider?);
+ method public void setTitle(CharSequence!);
+ method public void setTitle(int);
+ method public void setViewId(int);
+ method public final void setVisible(boolean);
+ method public void setWidgetLayoutResource(int);
+ method public boolean shouldDisableDependents();
+ method protected boolean shouldPersist();
+ field public static final int DEFAULT_ORDER = 2147483647; // 0x7fffffff
+ }
+
+ public static class Preference.BaseSavedState extends android.view.AbsSavedState {
+ ctor public Preference.BaseSavedState(android.os.Parcel!);
+ ctor public Preference.BaseSavedState(android.os.Parcelable!);
+ field public static final android.os.Parcelable.Creator<androidx.preference.Preference.BaseSavedState>! CREATOR;
+ }
+
+ public static interface Preference.OnPreferenceChangeListener {
+ method public boolean onPreferenceChange(androidx.preference.Preference!, Object!);
+ }
+
+ public static interface Preference.OnPreferenceClickListener {
+ method public boolean onPreferenceClick(androidx.preference.Preference!);
+ }
+
+ public static interface Preference.SummaryProvider<T extends androidx.preference.Preference> {
+ method public CharSequence! provideSummary(T!);
+ }
+
+ public class PreferenceCategory extends androidx.preference.PreferenceGroup {
+ ctor public PreferenceCategory(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public PreferenceCategory(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public PreferenceCategory(android.content.Context!, android.util.AttributeSet!);
+ ctor public PreferenceCategory(android.content.Context!);
+ }
+
+ public abstract class PreferenceDataStore {
+ ctor public PreferenceDataStore();
+ method public boolean getBoolean(String!, boolean);
+ method public float getFloat(String!, float);
+ method public int getInt(String!, int);
+ method public long getLong(String!, long);
+ method public String? getString(String!, String?);
+ method public java.util.Set<java.lang.String>? getStringSet(String!, java.util.Set<java.lang.String>?);
+ method public void putBoolean(String!, boolean);
+ method public void putFloat(String!, float);
+ method public void putInt(String!, int);
+ method public void putLong(String!, long);
+ method public void putString(String!, String?);
+ method public void putStringSet(String!, java.util.Set<java.lang.String>?);
+ }
+
+ @Deprecated public abstract class PreferenceDialogFragment extends android.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ ctor @Deprecated public PreferenceDialogFragment();
+ method @Deprecated public androidx.preference.DialogPreference! getPreference();
+ method @Deprecated protected void onBindDialogView(android.view.View!);
+ method @Deprecated public void onClick(android.content.DialogInterface!, int);
+ method @Deprecated protected android.view.View! onCreateDialogView(android.content.Context!);
+ method @Deprecated public abstract void onDialogClosed(boolean);
+ method @Deprecated protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder!);
+ field @Deprecated protected static final String ARG_KEY = "key";
+ }
+
+ public abstract class PreferenceDialogFragmentCompat extends androidx.fragment.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ ctor public PreferenceDialogFragmentCompat();
+ method public androidx.preference.DialogPreference! getPreference();
+ method protected void onBindDialogView(android.view.View!);
+ method public void onClick(android.content.DialogInterface!, int);
+ method protected android.view.View! onCreateDialogView(android.content.Context!);
+ method public abstract void onDialogClosed(boolean);
+ method protected void onPrepareDialogBuilder(androidx.appcompat.app.AlertDialog.Builder!);
+ field protected static final String ARG_KEY = "key";
+ }
+
+ @Deprecated public abstract class PreferenceFragment extends android.app.Fragment implements androidx.preference.DialogPreference.TargetFragment androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener androidx.preference.PreferenceManager.OnNavigateToScreenListener androidx.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ ctor @Deprecated public PreferenceFragment();
+ method @Deprecated public void addPreferencesFromResource(@XmlRes int);
+ method @Deprecated public <T extends androidx.preference.Preference> T! findPreference(CharSequence!);
+ method @Deprecated public final androidx.recyclerview.widget.RecyclerView! getListView();
+ method @Deprecated public androidx.preference.PreferenceManager! getPreferenceManager();
+ method @Deprecated public androidx.preference.PreferenceScreen! getPreferenceScreen();
+ method @Deprecated protected androidx.recyclerview.widget.RecyclerView.Adapter! onCreateAdapter(androidx.preference.PreferenceScreen!);
+ method @Deprecated public androidx.recyclerview.widget.RecyclerView.LayoutManager! onCreateLayoutManager();
+ method @Deprecated public abstract void onCreatePreferences(android.os.Bundle!, String!);
+ method @Deprecated public androidx.recyclerview.widget.RecyclerView! onCreateRecyclerView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
+ method @Deprecated public void onDisplayPreferenceDialog(androidx.preference.Preference!);
+ method @Deprecated public void onNavigateToScreen(androidx.preference.PreferenceScreen!);
+ method @Deprecated public boolean onPreferenceTreeClick(androidx.preference.Preference!);
+ method @Deprecated public void scrollToPreference(String!);
+ method @Deprecated public void scrollToPreference(androidx.preference.Preference!);
+ method @Deprecated public void setDivider(android.graphics.drawable.Drawable!);
+ method @Deprecated public void setDividerHeight(int);
+ method @Deprecated public void setPreferenceScreen(androidx.preference.PreferenceScreen!);
+ method @Deprecated public void setPreferencesFromResource(@XmlRes int, String?);
+ field @Deprecated public static final String ARG_PREFERENCE_ROOT = "androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
+ }
+
+ @Deprecated public static interface PreferenceFragment.OnPreferenceDisplayDialogCallback {
+ method @Deprecated public boolean onPreferenceDisplayDialog(androidx.preference.PreferenceFragment, androidx.preference.Preference!);
+ }
+
+ @Deprecated public static interface PreferenceFragment.OnPreferenceStartFragmentCallback {
+ method @Deprecated public boolean onPreferenceStartFragment(androidx.preference.PreferenceFragment!, androidx.preference.Preference!);
+ }
+
+ @Deprecated public static interface PreferenceFragment.OnPreferenceStartScreenCallback {
+ method @Deprecated public boolean onPreferenceStartScreen(androidx.preference.PreferenceFragment!, androidx.preference.PreferenceScreen!);
+ }
+
+ public abstract class PreferenceFragmentCompat extends androidx.fragment.app.Fragment implements androidx.preference.DialogPreference.TargetFragment androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener androidx.preference.PreferenceManager.OnNavigateToScreenListener androidx.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ ctor public PreferenceFragmentCompat();
+ method public void addPreferencesFromResource(@XmlRes int);
+ method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
+ method public final androidx.recyclerview.widget.RecyclerView! getListView();
+ method public androidx.preference.PreferenceManager! getPreferenceManager();
+ method public androidx.preference.PreferenceScreen! getPreferenceScreen();
+ method protected androidx.recyclerview.widget.RecyclerView.Adapter! onCreateAdapter(androidx.preference.PreferenceScreen!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager! onCreateLayoutManager();
+ method public abstract void onCreatePreferences(android.os.Bundle!, String!);
+ method public androidx.recyclerview.widget.RecyclerView! onCreateRecyclerView(android.view.LayoutInflater!, android.view.ViewGroup!, android.os.Bundle!);
+ method public void onDisplayPreferenceDialog(androidx.preference.Preference!);
+ method public void onNavigateToScreen(androidx.preference.PreferenceScreen!);
+ method public boolean onPreferenceTreeClick(androidx.preference.Preference!);
+ method public void scrollToPreference(String!);
+ method public void scrollToPreference(androidx.preference.Preference!);
+ method public void setDivider(android.graphics.drawable.Drawable!);
+ method public void setDividerHeight(int);
+ method public void setPreferenceScreen(androidx.preference.PreferenceScreen!);
+ method public void setPreferencesFromResource(@XmlRes int, String?);
+ field public static final String ARG_PREFERENCE_ROOT = "androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
+ }
+
+ public static interface PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
+ method public boolean onPreferenceDisplayDialog(androidx.preference.PreferenceFragmentCompat, androidx.preference.Preference!);
+ }
+
+ public static interface PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+ method public boolean onPreferenceStartFragment(androidx.preference.PreferenceFragmentCompat!, androidx.preference.Preference!);
+ }
+
+ public static interface PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+ method public boolean onPreferenceStartScreen(androidx.preference.PreferenceFragmentCompat!, androidx.preference.PreferenceScreen!);
+ }
+
+ public abstract class PreferenceGroup extends androidx.preference.Preference {
+ ctor public PreferenceGroup(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public PreferenceGroup(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public PreferenceGroup(android.content.Context!, android.util.AttributeSet!);
+ method public void addItemFromInflater(androidx.preference.Preference!);
+ method public boolean addPreference(androidx.preference.Preference!);
+ method protected void dispatchRestoreInstanceState(android.os.Bundle!);
+ method protected void dispatchSaveInstanceState(android.os.Bundle!);
+ method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
+ method public int getInitialExpandedChildrenCount();
+ method public androidx.preference.Preference! getPreference(int);
+ method public int getPreferenceCount();
+ method protected boolean isOnSameScreenAsChildren();
+ method public boolean isOrderingAsAdded();
+ method protected boolean onPrepareAddPreference(androidx.preference.Preference!);
+ method public void removeAll();
+ method public boolean removePreference(androidx.preference.Preference!);
+ method public boolean removePreferenceRecursively(CharSequence);
+ method public void setInitialExpandedChildrenCount(int);
+ method public void setOrderingAsAdded(boolean);
+ }
+
+ public static interface PreferenceGroup.PreferencePositionCallback {
+ method public int getPreferenceAdapterPosition(String!);
+ method public int getPreferenceAdapterPosition(androidx.preference.Preference!);
+ }
+
+ public class PreferenceManager {
+ method public androidx.preference.PreferenceScreen! createPreferenceScreen(android.content.Context!);
+ method public <T extends androidx.preference.Preference> T? findPreference(CharSequence);
+ method public android.content.Context! getContext();
+ method public static android.content.SharedPreferences! getDefaultSharedPreferences(android.content.Context!);
+ method public androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener! getOnDisplayPreferenceDialogListener();
+ method public androidx.preference.PreferenceManager.OnNavigateToScreenListener! getOnNavigateToScreenListener();
+ method public androidx.preference.PreferenceManager.OnPreferenceTreeClickListener! getOnPreferenceTreeClickListener();
+ method public androidx.preference.PreferenceManager.PreferenceComparisonCallback! getPreferenceComparisonCallback();
+ method public androidx.preference.PreferenceDataStore? getPreferenceDataStore();
+ method public androidx.preference.PreferenceScreen! getPreferenceScreen();
+ method public android.content.SharedPreferences! getSharedPreferences();
+ method public int getSharedPreferencesMode();
+ method public String! getSharedPreferencesName();
+ method public boolean isStorageDefault();
+ method public boolean isStorageDeviceProtected();
+ method public static void setDefaultValues(android.content.Context!, int, boolean);
+ method public static void setDefaultValues(android.content.Context!, String!, int, int, boolean);
+ method public void setOnDisplayPreferenceDialogListener(androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener!);
+ method public void setOnNavigateToScreenListener(androidx.preference.PreferenceManager.OnNavigateToScreenListener!);
+ method public void setOnPreferenceTreeClickListener(androidx.preference.PreferenceManager.OnPreferenceTreeClickListener!);
+ method public void setPreferenceComparisonCallback(androidx.preference.PreferenceManager.PreferenceComparisonCallback!);
+ method public void setPreferenceDataStore(androidx.preference.PreferenceDataStore!);
+ method public boolean setPreferences(androidx.preference.PreferenceScreen!);
+ method public void setSharedPreferencesMode(int);
+ method public void setSharedPreferencesName(String!);
+ method public void setStorageDefault();
+ method public void setStorageDeviceProtected();
+ method public void showDialog(androidx.preference.Preference!);
+ field public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
+ }
+
+ public static interface PreferenceManager.OnDisplayPreferenceDialogListener {
+ method public void onDisplayPreferenceDialog(androidx.preference.Preference!);
+ }
+
+ public static interface PreferenceManager.OnNavigateToScreenListener {
+ method public void onNavigateToScreen(androidx.preference.PreferenceScreen!);
+ }
+
+ public static interface PreferenceManager.OnPreferenceTreeClickListener {
+ method public boolean onPreferenceTreeClick(androidx.preference.Preference!);
+ }
+
+ public abstract static class PreferenceManager.PreferenceComparisonCallback {
+ ctor public PreferenceManager.PreferenceComparisonCallback();
+ method public abstract boolean arePreferenceContentsTheSame(androidx.preference.Preference!, androidx.preference.Preference!);
+ method public abstract boolean arePreferenceItemsTheSame(androidx.preference.Preference!, androidx.preference.Preference!);
+ }
+
+ public static class PreferenceManager.SimplePreferenceComparisonCallback extends androidx.preference.PreferenceManager.PreferenceComparisonCallback {
+ ctor public PreferenceManager.SimplePreferenceComparisonCallback();
+ method public boolean arePreferenceContentsTheSame(androidx.preference.Preference!, androidx.preference.Preference!);
+ method public boolean arePreferenceItemsTheSame(androidx.preference.Preference!, androidx.preference.Preference!);
+ }
+
+ public final class PreferenceScreen extends androidx.preference.PreferenceGroup {
+ method public void setShouldUseGeneratedIds(boolean);
+ method public boolean shouldUseGeneratedIds();
+ }
+
+ public class PreferenceViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
+ method public android.view.View! findViewById(@IdRes int);
+ method public boolean isDividerAllowedAbove();
+ method public boolean isDividerAllowedBelow();
+ method public void setDividerAllowedAbove(boolean);
+ method public void setDividerAllowedBelow(boolean);
+ }
+
+ public class SeekBarPreference extends androidx.preference.Preference {
+ ctor public SeekBarPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public SeekBarPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SeekBarPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public SeekBarPreference(android.content.Context!);
+ method public int getMax();
+ method public int getMin();
+ method public final int getSeekBarIncrement();
+ method public boolean getShowSeekBarValue();
+ method public boolean getUpdatesContinuously();
+ method public int getValue();
+ method public boolean isAdjustable();
+ method public void setAdjustable(boolean);
+ method public final void setMax(int);
+ method public void setMin(int);
+ method public final void setSeekBarIncrement(int);
+ method public void setShowSeekBarValue(boolean);
+ method public void setUpdatesContinuously(boolean);
+ method public void setValue(int);
+ }
+
+ public class SwitchPreference extends androidx.preference.TwoStatePreference {
+ ctor public SwitchPreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public SwitchPreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SwitchPreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public SwitchPreference(android.content.Context!);
+ method public CharSequence! getSwitchTextOff();
+ method public CharSequence! getSwitchTextOn();
+ method public void setSwitchTextOff(CharSequence!);
+ method public void setSwitchTextOff(int);
+ method public void setSwitchTextOn(CharSequence!);
+ method public void setSwitchTextOn(int);
+ }
+
+ public class SwitchPreferenceCompat extends androidx.preference.TwoStatePreference {
+ ctor public SwitchPreferenceCompat(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public SwitchPreferenceCompat(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public SwitchPreferenceCompat(android.content.Context!, android.util.AttributeSet!);
+ ctor public SwitchPreferenceCompat(android.content.Context!);
+ method public CharSequence! getSwitchTextOff();
+ method public CharSequence! getSwitchTextOn();
+ method public void setSwitchTextOff(CharSequence!);
+ method public void setSwitchTextOff(int);
+ method public void setSwitchTextOn(CharSequence!);
+ method public void setSwitchTextOn(int);
+ }
+
+ public abstract class TwoStatePreference extends androidx.preference.Preference {
+ ctor public TwoStatePreference(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public TwoStatePreference(android.content.Context!, android.util.AttributeSet!, int);
+ ctor public TwoStatePreference(android.content.Context!, android.util.AttributeSet!);
+ ctor public TwoStatePreference(android.content.Context!);
+ method public boolean getDisableDependentsState();
+ method public CharSequence! getSummaryOff();
+ method public CharSequence! getSummaryOn();
+ method public boolean isChecked();
+ method public void setChecked(boolean);
+ method public void setDisableDependentsState(boolean);
+ method public void setSummaryOff(CharSequence!);
+ method public void setSummaryOff(int);
+ method public void setSummaryOn(CharSequence!);
+ method public void setSummaryOn(int);
+ method protected void syncSummaryView(androidx.preference.PreferenceViewHolder!);
+ field protected boolean mChecked;
+ }
+
+}
+
diff --git a/preference/api/res-1.1.0-alpha05.txt b/preference/api/res-1.1.0-alpha05.txt
new file mode 100644
index 0000000..3afd07c
--- /dev/null
+++ b/preference/api/res-1.1.0-alpha05.txt
@@ -0,0 +1,69 @@
+style Preference
+style Preference_Category
+style Preference_CheckBoxPreference
+style Preference_DialogPreference
+style Preference_DialogPreference_EditTextPreference
+style Preference_DropDown
+style Preference_Information
+style Preference_PreferenceScreen
+style Preference_SeekBarPreference
+style Preference_SwitchPreferenceCompat
+style PreferenceFragment
+style PreferenceFragmentList
+style PreferenceThemeOverlay
+attr adjustable
+attr allowDividerAbove
+attr allowDividerAfterLastItem
+attr allowDividerBelow
+attr checkBoxPreferenceStyle
+attr defaultValue
+attr dependency
+attr dialogIcon
+attr dialogLayout
+attr dialogMessage
+attr dialogPreferenceStyle
+attr dialogTitle
+attr disableDependentsState
+attr dropdownPreferenceStyle
+attr editTextPreferenceStyle
+attr enabled
+attr entries
+attr entryValues
+attr fragment
+attr icon
+attr iconSpaceReserved
+attr key
+attr layout
+attr maxHeight
+attr maxWidth
+attr min
+attr negativeButtonText
+attr order
+attr orderingFromXml
+attr persistent
+attr positiveButtonText
+attr preferenceCategoryStyle
+attr preferenceCategoryTitleTextAppearance
+attr preferenceFragmentCompatStyle
+attr preferenceFragmentListStyle
+attr preferenceFragmentStyle
+attr preferenceInformationStyle
+attr preferenceScreenStyle
+attr preferenceStyle
+attr preferenceTheme
+attr seekBarIncrement
+attr seekBarPreferenceStyle
+attr selectable
+attr selectableItemBackground
+attr shouldDisableView
+attr showSeekBarValue
+attr singleLineTitle
+attr summary
+attr summaryOff
+attr summaryOn
+attr switchPreferenceCompatStyle
+attr switchPreferenceStyle
+attr switchTextOff
+attr switchTextOn
+attr title
+attr widgetLayout
diff --git a/preference/api/restricted_1.1.0-alpha05.ignore b/preference/api/restricted_1.1.0-alpha05.ignore
new file mode 100644
index 0000000..37595b8
--- /dev/null
+++ b/preference/api/restricted_1.1.0-alpha05.ignore
@@ -0,0 +1,21 @@
+// Baseline format: 1.0
+RemovedClass: androidx.preference.internal.AbstractMultiSelectListPreference:
+ Removed class androidx.preference.internal.AbstractMultiSelectListPreference
+
+
+RemovedField: androidx.preference.AndroidResources#ANDROID_R_EDITTEXT_PREFERENCE_STYLE:
+ Removed field androidx.preference.AndroidResources.ANDROID_R_EDITTEXT_PREFERENCE_STYLE
+RemovedField: androidx.preference.AndroidResources#ANDROID_R_LIST_CONTAINER:
+ Removed field androidx.preference.AndroidResources.ANDROID_R_LIST_CONTAINER
+RemovedField: androidx.preference.AndroidResources#ANDROID_R_PREFERENCE_FRAGMENT_STYLE:
+ Removed field androidx.preference.AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE
+RemovedField: androidx.preference.AndroidResources#ANDROID_R_SWITCH_WIDGET:
+ Removed field androidx.preference.AndroidResources.ANDROID_R_SWITCH_WIDGET
+
+
+RemovedMethod: androidx.preference.Preference#clearWasDetached():
+ Removed method androidx.preference.Preference.clearWasDetached()
+RemovedMethod: androidx.preference.Preference#wasDetached():
+ Removed method androidx.preference.Preference.wasDetached()
+
+
diff --git a/preference/api/restricted_1.1.0-alpha05.txt b/preference/api/restricted_1.1.0-alpha05.txt
new file mode 100644
index 0000000..d8a2d7c
--- /dev/null
+++ b/preference/api/restricted_1.1.0-alpha05.txt
@@ -0,0 +1,75 @@
+// Signature format: 3.0
+package androidx.preference {
+
+
+ public class Preference implements java.lang.Comparable<androidx.preference.Preference> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void onAttachedToHierarchy(androidx.preference.PreferenceManager!, long);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void performClick(android.view.View!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void performClick();
+ }
+
+ @Deprecated public abstract class PreferenceDialogFragment extends android.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ }
+
+ public abstract class PreferenceDialogFragmentCompat extends androidx.fragment.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ }
+
+ @Deprecated public abstract class PreferenceFragment extends android.app.Fragment implements androidx.preference.DialogPreference.TargetFragment androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener androidx.preference.PreferenceManager.OnNavigateToScreenListener androidx.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ }
+
+ public abstract class PreferenceFragmentCompat extends androidx.fragment.app.Fragment implements androidx.preference.DialogPreference.TargetFragment androidx.preference.PreferenceManager.OnDisplayPreferenceDialogListener androidx.preference.PreferenceManager.OnNavigateToScreenListener androidx.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.fragment.app.Fragment! getCallbackFragment();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void onBindPreferences();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected void onUnbindPreferences();
+ }
+
+ public abstract class PreferenceGroup extends androidx.preference.Preference {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.preference.PreferenceGroup.OnExpandButtonClickListener? getOnExpandButtonClickListener();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setOnExpandButtonClickListener(androidx.preference.PreferenceGroup.OnExpandButtonClickListener?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface PreferenceGroup.OnExpandButtonClickListener {
+ method public void onExpandButtonClick();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PreferenceGroupAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.preference.PreferenceViewHolder> implements androidx.preference.PreferenceGroup.PreferencePositionCallback {
+ ctor public PreferenceGroupAdapter(androidx.preference.PreferenceGroup!);
+ method public androidx.preference.Preference! getItem(int);
+ method public int getItemCount();
+ method public int getPreferenceAdapterPosition(String!);
+ method public int getPreferenceAdapterPosition(androidx.preference.Preference!);
+ method public void onBindViewHolder(androidx.preference.PreferenceViewHolder, int);
+ method public androidx.preference.PreferenceViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onPreferenceChange(androidx.preference.Preference!);
+ method public void onPreferenceHierarchyChange(androidx.preference.Preference!);
+ method public void onPreferenceVisibilityChange(androidx.preference.Preference!);
+ }
+
+ public class PreferenceManager {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PreferenceManager(android.content.Context!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.preference.PreferenceScreen! inflateFromResource(android.content.Context!, int, androidx.preference.PreferenceScreen!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class PreferenceRecyclerViewAccessibilityDelegate extends androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate {
+ ctor public PreferenceRecyclerViewAccessibilityDelegate(androidx.recyclerview.widget.RecyclerView!);
+ }
+
+ public final class PreferenceScreen extends androidx.preference.PreferenceGroup {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public PreferenceScreen(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public class PreferenceViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.TESTS) public static androidx.preference.PreferenceViewHolder! createInstanceForTests(android.view.View!);
+ }
+
+ public abstract class TwoStatePreference extends androidx.preference.Preference {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class UnPressableLinearLayout extends android.widget.LinearLayout {
+ ctor public UnPressableLinearLayout(android.content.Context!);
+ ctor public UnPressableLinearLayout(android.content.Context!, android.util.AttributeSet!);
+ }
+
+}
+
+
diff --git a/preference/build.gradle b/preference/build.gradle
index 902a866..680046c 100644
--- a/preference/build.gradle
+++ b/preference/build.gradle
@@ -30,7 +30,7 @@
// TODO: change to alpha05 after release
api(project(":core"))
implementation("androidx.collection:collection:1.0.0")
- api("androidx.fragment:fragment:1.1.0-alpha04")
+ api(project(":fragment"))
api("androidx.recyclerview:recyclerview:1.0.0")
androidTestImplementation(TEST_EXT_JUNIT)
@@ -51,6 +51,10 @@
]
}
+ defaultConfig {
+ vectorDrawables.useSupportLibrary = true
+ }
+
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
diff --git a/preference/ktx/api/1.1.0-alpha05.txt b/preference/ktx/api/1.1.0-alpha05.txt
new file mode 100644
index 0000000..236569e
--- /dev/null
+++ b/preference/ktx/api/1.1.0-alpha05.txt
@@ -0,0 +1,21 @@
+// Signature format: 3.0
+package androidx.preference {
+
+ public final class PreferenceGroupKt {
+ ctor public PreferenceGroupKt();
+ method public static operator boolean contains(androidx.preference.PreferenceGroup, androidx.preference.Preference preference);
+ method public static inline void forEach(androidx.preference.PreferenceGroup, kotlin.jvm.functions.Function1<? super androidx.preference.Preference,kotlin.Unit> action);
+ method public static inline void forEachIndexed(androidx.preference.PreferenceGroup, kotlin.jvm.functions.Function2<? super java.lang.Integer,? super androidx.preference.Preference,kotlin.Unit> action);
+ method public static inline operator <T extends androidx.preference.Preference> T? get(androidx.preference.PreferenceGroup, CharSequence key);
+ method public static operator androidx.preference.Preference get(androidx.preference.PreferenceGroup, int index);
+ method public static kotlin.sequences.Sequence<androidx.preference.Preference> getChildren(androidx.preference.PreferenceGroup);
+ method public static inline int getSize(androidx.preference.PreferenceGroup);
+ method public static inline boolean isEmpty(androidx.preference.PreferenceGroup);
+ method public static inline boolean isNotEmpty(androidx.preference.PreferenceGroup);
+ method public static operator java.util.Iterator<androidx.preference.Preference> iterator(androidx.preference.PreferenceGroup);
+ method public static inline operator void minusAssign(androidx.preference.PreferenceGroup, androidx.preference.Preference preference);
+ method public static inline operator void plusAssign(androidx.preference.PreferenceGroup, androidx.preference.Preference preference);
+ }
+
+}
+
diff --git a/preference/ktx/api/res-1.1.0-alpha05.txt b/preference/ktx/api/res-1.1.0-alpha05.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/preference/ktx/api/res-1.1.0-alpha05.txt
diff --git a/preference/ktx/api/restricted_1.1.0-alpha05.txt b/preference/ktx/api/restricted_1.1.0-alpha05.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/preference/ktx/api/restricted_1.1.0-alpha05.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/preference/res/drawable-v21/ic_arrow_down_24dp.xml b/preference/res/drawable-v21/ic_arrow_down_24dp.xml
index 1815b8c..f32f5f4d 100644
--- a/preference/res/drawable-v21/ic_arrow_down_24dp.xml
+++ b/preference/res/drawable-v21/ic_arrow_down_24dp.xml
@@ -19,8 +19,8 @@
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
- android:tint="?android:attr/colorAccent">
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FF000000"
- android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
+ android:pathData="M12,16.41l-6.71,-6.7l1.42,-1.42l5.29,5.3l5.29,-5.3l1.42,1.42z"/>
</vector>
diff --git a/preference/res/layout/expand_button.xml b/preference/res/layout/expand_button.xml
index 6bf02a2..3bcb489 100644
--- a/preference/res/layout/expand_button.xml
+++ b/preference/res/layout/expand_button.xml
@@ -23,8 +23,8 @@
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:clipToPadding="false"
diff --git a/preference/res/layout/image_frame.xml b/preference/res/layout/image_frame.xml
index 155896b..adc3613 100644
--- a/preference/res/layout/image_frame.xml
+++ b/preference/res/layout/image_frame.xml
@@ -23,6 +23,8 @@
android:minWidth="56dp"
android:gravity="start|center_vertical"
android:orientation="horizontal"
+ android:paddingLeft="0dp"
+ android:paddingStart="0dp"
android:paddingRight="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
diff --git a/preference/res/layout/preference_category_material.xml b/preference/res/layout/preference_category_material.xml
index c4566c7..8611e1e 100644
--- a/preference/res/layout/preference_category_material.xml
+++ b/preference/res/layout/preference_category_material.xml
@@ -20,8 +20,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:baselineAligned="false"
diff --git a/preference/res/layout/preference_material.xml b/preference/res/layout/preference_material.xml
index f2e02fb..6e55cfc 100644
--- a/preference/res/layout/preference_material.xml
+++ b/preference/res/layout/preference_material.xml
@@ -22,8 +22,8 @@
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:clipToPadding="false"
@@ -69,6 +69,8 @@
android:gravity="end|center_vertical"
android:paddingLeft="16dp"
android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
android:orientation="vertical"/>
</LinearLayout>
diff --git a/preference/res/layout/preference_widget_seekbar_material.xml b/preference/res/layout/preference_widget_seekbar_material.xml
index 25872c1..3689e4d 100644
--- a/preference/res/layout/preference_widget_seekbar_material.xml
+++ b/preference/res/layout/preference_widget_seekbar_material.xml
@@ -23,8 +23,8 @@
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
android:clipChildren="false"
@@ -77,6 +77,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
+ android:paddingLeft="0dp"
+ android:paddingStart="0dp"
android:paddingRight="16dp"
android:paddingEnd="16dp"
android:clipChildren="false"
@@ -96,8 +98,8 @@
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/preference_seekbar_padding_horizontal"
- android:paddingRight="@dimen/preference_seekbar_padding_horizontal"
android:paddingStart="@dimen/preference_seekbar_padding_horizontal"
+ android:paddingRight="@dimen/preference_seekbar_padding_horizontal"
android:paddingEnd="@dimen/preference_seekbar_padding_horizontal"
android:paddingTop="@dimen/preference_seekbar_padding_vertical"
android:paddingBottom="@dimen/preference_seekbar_padding_vertical"
@@ -111,6 +113,8 @@
android:minWidth="@dimen/preference_seekbar_value_minWidth"
android:paddingLeft="8dp"
android:paddingStart="8dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
diff --git a/preference/res/values/styles.xml b/preference/res/values/styles.xml
index 001e097..6ab5a60 100644
--- a/preference/res/values/styles.xml
+++ b/preference/res/values/styles.xml
@@ -22,8 +22,8 @@
<style name="PreferenceFragment">
<item name="android:paddingLeft">0dp</item>
- <item name="android:paddingRight">0dp</item>
<item name="android:paddingStart">0dp</item>
+ <item name="android:paddingRight">0dp</item>
<item name="android:paddingEnd">0dp</item>
<item name="android:divider">?android:attr/listDivider</item>
</style>
@@ -72,8 +72,8 @@
<style name="PreferenceFragmentList">
<item name="android:paddingLeft">16dp</item>
- <item name="android:paddingRight">16dp</item>
<item name="android:paddingStart">16dp</item>
+ <item name="android:paddingRight">16dp</item>
<item name="android:paddingEnd">16dp</item>
</style>
@@ -175,8 +175,8 @@
<style name="PreferenceFragmentList.Material">
<item name="android:paddingLeft">0dp</item>
- <item name="android:paddingRight">0dp</item>
<item name="android:paddingStart">0dp</item>
+ <item name="android:paddingRight">0dp</item>
<item name="android:paddingEnd">0dp</item>
</style>
diff --git a/preference/src/main/java/androidx/preference/Preference.java b/preference/src/main/java/androidx/preference/Preference.java
index 9530747..ce1c2b5 100644
--- a/preference/src/main/java/androidx/preference/Preference.java
+++ b/preference/src/main/java/androidx/preference/Preference.java
@@ -498,9 +498,22 @@
* returns.
*/
public void onBindViewHolder(PreferenceViewHolder holder) {
+ Integer summaryTextColor = null;
holder.itemView.setOnClickListener(mClickListener);
holder.itemView.setId(mViewId);
+ final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
+ if (summaryView != null) {
+ final CharSequence summary = getSummary();
+ if (!TextUtils.isEmpty(summary)) {
+ summaryView.setText(summary);
+ summaryView.setVisibility(View.VISIBLE);
+ summaryTextColor = summaryView.getCurrentTextColor();
+ } else {
+ summaryView.setVisibility(View.GONE);
+ }
+ }
+
final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
if (titleView != null) {
final CharSequence title = getTitle();
@@ -510,22 +523,16 @@
if (mHasSingleLineTitleAttr) {
titleView.setSingleLine(mSingleLineTitle);
}
+ // If this Preference is not selectable, but still enabled, we should set the
+ // title text colour to the same colour used for the summary text
+ if (!isSelectable() && isEnabled() && summaryTextColor != null) {
+ titleView.setTextColor(summaryTextColor);
+ }
} else {
titleView.setVisibility(View.GONE);
}
}
- final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary);
- if (summaryView != null) {
- final CharSequence summary = getSummary();
- if (!TextUtils.isEmpty(summary)) {
- summaryView.setText(summary);
- summaryView.setVisibility(View.VISIBLE);
- } else {
- summaryView.setVisibility(View.GONE);
- }
- }
-
final ImageView imageView = (ImageView) holder.findViewById(android.R.id.icon);
if (imageView != null) {
if (mIconResId != 0 || mIcon != null) {
@@ -572,6 +579,7 @@
mOnCopyListener = new OnPreferenceCopyListener(this);
}
holder.itemView.setOnCreateContextMenuListener(isCopyingEnabled() ? mOnCopyListener : null);
+ holder.itemView.setLongClickable(isCopyingEnabled());
}
/**
@@ -834,7 +842,7 @@
* the group is visible.
*
* @param visible Set false if this preference should be hidden from the user
- * {@link R.attr#isPreferenceVisible}
+ * {@link androidx.preference.R.attr#isPreferenceVisible}
* @see #isShown()
*/
public final void setVisible(boolean visible) {
@@ -996,7 +1004,7 @@
* letting it wrap onto multiple lines.
*
* @param singleLineTitle Set {@code true} if the title should be constrained to one line
- * {@link R.attr#android_singleLineTitle}
+ * {@link android.R.attr#singleLineTitle}
*/
public void setSingleLineTitle(boolean singleLineTitle) {
mHasSingleLineTitleAttr = true;
@@ -1007,7 +1015,7 @@
* Gets whether the title of this preference is constrained to a single line.
*
* @return {@code true} if the title of this preference is constrained to a single line
- * {@link R.attr#android_singleLineTitle}
+ * {@link android.R.attr#singleLineTitle}
* @see #setSingleLineTitle(boolean)
*/
public boolean isSingleLineTitle() {
@@ -1020,7 +1028,7 @@
* other preferences having icons.
*
* @param iconSpaceReserved Set {@code true} if the space for the icon view should be reserved
- * {@link R.attr#android_iconSpaceReserved}
+ * {@link android.R.attr#iconSpaceReserved}
*/
public void setIconSpaceReserved(boolean iconSpaceReserved) {
if (mIconSpaceReserved != iconSpaceReserved) {
@@ -1033,7 +1041,7 @@
* Returns whether the space of this preference icon view is reserved.
*
* @return {@code true} if the space of this preference icon view is reserved
- * {@link R.attr#android_iconSpaceReserved}
+ * {@link android.R.attr#iconSpaceReserved}
* @see #setIconSpaceReserved(boolean)
*/
public boolean isIconSpaceReserved() {
@@ -1068,7 +1076,7 @@
* is requested. Set {@code null} to remove the existing SummaryProvider.
*
* @param summaryProvider The {@link SummaryProvider} that will be invoked whenever the
- * summary of this preference is requested
+ * summary of this preference is requested
* @see SummaryProvider
*/
public final void setSummaryProvider(@Nullable SummaryProvider summaryProvider) {
diff --git a/preference/src/main/java/androidx/preference/PreferenceFragment.java b/preference/src/main/java/androidx/preference/PreferenceFragment.java
index 50ab41d..bdc6851 100644
--- a/preference/src/main/java/androidx/preference/PreferenceFragment.java
+++ b/preference/src/main/java/androidx/preference/PreferenceFragment.java
@@ -244,7 +244,7 @@
* call {@link #setDividerHeight(int)}.
*
* @param divider The drawable to use
- * {@link R.attr#android_divider}
+ * {@link android.R.attr#divider}
*
* @deprecated Use {@link PreferenceFragmentCompat} instead
*/
@@ -258,7 +258,7 @@
* this will override the intrinsic height as set by {@link #setDivider(Drawable)}.
*
* @param height The new height of the divider in pixels
- * {@link R.attr#android_dividerHeight}
+ * {@link android.R.attr#dividerHeight}
*
* @deprecated Use {@link PreferenceFragmentCompat} instead
*/
diff --git a/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java b/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
index f490c55..1750f60 100644
--- a/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
+++ b/preference/src/main/java/androidx/preference/PreferenceFragmentCompat.java
@@ -237,7 +237,7 @@
* call {@link #setDividerHeight(int)}.
*
* @param divider The drawable to use
- * {@link R.attr#android_divider}
+ * {@link android.R.attr#divider}
*/
public void setDivider(Drawable divider) {
mDividerDecoration.setDivider(divider);
@@ -248,7 +248,7 @@
* this will override the intrinsic height as set by {@link #setDivider(Drawable)}.
*
* @param height The new height of the divider in pixels
- * {@link R.attr#android_dividerHeight}
+ * {@link android.R.attr#dividerHeight}
*/
public void setDividerHeight(int height) {
mDividerDecoration.setDividerHeight(height);
@@ -417,7 +417,7 @@
.getSupportFragmentManager();
final Bundle args = preference.getExtras();
final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
- requireActivity().getClassLoader(), preference.getFragment(), args);
+ requireActivity().getClassLoader(), preference.getFragment());
fragment.setArguments(args);
fragment.setTargetFragment(this, 0);
fragmentManager.beginTransaction()
@@ -604,8 +604,11 @@
} else if (preference instanceof MultiSelectListPreference) {
f = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else {
- throw new IllegalArgumentException("Tried to display dialog for unknown " +
- "preference type. Did you forget to override onDisplayPreferenceDialog()?");
+ throw new IllegalArgumentException(
+ "Cannot display dialog for an unknown Preference type: "
+ + preference.getClass().getSimpleName()
+ + ". Make sure to implement onPreferenceDisplayDialog() to handle "
+ + "displaying a custom dialog for this Preference.");
}
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
diff --git a/preference/src/main/java/androidx/preference/PreferenceGroup.java b/preference/src/main/java/androidx/preference/PreferenceGroup.java
index 90850b5..9c8f284 100644
--- a/preference/src/main/java/androidx/preference/PreferenceGroup.java
+++ b/preference/src/main/java/androidx/preference/PreferenceGroup.java
@@ -143,7 +143,7 @@
* correctly persist state.
*
* @param expandedCount The number of children that is initially shown
- * {@link R.attr#initialExpandedChildrenCount}
+ * {@link androidx.preference.R.attr#initialExpandedChildrenCount}
*/
public void setInitialExpandedChildrenCount(int expandedCount) {
if (expandedCount != Integer.MAX_VALUE && !hasKey()) {
@@ -157,7 +157,7 @@
* Gets the maximal number of children that are initially shown.
*
* @return The maximal number of children that are initially shown
- * {@link R.attr#initialExpandedChildrenCount}
+ * {@link androidx.preference.R.attr#initialExpandedChildrenCount}
*/
public int getInitialExpandedChildrenCount() {
return mInitialExpandedChildrenCount;
diff --git a/recyclerview/benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt b/recyclerview/benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
index 7e112af..3162fef 100644
--- a/recyclerview/benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
+++ b/recyclerview/benchmark/src/androidTest/java/androidx/recyclerview/benchmark/ScrollBenchmark.kt
@@ -63,11 +63,9 @@
@UiThreadTest
@Test
fun offset() {
- val state = benchmarkRule.state
-
val rv = activityRule.activity.recyclerView
var offset = 10
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
// keep scrolling up and down - no new item should be revealed
rv.scrollBy(0, offset)
offset *= -1
@@ -77,10 +75,8 @@
@UiThreadTest
@Test
fun bindOffset() {
- val state = benchmarkRule.state
-
val rv = activityRule.activity.recyclerView
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
// each scroll should reveal a new item
rv.scrollBy(0, 100)
}
@@ -89,7 +85,6 @@
@UiThreadTest
@Test
fun createBindOffset() {
- val state = benchmarkRule.state
trivialAdapter.disableReuse = true
trivialAdapter.inflater = {
val view = View(it.context)
@@ -98,7 +93,7 @@
}
val rv = activityRule.activity.recyclerView
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
// each scroll should reveal a new item that must be inflated
rv.scrollBy(0, 100)
}
@@ -107,11 +102,10 @@
@UiThreadTest
@Test
fun inflateBindOffset() {
- val state = benchmarkRule.state
trivialAdapter.disableReuse = true
val rv = activityRule.activity.recyclerView
- while (state.keepRunning()) {
+ benchmarkRule.keepRunning {
// each scroll should reveal a new item that must be inflated
rv.scrollBy(0, 100)
}
@@ -135,7 +129,7 @@
var inflater: (ViewGroup) -> View = {
LayoutInflater.from(it.context).inflate(
- R.layout.item_view, it, false)
+ R.layout.item_view, it, false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrivialViewHolder {
diff --git a/recyclerview/recyclerview/api/1.1.0-alpha04.txt b/recyclerview/recyclerview/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..5d61aaf
--- /dev/null
+++ b/recyclerview/recyclerview/api/1.1.0-alpha04.txt
@@ -0,0 +1,1033 @@
+// Signature format: 3.0
+package androidx.recyclerview.widget {
+
+ public final class AdapterListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public AdapterListUpdateCallback(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public final class AsyncDifferConfig<T> {
+ method public java.util.concurrent.Executor getBackgroundThreadExecutor();
+ method public androidx.recyclerview.widget.DiffUtil.ItemCallback<T> getDiffCallback();
+ }
+
+ public static final class AsyncDifferConfig.Builder<T> {
+ ctor public AsyncDifferConfig.Builder(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>);
+ method public androidx.recyclerview.widget.AsyncDifferConfig<T> build();
+ method public androidx.recyclerview.widget.AsyncDifferConfig.Builder<T> setBackgroundThreadExecutor(java.util.concurrent.Executor!);
+ }
+
+ public class AsyncListDiffer<T> {
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T>);
+ ctor public AsyncListDiffer(androidx.recyclerview.widget.ListUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T>);
+ method public void addListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T>);
+ method public java.util.List<T> getCurrentList();
+ method public void removeListListener(androidx.recyclerview.widget.AsyncListDiffer.ListListener<T>);
+ method public void submitList(java.util.List<T>?);
+ method public void submitList(java.util.List<T>?, Runnable?);
+ }
+
+ public static interface AsyncListDiffer.ListListener<T> {
+ method public void onCurrentListChanged(java.util.List<T>, java.util.List<T>);
+ }
+
+ public class AsyncListUtil<T> {
+ ctor public AsyncListUtil(Class<T>, int, androidx.recyclerview.widget.AsyncListUtil.DataCallback<T>, androidx.recyclerview.widget.AsyncListUtil.ViewCallback);
+ method public T? getItem(int);
+ method public int getItemCount();
+ method public void onRangeChanged();
+ method public void refresh();
+ }
+
+ public abstract static class AsyncListUtil.DataCallback<T> {
+ ctor public AsyncListUtil.DataCallback();
+ method @WorkerThread public abstract void fillData(T[], int, int);
+ method @WorkerThread public int getMaxCachedTiles();
+ method @WorkerThread public void recycleData(T[], int);
+ method @WorkerThread public abstract int refreshData();
+ }
+
+ public abstract static class AsyncListUtil.ViewCallback {
+ ctor public AsyncListUtil.ViewCallback();
+ method @UiThread public void extendRangeInto(int[], int[], int);
+ method @UiThread public abstract void getItemRangeInto(int[]);
+ method @UiThread public abstract void onDataRefresh();
+ method @UiThread public abstract void onItemLoaded(int);
+ field public static final int HINT_SCROLL_ASC = 2; // 0x2
+ field public static final int HINT_SCROLL_DESC = 1; // 0x1
+ field public static final int HINT_SCROLL_NONE = 0; // 0x0
+ }
+
+ public class BatchingListUpdateCallback implements androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public BatchingListUpdateCallback(androidx.recyclerview.widget.ListUpdateCallback);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int, Object!);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class DefaultItemAnimator extends androidx.recyclerview.widget.SimpleItemAnimator {
+ ctor public DefaultItemAnimator();
+ method public boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void endAnimations();
+ method public boolean isRunning();
+ method public void runPendingAnimations();
+ }
+
+ public class DiffUtil {
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback);
+ method public static androidx.recyclerview.widget.DiffUtil.DiffResult calculateDiff(androidx.recyclerview.widget.DiffUtil.Callback, boolean);
+ }
+
+ public abstract static class DiffUtil.Callback {
+ ctor public DiffUtil.Callback();
+ method public abstract boolean areContentsTheSame(int, int);
+ method public abstract boolean areItemsTheSame(int, int);
+ method public Object? getChangePayload(int, int);
+ method public abstract int getNewListSize();
+ method public abstract int getOldListSize();
+ }
+
+ public static class DiffUtil.DiffResult {
+ method public int convertNewPositionToOld(@IntRange(from=0) int);
+ method public int convertOldPositionToNew(@IntRange(from=0) int);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.RecyclerView.Adapter);
+ method public void dispatchUpdatesTo(androidx.recyclerview.widget.ListUpdateCallback);
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ }
+
+ public abstract static class DiffUtil.ItemCallback<T> {
+ ctor public DiffUtil.ItemCallback();
+ method public abstract boolean areContentsTheSame(T, T);
+ method public abstract boolean areItemsTheSame(T, T);
+ method public Object? getChangePayload(T, T);
+ }
+
+ public class DividerItemDecoration extends androidx.recyclerview.widget.RecyclerView.ItemDecoration {
+ ctor public DividerItemDecoration(android.content.Context!, int);
+ method public void setDrawable(android.graphics.drawable.Drawable);
+ method public void setOrientation(int);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public class GridLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager {
+ ctor public GridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public GridLayoutManager(android.content.Context!, int);
+ ctor public GridLayoutManager(android.content.Context!, int, int, boolean);
+ method public int getSpanCount();
+ method public androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup! getSpanSizeLookup();
+ method public boolean isUsingSpansToEstimateScrollbarDimensions();
+ method public void setSpanCount(int);
+ method public void setSpanSizeLookup(androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup!);
+ method public void setUsingSpansToEstimateScrollbarDimensions(boolean);
+ field public static final int DEFAULT_SPAN_COUNT = -1; // 0xffffffff
+ }
+
+ public static final class GridLayoutManager.DefaultSpanSizeLookup extends androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.DefaultSpanSizeLookup();
+ method public int getSpanSize(int);
+ }
+
+ public static class GridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public GridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public GridLayoutManager.LayoutParams(int, int);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public GridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getSpanIndex();
+ method public int getSpanSize();
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+ public abstract static class GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.SpanSizeLookup();
+ method public int getSpanGroupIndex(int, int);
+ method public int getSpanIndex(int, int);
+ method public abstract int getSpanSize(int);
+ method public void invalidateSpanGroupIndexCache();
+ method public void invalidateSpanIndexCache();
+ method public boolean isSpanGroupIndexCacheEnabled();
+ method public boolean isSpanIndexCacheEnabled();
+ method public void setSpanGroupIndexCacheEnabled(boolean);
+ method public void setSpanIndexCacheEnabled(boolean);
+ }
+
+ public class ItemTouchHelper extends androidx.recyclerview.widget.RecyclerView.ItemDecoration implements androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener {
+ ctor public ItemTouchHelper(androidx.recyclerview.widget.ItemTouchHelper.Callback);
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?);
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ method public void startDrag(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void startSwipe(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ field public static final int ACTION_STATE_DRAG = 2; // 0x2
+ field public static final int ACTION_STATE_IDLE = 0; // 0x0
+ field public static final int ACTION_STATE_SWIPE = 1; // 0x1
+ field public static final int ANIMATION_TYPE_DRAG = 8; // 0x8
+ field public static final int ANIMATION_TYPE_SWIPE_CANCEL = 4; // 0x4
+ field public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 2; // 0x2
+ field public static final int DOWN = 2; // 0x2
+ field public static final int END = 32; // 0x20
+ field public static final int LEFT = 4; // 0x4
+ field public static final int RIGHT = 8; // 0x8
+ field public static final int START = 16; // 0x10
+ field public static final int UP = 1; // 0x1
+ }
+
+ public abstract static class ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.Callback();
+ method public boolean canDropOver(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>, int, int);
+ method public void clearView(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int convertToAbsoluteDirection(int, int);
+ method public static int convertToRelativeDirection(int, int);
+ method public long getAnimationDuration(androidx.recyclerview.widget.RecyclerView, int, float, float);
+ method public int getBoundingBoxMargin();
+ method public static androidx.recyclerview.widget.ItemTouchUIUtil getDefaultUIUtil();
+ method public float getMoveThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeEscapeVelocity(float);
+ method public float getSwipeThreshold(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public float getSwipeVelocityThreshold(float);
+ method public int interpolateOutOfBoundsScroll(androidx.recyclerview.widget.RecyclerView, int, int, int, long);
+ method public boolean isItemViewSwipeEnabled();
+ method public boolean isLongPressDragEnabled();
+ method public static int makeFlag(int, int);
+ method public static int makeMovementFlags(int, int);
+ method public void onChildDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, float, float, int, boolean);
+ method public void onChildDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder!, float, float, int, boolean);
+ method public abstract boolean onMove(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onMoved(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int);
+ method public void onSelectedChanged(androidx.recyclerview.widget.RecyclerView.ViewHolder?, int);
+ method public abstract void onSwiped(androidx.recyclerview.widget.RecyclerView.ViewHolder, int);
+ field public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; // 0xc8
+ field public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; // 0xfa
+ }
+
+ public abstract static class ItemTouchHelper.SimpleCallback extends androidx.recyclerview.widget.ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.SimpleCallback(int, int);
+ method public int getDragDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getMovementFlags(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public int getSwipeDirs(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void setDefaultDragDirs(int);
+ method public void setDefaultSwipeDirs(int);
+ }
+
+ public static interface ItemTouchHelper.ViewDropHandler {
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ }
+
+ public interface ItemTouchUIUtil {
+ method public void clearView(android.view.View!);
+ method public void onDraw(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onDrawOver(android.graphics.Canvas!, androidx.recyclerview.widget.RecyclerView!, android.view.View!, float, float, int, boolean);
+ method public void onSelected(android.view.View!);
+ }
+
+ public class LinearLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.ItemTouchHelper.ViewDropHandler androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public LinearLayoutManager(android.content.Context!);
+ ctor public LinearLayoutManager(android.content.Context!, int, boolean);
+ ctor public LinearLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int findFirstCompletelyVisibleItemPosition();
+ method public int findFirstVisibleItemPosition();
+ method public int findLastCompletelyVisibleItemPosition();
+ method public int findLastVisibleItemPosition();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method protected int getExtraLayoutSpace(androidx.recyclerview.widget.RecyclerView.State!);
+ method public int getInitialPrefetchItemCount();
+ method public int getOrientation();
+ method public boolean getRecycleChildrenOnDetach();
+ method public boolean getReverseLayout();
+ method public boolean getStackFromEnd();
+ method protected boolean isLayoutRTL();
+ method public boolean isSmoothScrollbarEnabled();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void prepareForDrop(android.view.View, android.view.View, int, int);
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setInitialPrefetchItemCount(int);
+ method public void setOrientation(int);
+ method public void setRecycleChildrenOnDetach(boolean);
+ method public void setReverseLayout(boolean);
+ method public void setSmoothScrollbarEnabled(boolean);
+ method public void setStackFromEnd(boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_OFFSET = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ protected static class LinearLayoutManager.LayoutChunkResult {
+ ctor protected LinearLayoutManager.LayoutChunkResult();
+ field public int mConsumed;
+ field public boolean mFinished;
+ field public boolean mFocusable;
+ field public boolean mIgnoreConsumed;
+ }
+
+ public class LinearSmoothScroller extends androidx.recyclerview.widget.RecyclerView.SmoothScroller {
+ ctor public LinearSmoothScroller(android.content.Context!);
+ method public int calculateDtToFit(int, int, int, int, int);
+ method public int calculateDxToMakeVisible(android.view.View!, int);
+ method public int calculateDyToMakeVisible(android.view.View!, int);
+ method protected float calculateSpeedPerPixel(android.util.DisplayMetrics!);
+ method protected int calculateTimeForDeceleration(int);
+ method protected int calculateTimeForScrolling(int);
+ method protected int getHorizontalSnapPreference();
+ method protected int getVerticalSnapPreference();
+ method protected void onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void onStart();
+ method protected void onStop();
+ method protected void onTargetFound(android.view.View!, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ method protected void updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action!);
+ field public static final int SNAP_TO_ANY = 0; // 0x0
+ field public static final int SNAP_TO_END = 1; // 0x1
+ field public static final int SNAP_TO_START = -1; // 0xffffffff
+ field protected final android.view.animation.DecelerateInterpolator! mDecelerateInterpolator;
+ field protected int mInterimTargetDx;
+ field protected int mInterimTargetDy;
+ field protected final android.view.animation.LinearInterpolator! mLinearInterpolator;
+ field protected android.graphics.PointF! mTargetVector;
+ }
+
+ public class LinearSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public LinearSnapHelper();
+ method public int[]! calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public android.view.View! findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public abstract class ListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+ ctor protected ListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>);
+ ctor protected ListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T>);
+ method public java.util.List<T> getCurrentList();
+ method protected T! getItem(int);
+ method public int getItemCount();
+ method public void onCurrentListChanged(java.util.List<T>, java.util.List<T>);
+ method public void submitList(java.util.List<T>?);
+ method public void submitList(java.util.List<T>?, Runnable?);
+ }
+
+ public interface ListUpdateCallback {
+ method public void onChanged(int, int, Object?);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract class OrientationHelper {
+ method public static androidx.recyclerview.widget.OrientationHelper! createHorizontalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public static androidx.recyclerview.widget.OrientationHelper! createOrientationHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int);
+ method public static androidx.recyclerview.widget.OrientationHelper! createVerticalHelper(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int getDecoratedEnd(android.view.View!);
+ method public abstract int getDecoratedMeasurement(android.view.View!);
+ method public abstract int getDecoratedMeasurementInOther(android.view.View!);
+ method public abstract int getDecoratedStart(android.view.View!);
+ method public abstract int getEnd();
+ method public abstract int getEndAfterPadding();
+ method public abstract int getEndPadding();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager! getLayoutManager();
+ method public abstract int getMode();
+ method public abstract int getModeInOther();
+ method public abstract int getStartAfterPadding();
+ method public abstract int getTotalSpace();
+ method public int getTotalSpaceChange();
+ method public abstract int getTransformedEndWithDecoration(android.view.View!);
+ method public abstract int getTransformedStartWithDecoration(android.view.View!);
+ method public abstract void offsetChild(android.view.View!, int);
+ method public abstract void offsetChildren(int);
+ method public void onLayoutComplete();
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field protected final androidx.recyclerview.widget.RecyclerView.LayoutManager! mLayoutManager;
+ }
+
+ public class PagerSnapHelper extends androidx.recyclerview.widget.SnapHelper {
+ ctor public PagerSnapHelper();
+ method public int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method protected androidx.recyclerview.widget.LinearSmoothScroller! createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ }
+
+ public class RecyclerView extends android.view.ViewGroup implements androidx.core.view.NestedScrollingChild2 androidx.core.view.NestedScrollingChild3 androidx.core.view.ScrollingView {
+ ctor public RecyclerView(android.content.Context);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet?, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration, int);
+ method public void addItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void addOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void addOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void clearOnChildAttachStateChangeListeners();
+ method public void clearOnScrollListeners();
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ method public int computeVerticalScrollRange();
+ method public boolean dispatchNestedPreScroll(int, int, int[]!, int[]!, int);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]!, int);
+ method public final void dispatchNestedScroll(int, int, int, int, int[]!, int, int[]!);
+ method public boolean drawChild(android.graphics.Canvas!, android.view.View!, long);
+ method public android.view.View? findChildViewUnder(float, float);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findContainingViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForAdapterPosition(int);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! findViewHolderForItemId(long);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForLayoutPosition(int);
+ method @Deprecated public androidx.recyclerview.widget.RecyclerView.ViewHolder? findViewHolderForPosition(int);
+ method public boolean fling(int, int);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getChildAdapterPosition(android.view.View);
+ method public long getChildItemId(android.view.View);
+ method public int getChildLayoutPosition(android.view.View);
+ method @Deprecated public int getChildPosition(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder! getChildViewHolder(android.view.View);
+ method public androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate? getCompatAccessibilityDelegate();
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory getEdgeEffectFactory();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator? getItemAnimator();
+ method public androidx.recyclerview.widget.RecyclerView.ItemDecoration getItemDecorationAt(int);
+ method public int getItemDecorationCount();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getMaxFlingVelocity();
+ method public int getMinFlingVelocity();
+ method public androidx.recyclerview.widget.RecyclerView.OnFlingListener? getOnFlingListener();
+ method public boolean getPreserveFocusAfterLayout();
+ method public androidx.recyclerview.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
+ method public int getScrollState();
+ method public boolean hasFixedSize();
+ method public boolean hasNestedScrollingParent(int);
+ method public boolean hasPendingAdapterUpdates();
+ method public void invalidateItemDecorations();
+ method public boolean isAnimating();
+ method public boolean isComputingLayout();
+ method @Deprecated public boolean isLayoutFrozen();
+ method public final boolean isLayoutSuppressed();
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onChildAttachedToWindow(android.view.View);
+ method public void onChildDetachedFromWindow(android.view.View);
+ method public void onDraw(android.graphics.Canvas!);
+ method public void onScrollStateChanged(int);
+ method public void onScrolled(@Px int, @Px int);
+ method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
+ method public void removeItemDecorationAt(int);
+ method public void removeOnChildAttachStateChangeListener(androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener);
+ method public void scrollToPosition(int);
+ method public void setAccessibilityDelegateCompat(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate?);
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setChildDrawingOrderCallback(androidx.recyclerview.widget.RecyclerView.ChildDrawingOrderCallback?);
+ method public void setEdgeEffectFactory(androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory);
+ method public void setHasFixedSize(boolean);
+ method public void setItemAnimator(androidx.recyclerview.widget.RecyclerView.ItemAnimator?);
+ method public void setItemViewCacheSize(int);
+ method @Deprecated public void setLayoutFrozen(boolean);
+ method public void setLayoutManager(androidx.recyclerview.widget.RecyclerView.LayoutManager?);
+ method @Deprecated public void setLayoutTransition(android.animation.LayoutTransition!);
+ method public void setOnFlingListener(androidx.recyclerview.widget.RecyclerView.OnFlingListener?);
+ method @Deprecated public void setOnScrollListener(androidx.recyclerview.widget.RecyclerView.OnScrollListener?);
+ method public void setPreserveFocusAfterLayout(boolean);
+ method public void setRecycledViewPool(androidx.recyclerview.widget.RecyclerView.RecycledViewPool?);
+ method public void setRecyclerListener(androidx.recyclerview.widget.RecyclerView.RecyclerListener?);
+ method public void setScrollingTouchSlop(int);
+ method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
+ method public void smoothScrollBy(@Px int, @Px int);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollToPosition(int);
+ method public boolean startNestedScroll(int, int);
+ method public void stopNestedScroll(int);
+ method public void stopScroll();
+ method public final void suppressLayout(boolean);
+ method public void swapAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?, boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_TYPE = -1; // 0xffffffff
+ field public static final long NO_ID = -1L; // 0xffffffffffffffffL
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
+ field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public abstract static class RecyclerView.Adapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> {
+ ctor public RecyclerView.Adapter();
+ method public final void bindViewHolder(VH, int);
+ method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public abstract int getItemCount();
+ method public long getItemId(int);
+ method public int getItemViewType(int);
+ method public final boolean hasObservers();
+ method public final boolean hasStableIds();
+ method public final void notifyDataSetChanged();
+ method public final void notifyItemChanged(int);
+ method public final void notifyItemChanged(int, Object?);
+ method public final void notifyItemInserted(int);
+ method public final void notifyItemMoved(int, int);
+ method public final void notifyItemRangeChanged(int, int);
+ method public final void notifyItemRangeChanged(int, int, Object?);
+ method public final void notifyItemRangeInserted(int, int);
+ method public final void notifyItemRangeRemoved(int, int);
+ method public final void notifyItemRemoved(int);
+ method public void onAttachedToRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public abstract void onBindViewHolder(VH, int);
+ method public void onBindViewHolder(VH, int, java.util.List<java.lang.Object>);
+ method public abstract VH onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onDetachedFromRecyclerView(androidx.recyclerview.widget.RecyclerView);
+ method public boolean onFailedToRecycleView(VH);
+ method public void onViewAttachedToWindow(VH);
+ method public void onViewDetachedFromWindow(VH);
+ method public void onViewRecycled(VH);
+ method public void registerAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ method public void setHasStableIds(boolean);
+ method public void unregisterAdapterDataObserver(androidx.recyclerview.widget.RecyclerView.AdapterDataObserver);
+ }
+
+ public abstract static class RecyclerView.AdapterDataObserver {
+ ctor public RecyclerView.AdapterDataObserver();
+ method public void onChanged();
+ method public void onItemRangeChanged(int, int);
+ method public void onItemRangeChanged(int, int, Object?);
+ method public void onItemRangeInserted(int, int);
+ method public void onItemRangeMoved(int, int, int);
+ method public void onItemRangeRemoved(int, int);
+ }
+
+ public static interface RecyclerView.ChildDrawingOrderCallback {
+ method public int onGetChildDrawingOrder(int, int);
+ }
+
+ public static class RecyclerView.EdgeEffectFactory {
+ ctor public RecyclerView.EdgeEffectFactory();
+ method protected android.widget.EdgeEffect createEdgeEffect(androidx.recyclerview.widget.RecyclerView, @androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.EdgeDirection int);
+ field public static final int DIRECTION_BOTTOM = 3; // 0x3
+ field public static final int DIRECTION_LEFT = 0; // 0x0
+ field public static final int DIRECTION_RIGHT = 2; // 0x2
+ field public static final int DIRECTION_TOP = 1; // 0x1
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_LEFT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_RIGHT, androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM}) public static @interface RecyclerView.EdgeEffectFactory.EdgeDirection {
+ }
+
+ public abstract static class RecyclerView.ItemAnimator {
+ ctor public RecyclerView.ItemAnimator();
+ method public abstract boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public boolean canReuseUpdatedViewHolder(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<java.lang.Object>);
+ method public final void dispatchAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationsFinished();
+ method public abstract void endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public abstract void endAnimations();
+ method public long getAddDuration();
+ method public long getChangeDuration();
+ method public long getMoveDuration();
+ method public long getRemoveDuration();
+ method public abstract boolean isRunning();
+ method public final boolean isRunning(androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener?);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo obtainHolderInfo();
+ method public void onAnimationFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public void onAnimationStarted(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int, java.util.List<java.lang.Object>);
+ method public abstract void runPendingAnimations();
+ method public void setAddDuration(long);
+ method public void setChangeDuration(long);
+ method public void setMoveDuration(long);
+ method public void setRemoveDuration(long);
+ field public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096; // 0x1000
+ field public static final int FLAG_CHANGED = 2; // 0x2
+ field public static final int FLAG_INVALIDATED = 4; // 0x4
+ field public static final int FLAG_MOVED = 2048; // 0x800
+ field public static final int FLAG_REMOVED = 8; // 0x8
+ }
+
+ @IntDef(flag=true, value={androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_CHANGED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_REMOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_MOVED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_INVALIDATED, androidx.recyclerview.widget.RecyclerView.ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RecyclerView.ItemAnimator.AdapterChanges {
+ }
+
+ public static interface RecyclerView.ItemAnimator.ItemAnimatorFinishedListener {
+ method public void onAnimationsFinished();
+ }
+
+ public static class RecyclerView.ItemAnimator.ItemHolderInfo {
+ ctor public RecyclerView.ItemAnimator.ItemHolderInfo();
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ method public androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(androidx.recyclerview.widget.RecyclerView.ViewHolder, @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges int);
+ field public int bottom;
+ field @androidx.recyclerview.widget.RecyclerView.ItemAnimator.AdapterChanges public int changeFlags;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public abstract static class RecyclerView.ItemDecoration {
+ ctor public RecyclerView.ItemDecoration();
+ method @Deprecated public void getItemOffsets(android.graphics.Rect, int, androidx.recyclerview.widget.RecyclerView);
+ method public void getItemOffsets(android.graphics.Rect, android.view.View, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ method public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State);
+ method @Deprecated public void onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView);
+ }
+
+ public abstract static class RecyclerView.LayoutManager {
+ ctor public RecyclerView.LayoutManager();
+ method public void addDisappearingView(android.view.View!);
+ method public void addDisappearingView(android.view.View!, int);
+ method public void addView(android.view.View!);
+ method public void addView(android.view.View!, int);
+ method public void assertInLayoutOrScroll(String!);
+ method public void assertNotInLayoutOrScroll(String!);
+ method public void attachView(android.view.View, int, androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public void attachView(android.view.View, int);
+ method public void attachView(android.view.View);
+ method public void calculateItemDecorationsForChild(android.view.View, android.graphics.Rect);
+ method public boolean canScrollHorizontally();
+ method public boolean canScrollVertically();
+ method public boolean checkLayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public static int chooseSize(int, int, int);
+ method public void collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State!, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public void collectInitialPrefetchPositions(int, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry!);
+ method public int computeHorizontalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeHorizontalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollExtent(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollOffset(androidx.recyclerview.widget.RecyclerView.State);
+ method public int computeVerticalScrollRange(androidx.recyclerview.widget.RecyclerView.State);
+ method public void detachAndScrapAttachedViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachAndScrapViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void detachView(android.view.View);
+ method public void detachViewAt(int);
+ method public void endAnimation(android.view.View!);
+ method public android.view.View? findContainingItemView(android.view.View);
+ method public android.view.View? findViewByPosition(int);
+ method public abstract androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.view.ViewGroup.LayoutParams!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateLayoutParams(android.content.Context!, android.util.AttributeSet!);
+ method public int getBaseline();
+ method public int getBottomDecorationHeight(android.view.View);
+ method public android.view.View? getChildAt(int);
+ method public int getChildCount();
+ method @Deprecated public static int getChildMeasureSpec(int, int, int, boolean);
+ method public static int getChildMeasureSpec(int, int, int, int, boolean);
+ method public boolean getClipToPadding();
+ method public int getColumnCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getDecoratedBottom(android.view.View);
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
+ method public int getDecoratedLeft(android.view.View);
+ method public int getDecoratedMeasuredHeight(android.view.View);
+ method public int getDecoratedMeasuredWidth(android.view.View);
+ method public int getDecoratedRight(android.view.View);
+ method public int getDecoratedTop(android.view.View);
+ method public android.view.View? getFocusedChild();
+ method @Px public int getHeight();
+ method public int getHeightMode();
+ method public int getItemCount();
+ method public int getItemViewType(android.view.View);
+ method public int getLayoutDirection();
+ method public int getLeftDecorationWidth(android.view.View);
+ method @Px public int getMinimumHeight();
+ method @Px public int getMinimumWidth();
+ method @Px public int getPaddingBottom();
+ method @Px public int getPaddingEnd();
+ method @Px public int getPaddingLeft();
+ method @Px public int getPaddingRight();
+ method @Px public int getPaddingStart();
+ method @Px public int getPaddingTop();
+ method public int getPosition(android.view.View);
+ method public static androidx.recyclerview.widget.RecyclerView.LayoutManager.Properties! getProperties(android.content.Context, android.util.AttributeSet?, int, int);
+ method public int getRightDecorationWidth(android.view.View);
+ method public int getRowCountForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getSelectionModeForAccessibility(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public int getTopDecorationHeight(android.view.View);
+ method public void getTransformedBoundingBox(android.view.View, boolean, android.graphics.Rect);
+ method @Px public int getWidth();
+ method public int getWidthMode();
+ method public boolean hasFocus();
+ method public void ignoreView(android.view.View);
+ method public boolean isAttachedToWindow();
+ method public boolean isAutoMeasureEnabled();
+ method public boolean isFocused();
+ method public final boolean isItemPrefetchEnabled();
+ method public boolean isLayoutHierarchical(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public boolean isMeasurementCacheEnabled();
+ method public boolean isSmoothScrolling();
+ method public boolean isViewPartiallyVisible(android.view.View, boolean, boolean);
+ method public void layoutDecorated(android.view.View, int, int, int, int);
+ method public void layoutDecoratedWithMargins(android.view.View, int, int, int, int);
+ method public void measureChild(android.view.View, int, int);
+ method public void measureChildWithMargins(android.view.View, int, int);
+ method public void moveView(int, int);
+ method public void offsetChildrenHorizontal(@Px int);
+ method public void offsetChildrenVertical(@Px int);
+ method public void onAdapterChanged(androidx.recyclerview.widget.RecyclerView.Adapter?, androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public boolean onAddFocusables(androidx.recyclerview.widget.RecyclerView, java.util.ArrayList<android.view.View>, int, int);
+ method @CallSuper public void onAttachedToWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @Deprecated public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!);
+ method @CallSuper public void onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.Recycler!);
+ method public android.view.View? onFocusSearchFailed(android.view.View, int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State);
+ method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityEvent(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onInitializeAccessibilityNodeInfoForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, androidx.core.view.accessibility.AccessibilityNodeInfoCompat);
+ method public android.view.View? onInterceptFocusSearch(android.view.View, int);
+ method public void onItemsAdded(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsChanged(androidx.recyclerview.widget.RecyclerView);
+ method public void onItemsMoved(androidx.recyclerview.widget.RecyclerView, int, int, int);
+ method public void onItemsRemoved(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(androidx.recyclerview.widget.RecyclerView, int, int, Object?);
+ method public void onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State!);
+ method public void onMeasure(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, int);
+ method @Deprecated public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, android.view.View, android.view.View?);
+ method public boolean onRequestChildFocus(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, android.view.View, android.view.View?);
+ method public void onRestoreInstanceState(android.os.Parcelable!);
+ method public android.os.Parcelable? onSaveInstanceState();
+ method public void onScrollStateChanged(int);
+ method public boolean performAccessibilityAction(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, int, android.os.Bundle?);
+ method public boolean performAccessibilityActionForItem(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State, android.view.View, int, android.os.Bundle?);
+ method public void postOnAnimation(Runnable!);
+ method public void removeAllViews();
+ method public void removeAndRecycleAllViews(androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleView(android.view.View, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleViewAt(int, androidx.recyclerview.widget.RecyclerView.Recycler);
+ method public boolean removeCallbacks(Runnable!);
+ method public void removeDetachedView(android.view.View);
+ method public void removeView(android.view.View!);
+ method public void removeViewAt(int);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean);
+ method public boolean requestChildRectangleOnScreen(androidx.recyclerview.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean, boolean);
+ method public void requestLayout();
+ method public void requestSimpleAnimationsInNextLayout();
+ method public int scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method public void scrollToPosition(int);
+ method public int scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler!, androidx.recyclerview.widget.RecyclerView.State!);
+ method @Deprecated public void setAutoMeasureEnabled(boolean);
+ method public final void setItemPrefetchEnabled(boolean);
+ method public void setMeasuredDimension(android.graphics.Rect!, int, int);
+ method public void setMeasuredDimension(int, int);
+ method public void setMeasurementCacheEnabled(boolean);
+ method public void smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView!, androidx.recyclerview.widget.RecyclerView.State!, int);
+ method public void startSmoothScroll(androidx.recyclerview.widget.RecyclerView.SmoothScroller!);
+ method public void stopIgnoringView(android.view.View);
+ method public boolean supportsPredictiveItemAnimations();
+ }
+
+ public static interface RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+ method public void addPosition(int, int);
+ }
+
+ public static class RecyclerView.LayoutManager.Properties {
+ ctor public RecyclerView.LayoutManager.Properties();
+ field public int orientation;
+ field public boolean reverseLayout;
+ field public int spanCount;
+ field public boolean stackFromEnd;
+ }
+
+ public static class RecyclerView.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public RecyclerView.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public RecyclerView.LayoutParams(int, int);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public RecyclerView.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public int getViewAdapterPosition();
+ method public int getViewLayoutPosition();
+ method @Deprecated public int getViewPosition();
+ method public boolean isItemChanged();
+ method public boolean isItemRemoved();
+ method public boolean isViewInvalid();
+ method public boolean viewNeedsUpdate();
+ }
+
+ public static interface RecyclerView.OnChildAttachStateChangeListener {
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ }
+
+ public abstract static class RecyclerView.OnFlingListener {
+ ctor public RecyclerView.OnFlingListener();
+ method public abstract boolean onFling(int, int);
+ }
+
+ public static interface RecyclerView.OnItemTouchListener {
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.OnScrollListener {
+ ctor public RecyclerView.OnScrollListener();
+ method public void onScrollStateChanged(androidx.recyclerview.widget.RecyclerView, int);
+ method public void onScrolled(androidx.recyclerview.widget.RecyclerView, int, int);
+ }
+
+ public static class RecyclerView.RecycledViewPool {
+ ctor public RecyclerView.RecycledViewPool();
+ method public void clear();
+ method public androidx.recyclerview.widget.RecyclerView.ViewHolder? getRecycledView(int);
+ method public int getRecycledViewCount(int);
+ method public void putRecycledView(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setMaxRecycledViews(int, int);
+ }
+
+ public final class RecyclerView.Recycler {
+ ctor public RecyclerView.Recycler();
+ method public void bindViewToPosition(android.view.View, int);
+ method public void clear();
+ method public int convertPreLayoutPositionToPostLayout(int);
+ method public java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder> getScrapList();
+ method public android.view.View getViewForPosition(int);
+ method public void recycleView(android.view.View);
+ method public void setViewCacheSize(int);
+ }
+
+ public static interface RecyclerView.RecyclerListener {
+ method public void onViewRecycled(androidx.recyclerview.widget.RecyclerView.ViewHolder);
+ }
+
+ public static class RecyclerView.SimpleOnItemTouchListener implements androidx.recyclerview.widget.RecyclerView.OnItemTouchListener {
+ ctor public RecyclerView.SimpleOnItemTouchListener();
+ method public boolean onInterceptTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(androidx.recyclerview.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public abstract static class RecyclerView.SmoothScroller {
+ ctor public RecyclerView.SmoothScroller();
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ method public android.view.View! findViewByPosition(int);
+ method public int getChildCount();
+ method public int getChildPosition(android.view.View!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutManager? getLayoutManager();
+ method public int getTargetPosition();
+ method @Deprecated public void instantScrollToPosition(int);
+ method public boolean isPendingInitialRun();
+ method public boolean isRunning();
+ method protected void normalize(android.graphics.PointF);
+ method protected void onChildAttachedToWindow(android.view.View!);
+ method protected abstract void onSeekTargetStep(@Px int, @Px int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method protected abstract void onStart();
+ method protected abstract void onStop();
+ method protected abstract void onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action);
+ method public void setTargetPosition(int);
+ method protected final void stop();
+ }
+
+ public static class RecyclerView.SmoothScroller.Action {
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int);
+ ctor public RecyclerView.SmoothScroller.Action(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ method public int getDuration();
+ method @Px public int getDx();
+ method @Px public int getDy();
+ method public android.view.animation.Interpolator? getInterpolator();
+ method public void jumpTo(int);
+ method public void setDuration(int);
+ method public void setDx(@Px int);
+ method public void setDy(@Px int);
+ method public void setInterpolator(android.view.animation.Interpolator?);
+ method public void update(@Px int, @Px int, int, android.view.animation.Interpolator?);
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ }
+
+ public static interface RecyclerView.SmoothScroller.ScrollVectorProvider {
+ method public android.graphics.PointF? computeScrollVectorForPosition(int);
+ }
+
+ public static class RecyclerView.State {
+ ctor public RecyclerView.State();
+ method public boolean didStructureChange();
+ method public <T> T! get(int);
+ method public int getItemCount();
+ method public int getRemainingScrollHorizontal();
+ method public int getRemainingScrollVertical();
+ method public int getTargetScrollPosition();
+ method public boolean hasTargetScrollPosition();
+ method public boolean isMeasuring();
+ method public boolean isPreLayout();
+ method public void put(int, Object!);
+ method public void remove(int);
+ method public boolean willRunPredictiveAnimations();
+ method public boolean willRunSimpleAnimations();
+ }
+
+ public abstract static class RecyclerView.ViewCacheExtension {
+ ctor public RecyclerView.ViewCacheExtension();
+ method public abstract android.view.View? getViewForPositionAndType(androidx.recyclerview.widget.RecyclerView.Recycler, int, int);
+ }
+
+ public abstract static class RecyclerView.ViewHolder {
+ ctor public RecyclerView.ViewHolder(android.view.View);
+ method public final int getAdapterPosition();
+ method public final long getItemId();
+ method public final int getItemViewType();
+ method public final int getLayoutPosition();
+ method public final int getOldPosition();
+ method @Deprecated public final int getPosition();
+ method public final boolean isRecyclable();
+ method public final void setIsRecyclable(boolean);
+ field public final android.view.View itemView;
+ }
+
+ public class RecyclerViewAccessibilityDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate(androidx.recyclerview.widget.RecyclerView);
+ method public androidx.core.view.AccessibilityDelegateCompat getItemDelegate();
+ }
+
+ public static class RecyclerViewAccessibilityDelegate.ItemDelegate extends androidx.core.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate.ItemDelegate(androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate);
+ }
+
+ public abstract class SimpleItemAnimator extends androidx.recyclerview.widget.RecyclerView.ItemAnimator {
+ ctor public SimpleItemAnimator();
+ method public abstract boolean animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean animateAppearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder!, androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animateDisappearance(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo?);
+ method public abstract boolean animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder!, int, int, int, int);
+ method public boolean animatePersistence(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo, androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public final void dispatchMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public final void dispatchRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public boolean getSupportsChangeAnimations();
+ method public void onAddFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onAddStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onChangeFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onChangeStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!, boolean);
+ method public void onMoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onMoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void onRemoveStarting(androidx.recyclerview.widget.RecyclerView.ViewHolder!);
+ method public void setSupportsChangeAnimations(boolean);
+ }
+
+ public abstract class SnapHelper extends androidx.recyclerview.widget.RecyclerView.OnFlingListener {
+ ctor public SnapHelper();
+ method public void attachToRecyclerView(androidx.recyclerview.widget.RecyclerView?) throws java.lang.IllegalStateException;
+ method public abstract int[]? calculateDistanceToFinalSnap(androidx.recyclerview.widget.RecyclerView.LayoutManager, android.view.View);
+ method public int[]! calculateScrollDistance(int, int);
+ method protected androidx.recyclerview.widget.RecyclerView.SmoothScroller? createScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method @Deprecated protected androidx.recyclerview.widget.LinearSmoothScroller? createSnapScroller(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract android.view.View? findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager!);
+ method public abstract int findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager!, int, int);
+ method public boolean onFling(int, int);
+ }
+
+ public class SortedList<T> {
+ ctor public SortedList(Class<T>, androidx.recyclerview.widget.SortedList.Callback<T>);
+ ctor public SortedList(Class<T>, androidx.recyclerview.widget.SortedList.Callback<T>, int);
+ method public int add(T!);
+ method public void addAll(T[], boolean);
+ method public void addAll(T...);
+ method public void addAll(java.util.Collection<T>);
+ method public void beginBatchedUpdates();
+ method public void clear();
+ method public void endBatchedUpdates();
+ method public T! get(int) throws java.lang.IndexOutOfBoundsException;
+ method public int indexOf(T!);
+ method public void recalculatePositionOfItemAt(int);
+ method public boolean remove(T!);
+ method public T! removeItemAt(int);
+ method public void replaceAll(T[], boolean);
+ method public void replaceAll(T...);
+ method public void replaceAll(java.util.Collection<T>);
+ method public int size();
+ method public void updateItemAt(int, T!);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static class SortedList.BatchedCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2>!);
+ method public boolean areContentsTheSame(T2!, T2!);
+ method public boolean areItemsTheSame(T2!, T2!);
+ method public int compare(T2!, T2!);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public abstract static class SortedList.Callback<T2> implements java.util.Comparator<T2> androidx.recyclerview.widget.ListUpdateCallback {
+ ctor public SortedList.Callback();
+ method public abstract boolean areContentsTheSame(T2!, T2!);
+ method public abstract boolean areItemsTheSame(T2!, T2!);
+ method public abstract int compare(T2!, T2!);
+ method public Object? getChangePayload(T2!, T2!);
+ method public abstract void onChanged(int, int);
+ method public void onChanged(int, int, Object!);
+ }
+
+ public abstract class SortedListAdapterCallback<T2> extends androidx.recyclerview.widget.SortedList.Callback<T2> {
+ ctor public SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter!);
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public class StaggeredGridLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ ctor public StaggeredGridLayoutManager(android.content.Context!, android.util.AttributeSet!, int, int);
+ ctor public StaggeredGridLayoutManager(int, int);
+ method public android.graphics.PointF! computeScrollVectorForPosition(int);
+ method public int[]! findFirstCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findFirstVisibleItemPositions(int[]!);
+ method public int[]! findLastCompletelyVisibleItemPositions(int[]!);
+ method public int[]! findLastVisibleItemPositions(int[]!);
+ method public androidx.recyclerview.widget.RecyclerView.LayoutParams! generateDefaultLayoutParams();
+ method public int getGapStrategy();
+ method public int getOrientation();
+ method public boolean getReverseLayout();
+ method public int getSpanCount();
+ method public void invalidateSpanAssignments();
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setGapStrategy(int);
+ method public void setOrientation(int);
+ method public void setReverseLayout(boolean);
+ method public void setSpanCount(int);
+ field @Deprecated public static final int GAP_HANDLING_LAZY = 1; // 0x1
+ field public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; // 0x2
+ field public static final int GAP_HANDLING_NONE = 0; // 0x0
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class StaggeredGridLayoutManager.LayoutParams extends androidx.recyclerview.widget.RecyclerView.LayoutParams {
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.content.Context!, android.util.AttributeSet!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(int, int);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams!);
+ ctor public StaggeredGridLayoutManager.LayoutParams(androidx.recyclerview.widget.RecyclerView.LayoutParams!);
+ method public final int getSpanIndex();
+ method public boolean isFullSpan();
+ method public void setFullSpan(boolean);
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+}
+
diff --git a/recyclerview/recyclerview/api/res-1.1.0-alpha04.txt b/recyclerview/recyclerview/api/res-1.1.0-alpha04.txt
new file mode 100644
index 0000000..475bfc43
--- /dev/null
+++ b/recyclerview/recyclerview/api/res-1.1.0-alpha04.txt
@@ -0,0 +1,9 @@
+attr fastScrollEnabled
+attr fastScrollHorizontalThumbDrawable
+attr fastScrollHorizontalTrackDrawable
+attr fastScrollVerticalThumbDrawable
+attr fastScrollVerticalTrackDrawable
+attr layoutManager
+attr reverseLayout
+attr spanCount
+attr stackFromEnd
diff --git a/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.ignore b/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.ignore
new file mode 100644
index 0000000..7be19d9
--- /dev/null
+++ b/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+InvalidNullConversion: androidx.recyclerview.widget.AsyncDifferConfig#getMainThreadExecutor():
+ Attempted to change method return from @NonNull to @Nullable: incompatible change for method androidx.recyclerview.widget.AsyncDifferConfig.getMainThreadExecutor()
+
+
diff --git a/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.txt b/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.txt
new file mode 100644
index 0000000..c379bc1
--- /dev/null
+++ b/recyclerview/recyclerview/api/restricted_1.1.0-alpha04.txt
@@ -0,0 +1,40 @@
+// Signature format: 3.0
+package androidx.recyclerview.widget {
+
+ public final class AsyncDifferConfig<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.concurrent.Executor? getMainThreadExecutor();
+ }
+
+ public static final class AsyncDifferConfig.Builder<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.recyclerview.widget.AsyncDifferConfig.Builder<T> setMainThreadExecutor(java.util.concurrent.Executor!);
+ }
+
+ public class LinearLayoutManager extends androidx.recyclerview.widget.RecyclerView.LayoutManager implements androidx.recyclerview.widget.ItemTouchHelper.ViewDropHandler androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void prepareForDrop(android.view.View, android.view.View, int, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class LinearLayoutManager.SavedState implements android.os.Parcelable {
+ ctor public LinearLayoutManager.SavedState();
+ ctor public LinearLayoutManager.SavedState(androidx.recyclerview.widget.LinearLayoutManager.SavedState!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<androidx.recyclerview.widget.LinearLayoutManager.SavedState>! CREATOR;
+ }
+
+ @IntDef({androidx.recyclerview.widget.RecyclerView.HORIZONTAL, androidx.recyclerview.widget.RecyclerView.VERTICAL}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface RecyclerView.Orientation {
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class RecyclerView.SavedState extends androidx.customview.view.AbsSavedState {
+ field public static final android.os.Parcelable.Creator<androidx.recyclerview.widget.RecyclerView.SavedState>! CREATOR;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class StaggeredGridLayoutManager.SavedState implements android.os.Parcelable {
+ ctor public StaggeredGridLayoutManager.SavedState();
+ ctor public StaggeredGridLayoutManager.SavedState(androidx.recyclerview.widget.StaggeredGridLayoutManager.SavedState!);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel!, int);
+ field public static final android.os.Parcelable.Creator<androidx.recyclerview.widget.StaggeredGridLayoutManager.SavedState>! CREATOR;
+ }
+
+}
+
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.java
index fd8c464..c5f6e09 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewOnItemTouchListenerTest.java
@@ -18,17 +18,24 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -44,7 +51,7 @@
private FrameLayout mParent;
private RecyclerView mRecyclerView;
- private View mChildView;
+ private MyView mChildView;
private MyOnItemTouchListener mOnItemTouchListener;
private MotionEvent mActionDown;
private MotionEvent mActionMove1;
@@ -56,7 +63,7 @@
public void setup() {
Context context = ApplicationProvider.getApplicationContext();
- mChildView = new View(context);
+ mChildView = spy(new MyView(context));
mChildView.setMinimumWidth(1000);
mChildView.setMinimumHeight(1000);
@@ -165,6 +172,48 @@
}
@Test
+ public void listenerInterceptsDown_childOnTouchNotCalled() {
+ mChildView.setClickable(true);
+ when(mOnItemTouchListener
+ .onInterceptTouchEvent(mRecyclerView, mActionDown))
+ .thenReturn(true);
+
+ mParent.dispatchTouchEvent(mActionDown);
+ mParent.dispatchTouchEvent(mActionUp);
+
+ verify(mChildView, never()).onTouchEvent(any(MotionEvent.class));
+ }
+
+ @Test
+ public void listenerInterceptsMove_childOnTouchCalledWithCorrectEvents() {
+ mChildView.setClickable(true);
+ when(mOnItemTouchListener
+ .onInterceptTouchEvent(mRecyclerView, mActionMove1))
+ .thenReturn(true);
+
+ mParent.dispatchTouchEvent(mActionDown);
+ mParent.dispatchTouchEvent(mActionMove1);
+
+ verify(mChildView).onTouchEvent(mActionDown);
+ assertThat(mChildView.mLastAction, is(MotionEvent.ACTION_CANCEL));
+ }
+
+ @Test
+ public void listenerInterceptsUp_childOnTouchCalledWithCorrectEvents() {
+ mChildView.setClickable(true);
+ when(mOnItemTouchListener
+ .onInterceptTouchEvent(mRecyclerView, mActionUp))
+ .thenReturn(true);
+
+ mParent.dispatchTouchEvent(mActionDown);
+ mParent.dispatchTouchEvent(mActionUp);
+
+ verify(mChildView, times(2)).onTouchEvent(any(MotionEvent.class));
+ verify(mChildView).onTouchEvent(mActionDown);
+ assertThat(mChildView.mLastAction, is(MotionEvent.ACTION_CANCEL));
+ }
+
+ @Test
public void listenerInterceptsThenParentIntercepts_correctListenerCalls() {
when(mOnItemTouchListener
.onInterceptTouchEvent(mRecyclerView, mActionMove1))
@@ -202,13 +251,14 @@
}
@Override
- public MyViewHolder onCreateViewHolder(ViewGroup parent,
+ @NonNull
+ public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
int viewType) {
return new MyViewHolder(mView);
}
@Override
- public void onBindViewHolder(MyViewHolder holder,
+ public void onBindViewHolder(@NonNull MyViewHolder holder,
int position) {
}
@@ -229,12 +279,12 @@
int mLastAction = -1;
@Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
+ public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
return false;
}
@Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {
+ public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
mLastAction = e.getAction();
}
@@ -243,4 +293,27 @@
}
}
+
+ public class MyView extends View {
+
+ public int mLastAction = -1;
+
+ public MyView(Context context) {
+ super(context);
+ }
+
+ public MyView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mLastAction = event.getAction();
+ return super.onTouchEvent(event);
+ }
+ }
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
index 0e5bda8..9163de3 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/LinearLayoutManager.java
@@ -170,7 +170,7 @@
* Constructor used when layout manager is set in XML by RecyclerView attribute
* "layoutManager". Defaults to vertical orientation.
*
- * {@link androidx.recyclerview.R.attr#android_orientation}
+ * {@link android.R.attr#orientation}
* {@link androidx.recyclerview.R.attr#reverseLayout}
* {@link androidx.recyclerview.R.attr#stackFromEnd}
*/
@@ -1314,20 +1314,20 @@
ensureLayoutState();
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
- final int absDy = Math.abs(delta);
- updateLayoutState(layoutDirection, absDy, true, state);
+ final int absDelta = Math.abs(delta);
+ updateLayoutState(layoutDirection, absDelta, true, state);
collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
}
- int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
- if (getChildCount() == 0 || dy == 0) {
+ int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (getChildCount() == 0 || delta == 0) {
return 0;
}
ensureLayoutState();
mLayoutState.mRecycle = true;
- final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
- final int absDy = Math.abs(dy);
- updateLayoutState(layoutDirection, absDy, true, state);
+ final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+ final int absDelta = Math.abs(delta);
+ updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
@@ -1336,10 +1336,10 @@
}
return 0;
}
- final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+ final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
- Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+ Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
@@ -1529,7 +1529,7 @@
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
- if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
+ if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
@@ -1979,7 +1979,6 @@
return null;
}
ensureLayoutState();
- ensureLayoutState();
final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
updateLayoutState(layoutDir, maxScroll, false, state);
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 1ff33d9..5cf308d 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -3024,10 +3024,10 @@
// 3. All other MotionEvents should be passed to either onInterceptTouchEvent or
// onTouchEvent, not both.
- // Side Note: If we are to truly mimic how MotionEvents work in the view system, for every
- // MotionEvent, any OnItemTouchListener that is before the intercepting OnItemTouchEvent
- // should still have a chance to intercept, and if it does, the previously intercepting
- // OnItemTouchEvent should get an ACTION_CANCEL event.
+ // Side Note: We don't currently perfectly mimic how MotionEvents work in the view system.
+ // If we were to do so, for every MotionEvent, any OnItemTouchListener that is before the
+ // intercepting OnItemTouchEvent should still have a chance to intercept, and if it does,
+ // the previously intercepting OnItemTouchEvent should get an ACTION_CANCEL event.
if (mInterceptingOnItemTouchListener == null) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
@@ -3047,10 +3047,12 @@
/**
* Looks for an OnItemTouchListener that wants to intercept.
*
- * <p>Passes the MotionEvent to all registered OnItemTouchListeners one at a time. If one wants
- * to intercept and the action is not ACTION_UP or ACTION_CANCEL, saves the intercepting
- * OnItemTouchListener and immediately returns true. If none want to intercept
- * or the action is ACTION_UP or ACTION_CANCEL, returns false.
+ * <p>Calls {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} on each
+ * of the registered {@link OnItemTouchListener}s, passing in the
+ * MotionEvent. If one returns true and the action is not ACTION_CANCEL, saves the intercepting
+ * OnItemTouchListener to be called for future {@link RecyclerView#onTouchEvent(MotionEvent)}
+ * and immediately returns true. If none want to intercept or the action is ACTION_CANCEL,
+ * returns false.
*
* @param e The MotionEvent
* @return true if an OnItemTouchListener is saved as intercepting.
@@ -3060,8 +3062,7 @@
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
- if (listener.onInterceptTouchEvent(this, e)
- && action != MotionEvent.ACTION_UP && action != MotionEvent.ACTION_CANCEL) {
+ if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mInterceptingOnItemTouchListener = listener;
return true;
}
@@ -10491,7 +10492,7 @@
/**
* Parse the xml attributes to get the most common properties used by layout managers.
*
- * {@link androidx.recyclerview.R.attr#android_orientation}
+ * {@link android.R.attr#orientation}
* {@link androidx.recyclerview.R.attr#spanCount}
* {@link androidx.recyclerview.R.attr#reverseLayout}
* {@link androidx.recyclerview.R.attr#stackFromEnd}
@@ -10550,7 +10551,7 @@
* Some general properties that a LayoutManager may want to use.
*/
public static class Properties {
- /** {@link androidx.recyclerview.R.attr#android_orientation} */
+ /** {@link android.R.attr#orientation} */
public int orientation;
/** {@link androidx.recyclerview.R.attr#spanCount} */
public int spanCount;
diff --git a/recyclerview/selection/api/1.1.0-alpha04.txt b/recyclerview/selection/api/1.1.0-alpha04.txt
new file mode 100644
index 0000000..1b682ce
--- /dev/null
+++ b/recyclerview/selection/api/1.1.0-alpha04.txt
@@ -0,0 +1,158 @@
+// Signature format: 3.0
+package androidx.recyclerview.selection {
+
+ public abstract class BandPredicate {
+ ctor public BandPredicate();
+ method public abstract boolean canInitiate(android.view.MotionEvent!);
+ }
+
+ public static final class BandPredicate.EmptyArea extends androidx.recyclerview.selection.BandPredicate {
+ ctor public BandPredicate.EmptyArea(androidx.recyclerview.widget.RecyclerView);
+ method public boolean canInitiate(android.view.MotionEvent);
+ }
+
+ public static final class BandPredicate.NonDraggableArea extends androidx.recyclerview.selection.BandPredicate {
+ ctor public BandPredicate.NonDraggableArea(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.selection.ItemDetailsLookup);
+ method public boolean canInitiate(android.view.MotionEvent);
+ }
+
+ public abstract class FocusDelegate<K> {
+ ctor public FocusDelegate();
+ method public abstract void clearFocus();
+ method public abstract void focusItem(androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails<K>);
+ method public abstract int getFocusedPosition();
+ method public abstract boolean hasFocusedItem();
+ }
+
+ public abstract class ItemDetailsLookup<K> {
+ ctor public ItemDetailsLookup();
+ method public abstract androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails<K>? getItemDetails(android.view.MotionEvent);
+ }
+
+ public abstract static class ItemDetailsLookup.ItemDetails<K> {
+ ctor public ItemDetailsLookup.ItemDetails();
+ method public abstract int getPosition();
+ method public abstract K? getSelectionKey();
+ method public boolean hasSelectionKey();
+ method public boolean inDragRegion(android.view.MotionEvent);
+ method public boolean inSelectionHotspot(android.view.MotionEvent);
+ }
+
+ public abstract class ItemKeyProvider<K> {
+ ctor protected ItemKeyProvider(@androidx.recyclerview.selection.ItemKeyProvider.Scope int);
+ method public abstract K? getKey(int);
+ method public abstract int getPosition(K);
+ field public static final int SCOPE_CACHED = 1; // 0x1
+ field public static final int SCOPE_MAPPED = 0; // 0x0
+ }
+
+ @IntDef({androidx.recyclerview.selection.ItemKeyProvider.SCOPE_MAPPED, androidx.recyclerview.selection.ItemKeyProvider.SCOPE_CACHED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ItemKeyProvider.Scope {
+ }
+
+ public final class MutableSelection<K> extends androidx.recyclerview.selection.Selection<K> {
+ ctor public MutableSelection();
+ method public boolean add(K);
+ method public void clear();
+ method public void copyFrom(androidx.recyclerview.selection.Selection<K>);
+ method public boolean remove(K);
+ }
+
+ public interface OnContextClickListener {
+ method public boolean onContextClick(android.view.MotionEvent);
+ }
+
+ public interface OnDragInitiatedListener {
+ method public boolean onDragInitiated(android.view.MotionEvent);
+ }
+
+ public interface OnItemActivatedListener<K> {
+ method public boolean onItemActivated(androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails<K>, android.view.MotionEvent);
+ }
+
+ public final class OperationMonitor {
+ ctor public OperationMonitor();
+ method public void addListener(androidx.recyclerview.selection.OperationMonitor.OnChangeListener);
+ method public boolean isStarted();
+ method public void removeListener(androidx.recyclerview.selection.OperationMonitor.OnChangeListener);
+ }
+
+ public static interface OperationMonitor.OnChangeListener {
+ method public void onChanged();
+ }
+
+ public class Selection<K> implements java.lang.Iterable<K> {
+ method public boolean contains(K?);
+ method public boolean isEmpty();
+ method public java.util.Iterator<K>! iterator();
+ method public int size();
+ }
+
+ public final class SelectionPredicates {
+ method public static <K> androidx.recyclerview.selection.SelectionTracker.SelectionPredicate<K>! createSelectAnything();
+ method public static <K> androidx.recyclerview.selection.SelectionTracker.SelectionPredicate<K>! createSelectSingleAnything();
+ }
+
+ public abstract class SelectionTracker<K> {
+ ctor public SelectionTracker();
+ method public abstract void addObserver(androidx.recyclerview.selection.SelectionTracker.SelectionObserver!);
+ method public abstract boolean clearSelection();
+ method public abstract void copySelection(androidx.recyclerview.selection.MutableSelection<K>);
+ method public abstract boolean deselect(K);
+ method public abstract androidx.recyclerview.selection.Selection<K>! getSelection();
+ method public abstract boolean hasSelection();
+ method public abstract boolean isSelected(K?);
+ method public abstract void onRestoreInstanceState(android.os.Bundle?);
+ method public abstract void onSaveInstanceState(android.os.Bundle);
+ method protected abstract void restoreSelection(androidx.recyclerview.selection.Selection<K>);
+ method public abstract boolean select(K);
+ method public abstract boolean setItemsSelected(Iterable<K>, boolean);
+ field public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";
+ }
+
+ public static final class SelectionTracker.Builder<K> {
+ ctor public SelectionTracker.Builder(String, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.selection.ItemKeyProvider<K>, androidx.recyclerview.selection.ItemDetailsLookup<K>, androidx.recyclerview.selection.StorageStrategy<K>);
+ method public androidx.recyclerview.selection.SelectionTracker<K>! build();
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withBandOverlay(@DrawableRes int);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withBandPredicate(androidx.recyclerview.selection.BandPredicate);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withFocusDelegate(androidx.recyclerview.selection.FocusDelegate<K>);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withGestureTooltypes(int...!);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withOnContextClickListener(androidx.recyclerview.selection.OnContextClickListener);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withOnDragInitiatedListener(androidx.recyclerview.selection.OnDragInitiatedListener);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withOnItemActivatedListener(androidx.recyclerview.selection.OnItemActivatedListener<K>);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withOperationMonitor(androidx.recyclerview.selection.OperationMonitor);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withPointerTooltypes(int...!);
+ method public androidx.recyclerview.selection.SelectionTracker.Builder<K>! withSelectionPredicate(androidx.recyclerview.selection.SelectionTracker.SelectionPredicate<K>);
+ }
+
+ public abstract static class SelectionTracker.SelectionObserver<K> {
+ ctor public SelectionTracker.SelectionObserver();
+ method public void onItemStateChanged(K, boolean);
+ method public void onSelectionChanged();
+ method public void onSelectionRefresh();
+ method public void onSelectionRestored();
+ }
+
+ public abstract static class SelectionTracker.SelectionPredicate<K> {
+ ctor public SelectionTracker.SelectionPredicate();
+ method public abstract boolean canSelectMultiple();
+ method public abstract boolean canSetStateAtPosition(int, boolean);
+ method public abstract boolean canSetStateForKey(K, boolean);
+ }
+
+ public final class StableIdKeyProvider extends androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long> {
+ ctor public StableIdKeyProvider(androidx.recyclerview.widget.RecyclerView);
+ method public Long? getKey(int);
+ method public int getPosition(Long);
+ }
+
+ public abstract class StorageStrategy<K> {
+ ctor public StorageStrategy(Class<K>);
+ method public abstract android.os.Bundle asBundle(androidx.recyclerview.selection.Selection<K>);
+ method public abstract androidx.recyclerview.selection.Selection<K>? asSelection(android.os.Bundle);
+ method public static androidx.recyclerview.selection.StorageStrategy<java.lang.Long>! createLongStorage();
+ method public static <K extends android.os.Parcelable> androidx.recyclerview.selection.StorageStrategy<K>! createParcelableStorage(Class<K>!);
+ method public static androidx.recyclerview.selection.StorageStrategy<java.lang.String>! createStringStorage();
+ }
+
+}
+
diff --git a/recyclerview/selection/api/res-1.1.0-alpha04.txt b/recyclerview/selection/api/res-1.1.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/recyclerview/selection/api/res-1.1.0-alpha04.txt
diff --git a/recyclerview/selection/api/restricted_1.1.0-alpha04.ignore b/recyclerview/selection/api/restricted_1.1.0-alpha04.ignore
new file mode 100644
index 0000000..d242801
--- /dev/null
+++ b/recyclerview/selection/api/restricted_1.1.0-alpha04.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedClass: androidx.recyclerview.selection.EventBridge:
+ Removed class androidx.recyclerview.selection.EventBridge
+
+
diff --git a/recyclerview/selection/api/restricted_1.1.0-alpha04.txt b/recyclerview/selection/api/restricted_1.1.0-alpha04.txt
new file mode 100644
index 0000000..3b7463f
--- /dev/null
+++ b/recyclerview/selection/api/restricted_1.1.0-alpha04.txt
@@ -0,0 +1,54 @@
+// Signature format: 3.0
+package androidx.recyclerview.selection {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class AutoScroller {
+ ctor public AutoScroller();
+ method public abstract void reset();
+ method public abstract void scroll(android.graphics.Point);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DefaultSelectionTracker<K> extends androidx.recyclerview.selection.SelectionTracker<K> {
+ ctor public DefaultSelectionTracker(String, androidx.recyclerview.selection.ItemKeyProvider, androidx.recyclerview.selection.SelectionTracker.SelectionPredicate, androidx.recyclerview.selection.StorageStrategy<K>);
+ method public void addObserver(androidx.recyclerview.selection.SelectionTracker.SelectionObserver);
+ method public void anchorRange(int);
+ method public void clearProvisionalSelection();
+ method public boolean clearSelection();
+ method public void copySelection(androidx.recyclerview.selection.MutableSelection);
+ method public boolean deselect(K);
+ method public void endRange();
+ method public void extendProvisionalRange(int);
+ method public void extendRange(int);
+ method protected androidx.recyclerview.widget.RecyclerView.AdapterDataObserver! getAdapterDataObserver();
+ method public androidx.recyclerview.selection.Selection! getSelection();
+ method public boolean hasSelection();
+ method public boolean isRangeActive();
+ method public boolean isSelected(K?);
+ method public void mergeProvisionalSelection();
+ method public final void onRestoreInstanceState(android.os.Bundle?);
+ method public final void onSaveInstanceState(android.os.Bundle);
+ method protected void restoreSelection(androidx.recyclerview.selection.Selection);
+ method public boolean select(K);
+ method public boolean setItemsSelected(Iterable<K>, boolean);
+ method public void setProvisionalSelection(java.util.Set<K>);
+ method public void startRange(int);
+ }
+
+ public abstract class ItemDetailsLookup<K> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected boolean overItemWithSelectionKey(android.view.MotionEvent);
+ }
+
+ public abstract class SelectionTracker<K> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract void anchorRange(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected abstract void clearProvisionalSelection();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract void endRange();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected abstract void extendProvisionalRange(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract void extendRange(int);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected abstract androidx.recyclerview.widget.RecyclerView.AdapterDataObserver! getAdapterDataObserver();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract boolean isRangeActive();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected abstract void mergeProvisionalSelection();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) protected abstract void setProvisionalSelection(java.util.Set<K>);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract void startRange(int);
+ }
+
+}
+
diff --git a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
index 45df1a8..5a85072 100644
--- a/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
+++ b/room/benchmark/src/androidTest/java/androidx/room/benchmark/InvalidationTrackerBenchmark.kt
@@ -16,6 +16,7 @@
package androidx.room.benchmark
+import android.os.Build
import androidx.benchmark.BenchmarkRule
import androidx.room.Dao
import androidx.room.Database
@@ -28,6 +29,7 @@
import androidx.room.RoomDatabase
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -38,6 +40,7 @@
@LargeTest
@RunWith(Parameterized::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) // TODO Fix me for API 15 - b/120098504
class InvalidationTrackerBenchmark(private val sampleSize: Int, private val mode: Mode) {
@get:Rule
@@ -66,11 +69,9 @@
}
db.invalidationTracker.addObserver(observer)
- benchmarkRule.state.pauseTiming()
val users = List(sampleSize) { User(it, "name$it") }
- benchmarkRule.state.resumeTiming()
- while (benchmarkRule.state.keepRunning()) {
+ benchmarkRule.keepRunning {
runMeasured(pauseTiming = mode == Mode.MEASURE_DELETE) {
// Insert the sample size
db.runInTransaction {
@@ -142,4 +143,4 @@
MEASURE_INSERT,
MEASURE_DELETE,
MEASURE_INSERT_AND_DELETE
-}
\ No newline at end of file
+}
diff --git a/room/common-java8/build.gradle b/room/common-java8/build.gradle
new file mode 100644
index 0000000..4bd7f60
--- /dev/null
+++ b/room/common-java8/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.SupportLibraryExtension;
+
+plugins {
+ id("SupportJavaLibraryPlugin")
+}
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
+
+dependencies {
+ compile(ANDROIDX_ANNOTATION)
+}
+
+supportLibrary {
+ name = "Android Room-Common-Java8"
+ publish = false
+ mavenVersion = LibraryVersions.ROOM
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2019"
+ description = "Android Room-Common-Java8"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
\ No newline at end of file
diff --git a/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java b/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java
new file mode 100644
index 0000000..21497d2
--- /dev/null
+++ b/room/common-java8/src/main/java/androidx/room/util/SneakyThrow.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Java 8 Sneaky Throw technique.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+public class SneakyThrow {
+
+ /**
+ * Re-throws a checked exception as if it was a runtime exception without wrapping it.
+ *
+ * @param e the exception to re-throw.
+ */
+ public static void reThrow(@NonNull Exception e) {
+ sneakyThrow(e);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> void sneakyThrow(@NonNull Throwable e) throws E {
+ throw (E) e;
+ }
+
+ private SneakyThrow() {
+
+ }
+}
diff --git a/room/common/api/2.1.0-alpha06.txt b/room/common/api/2.1.0-alpha06.txt
new file mode 100644
index 0000000..67cc6c6
--- /dev/null
+++ b/room/common/api/2.1.0-alpha06.txt
@@ -0,0 +1,185 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ColumnInfo {
+ method @androidx.room.ColumnInfo.Collate public abstract int collate() default androidx.room.ColumnInfo.UNSPECIFIED;
+ method public abstract boolean index() default false;
+ method public abstract String name() default androidx.room.ColumnInfo.INHERIT_FIELD_NAME;
+ method @androidx.room.ColumnInfo.SQLiteTypeAffinity public abstract int typeAffinity() default androidx.room.ColumnInfo.UNDEFINED;
+ field public static final int BINARY = 2; // 0x2
+ field public static final int BLOB = 5; // 0x5
+ field public static final String INHERIT_FIELD_NAME = "[field-name]";
+ field public static final int INTEGER = 3; // 0x3
+ field @RequiresApi(21) public static final int LOCALIZED = 5; // 0x5
+ field public static final int NOCASE = 3; // 0x3
+ field public static final int REAL = 4; // 0x4
+ field public static final int RTRIM = 4; // 0x4
+ field public static final int TEXT = 2; // 0x2
+ field public static final int UNDEFINED = 1; // 0x1
+ field @RequiresApi(21) public static final int UNICODE = 6; // 0x6
+ field public static final int UNSPECIFIED = 1; // 0x1
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNSPECIFIED, androidx.room.ColumnInfo.BINARY, androidx.room.ColumnInfo.NOCASE, androidx.room.ColumnInfo.RTRIM, androidx.room.ColumnInfo.LOCALIZED, androidx.room.ColumnInfo.UNICODE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.Collate {
+ }
+
+ @IntDef({androidx.room.ColumnInfo.UNDEFINED, androidx.room.ColumnInfo.TEXT, androidx.room.ColumnInfo.INTEGER, androidx.room.ColumnInfo.REAL, androidx.room.ColumnInfo.BLOB}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface ColumnInfo.SQLiteTypeAffinity {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Dao {
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Database {
+ method public abstract Class[] entities();
+ method public abstract boolean exportSchema() default true;
+ method public abstract int version();
+ method public abstract Class[] views() default {};
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface DatabaseView {
+ method public abstract String value() default "";
+ method public abstract String viewName() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Delete {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Embedded {
+ method public abstract String prefix() default "";
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Entity {
+ method public abstract androidx.room.ForeignKey[] foreignKeys() default {};
+ method public abstract String[] ignoredColumns() default {};
+ method public abstract androidx.room.Index[] indices() default {};
+ method public abstract boolean inheritSuperIndices() default false;
+ method public abstract String[] primaryKeys() default {};
+ method public abstract String tableName() default "";
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ForeignKey {
+ method public abstract String[] childColumns();
+ method public abstract boolean deferred() default false;
+ method public abstract Class entity();
+ method @androidx.room.ForeignKey.Action public abstract int onDelete() default androidx.room.ForeignKey.NO_ACTION;
+ method @androidx.room.ForeignKey.Action public abstract int onUpdate() default androidx.room.ForeignKey.NO_ACTION;
+ method public abstract String[] parentColumns();
+ field public static final int CASCADE = 5; // 0x5
+ field public static final int NO_ACTION = 1; // 0x1
+ field public static final int RESTRICT = 2; // 0x2
+ field public static final int SET_DEFAULT = 4; // 0x4
+ field public static final int SET_NULL = 3; // 0x3
+ }
+
+ @IntDef({androidx.room.ForeignKey.NO_ACTION, androidx.room.ForeignKey.RESTRICT, androidx.room.ForeignKey.SET_NULL, androidx.room.ForeignKey.SET_DEFAULT, androidx.room.ForeignKey.CASCADE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface ForeignKey.Action {
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts3 {
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ @RequiresApi(16) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE) public @interface Fts4 {
+ method public abstract Class contentEntity() default java.lang.Object.class;
+ method public abstract String languageId() default "";
+ method public abstract androidx.room.FtsOptions.MatchInfo matchInfo() default androidx.room.FtsOptions.MatchInfo.FTS4;
+ method public abstract String[] notIndexed() default {};
+ method public abstract androidx.room.FtsOptions.Order order() default androidx.room.FtsOptions.Order.ASC;
+ method public abstract int[] prefix() default {};
+ method public abstract String tokenizer() default androidx.room.FtsOptions.TOKENIZER_SIMPLE;
+ method public abstract String[] tokenizerArgs() default {};
+ }
+
+ public class FtsOptions {
+ field public static final String TOKENIZER_ICU = "icu";
+ field public static final String TOKENIZER_PORTER = "porter";
+ field public static final String TOKENIZER_SIMPLE = "simple";
+ field @RequiresApi(21) public static final String TOKENIZER_UNICODE61 = "unicode61";
+ }
+
+ public enum FtsOptions.MatchInfo {
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS3;
+ enum_constant public static final androidx.room.FtsOptions.MatchInfo FTS4;
+ }
+
+ public enum FtsOptions.Order {
+ enum_constant public static final androidx.room.FtsOptions.Order ASC;
+ enum_constant public static final androidx.room.FtsOptions.Order DESC;
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.CONSTRUCTOR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Ignore {
+ }
+
+ @java.lang.annotation.Target({}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Index {
+ method public abstract String name() default "";
+ method public abstract boolean unique() default false;
+ method public abstract String[] value();
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Insert {
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.room.OnConflictStrategy.REPLACE, androidx.room.OnConflictStrategy.ROLLBACK, androidx.room.OnConflictStrategy.ABORT, androidx.room.OnConflictStrategy.FAIL, androidx.room.OnConflictStrategy.IGNORE}) public @interface OnConflictStrategy {
+ field public static final int ABORT = 3; // 0x3
+ field @Deprecated public static final int FAIL = 4; // 0x4
+ field public static final int IGNORE = 5; // 0x5
+ field public static final int REPLACE = 1; // 0x1
+ field @Deprecated public static final int ROLLBACK = 2; // 0x2
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface PrimaryKey {
+ method public abstract boolean autoGenerate() default false;
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Query {
+ method public abstract String value();
+ }
+
+ @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface RawQuery {
+ method public abstract Class[] observedEntities() default {};
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Relation {
+ method public abstract Class entity() default java.lang.Object.class;
+ method public abstract String entityColumn();
+ method public abstract String parentColumn();
+ method public abstract String[] projection() default {};
+ }
+
+ public class RoomWarnings {
+ ctor @Deprecated public RoomWarnings();
+ field public static final String CANNOT_CREATE_VERIFICATION_DATABASE = "ROOM_CANNOT_CREATE_VERIFICATION_DATABASE";
+ field public static final String CURSOR_MISMATCH = "ROOM_CURSOR_MISMATCH";
+ field public static final String DEFAULT_CONSTRUCTOR = "ROOM_DEFAULT_CONSTRUCTOR";
+ field public static final String INDEX_FROM_EMBEDDED_ENTITY_IS_DROPPED = "ROOM_EMBEDDED_ENTITY_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_EMBEDDED_FIELD_IS_DROPPED = "ROOM_EMBEDDED_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_FIELD_IS_DROPPED = "ROOM_PARENT_FIELD_INDEX_IS_DROPPED";
+ field public static final String INDEX_FROM_PARENT_IS_DROPPED = "ROOM_PARENT_INDEX_IS_DROPPED";
+ field public static final String MISSING_INDEX_ON_FOREIGN_KEY_CHILD = "ROOM_MISSING_FOREIGN_KEY_CHILD_INDEX";
+ field public static final String MISSING_JAVA_TMP_DIR = "ROOM_MISSING_JAVA_TMP_DIR";
+ field public static final String MISSING_SCHEMA_LOCATION = "ROOM_MISSING_SCHEMA_LOCATION";
+ field public static final String PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED = "ROOM_EMBEDDED_PRIMARY_KEY_IS_DROPPED";
+ field public static final String RELATION_QUERY_WITHOUT_TRANSACTION = "ROOM_RELATION_QUERY_WITHOUT_TRANSACTION";
+ field public static final String RELATION_TYPE_MISMATCH = "ROOM_RELATION_TYPE_MISMATCH";
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface SkipQueryVerification {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Transaction {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverter {
+ }
+
+ @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface TypeConverters {
+ method public abstract Class<?>[] value();
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface Update {
+ method @androidx.room.OnConflictStrategy public abstract int onConflict() default androidx.room.OnConflictStrategy.ABORT;
+ }
+
+}
+
diff --git a/room/common/api/restricted_2.1.0-alpha06.txt b/room/common/api/restricted_2.1.0-alpha06.txt
new file mode 100644
index 0000000..648a72e
--- /dev/null
+++ b/room/common/api/restricted_2.1.0-alpha06.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomMasterTable {
+ method public static String! createInsertQuery(String!);
+ field public static final String CREATE_QUERY = "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)";
+ field public static final String DEFAULT_ID = "42";
+ field public static final String NAME = "room_master_table";
+ field public static final String READ_QUERY = "SELECT identity_hash FROM room_master_table WHERE id = 42 LIMIT 1";
+ field public static final String TABLE_NAME = "room_master_table";
+ }
+
+}
+
diff --git a/room/compiler/SQLite.g4 b/room/compiler/SQLite.g4
index 8eb895d..ab60202 100644
--- a/room/compiler/SQLite.g4
+++ b/room/compiler/SQLite.g4
@@ -28,16 +28,16 @@
* https://github.com/bkiers/sqlite-parser
* Developed by : Bart Kiers, bart@big-o.nl
*/
-grammar SQLite;
+grammar SQLite; // For version 3.24.0 of SQLite
parse
: ( sql_stmt_list | error )* EOF
;
error
- : UNEXPECTED_CHAR
- {
- throw new RuntimeException("UNEXPECTED_CHAR=" + $UNEXPECTED_CHAR.text);
+ : UNEXPECTED_CHAR
+ {
+ throw new RuntimeException("UNEXPECTED_CHAR=" + $UNEXPECTED_CHAR.text);
}
;
@@ -51,7 +51,6 @@
| attach_stmt
| begin_stmt
| commit_stmt
- | compound_select_stmt
| create_index_stmt
| create_table_stmt
| create_trigger_stmt
@@ -64,14 +63,12 @@
| drop_table_stmt
| drop_trigger_stmt
| drop_view_stmt
- | factored_select_stmt
| insert_stmt
| pragma_stmt
| reindex_stmt
| release_stmt
| rollback_stmt
| savepoint_stmt
- | simple_select_stmt
| select_stmt
| update_stmt
| update_stmt_limited
@@ -79,18 +76,18 @@
;
alter_table_stmt
- : K_ALTER K_TABLE ( database_name '.' )? table_name
+ : K_ALTER K_TABLE ( schema_name '.' )? table_name
( K_RENAME K_TO new_table_name
| K_ADD K_COLUMN? column_def
)
;
analyze_stmt
- : K_ANALYZE ( database_name | table_or_index_name | database_name '.' table_or_index_name )?
+ : K_ANALYZE ( schema_name | table_or_index_name | schema_name '.' table_or_index_name )?
;
attach_stmt
- : K_ATTACH K_DATABASE? expr K_AS database_name
+ : K_ATTACH K_DATABASE? expr K_AS schema_name
;
begin_stmt
@@ -101,43 +98,36 @@
: ( K_COMMIT | K_END ) ( K_TRANSACTION transaction_name? )?
;
-compound_select_stmt
- : with_clause?
- select_core ( ( K_UNION K_ALL? | K_INTERSECT | K_EXCEPT ) select_core )+
- ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
- ;
-
create_index_stmt
: K_CREATE K_UNIQUE? K_INDEX ( K_IF K_NOT K_EXISTS )?
- ( database_name '.' )? index_name K_ON table_name '(' indexed_column ( ',' indexed_column )* ')'
+ ( schema_name '.' )? index_name K_ON table_name '(' indexed_column ( ',' indexed_column )* ')'
( K_WHERE expr )?
;
create_table_stmt
: K_CREATE ( K_TEMP | K_TEMPORARY )? K_TABLE ( K_IF K_NOT K_EXISTS )?
- ( database_name '.' )? table_name
- ( '(' column_def ( ',' column_def )*? ( ',' table_constraint )* ')' ( K_WITHOUT IDENTIFIER )?
+ ( schema_name '.' )? table_name
+ ( '(' column_def ( ',' column_def )*? ( ',' table_constraint )* ')' WITHOUT_ROWID?
| K_AS select_stmt
)
;
create_trigger_stmt
: K_CREATE ( K_TEMP | K_TEMPORARY )? K_TRIGGER ( K_IF K_NOT K_EXISTS )?
- ( database_name '.' )? trigger_name ( K_BEFORE | K_AFTER | K_INSTEAD K_OF )?
- ( K_DELETE | K_INSERT | K_UPDATE ( K_OF column_name ( ',' column_name )* )? ) K_ON ( database_name '.' )? table_name
+ ( schema_name '.' )? trigger_name ( K_BEFORE | K_AFTER | K_INSTEAD K_OF )?
+ ( K_DELETE | K_INSERT | K_UPDATE ( K_OF column_name ( ',' column_name )* )? ) K_ON ( schema_name '.' )? table_name
( K_FOR K_EACH K_ROW )? ( K_WHEN expr )?
K_BEGIN ( ( update_stmt | insert_stmt | delete_stmt | select_stmt ) ';' )+ K_END
;
create_view_stmt
: K_CREATE ( K_TEMP | K_TEMPORARY )? K_VIEW ( K_IF K_NOT K_EXISTS )?
- ( database_name '.' )? view_name K_AS select_stmt
+ ( schema_name '.' )? view_name ( column_name ( ',' column_name )* )? K_AS select_stmt
;
create_virtual_table_stmt
: K_CREATE K_VIRTUAL K_TABLE ( K_IF K_NOT K_EXISTS )?
- ( database_name '.' )? table_name
+ ( schema_name '.' )? table_name
K_USING module_name ( '(' module_argument ( ',' module_argument )* ')' )?
;
@@ -149,36 +139,27 @@
delete_stmt_limited
: with_clause? K_DELETE K_FROM qualified_table_name
( K_WHERE expr )?
- ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
- )?
+ ( order_clause? limit_clause )?
;
detach_stmt
- : K_DETACH K_DATABASE? database_name
+ : K_DETACH K_DATABASE? schema_name
;
drop_index_stmt
- : K_DROP K_INDEX ( K_IF K_EXISTS )? ( database_name '.' )? index_name
+ : K_DROP K_INDEX ( K_IF K_EXISTS )? ( schema_name '.' )? index_name
;
drop_table_stmt
- : K_DROP K_TABLE ( K_IF K_EXISTS )? ( database_name '.' )? table_name
+ : K_DROP K_TABLE ( K_IF K_EXISTS )? ( schema_name '.' )? table_name
;
drop_trigger_stmt
- : K_DROP K_TRIGGER ( K_IF K_EXISTS )? ( database_name '.' )? trigger_name
+ : K_DROP K_TRIGGER ( K_IF K_EXISTS )? ( schema_name '.' )? trigger_name
;
drop_view_stmt
- : K_DROP K_VIEW ( K_IF K_EXISTS )? ( database_name '.' )? view_name
- ;
-
-factored_select_stmt
- : with_clause?
- select_core ( compound_operator select_core )*
- ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ : K_DROP K_VIEW ( K_IF K_EXISTS )? ( schema_name '.' )? view_name
;
insert_stmt
@@ -189,21 +170,30 @@
| K_INSERT K_OR K_ABORT
| K_INSERT K_OR K_FAIL
| K_INSERT K_OR K_IGNORE ) K_INTO
- ( database_name '.' )? table_name ( '(' column_name ( ',' column_name )* ')' )?
+ ( schema_name '.' )? table_name ( K_AS table_alias )? ( '(' column_name ( ',' column_name )* ')' )?
( K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
| select_stmt
| K_DEFAULT K_VALUES
)
+ upsert_clause?
+ ;
+
+upsert_clause
+ : K_ON K_CONFLICT ( '(' indexed_column ( ',' indexed_column )* ')' ( K_WHERE expr )? )?
+ ( DO_NOTHING
+ | DO_UPDATE K_SET ( column_name | column_name_list ) '=' expr
+ ( ',' ( column_name | column_name_list ) '=' expr )*
+ ( K_WHERE expr )?
+ )
;
pragma_stmt
- : K_PRAGMA ( database_name '.' )? pragma_name ( '=' pragma_value
- | '(' pragma_value ')' )?
+ : K_PRAGMA ( schema_name '.' )? pragma_name ( '=' pragma_value | '(' pragma_value ')' )?
;
reindex_stmt
: K_REINDEX ( collation_name
- | ( database_name '.' )? ( table_name | index_name )
+ | ( schema_name '.' )? ( table_name | index_name )
)?
;
@@ -219,17 +209,11 @@
: K_SAVEPOINT savepoint_name
;
-simple_select_stmt
- : with_clause?
- select_core ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
- ;
-
select_stmt
: with_clause?
select_or_values ( compound_operator select_or_values )*
- ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
+ order_clause?
+ limit_clause?
;
select_or_values
@@ -246,7 +230,8 @@
| K_OR K_REPLACE
| K_OR K_FAIL
| K_OR K_IGNORE )? qualified_table_name
- K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
+ K_SET ( column_name | column_name_list ) '=' expr ( ',' ( column_name | column_name_list ) '=' expr )*
+ ( K_WHERE expr )?
;
update_stmt_limited
@@ -255,14 +240,13 @@
| K_OR K_REPLACE
| K_OR K_FAIL
| K_OR K_IGNORE )? qualified_table_name
- K_SET column_name '=' expr ( ',' column_name '=' expr )* ( K_WHERE expr )?
- ( ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
- K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
- )?
+ K_SET ( column_name | column_name_list ) '=' expr ( ',' ( column_name | column_name_list ) '=' expr )*
+ ( K_WHERE expr )?
+ ( order_clause? limit_clause )?
;
vacuum_stmt
- : K_VACUUM
+ : K_VACUUM schema_name?
;
column_def
@@ -271,7 +255,7 @@
type_name
: name+? ( '(' signed_number ')'
- | '(' signed_number ',' signed_number ')' )?
+ | '(' signed_number ',' signed_number ')' )?
;
column_constraint
@@ -296,45 +280,23 @@
)?
;
-/*
- SQLite understands the following binary operators, in order from highest to
- lowest precedence:
-
- ||
- * / %
- + -
- << >> & |
- < <= > >=
- = == != <> IS IS NOT IN LIKE GLOB MATCH REGEXP
- AND
- OR
-*/
expr
: literal_value
| BIND_PARAMETER
- | ( ( database_name '.' )? table_name '.' )? column_name
+ | ( ( schema_name '.' )? table_name '.' )? column_name
| unary_operator expr
- | expr '||' expr
- | expr ( '*' | '/' | '%' ) expr
- | expr ( '+' | '-' ) expr
- | expr ( '<<' | '>>' | '&' | '|' ) expr
- | expr ( '<' | '<=' | '>' | '>=' ) expr
- | expr ( '=' | '==' | '!=' | '<>' ) expr
- | expr K_AND expr
- | expr K_OR expr
+ | expr binary_operator expr
| function_name '(' ( K_DISTINCT? expr ( ',' expr )* | '*' )? ')'
- | '(' expr ')'
+ | '(' expr ( ',' expr )* ')'
| K_CAST '(' expr K_AS type_name ')'
| expr K_COLLATE collation_name
| expr K_NOT? ( K_LIKE | K_GLOB | K_REGEXP | K_MATCH ) expr ( K_ESCAPE expr )?
| expr ( K_ISNULL | K_NOTNULL | K_NOT K_NULL )
| expr K_IS K_NOT? expr
| expr K_NOT? K_BETWEEN expr K_AND expr
- | expr K_NOT? K_IN ( '(' ( select_stmt
- | expr ( ',' expr )*
- )?
- ')'
- | ( database_name '.' )? table_name )
+ | expr K_NOT? K_IN ( '(' ( select_stmt | expr ( ',' expr )* )? ')'
+ | ( schema_name '.' )? table_name
+ | ( schema_name '.' )? table_function '(' ( expr ( ',' expr )* )? ')' )
| ( ( K_NOT )? K_EXISTS )? '(' select_stmt ')'
| K_CASE expr? ( K_WHEN expr K_THEN expr )+ ( K_ELSE expr )? K_END
| raise_function
@@ -360,7 +322,7 @@
;
indexed_column
- : column_name ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
+ : ( column_name | expr ) ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
;
table_constraint
@@ -375,23 +337,32 @@
: K_WITH K_RECURSIVE? common_table_expression ( ',' common_table_expression )*
;
+common_table_expression
+ : table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
+ ;
+
qualified_table_name
- : ( database_name '.' )? table_name ( K_INDEXED K_BY index_name
- | K_NOT K_INDEXED )?
+ : ( schema_name '.' )? table_name ( K_AS table_alias )?
+ ( K_INDEXED K_BY index_name | K_NOT K_INDEXED )?
+ ;
+
+order_clause
+ : K_ORDER K_BY ordering_term ( ',' ordering_term )*
;
ordering_term
: expr ( K_COLLATE collation_name )? ( K_ASC | K_DESC )?
;
+limit_clause
+ : K_LIMIT expr ( ( K_OFFSET | ',' ) expr )?
+ ;
+
pragma_value
: signed_number
| name
| STRING_LITERAL
- ;
-
-common_table_expression
- : table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
+ | boolean_literal
;
result_column
@@ -402,12 +373,9 @@
table_or_subquery
: ( schema_name '.' )? table_name ( K_AS? table_alias )?
- ( K_INDEXED K_BY index_name
- | K_NOT K_INDEXED )?
- | ( schema_name '.' )? table_function_name '(' ( expr ( ',' expr )* )? ')' ( K_AS? table_alias )?
- | '(' ( table_or_subquery ( ',' table_or_subquery )*
- | join_clause )
- ')'
+ ( K_INDEXED K_BY index_name | K_NOT K_INDEXED )?
+ | ( schema_name '.' )? table_function '(' ( expr ( ',' expr )* )? ')' ( K_AS? table_alias )?
+ | '(' ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) ')'
| '(' select_stmt ')' ( K_AS? table_alias )?
;
@@ -425,14 +393,6 @@
| K_USING '(' column_name ( ',' column_name )* ')' )?
;
-select_core
- : K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )*
- ( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )?
- ( K_WHERE expr )?
- ( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )?
- | K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
- ;
-
compound_operator
: K_UNION
| K_UNION K_ALL
@@ -452,6 +412,12 @@
| K_CURRENT_TIME
| K_CURRENT_DATE
| K_CURRENT_TIMESTAMP
+ | boolean_literal
+ ;
+
+boolean_literal
+ : TRUE
+ | FALSE
;
unary_operator
@@ -461,6 +427,33 @@
| K_NOT
;
+/*
+ SQLite understands the following binary operators, in order from highest to
+ lowest precedence:
+
+ ||
+ * / %
+ + -
+ << >> & |
+ < <= > >=
+ = == != <> IS IS NOT IN LIKE GLOB MATCH REGEXP
+ AND
+ OR
+
+ This rule is only used in `expr`, which has more complete directives for `IS` through `REGEXP`,
+ so we leave them out here.
+*/
+binary_operator
+ : '||'
+ | ( '*' | '/' | '%' )
+ | ( '+' | '-' )
+ | ( '<<' | '>>' | '&' | '|' )
+ | ( '<' | '<=' | '>' | '>=' )
+ | ( '=' | '==' | '!=' | '<>' )
+ | K_AND
+ | K_OR
+ ;
+
error_message
: STRING_LITERAL
;
@@ -475,6 +468,10 @@
| STRING_LITERAL
;
+column_name_list
+ : '(' column_name ( ',' column_name )* ')'
+ ;
+
keyword
: K_ABORT
| K_ACTION
@@ -612,15 +609,11 @@
: any_name
;
-database_name
- : any_name
- ;
-
schema_name
: any_name
;
-table_function_name
+table_function
: any_name
;
@@ -713,6 +706,8 @@
EQ : '==';
NOT_EQ1 : '!=';
NOT_EQ2 : '<>';
+TRUE : T R U E;
+FALSE : F A L S E;
// http://www.sqlite.org/lang_keywords.html
K_ABORT : A B O R T;
@@ -840,16 +835,22 @@
K_WITH : W I T H;
K_WITHOUT : W I T H O U T;
+// These are not keywords, but their constituents might be wrongly matched as identifiers.
+WITHOUT_ROWID: K_WITHOUT SPACES R O W I D;
+DO_NOTHING: D O SPACES N O T H I N G;
+DO_UPDATE: D O SPACES K_UPDATE;
+
IDENTIFIER
: '"' (~'"' | '""')* '"'
| '`' (~'`' | '``')* '`'
| '[' ~']'* ']'
- | [a-zA-Z_] [a-zA-Z_0-9]* // TODO check: needs more chars in set
+ | [a-zA-Z_\u00a1-\uffff] [a-zA-Z_0-9\u00a1-\uffff]*
;
NUMERIC_LITERAL
: DIGIT+ ( '.' DIGIT* )? ( E [-+]? DIGIT+ )?
| '.' DIGIT+ ( E [-+]? DIGIT+ )?
+ | '0' X HEXDIGIT+
;
BIND_PARAMETER
@@ -882,6 +883,7 @@
;
fragment DIGIT : [0-9];
+fragment HEXDIGIT : [0-9a-fA-F];
fragment A : [aA];
fragment B : [bB];
diff --git a/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
index dba9aa7..bb93c23 100644
--- a/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/parser/SqlParser.kt
@@ -51,16 +51,11 @@
private fun findQueryType(statement: ParseTree): QueryType {
return when (statement) {
- is SQLiteParser.Factored_select_stmtContext,
- is SQLiteParser.Compound_select_stmtContext,
- is SQLiteParser.Select_stmtContext,
- is SQLiteParser.Simple_select_stmtContext ->
+ is SQLiteParser.Select_stmtContext ->
QueryType.SELECT
-
is SQLiteParser.Delete_stmt_limitedContext,
is SQLiteParser.Delete_stmtContext ->
QueryType.DELETE
-
is SQLiteParser.Insert_stmtContext ->
QueryType.INSERT
is SQLiteParser.Update_stmtContext,
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
index 8dc5742..4d8cfa7 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/MethodProcessorDelegate.kt
@@ -201,9 +201,10 @@
adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(returnType, query)
) { callableImpl, dbField ->
addStatement(
- "return $T.execute($N, $L, $N)",
+ "return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
+ "true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
@@ -217,9 +218,10 @@
adapter = context.typeAdapterStore.findInsertAdapter(returnType, params)
) { callableImpl, dbField ->
addStatement(
- "return $T.execute($N, $L, $N)",
+ "return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
+ "true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
@@ -231,9 +233,10 @@
adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(returnType)
) { callableImpl, dbField ->
addStatement(
- "return $T.execute($N, $L, $N)",
+ "return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
+ "true", // inTransaction
callableImpl,
continuationParam.simpleName.toString()
)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
index 95e4e71..54b3d23 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/prepared/binderprovider/GuavaListenableFuturePreparedQueryResultBinderProvider.kt
@@ -52,9 +52,10 @@
adapter = context.typeAdapterStore.findPreparedQueryResultAdapter(typeArg, query)
) { callableImpl, dbField ->
addStatement(
- "return $T.createListenableFuture($N, $L)",
+ "return $T.createListenableFuture($N, $L, $L)",
RoomGuavaTypeNames.GUAVA_ROOM,
dbField,
+ "true", // inTransaction
callableImpl
)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
index b59ecc0..d97ba61 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/CoroutineResultBinder.kt
@@ -57,9 +57,10 @@
scope.builder().apply {
addStatement(
- "return $T.execute($N, $L, $N)",
+ "return $T.execute($N, $L, $L, $N)",
RoomCoroutinesTypeNames.COROUTINES_ROOM,
dbField,
+ if (inTransaction) "true" else "false",
callableImpl,
continuationParamName)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
index 7141786..722242e 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/GuavaListenableFutureQueryResultBinder.kt
@@ -56,9 +56,10 @@
scope.builder().apply {
addStatement(
- "return $T.createListenableFuture($N, $L, $L, $L)",
+ "return $T.createListenableFuture($N, $L, $L, $L, $L)",
RoomGuavaTypeNames.GUAVA_ROOM,
dbField,
+ if (inTransaction) "true" else "false",
callableImpl,
roomSQLiteQueryVar,
canReleaseQuery
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
index 2eebcda5..cd84015 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/LiveDataQueryResultBinder.kt
@@ -67,8 +67,12 @@
scope.builder().apply {
val tableNamesList = tableNames.joinToString(",") { "\"$it\"" }
addStatement(
- "return $N.getInvalidationTracker().createLiveData(new $T{$L}, $L)",
- dbField, String::class.arrayTypeName(), tableNamesList, callableImpl
+ "return $N.getInvalidationTracker().createLiveData(new $T{$L}, $L, $L)",
+ dbField,
+ String::class.arrayTypeName(),
+ tableNamesList,
+ if (inTransaction) "true" else "false",
+ callableImpl
)
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
index 7b2e54b..d52e804 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/RxQueryResultBinder.kt
@@ -58,9 +58,14 @@
}.build()
scope.builder().apply {
val tableNamesList = queryTableNames.joinToString(",") { "\"$it\"" }
- addStatement("return $T.$N($N, new $T{$L}, $L)",
- RoomRxJava2TypeNames.RX_ROOM, rxType.methodName, dbField,
- String::class.arrayTypeName(), tableNamesList, callableImpl)
+ addStatement("return $T.$N($N, $L, new $T{$L}, $L)",
+ RoomRxJava2TypeNames.RX_ROOM,
+ rxType.methodName,
+ dbField,
+ if (inTransaction) "true" else "false",
+ String::class.arrayTypeName(),
+ tableNamesList,
+ callableImpl)
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
index b43e23c..106bc14 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureDeleteOrUpdateMethodBinderProvider.kt
@@ -54,9 +54,10 @@
val adapter = context.typeAdapterStore.findDeleteOrUpdateAdapter(typeArg)
return createDeleteOrUpdateBinder(typeArg, adapter) { callableImpl, dbField ->
addStatement(
- "return $T.createListenableFuture($N, $L)",
+ "return $T.createListenableFuture($N, $L, $L)",
RoomGuavaTypeNames.GUAVA_ROOM,
dbField,
+ "true", // inTransaction
callableImpl
)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
index 7b3cb47..f603510 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/shortcut/binderprovider/GuavaListenableFutureInsertMethodBinderProvider.kt
@@ -58,9 +58,10 @@
val adapter = context.typeAdapterStore.findInsertAdapter(typeArg, params)
return createInsertBinder(typeArg, adapter) { callableImpl, dbField ->
addStatement(
- "return $T.createListenableFuture($N, $L)",
+ "return $T.createListenableFuture($N, $L, $L)",
RoomGuavaTypeNames.GUAVA_ROOM,
dbField,
+ "true", // inTransaction
callableImpl
)
}
diff --git a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
index bb945a3..f047ecd4 100644
--- a/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/ComplexDao.java
@@ -281,7 +281,7 @@
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, id);
- return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<User>() {
+ return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<User>() {
@Override
public User call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false);
@@ -330,7 +330,7 @@
_statement.bindLong(_argIndex, _item);
_argIndex ++;
}
- return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, new Callable<List<User>>() {
+ return __db.getInvalidationTracker().createLiveData(new String[]{"user"}, false, new Callable<List<User>>() {
@Override
public List<User> call() throws Exception {
final Cursor _cursor = DBUtil.query(__db, _statement, false);
diff --git a/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
index 885f7bd..f939e26 100644
--- a/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/parser/SqlParserTest.kt
@@ -66,6 +66,17 @@
}
@Test
+ fun upsertQuery() {
+ val parsed = SqlParser.parse(
+ "INSERT INTO notes (id, content) VALUES (:id, :content) " +
+ "ON CONFLICT (id) DO UPDATE SET content = excluded.content, " +
+ "revision = revision + 1, modifiedTime = strftime('%s','now')"
+ )
+ assertThat(parsed.errors, `is`(emptyList()))
+ assertThat(parsed.type, `is`(QueryType.INSERT))
+ }
+
+ @Test
fun explain() {
assertErrors("EXPLAIN QUERY PLAN SELECT * FROM users",
ParserErrors.invalidQueryType(QueryType.EXPLAIN))
@@ -140,6 +151,19 @@
}
@Test
+ fun unicodeInIdentifiers() {
+ val query = SqlParser.parse("SELECT 名, 色 FROM 猫")
+ assertThat(query.errors, `is`(emptyList()))
+ assertThat(query.tables, `is`(setOf(Table("猫", "猫"))))
+ }
+
+ @Test
+ fun rowValue_where() {
+ val query = SqlParser.parse("SELECT * FROM notes WHERE (id, content) > (:id, :content)")
+ assertThat(query.errors, `is`(emptyList()))
+ }
+
+ @Test
fun findBindVariables() {
assertVariables("select * from users")
assertVariables("select * from users where name like ?", "?")
diff --git a/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
index d4f6c2f..4e2f500 100644
--- a/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
+++ b/room/guava/src/main/java/androidx/room/guava/GuavaRoom.java
@@ -48,8 +48,8 @@
* Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
* {@link ArchTaskExecutor}'s background-threaded Executor.
*
- * @deprecated
- * Use {@link #createListenableFuture(RoomDatabase, Callable, RoomSQLiteQuery, boolean)}
+ * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable,
+ * RoomSQLiteQuery, boolean)}
*/
@Deprecated
public static <T> ListenableFuture<T> createListenableFuture(
@@ -63,7 +63,11 @@
/**
* Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
* {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+ *
+ * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable,
+ * RoomSQLiteQuery, boolean)}
*/
+ @Deprecated
public static <T> ListenableFuture<T> createListenableFuture(
final RoomDatabase roomDatabase,
final Callable<T> callable,
@@ -73,6 +77,20 @@
roomDatabase.getQueryExecutor(), callable, query, releaseQuery);
}
+ /**
+ * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
+ * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+ */
+ public static <T> ListenableFuture<T> createListenableFuture(
+ final RoomDatabase roomDatabase,
+ final boolean inTransaction,
+ final Callable<T> callable,
+ final RoomSQLiteQuery query,
+ final boolean releaseQuery) {
+ return createListenableFuture(
+ getExecutor(roomDatabase, inTransaction), callable, query, releaseQuery);
+ }
+
private static <T> ListenableFuture<T> createListenableFuture(
final Executor executor,
final Callable<T> callable,
@@ -104,12 +122,34 @@
/**
* Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
* {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+ *
+ * @deprecated Use {@link #createListenableFuture(RoomDatabase, boolean, Callable)}
*/
+ @Deprecated
public static <T> ListenableFuture<T> createListenableFuture(
final RoomDatabase roomDatabase,
final Callable<T> callable) {
+ return createListenableFuture(roomDatabase, false, callable);
+ }
+
+ /**
+ * Returns a {@link ListenableFuture<T>} created by submitting the input {@code callable} to
+ * {@link RoomDatabase}'s {@link java.util.concurrent.Executor}.
+ */
+ public static <T> ListenableFuture<T> createListenableFuture(
+ final RoomDatabase roomDatabase,
+ final boolean inTransaction,
+ final Callable<T> callable) {
ListenableFutureTask<T> listenableFutureTask = ListenableFutureTask.create(callable);
- roomDatabase.getQueryExecutor().execute(listenableFutureTask);
+ getExecutor(roomDatabase, inTransaction).execute(listenableFutureTask);
return listenableFutureTask;
}
+
+ private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
+ if (inTransaction) {
+ return database.getTransactionExecutor();
+ } else {
+ return database.getQueryExecutor();
+ }
+ }
}
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt
new file mode 100644
index 0000000..f462d00
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SneakyThrowTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.kotlintestapp.test
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.json.JSONException
+import org.json.JSONObject
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.Callable
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SneakyThrowTest : TestDatabaseTest() {
+
+ @Test
+ fun testCheckedException() {
+ try {
+ database.runInTransaction(Callable<String> {
+ val json = JSONObject()
+ json.getString("key") // method declares that it throws JSONException
+ })
+ fail("runInTransaction should have thrown an exception")
+ } catch (ex: JSONException) {
+ // no-op on purpose
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
index 390c481..b8e81fa 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
@@ -17,6 +17,7 @@
package androidx.room.integration.kotlintestapp.test
import android.os.Build
+import androidx.arch.core.executor.ArchTaskExecutor
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.integration.kotlintestapp.NewThreadDispatcher
@@ -39,15 +40,16 @@
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
-import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
@LargeTest
@RunWith(AndroidJUnit4::class)
@@ -437,20 +439,6 @@
}
@Test
- fun withTransaction_cancelCoroutine_beforeThreadAcquire() {
- runBlocking {
- val job = launch {
- database.withTransaction {
- fail("This coroutine should never run.")
- }
- }
-
- yield()
- job.cancelAndJoin()
- }
- }
-
- @Test
fun withTransaction_blockingDaoMethods() {
runBlocking {
database.withTransaction {
@@ -648,6 +636,52 @@
@Test
@Suppress("DeferredResultUnused")
+ fun withTransaction_multipleTransactions_verifyThreadUsage() {
+ val busyThreadsCount = AtomicInteger()
+ // Executor wrapper that counts threads that are busy executing commands.
+ class WrappedService(val delegate: ExecutorService) : ExecutorService by delegate {
+ override fun execute(command: Runnable) {
+ delegate.execute {
+ busyThreadsCount.incrementAndGet()
+ try {
+ command.run()
+ } finally {
+ busyThreadsCount.decrementAndGet()
+ }
+ }
+ }
+ }
+ val wrappedExecutor = WrappedService(Executors.newCachedThreadPool())
+ val localDatabase = Room.inMemoryDatabaseBuilder(
+ ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
+ .setQueryExecutor(ArchTaskExecutor.getIOThreadExecutor())
+ .setTransactionExecutor(wrappedExecutor)
+ .build()
+
+ // Run two parallel transactions but verify that only 1 thread is busy when the transactions
+ // execute, indicating that threads are not busy waiting on sql connections but are instead
+ // suspended.
+ runBlocking(Dispatchers.IO) {
+ async {
+ localDatabase.withTransaction {
+ delay(200) // delay a bit to let the other transaction proceed
+ assertThat(busyThreadsCount.get()).isEqualTo(1)
+ }
+ }
+
+ async {
+ localDatabase.withTransaction {
+ delay(200) // delay a bit to let the other transaction proceed
+ assertThat(busyThreadsCount.get()).isEqualTo(1)
+ }
+ }
+ }
+
+ wrappedExecutor.awaitTermination(1, TimeUnit.SECONDS)
+ }
+
+ @Test
+ @Suppress("DeferredResultUnused")
fun withTransaction_leakTransactionContext_async() {
runBlocking {
val leakedContext = database.withTransaction {
@@ -715,7 +749,7 @@
val executorService = Executors.newSingleThreadExecutor()
val localDatabase = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
- .setQueryExecutor(executorService)
+ .setTransactionExecutor(executorService)
.build()
// Simulate a busy executor, no thread to acquire for transaction.
@@ -750,7 +784,7 @@
val executorService = Executors.newCachedThreadPool()
val localDatabase = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(), TestDatabase::class.java)
- .setQueryExecutor(executorService)
+ .setTransactionExecutor(executorService)
.build()
executorService.shutdownNow()
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SongDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SongDao.java
index 77794cb..993f56b 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SongDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/SongDao.java
@@ -16,6 +16,7 @@
package androidx.room.integration.testapp.dao;
+import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@@ -40,4 +41,10 @@
+ "Song as s JOIN SongDescription as fts ON (docid = mSongId) "
+ "WHERE fts.mTitle MATCH :searchQuery")
List<Song> getSongs(String searchQuery);
+
+ @Query("SELECT * FROM Song")
+ LiveData<List<Song>> getLiveDataSong();
+
+ @Query("SELECT * FROM SongDescription")
+ LiveData<List<SongDescription>> getLiveDataSongDescription();
}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
index d49779a..6b88e33 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/InvalidationTrackerTest.java
@@ -125,7 +125,7 @@
public void createLiveData() throws ExecutionException, InterruptedException, TimeoutException {
final LiveData<Item> liveData = mDb
.getInvalidationTracker()
- .createLiveData(new String[]{"Item"}, () -> mDb.getItemDao().itemById(1));
+ .createLiveData(new String[]{"Item"}, false, () -> mDb.getItemDao().itemById(1));
mDb.getItemDao().insert(new Item(1, "v1"));
@@ -147,7 +147,7 @@
throws ExecutionException, InterruptedException, TimeoutException {
LiveData<Item> liveData = mDb
.getInvalidationTracker()
- .createLiveData(new String[]{"Item"}, () -> mDb.getItemDao().itemById(1));
+ .createLiveData(new String[]{"Item"}, false, () -> mDb.getItemDao().itemById(1));
mDb.getItemDao().insert(new Item(1, "v1"));
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java
index 1329938..511f292 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/LiveDataQueryTest.java
@@ -33,11 +33,14 @@
import androidx.room.Room;
import androidx.room.integration.testapp.FtsTestDatabase;
import androidx.room.integration.testapp.dao.MailDao;
+import androidx.room.integration.testapp.dao.SongDao;
import androidx.room.integration.testapp.vo.AvgWeightByAge;
import androidx.room.integration.testapp.vo.Mail;
import androidx.room.integration.testapp.vo.Pet;
import androidx.room.integration.testapp.vo.PetWithUser;
import androidx.room.integration.testapp.vo.PetsToys;
+import androidx.room.integration.testapp.vo.Song;
+import androidx.room.integration.testapp.vo.SongDescription;
import androidx.room.integration.testapp.vo.Toy;
import androidx.room.integration.testapp.vo.User;
import androidx.room.integration.testapp.vo.UserAndAllPets;
@@ -328,6 +331,66 @@
assertThat(observer.get().get(0), is(mail));
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
+ public void withExternalContentFtsTable()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final FtsTestDatabase db = Room.inMemoryDatabaseBuilder(context, FtsTestDatabase.class)
+ .build();
+ final SongDao songDao = db.getSongDao();
+ final TestLifecycleOwner lifecycleOwner = new TestLifecycleOwner();
+ lifecycleOwner.handleEvent(Lifecycle.Event.ON_START);
+
+ final TestObserver<List<Song>> songObserver = new MyTestObserver<>();
+ final TestObserver<List<SongDescription>> songDescriptionObserver = new MyTestObserver<>();
+ LiveData<List<Song>> songData = songDao.getLiveDataSong();
+ LiveData<List<SongDescription>> songDescriptionData = songDao.getLiveDataSongDescription();
+ TestUtil.observeOnMainThread(songData, lifecycleOwner, songObserver);
+ TestUtil.observeOnMainThread(songDescriptionData, lifecycleOwner, songDescriptionObserver);
+
+ assertThat(songObserver.get(), is(Collections.emptyList()));
+ assertThat(songDescriptionObserver.get(), is(Collections.emptyList()));
+
+ songObserver.reset();
+ songDescriptionObserver.reset();
+
+ Song song1 = new Song(
+ 1,
+ "Estamos Bien",
+ "Bad Bunny",
+ "X 100Pre",
+ 208,
+ 2018);
+
+ songDao.insert(song1);
+
+ assertThat(songObserver.get().get(0), is(song1));
+ assertThat(songDescriptionObserver.get().size(), is(1));
+
+ songObserver.reset();
+ songDescriptionObserver.reset();
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ songDescriptionData.removeObserver(songDescriptionObserver);
+ }
+ });
+
+ Song song2 = new Song(
+ 2,
+ "RLNDT",
+ "Bad Bunny",
+ "X 100Pre",
+ 284,
+ 2018);
+
+ songDao.insert(song2);
+
+ assertThat(songObserver.get().get(1), is(song2));
+ }
+
@MediumTest
@Test
public void handleGc() throws ExecutionException, InterruptedException, TimeoutException {
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java
new file mode 100644
index 0000000..ca96fdc
--- /dev/null
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SneakyThrowTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.integration.testapp.test;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class SneakyThrowTest extends TestDatabaseTest {
+
+ @Test
+ public void testRuntimeException_catchRuntimeException() {
+ try {
+ mDatabase.runInTransaction(() -> {
+ throw new IllegalStateException("Boom");
+ });
+ fail("runInTransaction should have thrown an exception");
+ } catch (IllegalStateException e) {
+ // no-op on purpose
+ }
+ }
+
+ @Test
+ public void testCheckedException_catchThrowable() {
+ try {
+ mDatabase.runInTransaction(() -> {
+ JSONObject json = new JSONObject();
+ return json.getString("key"); // method declares that it throws JSONException
+ });
+ fail("runInTransaction should have thrown an exception");
+ } catch (Throwable e) {
+ assertTrue(e instanceof JSONException);
+ }
+ }
+
+ @Test
+ public void testCheckedException_catchException() {
+ try {
+ mDatabase.runInTransaction(() -> {
+ JSONObject json = new JSONObject();
+ return json.getString("key"); // method declares that it throws JSONException
+ });
+ fail("runInTransaction should have thrown an exception");
+ } catch (Exception e) {
+ assertTrue(e instanceof JSONException);
+ }
+ }
+
+ @Test
+ public void testCheckedException_catchCheckedException() {
+ try {
+ // Must move the lambda to a method that declares throwing the checked exception,
+ // otherwise compiler complains about 'exception not thrown in corresponding block', a
+ // limitation of the sneaky throw technique.
+ doJsonWork();
+ fail("doJsonWork should have thrown an exception");
+ } catch (JSONException e) {
+ // no-op on purpose
+ }
+ }
+
+ private void doJsonWork() throws JSONException {
+ mDatabase.runInTransaction(() -> {
+ JSONObject json = new JSONObject();
+ return json.getString("key"); // method declares that it throws JSONException
+ });
+ }
+}
diff --git a/room/ktx/api/2.1.0-alpha06.txt b/room/ktx/api/2.1.0-alpha06.txt
new file mode 100644
index 0000000..851eb86
--- /dev/null
+++ b/room/ktx/api/2.1.0-alpha06.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public final class CoroutinesRoomKt {
+ ctor public CoroutinesRoomKt();
+ }
+
+ public final class RoomDatabaseKt {
+ ctor public RoomDatabaseKt();
+ method public static suspend Object? acquireTransactionThread(java.util.concurrent.Executor, kotlinx.coroutines.Job controlJob, kotlin.coroutines.experimental.Continuation<? super kotlin.coroutines.ContinuationInterceptor> p);
+ method public static suspend Object? createTransactionContext(androidx.room.RoomDatabase, kotlin.coroutines.experimental.Continuation<? super kotlin.coroutines.CoroutineContext> p);
+ method public static suspend <R> Object? withTransaction(androidx.room.RoomDatabase, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.experimental.Continuation<? super R>,?> block, kotlin.coroutines.experimental.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/room/ktx/api/current.txt b/room/ktx/api/current.txt
index f5dcd16..851eb86 100644
--- a/room/ktx/api/current.txt
+++ b/room/ktx/api/current.txt
@@ -1,6 +1,10 @@
// Signature format: 3.0
package androidx.room {
+ public final class CoroutinesRoomKt {
+ ctor public CoroutinesRoomKt();
+ }
+
public final class RoomDatabaseKt {
ctor public RoomDatabaseKt();
method public static suspend Object? acquireTransactionThread(java.util.concurrent.Executor, kotlinx.coroutines.Job controlJob, kotlin.coroutines.experimental.Continuation<? super kotlin.coroutines.ContinuationInterceptor> p);
diff --git a/room/ktx/api/res-2.1.0-alpha06.txt b/room/ktx/api/res-2.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/ktx/api/res-2.1.0-alpha06.txt
diff --git a/room/ktx/api/restricted_2.1.0-alpha06.txt b/room/ktx/api/restricted_2.1.0-alpha06.txt
new file mode 100644
index 0000000..866bb4d
--- /dev/null
+++ b/room/ktx/api/restricted_2.1.0-alpha06.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room {
+
+ @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class CoroutinesRoom {
+ method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.experimental.Continuation<? super R> callable);
+ field public static final androidx.room.CoroutinesRoom.Companion! Companion;
+ }
+
+ public static final class CoroutinesRoom.Companion {
+ method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
+ }
+
+}
+
diff --git a/room/ktx/api/restricted_current.txt b/room/ktx/api/restricted_current.txt
index f31bf4d..866bb4d 100644
--- a/room/ktx/api/restricted_current.txt
+++ b/room/ktx/api/restricted_current.txt
@@ -2,12 +2,12 @@
package androidx.room {
@RestrictTo({RestrictTo.Scope.LIBRARY_GROUP_PREFIX}) public final class CoroutinesRoom {
- method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, java.util.concurrent.Callable<R> db, kotlin.coroutines.experimental.Continuation<? super R> callable);
+ method public static suspend <R> Object? execute(androidx.room.RoomDatabase p, boolean db, java.util.concurrent.Callable<R> inTransaction, kotlin.coroutines.experimental.Continuation<? super R> callable);
field public static final androidx.room.CoroutinesRoom.Companion! Companion;
}
public static final class CoroutinesRoom.Companion {
- method public suspend <R> Object? execute(androidx.room.RoomDatabase db, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
+ method public suspend <R> Object? execute(androidx.room.RoomDatabase db, boolean inTransaction, java.util.concurrent.Callable<R> callable, kotlin.coroutines.experimental.Continuation<? super R> p);
}
}
diff --git a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
index f4e3cc9..d38ed58 100644
--- a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
+++ b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
@@ -17,6 +17,7 @@
package androidx.room
import androidx.annotation.RestrictTo
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext
import java.util.concurrent.Callable
@@ -33,18 +34,42 @@
companion object {
@JvmStatic
- suspend fun <R> execute(db: RoomDatabase, callable: Callable<R>): R {
+ suspend fun <R> execute(
+ db: RoomDatabase,
+ inTransaction: Boolean,
+ callable: Callable<R>
+ ): R {
if (db.isOpen && db.inTransaction()) {
return callable.call()
}
// Use the transaction dispatcher if we are on a transaction coroutine, otherwise
- // use the query executor as dispatcher.
+ // use the database dispatchers.
val context = coroutineContext[TransactionElement]?.transactionDispatcher
- ?: db.queryExecutor.asCoroutineDispatcher()
+ ?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
return withContext(context) {
callable.call()
}
}
}
-}
\ No newline at end of file
+}
+
+/**
+ * Gets the query coroutine dispatcher.
+ *
+ * @hide
+ */
+internal val RoomDatabase.queryDispatcher: CoroutineDispatcher
+ get() = backingFieldMap.getOrPut("QueryDispatcher") {
+ queryExecutor.asCoroutineDispatcher()
+ } as CoroutineDispatcher
+
+/**
+ * Gets the transaction coroutine dispatcher.
+ *
+ * @hide
+ */
+internal val RoomDatabase.transactionDispatcher: CoroutineDispatcher
+ get() = backingFieldMap.getOrPut("TransactionDispatcher") {
+ queryExecutor.asCoroutineDispatcher()
+ } as CoroutineDispatcher
diff --git a/room/ktx/src/main/java/androidx/room/RoomDatabase.kt b/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
index 86f1fde..49a972a 100644
--- a/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
+++ b/room/ktx/src/main/java/androidx/room/RoomDatabase.kt
@@ -91,7 +91,7 @@
*/
private suspend fun RoomDatabase.createTransactionContext(): CoroutineContext {
val controlJob = Job()
- val dispatcher = queryExecutor.acquireTransactionThread(controlJob)
+ val dispatcher = transactionExecutor.acquireTransactionThread(controlJob)
val transactionElement = TransactionElement(controlJob, dispatcher)
val threadLocalElement =
suspendingTransactionId.asContextElement(System.identityHashCode(controlJob))
diff --git a/room/runtime/api/2.1.0-alpha06.txt b/room/runtime/api/2.1.0-alpha06.txt
new file mode 100644
index 0000000..882b112
--- /dev/null
+++ b/room/runtime/api/2.1.0-alpha06.txt
@@ -0,0 +1,111 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class DatabaseConfiguration {
+ method public boolean isMigrationRequired(int, int);
+ method @Deprecated public boolean isMigrationRequiredFrom(int);
+ field public final boolean allowDestructiveMigrationOnDowngrade;
+ field public final boolean allowMainThreadQueries;
+ field public final java.util.List<androidx.room.RoomDatabase.Callback>? callbacks;
+ field public final android.content.Context context;
+ field public final androidx.room.RoomDatabase.JournalMode! journalMode;
+ field public final androidx.room.RoomDatabase.MigrationContainer migrationContainer;
+ field public final boolean multiInstanceInvalidation;
+ field public final String? name;
+ field public final java.util.concurrent.Executor queryExecutor;
+ field public final boolean requireMigration;
+ field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+ field public final java.util.concurrent.Executor transactionExecutor;
+ }
+
+ public class InvalidationTracker {
+ method @WorkerThread public void addObserver(androidx.room.InvalidationTracker.Observer);
+ method public void refreshVersionsAsync();
+ method @WorkerThread public void removeObserver(androidx.room.InvalidationTracker.Observer);
+ }
+
+ public abstract static class InvalidationTracker.Observer {
+ ctor protected InvalidationTracker.Observer(String, java.lang.String...!);
+ ctor public InvalidationTracker.Observer(String[]);
+ method public abstract void onInvalidated(java.util.Set<java.lang.String>);
+ }
+
+ public class Room {
+ ctor @Deprecated public Room();
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> databaseBuilder(android.content.Context, Class<T>, String);
+ method public static <T extends androidx.room.RoomDatabase> androidx.room.RoomDatabase.Builder<T> inMemoryDatabaseBuilder(android.content.Context, Class<T>);
+ field public static final String MASTER_TABLE_NAME = "room_master_table";
+ }
+
+ public abstract class RoomDatabase {
+ ctor public RoomDatabase();
+ method @Deprecated public void beginTransaction();
+ method @WorkerThread public abstract void clearAllTables();
+ method public void close();
+ method public androidx.sqlite.db.SupportSQLiteStatement! compileStatement(String);
+ method protected abstract androidx.room.InvalidationTracker createInvalidationTracker();
+ method protected abstract androidx.sqlite.db.SupportSQLiteOpenHelper createOpenHelper(androidx.room.DatabaseConfiguration!);
+ method @Deprecated public void endTransaction();
+ method public androidx.room.InvalidationTracker getInvalidationTracker();
+ method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
+ method public java.util.concurrent.Executor getQueryExecutor();
+ method public java.util.concurrent.Executor getTransactionExecutor();
+ method public boolean inTransaction();
+ method @CallSuper public void init(androidx.room.DatabaseConfiguration);
+ method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public boolean isOpen();
+ method public android.database.Cursor! query(String!, Object[]?);
+ method public android.database.Cursor! query(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public void runInTransaction(Runnable);
+ method public <V> V! runInTransaction(java.util.concurrent.Callable<V>);
+ method @Deprecated public void setTransactionSuccessful();
+ field @Deprecated protected java.util.List<androidx.room.RoomDatabase.Callback>? mCallbacks;
+ field @Deprecated protected volatile androidx.sqlite.db.SupportSQLiteDatabase! mDatabase;
+ }
+
+ public static class RoomDatabase.Builder<T extends androidx.room.RoomDatabase> {
+ method public androidx.room.RoomDatabase.Builder<T> addCallback(androidx.room.RoomDatabase.Callback);
+ method public androidx.room.RoomDatabase.Builder<T> addMigrations(androidx.room.migration.Migration...);
+ method public androidx.room.RoomDatabase.Builder<T> allowMainThreadQueries();
+ method public T build();
+ method public androidx.room.RoomDatabase.Builder<T> enableMultiInstanceInvalidation();
+ method public androidx.room.RoomDatabase.Builder<T> fallbackToDestructiveMigration();
+ method public androidx.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationFrom(int...!);
+ method public androidx.room.RoomDatabase.Builder<T> fallbackToDestructiveMigrationOnDowngrade();
+ method public androidx.room.RoomDatabase.Builder<T> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
+ method public androidx.room.RoomDatabase.Builder<T> setJournalMode(androidx.room.RoomDatabase.JournalMode);
+ method public androidx.room.RoomDatabase.Builder<T> setQueryExecutor(java.util.concurrent.Executor);
+ method public androidx.room.RoomDatabase.Builder<T> setTransactionExecutor(java.util.concurrent.Executor);
+ }
+
+ public abstract static class RoomDatabase.Callback {
+ ctor public RoomDatabase.Callback();
+ method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase);
+ method public void onOpen(androidx.sqlite.db.SupportSQLiteDatabase);
+ }
+
+ public enum RoomDatabase.JournalMode {
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode AUTOMATIC;
+ enum_constant public static final androidx.room.RoomDatabase.JournalMode TRUNCATE;
+ enum_constant @RequiresApi(android.os.Build.VERSION_CODES.JELLY_BEAN) public static final androidx.room.RoomDatabase.JournalMode WRITE_AHEAD_LOGGING;
+ }
+
+ public static class RoomDatabase.MigrationContainer {
+ ctor public RoomDatabase.MigrationContainer();
+ method public void addMigrations(androidx.room.migration.Migration...);
+ method public java.util.List<androidx.room.migration.Migration>? findMigrationPath(int, int);
+ }
+
+}
+
+package androidx.room.migration {
+
+ public abstract class Migration {
+ ctor public Migration(int, int);
+ method public abstract void migrate(androidx.sqlite.db.SupportSQLiteDatabase);
+ field public final int endVersion;
+ field public final int startVersion;
+ }
+
+}
+
diff --git a/room/runtime/api/current.txt b/room/runtime/api/current.txt
index 00d448a..882b112 100644
--- a/room/runtime/api/current.txt
+++ b/room/runtime/api/current.txt
@@ -15,6 +15,7 @@
field public final java.util.concurrent.Executor queryExecutor;
field public final boolean requireMigration;
field public final androidx.sqlite.db.SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory;
+ field public final java.util.concurrent.Executor transactionExecutor;
}
public class InvalidationTracker {
@@ -48,6 +49,7 @@
method public androidx.room.InvalidationTracker getInvalidationTracker();
method public androidx.sqlite.db.SupportSQLiteOpenHelper getOpenHelper();
method public java.util.concurrent.Executor getQueryExecutor();
+ method public java.util.concurrent.Executor getTransactionExecutor();
method public boolean inTransaction();
method @CallSuper public void init(androidx.room.DatabaseConfiguration);
method protected void internalInitInvalidationTracker(androidx.sqlite.db.SupportSQLiteDatabase);
@@ -73,6 +75,7 @@
method public androidx.room.RoomDatabase.Builder<T> openHelperFactory(androidx.sqlite.db.SupportSQLiteOpenHelper.Factory?);
method public androidx.room.RoomDatabase.Builder<T> setJournalMode(androidx.room.RoomDatabase.JournalMode);
method public androidx.room.RoomDatabase.Builder<T> setQueryExecutor(java.util.concurrent.Executor);
+ method public androidx.room.RoomDatabase.Builder<T> setTransactionExecutor(java.util.concurrent.Executor);
}
public abstract static class RoomDatabase.Callback {
diff --git a/room/runtime/api/res-2.1.0-alpha06.txt b/room/runtime/api/res-2.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/runtime/api/res-2.1.0-alpha06.txt
diff --git a/room/runtime/api/restricted_2.1.0-alpha06.ignore b/room/runtime/api/restricted_2.1.0-alpha06.ignore
new file mode 100644
index 0000000..83c78c1
--- /dev/null
+++ b/room/runtime/api/restricted_2.1.0-alpha06.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.room.DatabaseConfiguration#DatabaseConfiguration(android.content.Context, String, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>, boolean, androidx.room.RoomDatabase.JournalMode, java.util.concurrent.Executor, boolean, java.util.Set<java.lang.Integer>):
+ Removed constructor androidx.room.DatabaseConfiguration(android.content.Context,String,androidx.sqlite.db.SupportSQLiteOpenHelper.Factory,androidx.room.RoomDatabase.MigrationContainer,java.util.List<androidx.room.RoomDatabase.Callback>,boolean,androidx.room.RoomDatabase.JournalMode,java.util.concurrent.Executor,boolean,java.util.Set<java.lang.Integer>)
+
+
diff --git a/room/runtime/api/restricted_2.1.0-alpha06.txt b/room/runtime/api/restricted_2.1.0-alpha06.txt
new file mode 100644
index 0000000..a792dc1
--- /dev/null
+++ b/room/runtime/api/restricted_2.1.0-alpha06.txt
@@ -0,0 +1,184 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class DatabaseConfiguration {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
+ ctor public EntityDeletionOrUpdateAdapter(androidx.room.RoomDatabase!);
+ method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+ method public final int handle(T!);
+ method public final int handleMultiple(Iterable<T>!);
+ method public final int handleMultiple(T[]!);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityInsertionAdapter<T> extends androidx.room.SharedSQLiteStatement {
+ ctor public EntityInsertionAdapter(androidx.room.RoomDatabase!);
+ method protected abstract void bind(androidx.sqlite.db.SupportSQLiteStatement!, T!);
+ method public final void insert(T!);
+ method public final void insert(T[]!);
+ method public final void insert(Iterable<T>!);
+ method public final long insertAndReturnId(T!);
+ method public final long[]! insertAndReturnIdsArray(java.util.Collection<T>!);
+ method public final long[]! insertAndReturnIdsArray(T[]!);
+ method public final Long[]! insertAndReturnIdsArrayBox(java.util.Collection<T>!);
+ method public final Long[]! insertAndReturnIdsArrayBox(T[]!);
+ method public final java.util.List<java.lang.Long>! insertAndReturnIdsList(T[]!);
+ method public final java.util.List<java.lang.Long>! insertAndReturnIdsList(java.util.Collection<T>!);
+ }
+
+ public class InvalidationTracker {
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String...!);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String,java.lang.String>!, java.util.Map<java.lang.String,java.util.Set<java.lang.String>>!, java.lang.String...!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, boolean, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MultiInstanceInvalidationService extends android.app.Service {
+ ctor public MultiInstanceInvalidationService();
+ method public android.os.IBinder? onBind(android.content.Intent!);
+ }
+
+ public abstract class RoomDatabase {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void assertNotMainThread();
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void assertNotSuspendingTransaction();
+ field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int MAX_BIND_PARAMETER_CNT = 999; // 0x3e7
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomOpenHelper extends androidx.sqlite.db.SupportSQLiteOpenHelper.Callback {
+ ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String, String);
+ ctor public RoomOpenHelper(androidx.room.DatabaseConfiguration, androidx.room.RoomOpenHelper.Delegate, String);
+ method public void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void onUpgrade(androidx.sqlite.db.SupportSQLiteDatabase!, int, int);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract static class RoomOpenHelper.Delegate {
+ ctor public RoomOpenHelper.Delegate(int);
+ method protected abstract void createAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void dropAllTables(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void onCreate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void onOpen(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected void onPostMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected void onPreMigrate(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method protected abstract void validateMigration(androidx.sqlite.db.SupportSQLiteDatabase!);
+ field public final int version;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RoomSQLiteQuery implements androidx.sqlite.db.SupportSQLiteProgram androidx.sqlite.db.SupportSQLiteQuery {
+ method public static androidx.room.RoomSQLiteQuery! acquire(String!, int);
+ method public void bindBlob(int, byte[]!);
+ method public void bindDouble(int, double);
+ method public void bindLong(int, long);
+ method public void bindNull(int);
+ method public void bindString(int, String!);
+ method public void bindTo(androidx.sqlite.db.SupportSQLiteProgram!);
+ method public void clearBindings();
+ method public void close();
+ method public void copyArgumentsFrom(androidx.room.RoomSQLiteQuery!);
+ method public static androidx.room.RoomSQLiteQuery! copyFrom(androidx.sqlite.db.SupportSQLiteQuery!);
+ method public int getArgCount();
+ method public String! getSql();
+ method public void release();
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class SharedSQLiteStatement {
+ ctor public SharedSQLiteStatement(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteStatement! acquire();
+ method protected void assertNotMainThread();
+ method protected abstract String! createQuery();
+ method public void release(androidx.sqlite.db.SupportSQLiteStatement!);
+ }
+
+}
+
+package androidx.room.paging {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class LimitOffsetDataSource<T> extends androidx.paging.PositionalDataSource<T> {
+ ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean, java.lang.String...!);
+ ctor protected LimitOffsetDataSource(androidx.room.RoomDatabase!, androidx.room.RoomSQLiteQuery!, boolean, java.lang.String...!);
+ method protected abstract java.util.List<T>! convertRows(android.database.Cursor!);
+ method public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T>);
+ method public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T>);
+ }
+
+}
+
+package androidx.room.util {
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class CursorUtil {
+ method public static android.database.Cursor copyAndClose(android.database.Cursor);
+ method public static int getColumnIndex(android.database.Cursor, String);
+ method public static int getColumnIndexOrThrow(android.database.Cursor, String);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class DBUtil {
+ method public static void dropFtsSyncTriggers(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public static android.database.Cursor query(androidx.room.RoomDatabase!, androidx.sqlite.db.SupportSQLiteQuery!, boolean);
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class FtsTableInfo {
+ ctor public FtsTableInfo(String!, java.util.Set<java.lang.String>!, java.util.Set<java.lang.String>!);
+ ctor public FtsTableInfo(String!, java.util.Set<java.lang.String>!, String!);
+ method public static androidx.room.util.FtsTableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public final java.util.Set<java.lang.String>! columns;
+ field public final String! name;
+ field public final java.util.Set<java.lang.String>! options;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class StringUtil {
+ method public static void appendPlaceholders(StringBuilder!, int);
+ method public static String? joinIntoString(java.util.List<java.lang.Integer>?);
+ method public static StringBuilder! newStringBuilder();
+ method public static java.util.List<java.lang.Integer>? splitToIntList(String?);
+ field public static final String[]! EMPTY_STRING_ARRAY;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class TableInfo {
+ ctor public TableInfo(String!, java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey>!, java.util.Set<androidx.room.util.TableInfo.Index>!);
+ ctor public TableInfo(String!, java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column>!, java.util.Set<androidx.room.util.TableInfo.ForeignKey>!);
+ method public static androidx.room.util.TableInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public final java.util.Map<java.lang.String,androidx.room.util.TableInfo.Column>! columns;
+ field public final java.util.Set<androidx.room.util.TableInfo.ForeignKey>! foreignKeys;
+ field public final java.util.Set<androidx.room.util.TableInfo.Index>? indices;
+ field public final String! name;
+ }
+
+ public static class TableInfo.Column {
+ ctor public TableInfo.Column(String!, String!, boolean, int);
+ method public boolean isPrimaryKey();
+ field public final int affinity;
+ field public final String! name;
+ field public final boolean notNull;
+ field public final int primaryKeyPosition;
+ field public final String! type;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.ForeignKey {
+ ctor public TableInfo.ForeignKey(String, String, String, java.util.List<java.lang.String>, java.util.List<java.lang.String>);
+ field public final java.util.List<java.lang.String> columnNames;
+ field public final String onDelete;
+ field public final String onUpdate;
+ field public final java.util.List<java.lang.String> referenceColumnNames;
+ field public final String referenceTable;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class TableInfo.Index {
+ ctor public TableInfo.Index(String!, boolean, java.util.List<java.lang.String>!);
+ field public static final String DEFAULT_PREFIX = "index_";
+ field public final java.util.List<java.lang.String>! columns;
+ field public final String! name;
+ field public final boolean unique;
+ }
+
+ @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class ViewInfo {
+ ctor public ViewInfo(String!, String!);
+ method public static androidx.room.util.ViewInfo! read(androidx.sqlite.db.SupportSQLiteDatabase!, String!);
+ field public final String! name;
+ field public final String! sql;
+ }
+
+}
+
diff --git a/room/runtime/api/restricted_current.txt b/room/runtime/api/restricted_current.txt
index 10b8084..a792dc1 100644
--- a/room/runtime/api/restricted_current.txt
+++ b/room/runtime/api/restricted_current.txt
@@ -2,7 +2,7 @@
package androidx.room {
public class DatabaseConfiguration {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
+ ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public DatabaseConfiguration(android.content.Context, String?, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory, androidx.room.RoomDatabase.MigrationContainer, java.util.List<androidx.room.RoomDatabase.Callback>?, boolean, androidx.room.RoomDatabase.JournalMode!, java.util.concurrent.Executor, java.util.concurrent.Executor, boolean, boolean, boolean, java.util.Set<java.lang.Integer>?);
}
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class EntityDeletionOrUpdateAdapter<T> extends androidx.room.SharedSQLiteStatement {
@@ -32,7 +32,8 @@
ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.lang.String...!);
ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public InvalidationTracker(androidx.room.RoomDatabase!, java.util.Map<java.lang.String,java.lang.String>!, java.util.Map<java.lang.String,java.util.Set<java.lang.String>>!, java.lang.String...!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void addWeakObserver(androidx.room.InvalidationTracker.Observer!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public <T> androidx.lifecycle.LiveData<T>! createLiveData(String[]!, boolean, java.util.concurrent.Callable<T>!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @WorkerThread public void refreshVersionsSync();
}
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index c7e7958..634aea6 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -32,6 +32,10 @@
dependencies {
api(project(":room:room-common"))
+ implementation fileTree(
+ dir: "${new File(project(":room:room-common-java8").buildDir, "libs")}",
+ include : "*.jar"
+ )
api(ANDROIDX_SQLITE_FRAMEWORK)
api(ANDROIDX_SQLITE)
implementation(ARCH_CORE_RUNTIME)
@@ -46,6 +50,7 @@
testImplementation(MOCKITO_CORE)
testImplementation(ARCH_LIFECYCLE_EXTENSIONS)
testImplementation(KOTLIN_STDLIB)
+ testImplementation(TRUTH)
androidTestImplementation(JUNIT)
androidTestImplementation(TEST_EXT_JUNIT)
@@ -56,15 +61,21 @@
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
}
-// Used by testCompile in room-compiler
android.libraryVariants.all { variant ->
def name = variant.name
def suffix = name.capitalize()
+
+ // Create jar<variant> task for testCompile in room-compiler.
project.tasks.create(name: "jar${suffix}", type: Jar){
dependsOn variant.javaCompileProvider.get()
from variant.javaCompileProvider.get().destinationDir
destinationDir new File(project.buildDir, "libJar")
}
+
+ // Make javaCompile task depend on room-common-java8 jar task.
+ variant.javaCompileProvider.configure { task ->
+ task.dependsOn(":room:room-common-java8:jar")
+ }
}
supportLibrary {
diff --git a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
index bb893fd..dae3a09 100644
--- a/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
+++ b/room/runtime/src/main/java/androidx/room/DatabaseConfiguration.java
@@ -74,6 +74,12 @@
public final Executor queryExecutor;
/**
+ * The Executor used to execute asynchronous transactions.
+ */
+ @NonNull
+ public final Executor transactionExecutor;
+
+ /**
* If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and
* synchronized with other instances of the same {@link RoomDatabase} file, including those
* in a separate process.
@@ -124,6 +130,7 @@
boolean allowMainThreadQueries,
RoomDatabase.JournalMode journalMode,
@NonNull Executor queryExecutor,
+ @NonNull Executor transactionExecutor,
boolean multiInstanceInvalidation,
boolean requireMigration,
boolean allowDestructiveMigrationOnDowngrade,
@@ -136,6 +143,7 @@
this.allowMainThreadQueries = allowMainThreadQueries;
this.journalMode = journalMode;
this.queryExecutor = queryExecutor;
+ this.transactionExecutor = transactionExecutor;
this.multiInstanceInvalidation = multiInstanceInvalidation;
this.requireMigration = requireMigration;
this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade;
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java b/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
index 4168f47..e1e8155 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationLiveDataContainer.java
@@ -43,8 +43,10 @@
mDatabase = database;
}
- <T> LiveData<T> create(String[] tableNames, Callable<T> computeFunction) {
- return new RoomTrackingLiveData<>(mDatabase, this, computeFunction, tableNames);
+ <T> LiveData<T> create(String[] tableNames, boolean inTransaction,
+ Callable<T> computeFunction) {
+ return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction,
+ tableNames);
}
void onActive(LiveData liveData) {
diff --git a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
index fe33a55..9e7be31 100644
--- a/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
+++ b/room/runtime/src/main/java/androidx/room/InvalidationTracker.java
@@ -30,7 +30,6 @@
import androidx.arch.core.internal.SafeIterableMap;
import androidx.collection.ArrayMap;
import androidx.collection.ArraySet;
-import androidx.collection.SparseArrayCompat;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteDatabase;
@@ -89,9 +88,6 @@
@VisibleForTesting
final ArrayMap<String, Integer> mTableIdLookup;
final String[] mTableNames;
- @NonNull
- @VisibleForTesting
- final SparseArrayCompat<String> mShadowTableLookup;
@NonNull
private Map<String, Set<String>> mViewTables;
@@ -145,7 +141,6 @@
mDatabase = database;
mObservedTableTracker = new ObservedTableTracker(tableNames.length);
mTableIdLookup = new ArrayMap<>();
- mShadowTableLookup = new SparseArrayCompat<>(shadowTablesMap.size());
mViewTables = viewTables;
mInvalidationLiveDataContainer = new InvalidationLiveDataContainer(mDatabase);
final int size = tableNames.length;
@@ -153,10 +148,20 @@
for (int id = 0; id < size; id++) {
final String tableName = tableNames[id].toLowerCase(Locale.US);
mTableIdLookup.put(tableName, id);
- mTableNames[id] = tableName;
String shadowTableName = shadowTablesMap.get(tableNames[id]);
if (shadowTableName != null) {
- mShadowTableLookup.append(id, shadowTableName.toLowerCase(Locale.US));
+ mTableNames[id] = shadowTableName.toLowerCase(Locale.US);
+ } else {
+ mTableNames[id] = tableName;
+ }
+ }
+ // Adjust table id lookup for those tables whose shadow table is another already mapped
+ // table (e.g. external content fts tables).
+ for (Map.Entry<String, String> shadowTableEntry : shadowTablesMap.entrySet()) {
+ String shadowTableName = shadowTableEntry.getValue().toLowerCase(Locale.US);
+ if (mTableIdLookup.containsKey(shadowTableName)) {
+ String tableName = shadowTableEntry.getKey().toLowerCase(Locale.US);
+ mTableIdLookup.put(tableName, mTableIdLookup.get(shadowTableName));
}
}
mTableInvalidStatus = new BitSet(tableNames.length);
@@ -212,7 +217,7 @@
}
private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
- final String tableName = mShadowTableLookup.get(tableId, mTableNames[tableId]);
+ final String tableName = mTableNames[tableId];
StringBuilder stringBuilder = new StringBuilder();
for (String trigger : TRIGGERS) {
stringBuilder.setLength(0);
@@ -225,7 +230,7 @@
private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) {
writableDb.execSQL(
"INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)");
- final String tableName = mShadowTableLookup.get(tableId, mTableNames[tableId]);
+ final String tableName = mTableNames[tableId];
StringBuilder stringBuilder = new StringBuilder();
for (String trigger : TRIGGERS) {
stringBuilder.setLength(0);
@@ -410,7 +415,7 @@
if (hasUpdatedTable) {
synchronized (mObserverMap) {
for (Map.Entry<Observer, ObserverWrapper> entry : mObserverMap) {
- entry.getValue().notifyByTableVersions(mTableInvalidStatus);
+ entry.getValue().notifyByTableInvalidStatus(mTableInvalidStatus);
}
}
// Reset invalidated status flags.
@@ -554,6 +559,8 @@
* <p>
* Holds a strong reference to the created LiveData as long as it is active.
*
+ * @deprecated Use {@link #createLiveData(String[], boolean, Callable)}
+ *
* @param computeFunction The function that calculates the value
* @param tableNames The list of tables to observe
* @param <T> The return type
@@ -561,10 +568,32 @@
* invalidates.
* @hide
*/
+ @Deprecated
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public <T> LiveData<T> createLiveData(String[] tableNames, Callable<T> computeFunction) {
+ return createLiveData(tableNames, false, computeFunction);
+ }
+
+ /**
+ * Creates a LiveData that computes the given function once and for every other invalidation
+ * of the database.
+ * <p>
+ * Holds a strong reference to the created LiveData as long as it is active.
+ *
+ * @param tableNames The list of tables to observe
+ * @param inTransaction True if the computeFunction will be done in a transaction, false
+ * otherwise.
+ * @param computeFunction The function that calculates the value
+ * @param <T> The return type
+ * @return A new LiveData that computes the given function when the given list of tables
+ * invalidates.
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public <T> LiveData<T> createLiveData(String[] tableNames, boolean inTransaction,
+ Callable<T> computeFunction) {
return mInvalidationLiveDataContainer.create(
- validateAndResolveTableNames(tableNames), computeFunction);
+ validateAndResolveTableNames(tableNames), inTransaction, computeFunction);
}
/**
@@ -594,12 +623,12 @@
}
/**
- * Updates the table versions and notifies the underlying {@link #mObserver} if any of the
- * observed tables are invalidated.
+ * Notifies the underlying {@link #mObserver} if any of the observed tables are invalidated
+ * based on the given invalid status set.
*
* @param tableInvalidStatus The table invalid statuses.
*/
- void notifyByTableVersions(BitSet tableInvalidStatus) {
+ void notifyByTableInvalidStatus(BitSet tableInvalidStatus) {
Set<String> invalidatedTables = null;
final int size = mTableIds.length;
for (int index = 0; index < size; index++) {
diff --git a/room/runtime/src/main/java/androidx/room/RoomDatabase.java b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
index a33383a..d89ac58 100644
--- a/room/runtime/src/main/java/androidx/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/androidx/room/RoomDatabase.java
@@ -34,6 +34,7 @@
import androidx.collection.SparseArrayCompat;
import androidx.core.app.ActivityManagerCompat;
import androidx.room.migration.Migration;
+import androidx.room.util.SneakyThrow;
import androidx.sqlite.db.SimpleSQLiteQuery;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
@@ -45,8 +46,10 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -77,6 +80,7 @@
@Deprecated
protected volatile SupportSQLiteDatabase mDatabase;
private Executor mQueryExecutor;
+ private Executor mTransactionExecutor;
private SupportSQLiteOpenHelper mOpenHelper;
private final InvalidationTracker mInvalidationTracker;
private boolean mAllowMainThreadQueries;
@@ -121,6 +125,19 @@
return mSuspendingTransactionId;
}
+
+ private final Map<String, Object> mBackingFieldMap = new ConcurrentHashMap<>();
+
+ /**
+ * Gets the map for storing extension properties of Kotlin type.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ Map<String, Object> getBackingFieldMap() {
+ return mBackingFieldMap;
+ }
+
/**
* Creates a RoomDatabase.
* <p>
@@ -147,6 +164,7 @@
}
mCallbacks = configuration.callbacks;
mQueryExecutor = configuration.queryExecutor;
+ mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor);
mAllowMainThreadQueries = configuration.allowMainThreadQueries;
mWriteAheadLoggingEnabled = wal;
if (configuration.multiInstanceInvalidation) {
@@ -336,6 +354,14 @@
}
/**
+ * @return The Executor in use by this database for async transactions.
+ */
+ @NonNull
+ public Executor getTransactionExecutor() {
+ return mTransactionExecutor;
+ }
+
+ /**
* Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}.
*
* @deprecated Use {@link #runInTransaction(Runnable)}
@@ -380,7 +406,8 @@
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
- throw new RuntimeException("Exception in transaction", e);
+ SneakyThrow.reThrow(e);
+ return null; // Unreachable code, but compiler doesn't know it.
} finally {
endTransaction();
}
@@ -481,6 +508,8 @@
/** The Executor used to run database queries. This should be background-threaded. */
private Executor mQueryExecutor;
+ /** The Executor used to run database transactions. This should be background-threaded. */
+ private Executor mTransactionExecutor;
private SupportSQLiteOpenHelper.Factory mFactory;
private boolean mAllowMainThreadQueries;
private JournalMode mJournalMode;
@@ -598,12 +627,19 @@
* queries and tasks, including {@code LiveData} invalidation, {@code Flowable} scheduling
* and {@code ListenableFuture} tasks.
* <p>
- * When unset, a default {@code Executor} will be used. The default {@code Executor}
- * allocates and shares threads amongst Architecture Components libraries.
+ * When both the query executor and transaction executor are unset, then a default
+ * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
+ * amongst Architecture Components libraries. If the query executor is unset but a
+ * transaction executor was set, then the same {@code Executor} will be used for queries.
+ * <p>
+ * For best performance the given {@code Executor} should be bounded (max number of threads
+ * is limited).
* <p>
* The input {@code Executor} cannot run tasks on the UI thread.
- *
+ **
* @return this
+ *
+ * @see #setTransactionExecutor(Executor)
*/
@NonNull
public Builder<T> setQueryExecutor(@NonNull Executor executor) {
@@ -612,6 +648,32 @@
}
/**
+ * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous
+ * transaction queries and tasks, including {@code LiveData} invalidation, {@code Flowable}
+ * scheduling and {@code ListenableFuture} tasks.
+ * <p>
+ * When both the transaction executor and query executor are unset, then a default
+ * {@code Executor} will be used. The default {@code Executor} allocates and shares threads
+ * amongst Architecture Components libraries. If the transaction executor is unset but a
+ * query executor was set, then the same {@code Executor} will be used for transactions.
+ * <p>
+ * If the given {@code Executor} is shared then it should be unbounded to avoid the
+ * possibility of a deadlock. Room will not use more than one thread at a time from this
+ * executor.
+ * <p>
+ * The input {@code Executor} cannot run tasks on the UI thread.
+ *
+ * @return this
+ *
+ * @see #setQueryExecutor(Executor)
+ */
+ @NonNull
+ public Builder<T> setTransactionExecutor(@NonNull Executor executor) {
+ mTransactionExecutor = executor;
+ return this;
+ }
+
+ /**
* Sets whether table invalidation in this instance of {@link RoomDatabase} should be
* broadcast and synchronized with other instances of the same {@link RoomDatabase},
* including those in a separate process. In order to enable multi-instance invalidation,
@@ -741,8 +803,12 @@
throw new IllegalArgumentException("Must provide an abstract class that"
+ " extends RoomDatabase");
}
- if (mQueryExecutor == null) {
- mQueryExecutor = ArchTaskExecutor.getIOThreadExecutor();
+ if (mQueryExecutor == null && mTransactionExecutor == null) {
+ mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor();
+ } else if (mQueryExecutor != null && mTransactionExecutor == null) {
+ mTransactionExecutor = mQueryExecutor;
+ } else if (mQueryExecutor == null && mTransactionExecutor != null) {
+ mQueryExecutor = mTransactionExecutor;
}
if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) {
@@ -763,12 +829,20 @@
mFactory = new FrameworkSQLiteOpenHelperFactory();
}
DatabaseConfiguration configuration =
- new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
- mCallbacks, mAllowMainThreadQueries, mJournalMode.resolve(mContext),
+ new DatabaseConfiguration(
+ mContext,
+ mName,
+ mFactory,
+ mMigrationContainer,
+ mCallbacks,
+ mAllowMainThreadQueries,
+ mJournalMode.resolve(mContext),
mQueryExecutor,
+ mTransactionExecutor,
mMultiInstanceInvalidation,
mRequireMigration,
- mAllowDestructiveMigrationOnDowngrade, mMigrationsNotRequiredFrom);
+ mAllowDestructiveMigrationOnDowngrade,
+ mMigrationsNotRequiredFrom);
T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
db.init(configuration);
return db;
diff --git a/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java b/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
index 61ef38e..8df1014 100644
--- a/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
+++ b/room/runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
@@ -27,6 +27,7 @@
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -48,6 +49,9 @@
final RoomDatabase mDatabase;
@SuppressWarnings("WeakerAccess")
+ final boolean mInTransaction;
+
+ @SuppressWarnings("WeakerAccess")
final Callable<T> mComputeFunction;
private final InvalidationLiveDataContainer mContainer;
@@ -116,7 +120,7 @@
boolean isActive = hasActiveObservers();
if (mInvalid.compareAndSet(false, true)) {
if (isActive) {
- mDatabase.getQueryExecutor().execute(mRefreshRunnable);
+ getQueryExecutor().execute(mRefreshRunnable);
}
}
}
@@ -125,9 +129,11 @@
RoomTrackingLiveData(
RoomDatabase database,
InvalidationLiveDataContainer container,
+ boolean inTransaction,
Callable<T> computeFunction,
String[] tableNames) {
mDatabase = database;
+ mInTransaction = inTransaction;
mComputeFunction = computeFunction;
mContainer = container;
mObserver = new InvalidationTracker.Observer(tableNames) {
@@ -142,7 +148,7 @@
protected void onActive() {
super.onActive();
mContainer.onActive(this);
- mDatabase.getQueryExecutor().execute(mRefreshRunnable);
+ getQueryExecutor().execute(mRefreshRunnable);
}
@Override
@@ -150,4 +156,12 @@
super.onInactive();
mContainer.onInactive(this);
}
+
+ Executor getQueryExecutor() {
+ if (mInTransaction) {
+ return mDatabase.getTransactionExecutor();
+ } else {
+ return mDatabase.getQueryExecutor();
+ }
+ }
}
diff --git a/room/runtime/src/main/java/androidx/room/TransactionExecutor.java b/room/runtime/src/main/java/androidx/room/TransactionExecutor.java
new file mode 100644
index 0000000..6a6bc12
--- /dev/null
+++ b/room/runtime/src/main/java/androidx/room/TransactionExecutor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+
+/**
+ * Executor wrapper for performing database transactions serially.
+ * <p>
+ * Since database transactions are exclusive, this executor ensures that transactions are performed
+ * in-order and one at a time, preventing threads from blocking each other when multiple concurrent
+ * transactions are attempted.
+ */
+class TransactionExecutor implements Executor {
+
+ private final Executor mExecutor;
+ private final ArrayDeque<Runnable> mTasks = new ArrayDeque<>();
+ private Runnable mActive;
+
+ TransactionExecutor(@NonNull Executor executor) {
+ mExecutor = executor;
+ }
+
+ public synchronized void execute(final Runnable command) {
+ mTasks.offer(new Runnable() {
+ public void run() {
+ try {
+ command.run();
+ } finally {
+ scheduleNext();
+ }
+ }
+ });
+ if (mActive == null) {
+ scheduleNext();
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ synchronized void scheduleNext() {
+ if ((mActive = mTasks.poll()) != null) {
+ mExecutor.execute(mActive);
+ }
+ }
+}
diff --git a/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
index 0607dd5..845b64e 100644
--- a/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
+++ b/room/runtime/src/main/java/androidx/room/paging/LimitOffsetDataSource.java
@@ -113,7 +113,7 @@
int firstLoadPosition = 0;
RoomSQLiteQuery sqLiteQuery = null;
Cursor cursor = null;
-
+ //noinspection deprecation
mDb.beginTransaction();
try {
totalCount = countItems();
@@ -125,6 +125,7 @@
sqLiteQuery = getSQLiteQuery(firstLoadPosition, firstLoadSize);
cursor = mDb.query(sqLiteQuery);
List<T> rows = convertRows(cursor);
+ //noinspection deprecation
mDb.setTransactionSuccessful();
list = rows;
}
@@ -132,6 +133,7 @@
if (cursor != null) {
cursor.close();
}
+ //noinspection deprecation
mDb.endTransaction();
if (sqLiteQuery != null) {
sqLiteQuery.release();
diff --git a/room/runtime/src/test/java/androidx/room/BuilderTest.java b/room/runtime/src/test/java/androidx/room/BuilderTest.java
index 0c29dd7..eabefdb 100644
--- a/room/runtime/src/test/java/androidx/room/BuilderTest.java
+++ b/room/runtime/src/test/java/androidx/room/BuilderTest.java
@@ -39,6 +39,7 @@
import org.junit.runners.JUnit4;
import java.util.List;
+import java.util.concurrent.Executor;
@SuppressWarnings("ArraysAsListWithZeroOrOneArgument")
@RunWith(JUnit4.class)
@@ -66,6 +67,41 @@
Room.databaseBuilder(mock(Context.class), RoomDatabase.class, " ").build();
}
+ public void executors_setQueryExecutor() {
+ Executor executor = mock(Executor.class);
+
+ TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+ .setQueryExecutor(executor)
+ .build();
+
+ assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
+ assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
+ }
+
+ public void executors_setTransactionExecutor() {
+ Executor executor = mock(Executor.class);
+
+ TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+ .setTransactionExecutor(executor)
+ .build();
+
+ assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor));
+ assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor));
+ }
+
+ public void executors_setBothExecutors() {
+ Executor executor1 = mock(Executor.class);
+ Executor executor2 = mock(Executor.class);
+
+ TestDatabase db = Room.databaseBuilder(mock(Context.class), TestDatabase.class, "foo")
+ .setQueryExecutor(executor1)
+ .setTransactionExecutor(executor2)
+ .build();
+
+ assertThat(db.mDatabaseConfiguration.queryExecutor, is(executor1));
+ assertThat(db.mDatabaseConfiguration.transactionExecutor, is(executor2));
+ }
+
@Test
public void migration() {
Migration m1 = new EmptyMigration(0, 1);
@@ -387,6 +423,14 @@
}
abstract static class TestDatabase extends RoomDatabase {
+
+ DatabaseConfiguration mDatabaseConfiguration;
+
+ @Override
+ public void init(@NonNull DatabaseConfiguration configuration) {
+ super.init(configuration);
+ mDatabaseConfiguration = configuration;
+ }
}
static class EmptyMigration extends Migration {
diff --git a/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt b/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
index 97c22d1..6034b67 100644
--- a/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
+++ b/room/runtime/src/test/java/androidx/room/InvalidationLiveDataContainerTest.kt
@@ -99,6 +99,7 @@
private fun createLiveData(): LiveData<Any> {
return container.create(
arrayOf("a", "b"),
+ false,
createComputeFunction<Any>()
) as LiveData
}
diff --git a/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
index 727ecaa8..f3caf34 100644
--- a/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
+++ b/room/runtime/src/test/java/androidx/room/InvalidationTrackerTest.java
@@ -97,13 +97,14 @@
//noinspection ResultOfMethodCallIgnored
doReturn(mOpenHelper).when(mRoomDatabase).getOpenHelper();
HashMap<String, String> shadowTables = new HashMap<>();
- shadowTables.put("C", "D");
+ shadowTables.put("C", "C_content");
+ shadowTables.put("d", "a");
HashMap<String, Set<String>> viewTables = new HashMap<>();
HashSet<String> tableSet = new HashSet<>();
tableSet.add("a");
viewTables.put("e", tableSet);
mTracker = new InvalidationTracker(mRoomDatabase, shadowTables, viewTables,
- "a", "B", "i", "C");
+ "a", "B", "i", "C", "d");
mTracker.internalInit(mSqliteDb);
reset(mSqliteDb);
}
@@ -120,10 +121,22 @@
@Test
public void tableIds() {
+ assertThat(mTracker.mTableIdLookup.size(), is(5));
assertThat(mTracker.mTableIdLookup.get("a"), is(0));
assertThat(mTracker.mTableIdLookup.get("b"), is(1));
assertThat(mTracker.mTableIdLookup.get("i"), is(2));
- assertThat(mTracker.mTableIdLookup.get("c"), is(3));
+ assertThat(mTracker.mTableIdLookup.get("c"), is(3)); // fts
+ assertThat(mTracker.mTableIdLookup.get("d"), is(0)); // external content fts
+ }
+
+ @Test
+ public void tableNames() {
+ assertThat(mTracker.mTableNames.length, is(5));
+ assertThat(mTracker.mTableNames[0], is("a"));
+ assertThat(mTracker.mTableNames[1], is("b"));
+ assertThat(mTracker.mTableNames[2], is("i"));
+ assertThat(mTracker.mTableNames[3], is("c_content")); // fts
+ assertThat(mTracker.mTableNames[4], is("a")); // external content fts
}
@Test
@@ -266,9 +279,10 @@
for (int i = 0; i < triggers.length; i++) {
assertThat(sqlCaptorValues.get(i + 1),
is("CREATE TEMP TRIGGER IF NOT EXISTS "
- + "`room_table_modification_trigger_d_" + triggers[i] + "` AFTER "
- + triggers[i] + " ON `d` BEGIN UPDATE room_table_modification_log "
- + "SET invalidated = 1 WHERE table_id = 3 AND invalidated = 0; END"
+ + "`room_table_modification_trigger_c_content_" + triggers[i]
+ + "` AFTER " + triggers[i] + " ON `c_content` BEGIN UPDATE "
+ + "room_table_modification_log SET invalidated = 1 WHERE table_id = 3 "
+ + "AND invalidated = 0; END"
));
}
@@ -280,12 +294,110 @@
sqlCaptorValues = sqlArgCaptor.getAllValues();
for (int i = 0; i < triggers.length; i++) {
assertThat(sqlCaptorValues.get(i),
- is("DROP TRIGGER IF EXISTS `room_table_modification_trigger_d_"
+ is("DROP TRIGGER IF EXISTS `room_table_modification_trigger_c_content_"
+ triggers[i] + "`"));
}
}
@Test
+ public void observeFtsTable() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "C");
+ mTracker.addObserver(observer);
+ setInvalidatedTables(3);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(1));
+ assertThat(observer.getInvalidatedTables(), hasItem("C"));
+
+ setInvalidatedTables(1);
+ observer.reset(1);
+ refreshSync();
+ assertThat(observer.await(), is(false));
+
+ setInvalidatedTables(0, 3);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(1));
+ assertThat(observer.getInvalidatedTables(), hasItem("C"));
+ }
+
+ @Test
+ public void observeExternalContentFtsTable() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "d");
+ mTracker.addObserver(observer);
+ setInvalidatedTables(0);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(1));
+ assertThat(observer.getInvalidatedTables(), hasItem("d"));
+
+ setInvalidatedTables(2, 3);
+ observer.reset(1);
+ refreshSync();
+ assertThat(observer.await(), is(false));
+
+ setInvalidatedTables(0, 1);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(1));
+ assertThat(observer.getInvalidatedTables(), hasItem("d"));
+ }
+
+ @Test
+ public void observeExternalContentFtsTableAndContentTable() throws InterruptedException {
+ LatchObserver observer = new LatchObserver(1, "d", "a");
+ mTracker.addObserver(observer);
+ setInvalidatedTables(0);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(2));
+ assertThat(observer.getInvalidatedTables(), hasItems("d", "a"));
+
+ setInvalidatedTables(2, 3);
+ observer.reset(1);
+ refreshSync();
+ assertThat(observer.await(), is(false));
+
+ setInvalidatedTables(0, 1);
+ refreshSync();
+ assertThat(observer.await(), is(true));
+ assertThat(observer.getInvalidatedTables().size(), is(2));
+ assertThat(observer.getInvalidatedTables(), hasItems("d", "a"));
+ }
+
+ @Test
+ public void observeExternalContentFatsTableAndContentTableSeparately()
+ throws InterruptedException {
+ LatchObserver observerA = new LatchObserver(1, "a");
+ LatchObserver observerD = new LatchObserver(1, "d");
+ mTracker.addObserver(observerA);
+ mTracker.addObserver(observerD);
+
+ setInvalidatedTables(0);
+ refreshSync();
+
+ assertThat(observerA.await(), is(true));
+ assertThat(observerD.await(), is(true));
+ assertThat(observerA.getInvalidatedTables().size(), is(1));
+ assertThat(observerD.getInvalidatedTables().size(), is(1));
+ assertThat(observerA.getInvalidatedTables(), hasItem("a"));
+ assertThat(observerD.getInvalidatedTables(), hasItem("d"));
+
+ // Remove observer 'd' which is backed by 'a', observers to 'a' should still work.
+ mTracker.removeObserver(observerD);
+
+ setInvalidatedTables(0);
+ observerA.reset(1);
+ observerD.reset(1);
+ refreshSync();
+
+ assertThat(observerA.await(), is(true));
+ assertThat(observerD.await(), is(false));
+ assertThat(observerA.getInvalidatedTables().size(), is(1));
+ assertThat(observerA.getInvalidatedTables(), hasItem("a"));
+ }
+
+ @Test
public void observeView() throws InterruptedException {
LatchObserver observer = new LatchObserver(1, "E");
mTracker.addObserver(observer);
@@ -337,7 +449,7 @@
}
/**
- * Key value pairs of VERSION, TABLE_ID
+ * Setup Cursor result to return INVALIDATED for given tableIds
*/
private void setInvalidatedTables(int... tableIds) throws InterruptedException {
// mockito does not like multi-threaded access so before setting versions, make sure we
@@ -367,8 +479,9 @@
Answer<Integer> intAnswer = new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
- return tableIds[index.intValue()
- + (Integer) invocation.getArguments()[0]];
+ // checkUpdatedTable only checks for column 0 (invalidated table id)
+ assert ((Integer) invocation.getArguments()[0]) == 0;
+ return tableIds[index.intValue()];
}
};
when(cursor.getInt(anyInt())).thenAnswer(intAnswer);
diff --git a/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt b/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
new file mode 100644
index 0000000..edda059
--- /dev/null
+++ b/room/runtime/src/test/java/androidx/room/TransactionExecutorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+@RunWith(JUnit4::class)
+class TransactionExecutorTest {
+
+ private val testExecutor = Executors.newCachedThreadPool()
+ private val transactionExecutor = TransactionExecutor(testExecutor)
+
+ @After
+ fun teardown() {
+ testExecutor.shutdownNow()
+ }
+
+ @Test
+ @Throws(InterruptedException::class)
+ fun testSerialExecution() {
+
+ val latch = CountDownLatch(3)
+ val runnableA = TimingRunnable(latch)
+ val runnableB = TimingRunnable(latch)
+ val runnableC = TimingRunnable(latch)
+
+ transactionExecutor.execute(runnableA)
+ transactionExecutor.execute(runnableB)
+ transactionExecutor.execute(runnableC)
+
+ // Await for the runnables to finish.
+ latch.await(1, TimeUnit.SECONDS)
+
+ // Assert that everything ran.
+ assertThat(runnableA.run).isTrue()
+ assertThat(runnableB.run).isTrue()
+ assertThat(runnableC.run).isTrue()
+
+ // Assert that runnables were run in order of submission.
+ assertThat(runnableA.start).isLessThan(runnableB.start)
+ assertThat(runnableB.start).isLessThan(runnableC.start)
+
+ // Assert that a runnable finishes before the runnable after it starts.
+ assertThat(runnableA.finish).isLessThan(runnableB.start)
+ assertThat(runnableB.finish).isLessThan(runnableC.start)
+ }
+
+ private class TimingRunnable(val latch: CountDownLatch) : Runnable {
+ var start: Long = 0
+ var finish: Long = 0
+ var run: Boolean = false
+
+ override fun run() {
+ run = true
+ start = System.nanoTime()
+ try {
+ // Sleep for a bit as if we were doing real work.
+ Thread.sleep(100)
+ } catch (e: InterruptedException) {
+ throw RuntimeException(e)
+ }
+ finish = System.nanoTime()
+ latch.countDown()
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/rxjava2/api/2.1.0-alpha06.txt b/room/rxjava2/api/2.1.0-alpha06.txt
new file mode 100644
index 0000000..8a7a0fb
--- /dev/null
+++ b/room/rxjava2/api/2.1.0-alpha06.txt
@@ -0,0 +1,16 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class EmptyResultSetException extends java.lang.RuntimeException {
+ ctor public EmptyResultSetException(String!);
+ }
+
+ public class RxRoom {
+ ctor @Deprecated public RxRoom();
+ method public static io.reactivex.Flowable<java.lang.Object>! createFlowable(androidx.room.RoomDatabase!, java.lang.String...!);
+ method public static io.reactivex.Observable<java.lang.Object>! createObservable(androidx.room.RoomDatabase!, java.lang.String...!);
+ field public static final Object! NOTHING;
+ }
+
+}
+
diff --git a/room/rxjava2/api/res-2.1.0-alpha06.txt b/room/rxjava2/api/res-2.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/rxjava2/api/res-2.1.0-alpha06.txt
diff --git a/room/rxjava2/api/restricted_2.1.0-alpha06.txt b/room/rxjava2/api/restricted_2.1.0-alpha06.txt
new file mode 100644
index 0000000..6731dfa
--- /dev/null
+++ b/room/rxjava2/api/restricted_2.1.0-alpha06.txt
@@ -0,0 +1,12 @@
+// Signature format: 3.0
+package androidx.room {
+
+ public class RxRoom {
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
+ }
+
+}
+
diff --git a/room/rxjava2/api/restricted_current.txt b/room/rxjava2/api/restricted_current.txt
index 36290b8..6731dfa 100644
--- a/room/rxjava2/api/restricted_current.txt
+++ b/room/rxjava2/api/restricted_current.txt
@@ -2,8 +2,10 @@
package androidx.room {
public class RxRoom {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Flowable<T>! createFlowable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, String[]!, java.util.concurrent.Callable<T>!);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static <T> io.reactivex.Observable<T>! createObservable(androidx.room.RoomDatabase!, boolean, String[]!, java.util.concurrent.Callable<T>!);
}
}
diff --git a/room/rxjava2/src/main/java/androidx/room/RxRoom.java b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
index 1e78572..3badde1 100644
--- a/room/rxjava2/src/main/java/androidx/room/RxRoom.java
+++ b/room/rxjava2/src/main/java/androidx/room/RxRoom.java
@@ -20,6 +20,7 @@
import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
@@ -97,12 +98,27 @@
* Helper method used by generated code to bind a Callable such that it will be run in
* our disk io thread and will automatically block null values since RxJava2 does not like null.
*
+ * @deprecated Use {@link #createFlowable(RoomDatabase, boolean, String[], Callable)}
+ *
+ * @hide
+ */
+ @Deprecated
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public static <T> Flowable<T> createFlowable(final RoomDatabase database,
+ final String[] tableNames, final Callable<T> callable) {
+ return createFlowable(database, false, tableNames, callable);
+ }
+
+ /**
+ * Helper method used by generated code to bind a Callable such that it will be run in
+ * our disk io thread and will automatically block null values since RxJava2 does not like null.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public static <T> Flowable<T> createFlowable(final RoomDatabase database,
- final String[] tableNames, final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(database.getQueryExecutor());
+ final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
+ Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
final Maybe<T> maybe = Maybe.fromCallable(callable);
return createFlowable(database, tableNames)
.subscribeOn(scheduler)
@@ -161,12 +177,27 @@
* Helper method used by generated code to bind a Callable such that it will be run in
* our disk io thread and will automatically block null values since RxJava2 does not like null.
*
+ * @deprecated Use {@link #createObservable(RoomDatabase, boolean, String[], Callable)}
+ *
+ * @hide
+ */
+ @Deprecated
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ public static <T> Observable<T> createObservable(final RoomDatabase database,
+ final String[] tableNames, final Callable<T> callable) {
+ return createObservable(database, false, tableNames, callable);
+ }
+
+ /**
+ * Helper method used by generated code to bind a Callable such that it will be run in
+ * our disk io thread and will automatically block null values since RxJava2 does not like null.
+ *
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public static <T> Observable<T> createObservable(final RoomDatabase database,
- final String[] tableNames, final Callable<T> callable) {
- Scheduler scheduler = Schedulers.from(database.getQueryExecutor());
+ final boolean inTransaction, final String[] tableNames, final Callable<T> callable) {
+ Scheduler scheduler = Schedulers.from(getExecutor(database, inTransaction));
final Maybe<T> maybe = Maybe.fromCallable(callable);
return createObservable(database, tableNames)
.subscribeOn(scheduler)
@@ -180,6 +211,14 @@
});
}
+ private static Executor getExecutor(RoomDatabase database, boolean inTransaction) {
+ if (inTransaction) {
+ return database.getTransactionExecutor();
+ } else {
+ return database.getQueryExecutor();
+ }
+ }
+
/** @deprecated This type should not be instantiated as it contains only static methods. */
@Deprecated
@SuppressWarnings("PrivateConstructorForUtilityClass")
diff --git a/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
index cc96b50..dd5bebc 100644
--- a/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
+++ b/room/rxjava2/src/test/java/androidx/room/RxRoomTest.java
@@ -167,7 +167,7 @@
final AtomicReference<String> value = new AtomicReference<>(null);
String[] tables = {"a", "b"};
Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
- final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, tables,
+ final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, false, tables,
new Callable<String>() {
@Override
public String call() throws Exception {
@@ -201,7 +201,7 @@
final AtomicReference<String> value = new AtomicReference<>(null);
String[] tables = {"a", "b"};
Set<String> tableSet = new HashSet<>(Arrays.asList(tables));
- final Observable<String> flowable = RxRoom.createObservable(mDatabase, tables,
+ final Observable<String> flowable = RxRoom.createObservable(mDatabase, false, tables,
new Callable<String>() {
@Override
public String call() throws Exception {
@@ -232,7 +232,7 @@
@Test
public void exception_Flowable() throws Exception {
- final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, new String[]{"a"},
+ final Flowable<String> flowable = RxRoom.createFlowable(mDatabase, false, new String[]{"a"},
new Callable<String>() {
@Override
public String call() throws Exception {
@@ -248,7 +248,8 @@
@Test
public void exception_Observable() throws Exception {
- final Observable<String> flowable = RxRoom.createObservable(mDatabase, new String[]{"a"},
+ final Observable<String> flowable = RxRoom.createObservable(mDatabase, false,
+ new String[]{"a"},
new Callable<String>() {
@Override
public String call() throws Exception {
diff --git a/room/testing/api/2.1.0-alpha06.txt b/room/testing/api/2.1.0-alpha06.txt
new file mode 100644
index 0000000..3d2ed97
--- /dev/null
+++ b/room/testing/api/2.1.0-alpha06.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.room.testing {
+
+ public class MigrationTestHelper extends org.junit.rules.TestWatcher {
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!);
+ ctor public MigrationTestHelper(android.app.Instrumentation!, String!, androidx.sqlite.db.SupportSQLiteOpenHelper.Factory!);
+ method public void closeWhenFinished(androidx.sqlite.db.SupportSQLiteDatabase!);
+ method public void closeWhenFinished(androidx.room.RoomDatabase!);
+ method public androidx.sqlite.db.SupportSQLiteDatabase! createDatabase(String!, int) throws java.io.IOException;
+ method public androidx.sqlite.db.SupportSQLiteDatabase! runMigrationsAndValidate(String!, int, boolean, androidx.room.migration.Migration...!) throws java.io.IOException;
+ }
+
+}
+
diff --git a/room/testing/api/res-2.1.0-alpha06.txt b/room/testing/api/res-2.1.0-alpha06.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/room/testing/api/res-2.1.0-alpha06.txt
diff --git a/room/testing/api/restricted_2.1.0-alpha06.txt b/room/testing/api/restricted_2.1.0-alpha06.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/room/testing/api/restricted_2.1.0-alpha06.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
index c0e1c4b..06b5a6b 100644
--- a/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
+++ b/room/testing/src/main/java/androidx/room/testing/MigrationTestHelper.java
@@ -158,6 +158,7 @@
true,
RoomDatabase.JournalMode.TRUNCATE,
ArchTaskExecutor.getIOThreadExecutor(),
+ ArchTaskExecutor.getIOThreadExecutor(),
false,
true,
false,
@@ -215,6 +216,7 @@
true,
RoomDatabase.JournalMode.TRUNCATE,
ArchTaskExecutor.getIOThreadExecutor(),
+ ArchTaskExecutor.getIOThreadExecutor(),
false,
true,
false,
diff --git a/samples/Support4Demos/src/main/AndroidManifest.xml b/samples/Support4Demos/src/main/AndroidManifest.xml
index 52d24be..295caf0 100644
--- a/samples/Support4Demos/src/main/AndroidManifest.xml
+++ b/samples/Support4Demos/src/main/AndroidManifest.xml
@@ -136,14 +136,6 @@
</intent-filter>
</activity>
- <activity android:name=".app.FragmentNestingTabsSupport"
- android:label="@string/fragment_nesting_tabs_support">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
- </intent-filter>
- </activity>
-
<activity android:name=".app.FragmentRetainInstanceSupport"
android:label="@string/fragment_retain_instance_support">
<intent-filter>
@@ -168,22 +160,6 @@
</intent-filter>
</activity>
- <activity android:name=".app.FragmentTabs"
- android:label="@string/fragment_tabs">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
- </intent-filter>
- </activity>
-
- <activity android:name=".app.FragmentTabsPager"
- android:label="@string/fragment_tabs_pager">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
- </intent-filter>
- </activity>
-
<activity android:name=".app.FragmentPagerSupport"
android:label="@string/fragment_pager_support">
<intent-filter>
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
index 0991eb8..61b37b7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentNestingTabsSupport.java
@@ -16,33 +16,4 @@
package com.example.android.supportv4.app;
//BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-public class FragmentNestingTabsSupport extends FragmentActivity {
- private FragmentTabHost mTabHost;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mTabHost = new FragmentTabHost(this);
- setContentView(mTabHost);
- mTabHost.setup(this, getSupportFragmentManager(), R.id.fragment1);
-
- mTabHost.addTab(mTabHost.newTabSpec("menus").setIndicator("Menus"),
- FragmentMenuFragmentSupport.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
- LoaderCursorSupport.CursorLoaderListFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("stack").setIndicator("Stack"),
- FragmentStackFragmentSupport.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("tabs").setIndicator("Tabs"),
- FragmentTabsFragmentSupport.class, null);
- }
-}
//END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
index c4bbd93..25cfea4 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabs.java
@@ -16,37 +16,4 @@
package com.example.android.supportv4.app;
//BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-/**
- * This demonstrates how you can implement switching between the tabs of a
- * TabHost through fragments, using FragmentTabHost.
- */
-public class FragmentTabs extends FragmentActivity {
- private FragmentTabHost mTabHost;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.fragment_tabs);
- mTabHost = (FragmentTabHost)findViewById(android.R.id.tabhost);
- mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
-
- mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
- FragmentStackSupport.CountingFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
- LoaderCursorSupport.CursorLoaderListFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
- LoaderCustomSupport.AppListFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
- LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
- }
-}
//END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
index 06a132b..61b37b7 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsFragmentSupport.java
@@ -16,42 +16,4 @@
package com.example.android.supportv4.app;
//BEGIN_INCLUDE(complete)
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentTabHost;
-
-import com.example.android.supportv4.R;
-
-public class FragmentTabsFragmentSupport extends Fragment {
- private FragmentTabHost mTabHost;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mTabHost = new FragmentTabHost(getActivity());
- mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.fragment1);
-
- mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
- FragmentStackSupport.CountingFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"),
- LoaderCursorSupport.CursorLoaderListFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"),
- LoaderCustomSupport.AppListFragment.class, null);
- mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
- LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
-
- return mTabHost;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mTabHost = null;
- }
-}
//END_INCLUDE(complete)
diff --git a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsPager.java b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsPager.java
index 16454b0..b9898a4 100644
--- a/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsPager.java
+++ b/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentTabsPager.java
@@ -151,7 +151,7 @@
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
Fragment fragment = mFragmentFactory.instantiate(mContext.getClassLoader(),
- info.clss.getName(), info.args);
+ info.clss.getName());
fragment.setArguments(info.args);
return fragment;
}
diff --git a/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml b/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml
deleted file mode 100644
index faea9cc..0000000
--- a/samples/Support4Demos/src/main/res/layout/fragment_tabs.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/layout/tab_content.xml
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- BEGIN_INCLUDE(complete) -->
-<androidx.fragment.app.FragmentTabHost
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/tabhost"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <TabWidget
- android:id="@android:id/tabs"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_weight="0"/>
-
- <FrameLayout
- android:id="@android:id/tabcontent"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="0"/>
-
- <FrameLayout
- android:id="@+id/realtabcontent"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- </LinearLayout>
-</androidx.fragment.app.FragmentTabHost>
-<!-- END_INCLUDE(complete) -->
diff --git a/samples/Support7Demos/build.gradle b/samples/Support7Demos/build.gradle
index ca85923..69c4d67 100644
--- a/samples/Support7Demos/build.gradle
+++ b/samples/Support7Demos/build.gradle
@@ -17,4 +17,8 @@
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
+ lintOptions {
+ // TODO: Enable lint after appcompat:1.1.0 release or use lint-baseline.xml instead.
+ abortOnError false
+ }
}
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
index 12dbf34a..ef18eb7 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/RadioButtonListItemActivity.java
@@ -18,7 +18,6 @@
import android.app.Activity;
import android.content.Context;
-import android.graphics.drawable.Icon;
import android.os.Bundle;
import androidx.car.app.CarListDialog;
@@ -79,24 +78,21 @@
items.add(item);
item = new RadioButtonListItem(this);
- item.setPrimaryActionIcon(
- Icon.createWithResource(this, android.R.drawable.sym_def_app_icon),
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
item.setText("Small icon - with action divider");
item.setShowRadioButtonDivider(true);
items.add(item);
item = new RadioButtonListItem(this);
- item.setPrimaryActionIcon(
- Icon.createWithResource(this, android.R.drawable.sym_def_app_icon),
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_MEDIUM);
item.setText("Avatar sized icon - with action divider");
item.setShowRadioButtonDivider(true);
items.add(item);
item = new RadioButtonListItem(this);
- item.setPrimaryActionIcon(
- Icon.createWithResource(this, android.R.drawable.sym_def_app_icon),
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
RadioButtonListItem.PRIMARY_ACTION_ICON_SIZE_LARGE);
item.setText("Large icon");
items.add(item);
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SeekbarListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SeekbarListItemActivity.java
index 1d92856..328c32d 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SeekbarListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SeekbarListItemActivity.java
@@ -18,7 +18,6 @@
import android.app.Activity;
import android.content.Context;
-import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.Toast;
@@ -109,29 +108,25 @@
// Only slider. No text.
item = initSeekbarListItem();
item.setText(null);
- item.setPrimaryActionIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
mItems.add(item);
// One line text.
item = initSeekbarListItem();
item.setText("one line text");
- item.setPrimaryActionIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
mItems.add(item);
// Long text.
item = initSeekbarListItem();
item.setText(longText);
- item.setPrimaryActionIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
mItems.add(item);
// Clickable PrimaryActionIcon.
item = initSeekbarListItem();
item.setText("with clickable Primary icon");
- item.setPrimaryActionIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
item.setPrimaryActionIconListener(v -> Toast.makeText(mContext,
"Primary icon clicked!", Toast.LENGTH_SHORT).show());
mItems.add(item);
@@ -139,12 +134,11 @@
// Clickable PrimaryActionIcon and end icon.
item = initSeekbarListItem();
item.setText("with clickable Primary icon and end icon");
- item.setPrimaryActionIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon));
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon);
item.setPrimaryActionIconListener(v -> Toast.makeText(mContext,
"Primary icon clicked!", Toast.LENGTH_SHORT).show());
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ true);
mItems.add(item);
// End icon with divider.
@@ -153,26 +147,26 @@
item = initSeekbarListItem();
item.setText(null);
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ true);
mItems.add(item);
item = initSeekbarListItem();
item.setText("one line text");
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ true);
mItems.add(item);
item = initSeekbarListItem();
item.setText(longText);
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ true);
mItems.add(item);
item = initSeekbarListItem();
item.setText("with clickable icon");
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), true);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ true);
item.setSupplementalIconListener(v -> Toast.makeText(mContext,
"Supplemental icon clicked!", Toast.LENGTH_SHORT).show());
mItems.add(item);
@@ -202,20 +196,20 @@
item = initSeekbarListItem();
item.setText(null);
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ false);
mItems.add(item);
item = initSeekbarListItem();
item.setText("one line text");
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ false);
mItems.add(item);
item = initSeekbarListItem();
item.setText(longText);
- item.setSupplementalIcon(
- Icon.createWithResource(context, android.R.drawable.sym_def_app_icon), false);
+ item.setSupplementalIcon(android.R.drawable.sym_def_app_icon,
+ false);
mItems.add(item);
// Empty end icon without divider.
diff --git a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
index 7b9a95a..857964e 100644
--- a/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
+++ b/samples/SupportCarDemos/src/main/java/com/example/androidx/car/SwitchListItemActivity.java
@@ -103,6 +103,23 @@
mItems.add(item);
item = new SwitchListItem(mContext);
+ item.setPrimaryActionIcon(android.R.drawable.sym_def_app_icon,
+ SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+ item.setTitle("Switch with Icon");
+ item.setBody(longText);
+ item.setSwitchOnCheckedChangeListener(mListener);
+ mItems.add(item);
+
+ item = new SwitchListItem(mContext);
+ item.setTitle("Switch with Drawable");
+ item.setPrimaryActionIcon(
+ mContext.getDrawable(android.R.drawable.sym_def_app_icon),
+ SwitchListItem.PRIMARY_ACTION_ICON_SIZE_SMALL);
+ item.setBody(longText);
+ item.setSwitchOnCheckedChangeListener(mListener);
+ mItems.add(item);
+
+ item = new SwitchListItem(mContext);
item.setTitle("Clicking item toggles switch");
item.setClickable(true);
item.setSwitchOnCheckedChangeListener(mListener);
diff --git a/samples/SupportContentDemos/build.gradle b/samples/SupportContentDemos/build.gradle
index f94c0cf..507b0f0 100644
--- a/samples/SupportContentDemos/build.gradle
+++ b/samples/SupportContentDemos/build.gradle
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import static androidx.build.dependencies.DependenciesKt.SUPPORT_DESIGN
+import static androidx.build.dependencies.DependenciesKt.MATERIAL
plugins {
id("AndroidXPlugin")
@@ -23,7 +23,7 @@
}
dependencies {
- implementation(SUPPORT_DESIGN, libs.exclude_for_material)
+ implementation(MATERIAL)
implementation(project(":transition"))
implementation(project(":recyclerview"))
implementation(project(":appcompat"))
diff --git a/samples/SupportDesignDemos/build.gradle b/samples/SupportDesignDemos/build.gradle
index a075d8d..18884c4 100644
--- a/samples/SupportDesignDemos/build.gradle
+++ b/samples/SupportDesignDemos/build.gradle
@@ -1,4 +1,4 @@
-import static androidx.build.dependencies.DependenciesKt.SUPPORT_DESIGN
+import static androidx.build.dependencies.DependenciesKt.MATERIAL
buildscript {
ext.kotlin_version = '1.2.0'
@@ -11,7 +11,7 @@
}
dependencies {
- implementation(SUPPORT_DESIGN, libs.exclude_for_material)
+ implementation(MATERIAL)
implementation(project(":transition"))
implementation(project(":recyclerview"))
implementation(project(":appcompat"))
diff --git a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/SearchSupportActivity.java b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/SearchSupportActivity.java
index e59850d..8d47e01 100644
--- a/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/SearchSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/main/java/com/example/android/leanback/SearchSupportActivity.java
@@ -62,6 +62,7 @@
if (DEBUG) Log.v(TAG, "onActivityResult requestCode=" + requestCode +
" resultCode=" + resultCode +
" data=" + data);
+ super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_SPEECH && resultCode == RESULT_OK) {
mFragment.setSearchQuery(data, true);
}
diff --git a/samples/SupportMediaDemos/build.gradle b/samples/SupportMediaDemos/build.gradle
index c3c990b..ef5cd97 100644
--- a/samples/SupportMediaDemos/build.gradle
+++ b/samples/SupportMediaDemos/build.gradle
@@ -21,6 +21,7 @@
dependencies {
implementation(project(":media2-widget"))
+ implementation("androidx.appcompat:appcompat:1.0.2")
implementation(project(":core"))
}
diff --git a/samples/SupportMediaDemos/src/main/AndroidManifest.xml b/samples/SupportMediaDemos/src/main/AndroidManifest.xml
index b85b33c..54b243f 100644
--- a/samples/SupportMediaDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportMediaDemos/src/main/AndroidManifest.xml
@@ -42,13 +42,6 @@
<action android:name="android.intent.action.VIEW"/>
</intent-filter>
</activity>
-
- <receiver android:name="androidx.media.session.MediaButtonReceiver">
- <intent-filter>
- <action android:name="android.intent.action.MEDIA_BUTTON" />
- </intent-filter>
- </receiver>
-
</application>
</manifest>
diff --git a/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java b/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
index 11e0280..0d5594dd 100644
--- a/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
+++ b/samples/SupportMediaDemos/src/main/java/com/example/androidx/media/VideoPlayerActivity.java
@@ -128,7 +128,7 @@
if (intent == null || (videoUri = intent.getData()) == null || !videoUri.isAbsolute()) {
errorString = "Invalid intent";
} else {
- UriMediaItem mediaItem = new UriMediaItem.Builder(this, videoUri).build();
+ UriMediaItem mediaItem = new UriMediaItem.Builder(videoUri).build();
mVideoView.setMediaItem(mediaItem);
mMediaControlView = new MediaControlView(this);
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
index ce9e983..0ad0c33 100644
--- a/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesLeanback.java
@@ -61,7 +61,7 @@
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
final Bundle args = pref.getExtras();
final Fragment f = getChildFragmentManager().getFragmentFactory().instantiate(
- requireActivity().getClassLoader(), pref.getFragment(), args);
+ requireActivity().getClassLoader(), pref.getFragment());
f.setArguments(args);
f.setTargetFragment(caller, 0);
if (f instanceof PreferenceFragmentCompat
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/LeanbackPreferences.java b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/LeanbackPreferences.java
index 6f6c3ec..33f438c 100644
--- a/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/LeanbackPreferences.java
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/LeanbackPreferences.java
@@ -60,7 +60,7 @@
public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
final Bundle args = pref.getExtras();
final Fragment f = getChildFragmentManager().getFragmentFactory().instantiate(
- requireActivity().getClassLoader(), pref.getFragment(), args);
+ requireActivity().getClassLoader(), pref.getFragment());
f.setArguments(args);
f.setTargetFragment(caller, 0);
if (f instanceof PreferenceFragmentCompat
diff --git a/samples/SupportPreferenceDemos/src/main/res/values-v21/styles.xml b/samples/SupportPreferenceDemos/src/main/res/values-v21/styles.xml
index 4cdb7ec..426aa43 100644
--- a/samples/SupportPreferenceDemos/src/main/res/values-v21/styles.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/values-v21/styles.xml
@@ -16,6 +16,8 @@
-->
<resources>
+ <!-- Theme for the main activity that shows all fragments, change PreferenceTheme in
+ values/styles.xml for the theme for the actual fragment hosting Preferences -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<item name="android:colorPrimary">#3F51B5</item>
<item name="android:colorPrimaryDark">#303F9F</item>
diff --git a/savedstate/api/1.0.0-alpha03.txt b/savedstate/api/1.0.0-alpha03.txt
new file mode 100644
index 0000000..ff8482b
--- /dev/null
+++ b/savedstate/api/1.0.0-alpha03.txt
@@ -0,0 +1,32 @@
+// Signature format: 3.0
+package androidx.savedstate {
+
+ public final class SavedStateRegistry {
+ method @MainThread public android.os.Bundle? consumeRestoredStateForKey(String);
+ method @MainThread public boolean isRestored();
+ method @MainThread public void registerSavedStateProvider(String, androidx.savedstate.SavedStateRegistry.SavedStateProvider);
+ method @MainThread public void runOnNextRecreation(Class<? extends androidx.savedstate.SavedStateRegistry.AutoRecreated>);
+ method @MainThread public void unregisterSavedStateProvider(String);
+ }
+
+ public static interface SavedStateRegistry.AutoRecreated {
+ method public void onRecreated(androidx.savedstate.SavedStateRegistryOwner);
+ }
+
+ public static interface SavedStateRegistry.SavedStateProvider {
+ method public android.os.Bundle saveState();
+ }
+
+ public final class SavedStateRegistryController {
+ method public static androidx.savedstate.SavedStateRegistryController create(androidx.savedstate.SavedStateRegistryOwner);
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ method @MainThread public void performRestore(android.os.Bundle?);
+ method @MainThread public void performSave(android.os.Bundle);
+ }
+
+ public interface SavedStateRegistryOwner extends androidx.lifecycle.LifecycleOwner {
+ method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
+ }
+
+}
+
diff --git a/savedstate/api/res-1.0.0-alpha03.txt b/savedstate/api/res-1.0.0-alpha03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/savedstate/api/res-1.0.0-alpha03.txt
diff --git a/savedstate/api/restricted_1.0.0-alpha03.txt b/savedstate/api/restricted_1.0.0-alpha03.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/savedstate/api/restricted_1.0.0-alpha03.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/security/crypto/api/1.0.0-alpha01.txt b/security/crypto/api/1.0.0-alpha01.txt
index da4f6cc..261883a 100644
--- a/security/crypto/api/1.0.0-alpha01.txt
+++ b/security/crypto/api/1.0.0-alpha01.txt
@@ -1 +1,296 @@
// Signature format: 3.0
+package androidx.security {
+
+ public class SecureConfig {
+ method public String getAndroidCAStore();
+ method public String getAndroidKeyStore();
+ method public String getAsymmetricBlockModes();
+ method public String getAsymmetricCipherTransformation();
+ method public String getAsymmetricKeyPairAlgorithm();
+ method public int getAsymmetricKeyPurposes();
+ method public int getAsymmetricKeySize();
+ method public String getAsymmetricPaddings();
+ method public boolean getAsymmetricRequireUserAuthEnabled();
+ method public int getAsymmetricRequireUserValiditySeconds();
+ method public boolean getAsymmetricSensitiveDataProtectionEnabled();
+ method public androidx.security.biometric.BiometricKeyAuthCallback getBiometricKeyAuthCallback();
+ method public String getCertPath();
+ method public String getCertPathValidator();
+ method public String[] getClientCertAlgorithms();
+ method public static androidx.security.SecureConfig getDefault();
+ method public String getKeystoreType();
+ method public static androidx.security.SecureConfig getNiapConfig();
+ method public static androidx.security.SecureConfig getNiapConfig(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public String getSignatureAlgorithm();
+ method public String[] getStrongSSLCiphers();
+ method public String getSymmetricBlockModes();
+ method public String getSymmetricCipherTransformation();
+ method public int getSymmetricGcmTagLength();
+ method public String getSymmetricKeyAlgorithm();
+ method public int getSymmetricKeyPurposes();
+ method public int getSymmetricKeySize();
+ method public String getSymmetricPaddings();
+ method public boolean getSymmetricRequireUserAuthEnabled();
+ method public int getSymmetricRequireUserValiditySeconds();
+ method public boolean getSymmetricSensitiveDataProtectionEnabled();
+ method public androidx.security.config.TrustAnchorOptions getTrustAnchorOptions();
+ method public boolean getUseStrongSSLCiphers();
+ method public boolean getUseStrongSSLCiphersEnabled();
+ method public void setAndroidCAStore(String);
+ method public void setAndroidKeyStore(String);
+ method public void setAsymmetricBlockModes(String);
+ method public void setAsymmetricCipherTransformation(String);
+ method public void setAsymmetricKeyPairAlgorithm(String);
+ method public void setAsymmetricKeyPurposes(int);
+ method public void setAsymmetricKeySize(int);
+ method public void setAsymmetricPaddings(String);
+ method public void setAsymmetricRequireUserAuth(boolean);
+ method public void setAsymmetricRequireUserValiditySeconds(int);
+ method public void setAsymmetricSensitiveDataProtection(boolean);
+ method public void setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public void setCertPath(String);
+ method public void setCertPathValidator(String);
+ method public void setClientCertAlgorithms(String[]);
+ method public void setKeystoreType(String);
+ method public void setSignatureAlgorithm(String);
+ method public void setStrongSSLCiphers(String[]);
+ method public void setSymmetricBlockModes(String);
+ method public void setSymmetricCipherTransformation(String);
+ method public void setSymmetricGcmTagLength(int);
+ method public void setSymmetricKeyAlgorithm(String);
+ method public void setSymmetricKeyPurposes(int);
+ method public void setSymmetricKeySize(int);
+ method public void setSymmetricPaddings(String);
+ method public void setSymmetricRequireUserAuth(boolean);
+ method public void setSymmetricRequireUserValiditySeconds(int);
+ method public void setSymmetricSensitiveDataProtection(boolean);
+ method public void setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
+ method public void setUseStrongSSLCiphers(boolean);
+ field public static final int AES_IV_SIZE_BYTES = 16; // 0x10
+ field public static final String ANDROID_CA_STORE = "AndroidCAStore";
+ field public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+ field public static final String SSL_TLS = "TLS";
+ }
+
+ public static class SecureConfig.Builder {
+ ctor public SecureConfig.Builder();
+ method public androidx.security.SecureConfig build();
+ method public androidx.security.SecureConfig.Builder forKeyStoreType(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricBlockModes(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricCipherTransformation(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeyPairAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeyPurposes(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeySize(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricPaddings(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserAuth(boolean);
+ method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserValiditySeconds(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricSensitiveDataProtection(boolean);
+ method public androidx.security.SecureConfig.Builder setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public androidx.security.SecureConfig.Builder setCertPath(String);
+ method public androidx.security.SecureConfig.Builder setCertPathValidator(String);
+ method public androidx.security.SecureConfig.Builder setClientCertAlgorithms(String[]);
+ method public androidx.security.SecureConfig.Builder setSignatureAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setStrongSSLCiphers(String[]);
+ method public androidx.security.SecureConfig.Builder setSymmetricBlockModes(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricCipherTransformation(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricGcmTagLength(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeyAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeyPurposes(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeySize(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricPaddings(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricRequireUserAuth(boolean);
+ method public androidx.security.SecureConfig.Builder setSymmetricRequireUserValiditySeconds(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricSensitiveDataProtection(boolean);
+ method public androidx.security.SecureConfig.Builder setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
+ method public androidx.security.SecureConfig.Builder setUseStrongSSLCiphers(boolean);
+ }
+
+}
+
+package androidx.security.biometric {
+
+ public class BiometricKeyAuth extends androidx.biometric.BiometricPrompt.AuthenticationCallback {
+ ctor public BiometricKeyAuth(androidx.fragment.app.FragmentActivity, androidx.security.biometric.BiometricKeyAuthCallback);
+ method public void authenticateKey(javax.crypto.Cipher, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(java.security.Signature, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ }
+
+ public abstract class BiometricKeyAuthCallback {
+ ctor public BiometricKeyAuthCallback();
+ method public abstract void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public abstract void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public abstract void onAuthenticationError(int, CharSequence);
+ method public abstract void onAuthenticationFailed();
+ method public abstract void onAuthenticationSucceeded();
+ method public abstract void onMessage(String);
+ }
+
+ public enum BiometricKeyAuthCallback.BiometricStatus {
+ method public static androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus ERROR;
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus FAILED;
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus SUCCESS;
+ }
+
+}
+
+package androidx.security.config {
+
+ public final class TldConstants {
+ field public static final java.util.List<java.lang.String> VALID_TLDS;
+ }
+
+ public enum TrustAnchorOptions {
+ method public static androidx.security.config.TrustAnchorOptions fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.config.TrustAnchorOptions LIMITED_SYSTEM;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions SYSTEM_ONLY;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions USER_ONLY;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions USER_SYSTEM;
+ }
+
+}
+
+package androidx.security.context {
+
+ public class SecureContextCompat {
+ ctor public SecureContextCompat(android.content.Context);
+ ctor public SecureContextCompat(android.content.Context, androidx.security.SecureConfig);
+ method public boolean deviceLocked();
+ method public void openEncryptedFileInput(String, java.util.concurrent.Executor, androidx.security.context.SecureContextCompat.EncryptedFileInputStreamListener) throws java.io.IOException;
+ method public java.io.FileOutputStream openEncryptedFileOutput(String, int) throws java.io.IOException;
+ method public java.io.FileOutputStream openEncryptedFileOutput(String, int, String) throws java.io.IOException;
+ }
+
+ public static interface SecureContextCompat.EncryptedFileInputStreamListener {
+ method public void onEncryptedFileInput(java.io.FileInputStream);
+ }
+
+}
+
+package androidx.security.crypto {
+
+ public class EphemeralSecretKey implements java.security.spec.KeySpec javax.crypto.SecretKey {
+ ctor public EphemeralSecretKey(byte[]);
+ ctor public EphemeralSecretKey(byte[], String);
+ ctor public EphemeralSecretKey(byte[], androidx.security.SecureConfig);
+ ctor public EphemeralSecretKey(byte[], String, androidx.security.SecureConfig);
+ ctor public EphemeralSecretKey(byte[], int, int, String);
+ method public void destroy();
+ method public void destroyCipherKey(javax.crypto.Cipher, int);
+ method public String getAlgorithm();
+ method public byte[] getEncoded();
+ method public String getFormat();
+ }
+
+ public class SecureCipher {
+ ctor public SecureCipher(androidx.security.SecureConfig);
+ method public void decrypt(String, byte[], byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public void decryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public void decryptEncodedData(byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public byte[] decryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[], byte[]);
+ method public byte[] encodeAsymmetricData(byte[], byte[]);
+ method public byte[] encodeEphemeralData(byte[], byte[], byte[], byte[]);
+ method public byte[] encodeSymmetricData(byte[], byte[], byte[]);
+ method public void encrypt(String, byte[], androidx.security.crypto.SecureCipher.SecureSymmetricEncryptionListener);
+ method public void encryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureAsymmetricEncryptionListener);
+ method public android.util.Pair<byte[],byte[]> encryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[]);
+ method public static androidx.security.crypto.SecureCipher getDefault();
+ method public static androidx.security.crypto.SecureCipher getDefault(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public static androidx.security.crypto.SecureCipher getInstance(androidx.security.SecureConfig);
+ method public void sign(String, byte[], androidx.security.crypto.SecureCipher.SecureSignListener);
+ method public boolean verify(String, byte[], byte[]);
+ }
+
+ public static interface SecureCipher.SecureAsymmetricEncryptionListener {
+ method public void encryptionComplete(byte[]);
+ }
+
+ public static interface SecureCipher.SecureAuthListener {
+ method public void authComplete(androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus);
+ }
+
+ public static interface SecureCipher.SecureDecryptionListener {
+ method public void decryptionComplete(byte[]);
+ }
+
+ public enum SecureCipher.SecureFileEncodingType {
+ method public static androidx.security.crypto.SecureCipher.SecureFileEncodingType fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType ASYMMETRIC;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType EPHEMERAL;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType NOT_ENCRYPTED;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType SYMMETRIC;
+ }
+
+ public static interface SecureCipher.SecureSignListener {
+ method public void signComplete(byte[]);
+ }
+
+ public static interface SecureCipher.SecureSymmetricEncryptionListener {
+ method public void encryptionComplete(byte[], byte[]);
+ }
+
+ public class SecureKeyGenerator {
+ method public boolean generateAsymmetricKeyPair(String);
+ method public androidx.security.crypto.EphemeralSecretKey generateEphemeralDataKey();
+ method public boolean generateKey(String);
+ method public static androidx.security.crypto.SecureKeyGenerator getDefault();
+ method public static androidx.security.crypto.SecureKeyGenerator getInstance(androidx.security.SecureConfig);
+ }
+
+ public class SecureKeyStore {
+ method public boolean checkKeyInsideSecureHardware(String);
+ method public boolean checkKeyInsideSecureHardwareAsymmetric(String);
+ method public void deleteKey(String);
+ method public static androidx.security.crypto.SecureKeyStore getDefault();
+ method public static androidx.security.crypto.SecureKeyStore getInstance(androidx.security.SecureConfig);
+ method public boolean keyExists(String);
+ }
+
+}
+
+package androidx.security.net {
+
+ public class SecureKeyManager implements android.security.KeyChainAliasCallback javax.net.ssl.X509KeyManager {
+ ctor public SecureKeyManager(String, androidx.security.SecureConfig);
+ method public void alias(String);
+ method public String chooseClientAlias(String[], java.security.Principal[], java.net.Socket);
+ method public final String chooseServerAlias(String, java.security.Principal[], java.net.Socket);
+ method public java.security.cert.X509Certificate[] getCertificateChain(String);
+ method public final String[] getClientAliases(String, java.security.Principal[]);
+ method public static androidx.security.net.SecureKeyManager getDefault(String);
+ method public static androidx.security.net.SecureKeyManager getDefault(String, androidx.security.SecureConfig);
+ method public java.security.PrivateKey getPrivateKey(String);
+ method public final String[] getServerAliases(String, java.security.Principal[]);
+ method public static androidx.security.net.SecureKeyManager installCertManually(androidx.security.net.SecureKeyManager.CertType, byte[], String, androidx.security.SecureConfig);
+ method public void setCertChain(java.security.cert.X509Certificate[]);
+ method public static void setContext(android.app.Activity);
+ method public void setPrivateKey(java.security.PrivateKey);
+ }
+
+ public enum SecureKeyManager.CertType {
+ method public static androidx.security.net.SecureKeyManager.CertType fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType NOT_SUPPORTED;
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType PKCS12;
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType X509;
+ }
+
+ public class SecureURL {
+ ctor public SecureURL(String) throws java.net.MalformedURLException;
+ ctor public SecureURL(String, String) throws java.net.MalformedURLException;
+ ctor public SecureURL(String, String, androidx.security.SecureConfig) throws java.net.MalformedURLException;
+ method public String getClientCertAlias();
+ method public String getHostname();
+ method public int getPort();
+ method public boolean isValid(javax.net.ssl.HttpsURLConnection);
+ method public java.net.URLConnection openConnection() throws java.io.IOException;
+ method public java.net.URLConnection openUserTrustedCertConnection(java.util.Map<java.lang.String,java.io.InputStream>) throws java.io.IOException;
+ }
+
+}
+
diff --git a/security/crypto/api/current.txt b/security/crypto/api/current.txt
index da4f6cc..261883a 100644
--- a/security/crypto/api/current.txt
+++ b/security/crypto/api/current.txt
@@ -1 +1,296 @@
// Signature format: 3.0
+package androidx.security {
+
+ public class SecureConfig {
+ method public String getAndroidCAStore();
+ method public String getAndroidKeyStore();
+ method public String getAsymmetricBlockModes();
+ method public String getAsymmetricCipherTransformation();
+ method public String getAsymmetricKeyPairAlgorithm();
+ method public int getAsymmetricKeyPurposes();
+ method public int getAsymmetricKeySize();
+ method public String getAsymmetricPaddings();
+ method public boolean getAsymmetricRequireUserAuthEnabled();
+ method public int getAsymmetricRequireUserValiditySeconds();
+ method public boolean getAsymmetricSensitiveDataProtectionEnabled();
+ method public androidx.security.biometric.BiometricKeyAuthCallback getBiometricKeyAuthCallback();
+ method public String getCertPath();
+ method public String getCertPathValidator();
+ method public String[] getClientCertAlgorithms();
+ method public static androidx.security.SecureConfig getDefault();
+ method public String getKeystoreType();
+ method public static androidx.security.SecureConfig getNiapConfig();
+ method public static androidx.security.SecureConfig getNiapConfig(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public String getSignatureAlgorithm();
+ method public String[] getStrongSSLCiphers();
+ method public String getSymmetricBlockModes();
+ method public String getSymmetricCipherTransformation();
+ method public int getSymmetricGcmTagLength();
+ method public String getSymmetricKeyAlgorithm();
+ method public int getSymmetricKeyPurposes();
+ method public int getSymmetricKeySize();
+ method public String getSymmetricPaddings();
+ method public boolean getSymmetricRequireUserAuthEnabled();
+ method public int getSymmetricRequireUserValiditySeconds();
+ method public boolean getSymmetricSensitiveDataProtectionEnabled();
+ method public androidx.security.config.TrustAnchorOptions getTrustAnchorOptions();
+ method public boolean getUseStrongSSLCiphers();
+ method public boolean getUseStrongSSLCiphersEnabled();
+ method public void setAndroidCAStore(String);
+ method public void setAndroidKeyStore(String);
+ method public void setAsymmetricBlockModes(String);
+ method public void setAsymmetricCipherTransformation(String);
+ method public void setAsymmetricKeyPairAlgorithm(String);
+ method public void setAsymmetricKeyPurposes(int);
+ method public void setAsymmetricKeySize(int);
+ method public void setAsymmetricPaddings(String);
+ method public void setAsymmetricRequireUserAuth(boolean);
+ method public void setAsymmetricRequireUserValiditySeconds(int);
+ method public void setAsymmetricSensitiveDataProtection(boolean);
+ method public void setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public void setCertPath(String);
+ method public void setCertPathValidator(String);
+ method public void setClientCertAlgorithms(String[]);
+ method public void setKeystoreType(String);
+ method public void setSignatureAlgorithm(String);
+ method public void setStrongSSLCiphers(String[]);
+ method public void setSymmetricBlockModes(String);
+ method public void setSymmetricCipherTransformation(String);
+ method public void setSymmetricGcmTagLength(int);
+ method public void setSymmetricKeyAlgorithm(String);
+ method public void setSymmetricKeyPurposes(int);
+ method public void setSymmetricKeySize(int);
+ method public void setSymmetricPaddings(String);
+ method public void setSymmetricRequireUserAuth(boolean);
+ method public void setSymmetricRequireUserValiditySeconds(int);
+ method public void setSymmetricSensitiveDataProtection(boolean);
+ method public void setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
+ method public void setUseStrongSSLCiphers(boolean);
+ field public static final int AES_IV_SIZE_BYTES = 16; // 0x10
+ field public static final String ANDROID_CA_STORE = "AndroidCAStore";
+ field public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+ field public static final String SSL_TLS = "TLS";
+ }
+
+ public static class SecureConfig.Builder {
+ ctor public SecureConfig.Builder();
+ method public androidx.security.SecureConfig build();
+ method public androidx.security.SecureConfig.Builder forKeyStoreType(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricBlockModes(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricCipherTransformation(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeyPairAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeyPurposes(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricKeySize(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricPaddings(String);
+ method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserAuth(boolean);
+ method public androidx.security.SecureConfig.Builder setAsymmetricRequireUserValiditySeconds(int);
+ method public androidx.security.SecureConfig.Builder setAsymmetricSensitiveDataProtection(boolean);
+ method public androidx.security.SecureConfig.Builder setBiometricKeyAuthCallback(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public androidx.security.SecureConfig.Builder setCertPath(String);
+ method public androidx.security.SecureConfig.Builder setCertPathValidator(String);
+ method public androidx.security.SecureConfig.Builder setClientCertAlgorithms(String[]);
+ method public androidx.security.SecureConfig.Builder setSignatureAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setStrongSSLCiphers(String[]);
+ method public androidx.security.SecureConfig.Builder setSymmetricBlockModes(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricCipherTransformation(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricGcmTagLength(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeyAlgorithm(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeyPurposes(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricKeySize(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricPaddings(String);
+ method public androidx.security.SecureConfig.Builder setSymmetricRequireUserAuth(boolean);
+ method public androidx.security.SecureConfig.Builder setSymmetricRequireUserValiditySeconds(int);
+ method public androidx.security.SecureConfig.Builder setSymmetricSensitiveDataProtection(boolean);
+ method public androidx.security.SecureConfig.Builder setTrustAnchorOptions(androidx.security.config.TrustAnchorOptions);
+ method public androidx.security.SecureConfig.Builder setUseStrongSSLCiphers(boolean);
+ }
+
+}
+
+package androidx.security.biometric {
+
+ public class BiometricKeyAuth extends androidx.biometric.BiometricPrompt.AuthenticationCallback {
+ ctor public BiometricKeyAuth(androidx.fragment.app.FragmentActivity, androidx.security.biometric.BiometricKeyAuthCallback);
+ method public void authenticateKey(javax.crypto.Cipher, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(java.security.Signature, androidx.biometric.BiometricPrompt.PromptInfo, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ }
+
+ public abstract class BiometricKeyAuthCallback {
+ ctor public BiometricKeyAuthCallback();
+ method public abstract void authenticateKey(javax.crypto.Cipher, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public abstract void authenticateKey(java.security.Signature, androidx.security.crypto.SecureCipher.SecureAuthListener);
+ method public abstract void onAuthenticationError(int, CharSequence);
+ method public abstract void onAuthenticationFailed();
+ method public abstract void onAuthenticationSucceeded();
+ method public abstract void onMessage(String);
+ }
+
+ public enum BiometricKeyAuthCallback.BiometricStatus {
+ method public static androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus ERROR;
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus FAILED;
+ enum_constant public static final androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus SUCCESS;
+ }
+
+}
+
+package androidx.security.config {
+
+ public final class TldConstants {
+ field public static final java.util.List<java.lang.String> VALID_TLDS;
+ }
+
+ public enum TrustAnchorOptions {
+ method public static androidx.security.config.TrustAnchorOptions fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.config.TrustAnchorOptions LIMITED_SYSTEM;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions SYSTEM_ONLY;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions USER_ONLY;
+ enum_constant public static final androidx.security.config.TrustAnchorOptions USER_SYSTEM;
+ }
+
+}
+
+package androidx.security.context {
+
+ public class SecureContextCompat {
+ ctor public SecureContextCompat(android.content.Context);
+ ctor public SecureContextCompat(android.content.Context, androidx.security.SecureConfig);
+ method public boolean deviceLocked();
+ method public void openEncryptedFileInput(String, java.util.concurrent.Executor, androidx.security.context.SecureContextCompat.EncryptedFileInputStreamListener) throws java.io.IOException;
+ method public java.io.FileOutputStream openEncryptedFileOutput(String, int) throws java.io.IOException;
+ method public java.io.FileOutputStream openEncryptedFileOutput(String, int, String) throws java.io.IOException;
+ }
+
+ public static interface SecureContextCompat.EncryptedFileInputStreamListener {
+ method public void onEncryptedFileInput(java.io.FileInputStream);
+ }
+
+}
+
+package androidx.security.crypto {
+
+ public class EphemeralSecretKey implements java.security.spec.KeySpec javax.crypto.SecretKey {
+ ctor public EphemeralSecretKey(byte[]);
+ ctor public EphemeralSecretKey(byte[], String);
+ ctor public EphemeralSecretKey(byte[], androidx.security.SecureConfig);
+ ctor public EphemeralSecretKey(byte[], String, androidx.security.SecureConfig);
+ ctor public EphemeralSecretKey(byte[], int, int, String);
+ method public void destroy();
+ method public void destroyCipherKey(javax.crypto.Cipher, int);
+ method public String getAlgorithm();
+ method public byte[] getEncoded();
+ method public String getFormat();
+ }
+
+ public class SecureCipher {
+ ctor public SecureCipher(androidx.security.SecureConfig);
+ method public void decrypt(String, byte[], byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public void decryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public void decryptEncodedData(byte[], androidx.security.crypto.SecureCipher.SecureDecryptionListener);
+ method public byte[] decryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[], byte[]);
+ method public byte[] encodeAsymmetricData(byte[], byte[]);
+ method public byte[] encodeEphemeralData(byte[], byte[], byte[], byte[]);
+ method public byte[] encodeSymmetricData(byte[], byte[], byte[]);
+ method public void encrypt(String, byte[], androidx.security.crypto.SecureCipher.SecureSymmetricEncryptionListener);
+ method public void encryptAsymmetric(String, byte[], androidx.security.crypto.SecureCipher.SecureAsymmetricEncryptionListener);
+ method public android.util.Pair<byte[],byte[]> encryptEphemeralData(androidx.security.crypto.EphemeralSecretKey, byte[]);
+ method public static androidx.security.crypto.SecureCipher getDefault();
+ method public static androidx.security.crypto.SecureCipher getDefault(androidx.security.biometric.BiometricKeyAuthCallback);
+ method public static androidx.security.crypto.SecureCipher getInstance(androidx.security.SecureConfig);
+ method public void sign(String, byte[], androidx.security.crypto.SecureCipher.SecureSignListener);
+ method public boolean verify(String, byte[], byte[]);
+ }
+
+ public static interface SecureCipher.SecureAsymmetricEncryptionListener {
+ method public void encryptionComplete(byte[]);
+ }
+
+ public static interface SecureCipher.SecureAuthListener {
+ method public void authComplete(androidx.security.biometric.BiometricKeyAuthCallback.BiometricStatus);
+ }
+
+ public static interface SecureCipher.SecureDecryptionListener {
+ method public void decryptionComplete(byte[]);
+ }
+
+ public enum SecureCipher.SecureFileEncodingType {
+ method public static androidx.security.crypto.SecureCipher.SecureFileEncodingType fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType ASYMMETRIC;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType EPHEMERAL;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType NOT_ENCRYPTED;
+ enum_constant public static final androidx.security.crypto.SecureCipher.SecureFileEncodingType SYMMETRIC;
+ }
+
+ public static interface SecureCipher.SecureSignListener {
+ method public void signComplete(byte[]);
+ }
+
+ public static interface SecureCipher.SecureSymmetricEncryptionListener {
+ method public void encryptionComplete(byte[], byte[]);
+ }
+
+ public class SecureKeyGenerator {
+ method public boolean generateAsymmetricKeyPair(String);
+ method public androidx.security.crypto.EphemeralSecretKey generateEphemeralDataKey();
+ method public boolean generateKey(String);
+ method public static androidx.security.crypto.SecureKeyGenerator getDefault();
+ method public static androidx.security.crypto.SecureKeyGenerator getInstance(androidx.security.SecureConfig);
+ }
+
+ public class SecureKeyStore {
+ method public boolean checkKeyInsideSecureHardware(String);
+ method public boolean checkKeyInsideSecureHardwareAsymmetric(String);
+ method public void deleteKey(String);
+ method public static androidx.security.crypto.SecureKeyStore getDefault();
+ method public static androidx.security.crypto.SecureKeyStore getInstance(androidx.security.SecureConfig);
+ method public boolean keyExists(String);
+ }
+
+}
+
+package androidx.security.net {
+
+ public class SecureKeyManager implements android.security.KeyChainAliasCallback javax.net.ssl.X509KeyManager {
+ ctor public SecureKeyManager(String, androidx.security.SecureConfig);
+ method public void alias(String);
+ method public String chooseClientAlias(String[], java.security.Principal[], java.net.Socket);
+ method public final String chooseServerAlias(String, java.security.Principal[], java.net.Socket);
+ method public java.security.cert.X509Certificate[] getCertificateChain(String);
+ method public final String[] getClientAliases(String, java.security.Principal[]);
+ method public static androidx.security.net.SecureKeyManager getDefault(String);
+ method public static androidx.security.net.SecureKeyManager getDefault(String, androidx.security.SecureConfig);
+ method public java.security.PrivateKey getPrivateKey(String);
+ method public final String[] getServerAliases(String, java.security.Principal[]);
+ method public static androidx.security.net.SecureKeyManager installCertManually(androidx.security.net.SecureKeyManager.CertType, byte[], String, androidx.security.SecureConfig);
+ method public void setCertChain(java.security.cert.X509Certificate[]);
+ method public static void setContext(android.app.Activity);
+ method public void setPrivateKey(java.security.PrivateKey);
+ }
+
+ public enum SecureKeyManager.CertType {
+ method public static androidx.security.net.SecureKeyManager.CertType fromId(int);
+ method public int getType();
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType NOT_SUPPORTED;
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType PKCS12;
+ enum_constant public static final androidx.security.net.SecureKeyManager.CertType X509;
+ }
+
+ public class SecureURL {
+ ctor public SecureURL(String) throws java.net.MalformedURLException;
+ ctor public SecureURL(String, String) throws java.net.MalformedURLException;
+ ctor public SecureURL(String, String, androidx.security.SecureConfig) throws java.net.MalformedURLException;
+ method public String getClientCertAlias();
+ method public String getHostname();
+ method public int getPort();
+ method public boolean isValid(javax.net.ssl.HttpsURLConnection);
+ method public java.net.URLConnection openConnection() throws java.io.IOException;
+ method public java.net.URLConnection openUserTrustedCertConnection(java.util.Map<java.lang.String,java.io.InputStream>) throws java.io.IOException;
+ }
+
+}
+
diff --git a/security/crypto/api/restricted_1.0.0-alpha01.txt b/security/crypto/api/restricted_1.0.0-alpha01.txt
index da4f6cc..23579e8 100644
--- a/security/crypto/api/restricted_1.0.0-alpha01.txt
+++ b/security/crypto/api/restricted_1.0.0-alpha01.txt
@@ -1 +1,6 @@
// Signature format: 3.0
+package androidx.security.crypto {
+
+
+}
+
diff --git a/security/crypto/api/restricted_current.txt b/security/crypto/api/restricted_current.txt
index da4f6cc..23579e8 100644
--- a/security/crypto/api/restricted_current.txt
+++ b/security/crypto/api/restricted_current.txt
@@ -1 +1,6 @@
// Signature format: 3.0
+package androidx.security.crypto {
+
+
+}
+
diff --git a/security/crypto/build.gradle b/security/crypto/build.gradle
index 03e11af..143950b 100644
--- a/security/crypto/build.gradle
+++ b/security/crypto/build.gradle
@@ -23,7 +23,17 @@
id("kotlin-android")
}
-dependencies {}
+dependencies {
+ api("androidx.annotation:annotation:1.0.0") { transitive = true}
+ api("androidx.core:core:1.0.0") { transitive = true}
+ api("androidx.fragment:fragment:1.0.0") { transitive = true}
+ api("androidx.biometric:biometric:1.0.0-alpha03")
+
+ androidTestImplementation(TEST_EXT_JUNIT)
+ androidTestImplementation(TEST_CORE)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(MOCKITO_CORE)
+}
android {
defaultConfig {
@@ -33,7 +43,7 @@
supportLibrary {
name = "AndroidX Security"
- publish = false
+ publish = true
mavenVersion = LibraryVersions.SECURITY
mavenGroup = LibraryGroups.SECURITY
inceptionYear = "2019"
diff --git a/security/crypto/gradle/wrapper/gradle-wrapper.jar b/security/crypto/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
--- /dev/null
+++ b/security/crypto/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/security/crypto/gradle/wrapper/gradle-wrapper.properties b/security/crypto/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..cd7db12
--- /dev/null
+++ b/security/crypto/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Feb 19 15:29:35 UTC 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
diff --git a/security/crypto/gradlew b/security/crypto/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/security/crypto/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/security/crypto/gradlew.bat b/security/crypto/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/security/crypto/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/security/crypto/src/androidTest/AndroidManifest.xml b/security/crypto/src/androidTest/AndroidManifest.xml
index 7affe51..4c91032 100644
--- a/security/crypto/src/androidTest/AndroidManifest.xml
+++ b/security/crypto/src/androidTest/AndroidManifest.xml
@@ -17,4 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.security.tests">
<uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
+ <uses-permission android:name="android.permission.INTERNET" />
</manifest>
diff --git a/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java b/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java
new file mode 100644
index 0000000..c526f13
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/context/SecureContextCompatTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.context;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+import androidx.security.crypto.SecureKeyGenerator;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class SecureContextCompatTest {
+
+
+ private Context mContext;
+ private static final String KEYPAIR = "file_key";
+
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(SecureConfig.getDefault());
+ keyGenerator.generateAsymmetricKeyPair(KEYPAIR);
+ }
+
+ @Test
+ public void testWriteEncryptedFile() {
+ String fileContent = "SOME TEST DATA!";
+ String fileName = "test_file";
+
+ SecureContextCompat secureContextCompat = new SecureContextCompat(mContext);
+ try {
+ FileOutputStream outputStream = secureContextCompat.openEncryptedFileOutput(fileName,
+ Context.MODE_PRIVATE, KEYPAIR);
+ outputStream.write(fileContent.getBytes("UTF-8"));
+ outputStream.flush();
+ outputStream.close();
+
+ FileInputStream fileInputStream = mContext.openFileInput(fileName);
+ byte[] rawBytes = new byte[fileInputStream.available()];
+ fileInputStream.read(rawBytes);
+ Assert.assertNotEquals("Contents should differ, data was not encrypted.",
+ fileContent, new String(rawBytes, "UTF-8"));
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testReadEncryptedFile() {
+ final String fileContent = "SOME TEST DATA!";
+ final String fileName = "test_file";
+ SecureContextCompat secureContextCompat = new SecureContextCompat(mContext);
+ try {
+ secureContextCompat.openEncryptedFileInput(fileName,
+ null,
+ new SecureContextCompat.EncryptedFileInputStreamListener() {
+ @Override
+ public void onEncryptedFileInput(@NonNull FileInputStream inputStream) {
+ try {
+ byte[] rawBytes = new byte[inputStream.available()];
+ inputStream.read(rawBytes);
+ Assert.assertNotEquals(
+ "Contents should be equal, data was encrypted.",
+ fileContent, new String(rawBytes, "UTF-8"));
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ });
+
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+}
+
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java
new file mode 100644
index 0000000..2529319
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureCipherTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class SecureCipherTest {
+
+ @Test
+ public void testEncryptDecryptSymmetricData() {
+ final String keyAlias = "test_signing_key";
+ final String original = "It's a secret...";
+ try {
+ SecureConfig config = SecureConfig.getDefault();
+ final SecureCipher cipher = new SecureCipher(config);
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(config);
+ keyGenerator.generateKey(keyAlias);
+ cipher.encrypt(keyAlias, original.getBytes("UTF-8"),
+ new SecureCipher.SecureSymmetricEncryptionListener() {
+ @Override
+ public void encryptionComplete(@NonNull byte[] cipherText,
+ @NonNull byte[] iv) {
+ cipher.decrypt(keyAlias, cipherText, iv,
+ new SecureCipher.SecureDecryptionListener() {
+ @Override
+ public void decryptionComplete(
+ @NonNull byte[] clearText) {
+ try {
+ Assert.assertEquals(
+ "Original should match"
+ + "encrypted/decrypted data",
+ original,
+ new String(clearText,
+ "UTF-8"));
+ } catch (UnsupportedEncodingException ex) {
+ ex.printStackTrace();
+ }
+ }
+ });
+
+ }
+ });
+
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+ }
+
+ @Test
+ public void testSignVerifyData() {
+ final String keyAlias = "test_signing_key";
+ final String original = "It's a secret...";
+ try {
+ SecureConfig config = SecureConfig.getDefault();
+ final SecureCipher cipher = new SecureCipher(config);
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getInstance(config);
+ keyGenerator.generateAsymmetricKeyPair(keyAlias);
+ cipher.sign(keyAlias, original.getBytes("UTF-8"),
+ new SecureCipher.SecureSignListener() {
+ @Override
+ public void signComplete(@NonNull byte[] signature) {
+ try {
+ Assert.assertTrue(
+ "Signature should verify",
+ cipher.verify(keyAlias,
+ original.getBytes("UTF-8"),
+ signature));
+ } catch (UnsupportedEncodingException ex) {
+ ex.printStackTrace();
+ }
+ }
+ });
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+ }
+
+}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java
new file mode 100644
index 0000000..293f9a2
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/SecureKeyStoreTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SecureKeyStoreTest {
+
+ @Test
+ public void testKeyDoesNotExist() throws Throwable {
+ SecureKeyStore secureKeyStore = SecureKeyStore.getDefault();
+ boolean keyExists = secureKeyStore.keyExists("Not_a_real_key");
+ Assert.assertFalse("Key has not been created and should not exist!", keyExists);
+ }
+
+ @Test
+ public void testSymmetricKeyExists() throws Throwable {
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
+ keyGenerator.generateKey("symmetric_key");
+
+ SecureKeyStore keyStore = SecureKeyStore.getDefault();
+ boolean keyExists = keyStore.keyExists("symmetric_key");
+
+ Assert.assertTrue("Symmetric Key should exist!", keyExists);
+ }
+
+ @Test
+ public void testDeleteSymmetricKey() throws Throwable {
+ SecureKeyStore keyStore = SecureKeyStore.getDefault();
+ keyStore.deleteKey("symmetric_key");
+
+ boolean keyExists = keyStore.keyExists("symmetric_key");
+
+ Assert.assertFalse("Symmetric Key should have been deleted!", keyExists);
+ }
+
+ @Test
+ public void testAsymmetricKeyExists() throws Throwable {
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
+ keyGenerator.generateAsymmetricKeyPair("asymmetric_key_pair");
+
+ SecureKeyStore keyStore = SecureKeyStore.getDefault();
+ boolean keyExists = keyStore.keyExists("asymmetric_key_pair");
+
+ Assert.assertTrue("Asymmetric Key should exist!", keyExists);
+ }
+
+ @Test
+ public void testDeleteAsymmetricKey() throws Throwable {
+ SecureKeyStore keyStore = SecureKeyStore.getDefault();
+ keyStore.deleteKey("asymmetric_key_pair");
+
+ boolean keyExists = keyStore.keyExists("asymmetric_key_pair");
+
+ Assert.assertFalse("Asymmetric Key should have been deleted!", keyExists);
+ }
+
+}
diff --git a/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java b/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java
new file mode 100644
index 0000000..793b8aa
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/net/SecureURLTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.net;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+import javax.net.ssl.HttpsURLConnection;
+
+@SuppressWarnings("unchecked")
+@RunWith(JUnit4.class)
+public class SecureURLTest {
+
+ @Test
+ public void testValidHttpsUrlConnection() {
+ String url = "https://www.google.com";
+ try {
+ SecureURL secureURL = new SecureURL(url);
+ HttpsURLConnection connection = (HttpsURLConnection) secureURL.openConnection();
+
+
+ boolean valid = secureURL.isValid(connection);
+
+ Assert.assertTrue("Connection to " + url + " should be valid.",
+ valid);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+
+ }
+
+ @Test
+ public void testInValidHttpsUrlConnection() {
+ String url = "https://revoked.badssl.com";
+ try {
+ SecureURL secureURL = new SecureURL(url);
+ HttpsURLConnection connection = (HttpsURLConnection) secureURL.openConnection();
+
+ boolean valid = secureURL.isValid(connection);
+
+ Assert.assertFalse("Connection to " + url
+ + " should be invalid, revoked cert.",
+ valid);
+
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+
+
+ }
+}
diff --git a/security/crypto/src/main/AndroidManifest.xml b/security/crypto/src/main/AndroidManifest.xml
index e85a787..7e7b70b 100644
--- a/security/crypto/src/main/AndroidManifest.xml
+++ b/security/crypto/src/main/AndroidManifest.xml
@@ -13,5 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<manifest package="androidx.security"/>
\ No newline at end of file
+<manifest package="androidx.security"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <uses-permission android:name="android.permission.USE_BIOMETRIC" />
+</manifest>
\ No newline at end of file
diff --git a/security/crypto/src/main/java/androidx/security/SecureConfig.java b/security/crypto/src/main/java/androidx/security/SecureConfig.java
new file mode 100644
index 0000000..8dc6ba9
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/SecureConfig.java
@@ -0,0 +1,1016 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.NonNull;
+import androidx.security.biometric.BiometricKeyAuthCallback;
+import androidx.security.config.TrustAnchorOptions;
+
+/**
+ * Class that defines constants used by the library. Includes predefined configurations for:
+ *
+ * Default:
+ * SecureConfig.getDefault() provides a good basic security configuration for encrypting data
+ * both in transit and at rest.
+ *
+ * NIAP:
+ * For government use, SecureConfig.getNiapConfig(biometric auth) which returns compliant security
+ * settings for NIAP use cases.
+ *
+ *
+ */
+public class SecureConfig {
+
+ public static final String ANDROID_KEYSTORE = "AndroidKeyStore";
+ public static final String ANDROID_CA_STORE = "AndroidCAStore";
+ public static final int AES_IV_SIZE_BYTES = 16;
+ public static final String SSL_TLS = "TLS";
+
+ String mAndroidKeyStore;
+ String mAndroidCAStore;
+ String mKeystoreType;
+
+ // Asymmetric Encryption Constants
+ String mAsymmetricKeyPairAlgorithm;
+ int mAsymmetricKeySize;
+ String mAsymmetricCipherTransformation;
+ String mAsymmetricBlockModes;
+ String mAsymmetricPaddings;
+ int mAsymmetricKeyPurposes;
+ // Sets KeyGenBuilder#setUnlockedDeviceRequired to true, requires Android 9 Pie.
+ boolean mAsymmetricSensitiveDataProtection;
+ boolean mAsymmetricRequireUserAuth;
+ int mAsymmetricRequireUserValiditySeconds;
+ String mAsymmetricDigests;
+
+ // Symmetric Encryption Constants
+ String mSymmetricKeyAlgorithm;
+ String mSymmetricBlockModes;
+ String mSymmetricPaddings;
+ int mSymmetricKeySize;
+ int mSymmetricGcmTagLength;
+ int mSymmetricKeyPurposes;
+ String mSymmetricCipherTransformation;
+ // Sets KeyGenBuilder#setUnlockedDeviceRequired to true, requires Android 9 Pie.
+ boolean mSymmetricSensitiveDataProtection;
+ boolean mSymmetricRequireUserAuth;
+ int mSymmetricRequireUserValiditySeconds;
+ private String mSymmetricDigests;
+
+ // Certificate Constants
+ String mCertPath;
+ String mCertPathValidator;
+ boolean mUseStrongSSLCiphers;
+ String[] mStrongSSLCiphers;
+ String[] mClientCertAlgorithms;
+ TrustAnchorOptions mTrustAnchorOptions;
+
+ BiometricKeyAuthCallback mBiometricKeyAuthCallback;
+
+ String mSignatureAlgorithm;
+
+ SecureConfig() {
+ }
+
+ /**
+ * SecureConfig.Builder configures SecureConfig.
+ */
+ public static class Builder {
+
+ public Builder() {
+ }
+
+ // Keystore Constants
+ String mAndroidKeyStore;
+ String mAndroidCAStore;
+ String mKeystoreType;
+
+ // Asymmetric Encryption Constants
+ String mAsymmetricKeyPairAlgorithm;
+ int mAsymmetricKeySize;
+ String mAsymmetricCipherTransformation;
+ int mAsymmetricKeyPurposes;
+ String mAsymmetricBlockModes;
+ String mAsymmetricPaddings;
+ boolean mAsymmetricSensitiveDataProtection;
+ boolean mAsymmetricRequireUserAuth;
+ int mAsymmetricRequireUserValiditySeconds;
+
+ /**
+ * Sets the keystore type
+ *
+ * @param keystoreType the KeystoreType to set
+ * @return
+ */
+ @NonNull
+ public Builder forKeyStoreType(@NonNull String keystoreType) {
+ this.mKeystoreType = keystoreType;
+ return this;
+ }
+
+ /**
+ * Sets the key pair algorithm.
+ *
+ * @param keyPairAlgorithm the key pair algorithm
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricKeyPairAlgorithm(@NonNull String keyPairAlgorithm) {
+ this.mAsymmetricKeyPairAlgorithm = keyPairAlgorithm;
+ return this;
+ }
+
+ /**
+ * @param keySize
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricKeySize(int keySize) {
+ this.mAsymmetricKeySize = keySize;
+ return this;
+ }
+
+ /**
+ * @param cipherTransformation
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricCipherTransformation(@NonNull String cipherTransformation) {
+ this.mAsymmetricCipherTransformation = cipherTransformation;
+ return this;
+ }
+
+ /**
+ * @param purposes
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricKeyPurposes(int purposes) {
+ this.mAsymmetricKeyPurposes = purposes;
+ return this;
+ }
+
+ /**
+ * @param blockModes
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricBlockModes(@NonNull String blockModes) {
+ this.mAsymmetricBlockModes = blockModes;
+ return this;
+ }
+
+ /**
+ * @param paddings
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricPaddings(@NonNull String paddings) {
+ this.mAsymmetricPaddings = paddings;
+ return this;
+ }
+
+ /**
+ * @param dataProtection
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricSensitiveDataProtection(boolean dataProtection) {
+ this.mAsymmetricSensitiveDataProtection = dataProtection;
+ return this;
+ }
+
+ /**
+ * @param userAuth
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricRequireUserAuth(boolean userAuth) {
+ this.mAsymmetricRequireUserAuth = userAuth;
+ return this;
+ }
+
+ /**
+ * @param authValiditySeconds
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setAsymmetricRequireUserValiditySeconds(int authValiditySeconds) {
+ this.mAsymmetricRequireUserValiditySeconds = authValiditySeconds;
+ return this;
+ }
+
+ // Symmetric Encryption Constants
+ String mSymmetricKeyAlgorithm;
+ String mSymmetricBlockModes;
+ String mSymmetricPaddings;
+ int mSymmetricKeySize;
+ int mSymmetricGcmTagLength;
+ int mSymmetricKeyPurposes;
+ String mSymmetricCipherTransformation;
+ boolean mSymmetricSensitiveDataProtection;
+ boolean mSymmetricRequireUserAuth;
+ int mSymmetricRequireUserValiditySeconds;
+
+ /**
+ * @param keyAlgorithm
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricKeyAlgorithm(@NonNull String keyAlgorithm) {
+ this.mSymmetricKeyAlgorithm = keyAlgorithm;
+ return this;
+ }
+
+ /**
+ * @param keySize
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricKeySize(int keySize) {
+ this.mSymmetricKeySize = keySize;
+ return this;
+ }
+
+ /**
+ * @param cipherTransformation
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricCipherTransformation(@NonNull String cipherTransformation) {
+ this.mSymmetricCipherTransformation = cipherTransformation;
+ return this;
+ }
+
+ /**
+ * @param purposes
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricKeyPurposes(int purposes) {
+ this.mSymmetricKeyPurposes = purposes;
+ return this;
+ }
+
+ /**
+ * @param gcmTagLength
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricGcmTagLength(int gcmTagLength) {
+ this.mSymmetricGcmTagLength = gcmTagLength;
+ return this;
+ }
+
+ /**
+ * @param blockModes
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricBlockModes(@NonNull String blockModes) {
+ this.mSymmetricBlockModes = blockModes;
+ return this;
+ }
+
+ /**
+ * @param paddings
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricPaddings(@NonNull String paddings) {
+ this.mSymmetricPaddings = paddings;
+ return this;
+ }
+
+ /**
+ * @param dataProtection
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricSensitiveDataProtection(boolean dataProtection) {
+ this.mSymmetricSensitiveDataProtection = dataProtection;
+ return this;
+ }
+
+ /**
+ * @param userAuth
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricRequireUserAuth(boolean userAuth) {
+ this.mSymmetricRequireUserAuth = userAuth;
+ return this;
+ }
+
+ /**
+ * @param authValiditySeconds
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSymmetricRequireUserValiditySeconds(int authValiditySeconds) {
+ this.mSymmetricRequireUserValiditySeconds = authValiditySeconds;
+ return this;
+ }
+
+ // Certificate Constants
+ String mCertPath;
+ String mCertPathValidator;
+ boolean mUseStrongSSLCiphers;
+ String[] mStrongSSLCiphers;
+ String[] mClientCertAlgorithms;
+ TrustAnchorOptions mAnchorOptions;
+ BiometricKeyAuthCallback mBiometricKeyAuthCallback;
+ String mSignatureAlgorithm;
+
+ /**
+ * @param certPath
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setCertPath(@NonNull String certPath) {
+ this.mCertPath = certPath;
+ return this;
+ }
+
+ /**
+ * @param certPathValidator
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setCertPathValidator(@NonNull String certPathValidator) {
+ this.mCertPathValidator = certPathValidator;
+ return this;
+ }
+
+ /**
+ * @param strongSSLCiphers
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setUseStrongSSLCiphers(boolean strongSSLCiphers) {
+ this.mUseStrongSSLCiphers = strongSSLCiphers;
+ return this;
+ }
+
+ /**
+ * @param strongSSLCiphers
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setStrongSSLCiphers(@NonNull String[] strongSSLCiphers) {
+ this.mStrongSSLCiphers = strongSSLCiphers;
+ return this;
+ }
+
+ /**
+ * @param clientCertAlgorithms
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setClientCertAlgorithms(@NonNull String[] clientCertAlgorithms) {
+ this.mClientCertAlgorithms = clientCertAlgorithms;
+ return this;
+ }
+
+ /**
+ * @param trustAnchorOptions
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setTrustAnchorOptions(@NonNull TrustAnchorOptions trustAnchorOptions) {
+ this.mAnchorOptions = trustAnchorOptions;
+ return this;
+ }
+
+ /**
+ * @param biometricKeyAuthCallback
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setBiometricKeyAuthCallback(
+ @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
+ this.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
+ return this;
+ }
+
+ /**
+ * @param signatureAlgorithm
+ * @return The configured builder
+ */
+ @NonNull
+ public Builder setSignatureAlgorithm(
+ @NonNull String signatureAlgorithm) {
+ this.mSignatureAlgorithm = signatureAlgorithm;
+ return this;
+ }
+
+
+
+ /**
+ * @return The configured builder
+ */
+ @NonNull
+ public SecureConfig build() {
+ SecureConfig secureConfig = new SecureConfig();
+ secureConfig.mAndroidKeyStore = this.mAndroidKeyStore;
+ secureConfig.mAndroidCAStore = this.mAndroidCAStore;
+ secureConfig.mKeystoreType = this.mKeystoreType;
+
+ secureConfig.mAsymmetricKeyPairAlgorithm = this.mAsymmetricKeyPairAlgorithm;
+ secureConfig.mAsymmetricKeySize = this.mAsymmetricKeySize;
+ secureConfig.mAsymmetricCipherTransformation = this.mAsymmetricCipherTransformation;
+ secureConfig.mAsymmetricKeyPurposes = this.mAsymmetricKeyPurposes;
+ secureConfig.mAsymmetricBlockModes = this.mAsymmetricBlockModes;
+ secureConfig.mAsymmetricPaddings = this.mAsymmetricPaddings;
+ secureConfig.mAsymmetricSensitiveDataProtection =
+ this.mAsymmetricSensitiveDataProtection;
+ secureConfig.mAsymmetricRequireUserAuth = this.mAsymmetricRequireUserAuth;
+ secureConfig.mAsymmetricRequireUserValiditySeconds =
+ this.mAsymmetricRequireUserValiditySeconds;
+
+ secureConfig.mSymmetricKeyAlgorithm = this.mSymmetricKeyAlgorithm;
+ secureConfig.mSymmetricBlockModes = this.mSymmetricBlockModes;
+ secureConfig.mSymmetricPaddings = this.mSymmetricPaddings;
+ secureConfig.mSymmetricKeySize = this.mSymmetricKeySize;
+ secureConfig.mSymmetricGcmTagLength = this.mSymmetricGcmTagLength;
+ secureConfig.mSymmetricKeyPurposes = this.mSymmetricKeyPurposes;
+ secureConfig.mSymmetricCipherTransformation = this.mSymmetricCipherTransformation;
+ secureConfig.mSymmetricSensitiveDataProtection =
+ this.mSymmetricSensitiveDataProtection;
+ secureConfig.mSymmetricRequireUserAuth = this.mSymmetricRequireUserAuth;
+ secureConfig.mSymmetricRequireUserValiditySeconds =
+ this.mSymmetricRequireUserValiditySeconds;
+
+ secureConfig.mCertPath = this.mCertPath;
+ secureConfig.mCertPathValidator = this.mCertPathValidator;
+ secureConfig.mUseStrongSSLCiphers = this.mUseStrongSSLCiphers;
+ secureConfig.mStrongSSLCiphers = this.mStrongSSLCiphers;
+ secureConfig.mClientCertAlgorithms = this.mClientCertAlgorithms;
+ secureConfig.mTrustAnchorOptions = this.mAnchorOptions;
+ secureConfig.mBiometricKeyAuthCallback = this.mBiometricKeyAuthCallback;
+ secureConfig.mSignatureAlgorithm = this.mSignatureAlgorithm;
+ return secureConfig;
+ }
+ }
+
+ /**
+ * @return A NIAP compliant configuration.
+ */
+ @NonNull
+ public static SecureConfig getNiapConfig() {
+ return getNiapConfig(null);
+ }
+
+ /**
+ * @return A default configuration with for consumer applications.
+ */
+ @NonNull
+ public static SecureConfig getDefault() {
+ SecureConfig.Builder builder = new SecureConfig.Builder();
+ builder.mAndroidKeyStore = SecureConfig.ANDROID_KEYSTORE;
+ builder.mAndroidCAStore = SecureConfig.ANDROID_CA_STORE;
+ builder.mKeystoreType = "PKCS12";
+
+ builder.mAsymmetricKeyPairAlgorithm = KeyProperties.KEY_ALGORITHM_RSA;
+ builder.mAsymmetricKeySize = 2048;
+ builder.mAsymmetricCipherTransformation = "RSA/ECB/PKCS1Padding";
+ builder.mAsymmetricBlockModes = KeyProperties.BLOCK_MODE_ECB;
+ builder.mAsymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+ builder.mAsymmetricKeyPurposes = KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_SIGN;
+ builder.mAsymmetricSensitiveDataProtection = false;
+ builder.mAsymmetricRequireUserAuth = false;
+ builder.mAsymmetricRequireUserValiditySeconds = -1;
+
+ builder.mSymmetricKeyAlgorithm = KeyProperties.KEY_ALGORITHM_AES;
+ builder.mSymmetricBlockModes = KeyProperties.BLOCK_MODE_GCM;
+ builder.mSymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_NONE;
+ builder.mSymmetricKeySize = 256;
+ builder.mSymmetricGcmTagLength = 128;
+ builder.mSymmetricKeyPurposes =
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
+ builder.mSymmetricCipherTransformation = "AES/GCM/NoPadding";
+ builder.mSymmetricSensitiveDataProtection = false;
+ builder.mSymmetricRequireUserAuth = false;
+ builder.mSymmetricRequireUserValiditySeconds = -1;
+
+ builder.mCertPath = "X.509";
+ builder.mCertPathValidator = "PKIX";
+ builder.mUseStrongSSLCiphers = false;
+ builder.mStrongSSLCiphers = null;
+ builder.mClientCertAlgorithms = new String[]{"RSA"};
+ builder.mAnchorOptions = TrustAnchorOptions.USER_SYSTEM;
+ builder.mBiometricKeyAuthCallback = null;
+ builder.mSignatureAlgorithm = "SHA256withECDSA";
+
+ return builder.build();
+ }
+
+ /**
+ * Create a Niap compliant configuration
+ *
+ * -Insert Link to Spec
+ *
+ * @param biometricKeyAuthCallback
+ * @return The NIAP compliant configuration
+ */
+ @NonNull
+ public static SecureConfig getNiapConfig(
+ @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
+ SecureConfig.Builder builder = new SecureConfig.Builder();
+ builder.mAndroidKeyStore = SecureConfig.ANDROID_KEYSTORE;
+ builder.mAndroidCAStore = SecureConfig.ANDROID_CA_STORE;
+ builder.mKeystoreType = "PKCS12";
+
+ builder.mAsymmetricKeyPairAlgorithm = KeyProperties.KEY_ALGORITHM_RSA;
+ builder.mAsymmetricKeySize = 4096;
+ builder.mAsymmetricCipherTransformation = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
+ builder.mAsymmetricBlockModes = KeyProperties.BLOCK_MODE_ECB;
+ builder.mAsymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
+ builder.mAsymmetricKeyPurposes = KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_SIGN;
+ builder.mAsymmetricSensitiveDataProtection = true;
+ builder.mAsymmetricRequireUserAuth = true;
+ builder.mAsymmetricRequireUserValiditySeconds = -1;
+
+ builder.mSymmetricKeyAlgorithm = KeyProperties.KEY_ALGORITHM_AES;
+ builder.mSymmetricBlockModes = KeyProperties.BLOCK_MODE_GCM;
+ builder.mSymmetricPaddings = KeyProperties.ENCRYPTION_PADDING_NONE;
+ builder.mSymmetricKeySize = 256;
+ builder.mSymmetricGcmTagLength = 128;
+ builder.mSymmetricKeyPurposes =
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT;
+ builder.mSymmetricCipherTransformation = "AES/GCM/NoPadding";
+ builder.mSymmetricSensitiveDataProtection = true;
+ builder.mSymmetricRequireUserAuth = true;
+ builder.mSymmetricRequireUserValiditySeconds = -1;
+
+ builder.mCertPath = "X.509";
+ builder.mCertPathValidator = "PKIX";
+ builder.mUseStrongSSLCiphers = false;
+ builder.mStrongSSLCiphers = new String[]{
+ "TLS_RSA_WITH_AES_128_CBC_SHA256",
+ "TLS_RSA_WITH_AES_256_CBC_SHA256",
+ "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ "TLS_RSA_WITH_AES_256_GCM_SHA384",
+ "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+ "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
+ "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
+ "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
+ };
+ builder.mClientCertAlgorithms = new String[]{"RSA"};
+ builder.mAnchorOptions = TrustAnchorOptions.USER_SYSTEM;
+ builder.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
+ builder.mSignatureAlgorithm = "SHA512withRSA/PSS";
+
+ return builder.build();
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAndroidKeyStore() {
+ return mAndroidKeyStore;
+ }
+
+ public void setAndroidKeyStore(@NonNull String androidKeyStore) {
+ this.mAndroidKeyStore = androidKeyStore;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAndroidCAStore() {
+ return mAndroidCAStore;
+ }
+
+ public void setAndroidCAStore(@NonNull String androidCAStore) {
+ this.mAndroidCAStore = androidCAStore;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getKeystoreType() {
+ return mKeystoreType;
+ }
+
+ /**
+ * @param keystoreType
+ */
+ @NonNull
+ public void setKeystoreType(@NonNull String keystoreType) {
+ this.mKeystoreType = keystoreType;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAsymmetricKeyPairAlgorithm() {
+ return mAsymmetricKeyPairAlgorithm;
+ }
+
+ /**
+ * @param asymmetricKeyPairAlgorithm
+ */
+ public void setAsymmetricKeyPairAlgorithm(@NonNull String asymmetricKeyPairAlgorithm) {
+ this.mAsymmetricKeyPairAlgorithm = asymmetricKeyPairAlgorithm;
+ }
+
+ /**
+ * @return
+ */
+ public int getAsymmetricKeySize() {
+ return mAsymmetricKeySize;
+ }
+
+ /**
+ * @param asymmetricKeySize
+ */
+ public void setAsymmetricKeySize(int asymmetricKeySize) {
+ this.mAsymmetricKeySize = asymmetricKeySize;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAsymmetricCipherTransformation() {
+ return mAsymmetricCipherTransformation;
+ }
+
+ /**
+ * @param asymmetricCipherTransformation
+ */
+ public void setAsymmetricCipherTransformation(@NonNull String asymmetricCipherTransformation) {
+ this.mAsymmetricCipherTransformation = asymmetricCipherTransformation;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAsymmetricBlockModes() {
+ return mAsymmetricBlockModes;
+ }
+
+ /**
+ * @param asymmetricBlockModes
+ */
+ public void setAsymmetricBlockModes(@NonNull String asymmetricBlockModes) {
+ this.mAsymmetricBlockModes = asymmetricBlockModes;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getAsymmetricPaddings() {
+ return mAsymmetricPaddings;
+ }
+
+ /**
+ * @param asymmetricPaddings
+ */
+ public void setAsymmetricPaddings(@NonNull String asymmetricPaddings) {
+ this.mAsymmetricPaddings = asymmetricPaddings;
+ }
+
+ /**
+ * @return
+ */
+ public int getAsymmetricKeyPurposes() {
+ return mAsymmetricKeyPurposes;
+ }
+
+ /**
+ * @param asymmetricKeyPurposes
+ */
+ public void setAsymmetricKeyPurposes(int asymmetricKeyPurposes) {
+ this.mAsymmetricKeyPurposes = asymmetricKeyPurposes;
+ }
+
+ /**
+ * @return
+ */
+ public boolean getAsymmetricSensitiveDataProtectionEnabled() {
+ return mAsymmetricSensitiveDataProtection;
+ }
+
+ /**
+ * @param asymmetricSensitiveDataProtection
+ */
+ public void setAsymmetricSensitiveDataProtection(boolean asymmetricSensitiveDataProtection) {
+ this.mAsymmetricSensitiveDataProtection = asymmetricSensitiveDataProtection;
+ }
+
+ /**
+ * @return
+ */
+ public boolean getAsymmetricRequireUserAuthEnabled() {
+ return mAsymmetricRequireUserAuth && mBiometricKeyAuthCallback != null;
+ }
+
+ /**
+ * @param requireUserAuth
+ */
+ public void setAsymmetricRequireUserAuth(boolean requireUserAuth) {
+ this.mAsymmetricRequireUserAuth = requireUserAuth;
+ }
+
+ /**
+ * @return
+ */
+ public int getAsymmetricRequireUserValiditySeconds() {
+ return this.mAsymmetricRequireUserValiditySeconds;
+ }
+
+ /**
+ * @param userValiditySeconds
+ */
+ public void setAsymmetricRequireUserValiditySeconds(int userValiditySeconds) {
+ this.mAsymmetricRequireUserValiditySeconds = userValiditySeconds;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getSymmetricKeyAlgorithm() {
+ return mSymmetricKeyAlgorithm;
+ }
+
+ /**
+ * @param symmetricKeyAlgorithm
+ */
+ public void setSymmetricKeyAlgorithm(@NonNull String symmetricKeyAlgorithm) {
+ this.mSymmetricKeyAlgorithm = symmetricKeyAlgorithm;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getSymmetricBlockModes() {
+ return mSymmetricBlockModes;
+ }
+
+ /**
+ * @param symmetricBlockModes
+ */
+ public void setSymmetricBlockModes(@NonNull String symmetricBlockModes) {
+ this.mSymmetricBlockModes = symmetricBlockModes;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getSymmetricPaddings() {
+ return mSymmetricPaddings;
+ }
+
+ /**
+ * @param symmetricPaddings
+ */
+ public void setSymmetricPaddings(@NonNull String symmetricPaddings) {
+ this.mSymmetricPaddings = symmetricPaddings;
+ }
+
+ /**
+ * @return
+ */
+ public int getSymmetricKeySize() {
+ return mSymmetricKeySize;
+ }
+
+ /**
+ * @param symmetricKeySize
+ */
+ public void setSymmetricKeySize(int symmetricKeySize) {
+ this.mSymmetricKeySize = symmetricKeySize;
+ }
+
+ /**
+ * @return
+ */
+ public int getSymmetricGcmTagLength() {
+ return mSymmetricGcmTagLength;
+ }
+
+ /**
+ * @param symmetricGcmTagLength
+ */
+ public void setSymmetricGcmTagLength(int symmetricGcmTagLength) {
+ this.mSymmetricGcmTagLength = symmetricGcmTagLength;
+ }
+
+ /**
+ * @return
+ */
+ public int getSymmetricKeyPurposes() {
+ return mSymmetricKeyPurposes;
+ }
+
+ /**
+ * @param symmetricKeyPurposes
+ */
+ public void setSymmetricKeyPurposes(int symmetricKeyPurposes) {
+ this.mSymmetricKeyPurposes = symmetricKeyPurposes;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getSymmetricCipherTransformation() {
+ return mSymmetricCipherTransformation;
+ }
+
+ /**
+ * @param symmetricCipherTransformation
+ */
+ public void setSymmetricCipherTransformation(@NonNull String symmetricCipherTransformation) {
+ this.mSymmetricCipherTransformation = symmetricCipherTransformation;
+ }
+
+ /**
+ * @return
+ */
+ public boolean getSymmetricSensitiveDataProtectionEnabled() {
+ return mSymmetricSensitiveDataProtection;
+ }
+
+ /**
+ * @param symmetricSensitiveDataProtection
+ */
+ public void setSymmetricSensitiveDataProtection(boolean symmetricSensitiveDataProtection) {
+ this.mSymmetricSensitiveDataProtection = symmetricSensitiveDataProtection;
+ }
+
+ public boolean getSymmetricRequireUserAuthEnabled() {
+ return mSymmetricRequireUserAuth && mBiometricKeyAuthCallback != null;
+ }
+
+ /**
+ * @param requireUserAuth
+ */
+ public void setSymmetricRequireUserAuth(boolean requireUserAuth) {
+ this.mSymmetricRequireUserAuth = requireUserAuth;
+ }
+
+ /**
+ * @return
+ */
+ public int getSymmetricRequireUserValiditySeconds() {
+ return this.mSymmetricRequireUserValiditySeconds;
+ }
+
+ /**
+ * @param userValiditySeconds
+ */
+ public void setSymmetricRequireUserValiditySeconds(int userValiditySeconds) {
+ this.mSymmetricRequireUserValiditySeconds = userValiditySeconds;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getCertPath() {
+ return mCertPath;
+ }
+
+ public void setCertPath(@NonNull String certPath) {
+ this.mCertPath = certPath;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getCertPathValidator() {
+ return mCertPathValidator;
+ }
+
+ /**
+ * @param certPathValidator
+ */
+ public void setCertPathValidator(@NonNull String certPathValidator) {
+ this.mCertPathValidator = certPathValidator;
+ }
+
+ /**
+ * @return
+ */
+ public boolean getUseStrongSSLCiphersEnabled() {
+ return mUseStrongSSLCiphers;
+ }
+
+ /**
+ * @param useStrongSSLCiphers
+ */
+ public void setUseStrongSSLCiphers(boolean useStrongSSLCiphers) {
+ this.mUseStrongSSLCiphers = useStrongSSLCiphers;
+ }
+
+ public boolean getUseStrongSSLCiphers() {
+ return mUseStrongSSLCiphers;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String[] getStrongSSLCiphers() {
+ return mStrongSSLCiphers;
+ }
+
+ /**
+ * @param strongSSLCiphers
+ */
+ public void setStrongSSLCiphers(@NonNull String[] strongSSLCiphers) {
+ this.mStrongSSLCiphers = strongSSLCiphers;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String[] getClientCertAlgorithms() {
+ return mClientCertAlgorithms;
+ }
+
+ public void setClientCertAlgorithms(@NonNull String[] clientCertAlgorithms) {
+ this.mClientCertAlgorithms = clientCertAlgorithms;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public TrustAnchorOptions getTrustAnchorOptions() {
+ return mTrustAnchorOptions;
+ }
+
+ /**
+ * @param trustAnchorOptions
+ */
+ public void setTrustAnchorOptions(@NonNull TrustAnchorOptions trustAnchorOptions) {
+ this.mTrustAnchorOptions = trustAnchorOptions;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public BiometricKeyAuthCallback getBiometricKeyAuthCallback() {
+ return mBiometricKeyAuthCallback;
+ }
+
+ /**
+ * @param biometricKeyAuthCallback
+ */
+ public void setBiometricKeyAuthCallback(
+ @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
+ this.mBiometricKeyAuthCallback = biometricKeyAuthCallback;
+ }
+
+ /**
+ * @return
+ */
+ @NonNull
+ public String getSignatureAlgorithm() {
+ return mSignatureAlgorithm;
+ }
+
+ /**
+ * @param signatureAlgorithm
+ */
+ public void setSignatureAlgorithm(
+ @NonNull String signatureAlgorithm) {
+ this.mSignatureAlgorithm = signatureAlgorithm;
+ }
+}
diff --git a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java
new file mode 100644
index 0000000..f15fbb3
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuth.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.biometric;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.biometric.BiometricPrompt;
+import androidx.fragment.app.FragmentActivity;
+import androidx.security.crypto.SecureCipher;
+
+import java.security.Signature;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+
+import javax.crypto.Cipher;
+
+/**
+ * Class that handles authenticating Ciphers using Biometric Prompt.
+ */
+public class BiometricKeyAuth extends BiometricPrompt.AuthenticationCallback {
+
+ private static final String TAG = "BiometricKeyAuth";
+
+ private FragmentActivity mActivity;
+ private SecureCipher.SecureAuthListener mSecureAuthListener;
+ private CountDownLatch mCountDownLatch = null;
+ private BiometricKeyAuthCallback mBiometricKeyAuthCallback;
+
+ /**
+ * @param activity The activity to use a parent
+ * @param callback Callback to reply with when complete.
+ */
+ public BiometricKeyAuth(@NonNull FragmentActivity activity,
+ @NonNull BiometricKeyAuthCallback callback) {
+ this.mActivity = activity;
+ this.mBiometricKeyAuthCallback = callback;
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ mBiometricKeyAuthCallback.onAuthenticationSucceeded();
+ Log.i(TAG, "Fingerprint success!");
+ mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.SUCCESS);
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ mBiometricKeyAuthCallback.onMessage(String.valueOf(errString));
+ mBiometricKeyAuthCallback.onAuthenticationError(errorCode, errString);
+ mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.FAILED);
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ super.onAuthenticationFailed();
+ mBiometricKeyAuthCallback.onAuthenticationFailed();
+ mSecureAuthListener.authComplete(BiometricKeyAuthCallback.BiometricStatus.FAILED);
+ mCountDownLatch.countDown();
+ }
+
+ /**
+ * Authenticates a key, via the Cipher it's used with
+ *
+ * @param cipher The cipher to authenticate
+ * @param promptInfo The prompt info for the auth fragment
+ * @param listener the listener to call back when complete
+ */
+ public void authenticateKey(@NonNull Cipher cipher,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @NonNull SecureCipher.SecureAuthListener listener) {
+ authenticateKeyObject(cipher, promptInfo, listener);
+ }
+
+ /**
+ * Authenticates a key, via the Cipher it's used with
+ *
+ * @param signature The signature to authenticate
+ * @param promptInfo The prompt info for the auth fragment
+ * @param listener the listener to call back when complete
+ */
+ public void authenticateKey(@NonNull Signature signature,
+ @NonNull BiometricPrompt.PromptInfo promptInfo,
+ @NonNull SecureCipher.SecureAuthListener listener) {
+ authenticateKeyObject(signature, promptInfo, listener);
+ }
+
+ private void authenticateKeyObject(Object crypto,
+ BiometricPrompt.PromptInfo promptInfo,
+ SecureCipher.SecureAuthListener listener) {
+ mCountDownLatch = new CountDownLatch(1);
+ mSecureAuthListener = listener;
+
+ BiometricPrompt prompt = new BiometricPrompt(mActivity,
+ Executors.newSingleThreadExecutor(),
+ this);
+ BiometricPrompt.CryptoObject cryptoObject;
+ if (crypto instanceof Cipher) {
+ cryptoObject = new BiometricPrompt.CryptoObject((Cipher) crypto);
+ } else { /*if(crypto instanceof Signature) {*/
+ cryptoObject = new BiometricPrompt.CryptoObject((Signature) crypto);
+ }
+ prompt.authenticate(promptInfo, cryptoObject);
+ try {
+ mCountDownLatch.await();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Authenticates a key, via the Cipher it's used with. Provides a default implementation of
+ * PromptInfo.
+ *
+ * @param cipher The cipher to authenticate
+ * @param listener the listener to call back when complete
+ */
+ public void authenticateKey(@NonNull Cipher cipher,
+ @NonNull SecureCipher.SecureAuthListener listener) {
+ authenticateKeyObject(cipher, listener);
+ }
+
+ /**
+ * Authenticates a key, via the Signature it's used with. Provides a default implementation of
+ * PromptInfo.
+ *
+ * @param signature The signature to authenticate
+ * @param listener the listener to call back when complete
+ */
+ public void authenticateKey(@NonNull Signature signature,
+ @NonNull SecureCipher.SecureAuthListener listener) {
+ authenticateKeyObject(signature, listener);
+ }
+
+ private void authenticateKeyObject(Object crypto,
+ @NonNull SecureCipher.SecureAuthListener listener) {
+ authenticateKeyObject(crypto, new BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Please Auth for key usage.")
+ .setSubtitle("Key used for encrypting files")
+ .setDescription("User authentication required to access key.")
+ .setNegativeButtonText("Cancel")
+ .build(), listener);
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java
new file mode 100644
index 0000000..ff60d31
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/biometric/BiometricKeyAuthCallback.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.security.biometric;
+
+import androidx.annotation.NonNull;
+import androidx.security.crypto.SecureCipher;
+
+import java.security.Signature;
+
+import javax.crypto.Cipher;
+
+
+
+/**
+ * Callback for Biometric Key auth.
+ *
+ */
+public abstract class BiometricKeyAuthCallback {
+
+ /**
+ * Statuses of Biometric auth
+ */
+ public enum BiometricStatus {
+ SUCCESS(0),
+ FAILED(1),
+ ERROR(2);
+
+ private final int mType;
+
+ BiometricStatus(int type) {
+ this.mType = type;
+ }
+
+ /**
+ * @return the mType
+ */
+ public int getType() {
+ return this.mType;
+ }
+
+ /**
+ * @return the status that matches the id
+ */
+ @NonNull
+ public static BiometricStatus fromId(int id) {
+ switch (id) {
+ case 0:
+ return SUCCESS;
+ case 1:
+ return FAILED;
+ case 2:
+ return ERROR;
+ }
+ return ERROR;
+ }
+ }
+
+ /**
+ *
+ */
+ public abstract void onAuthenticationSucceeded();
+
+ /**
+ *
+ */
+ public abstract void onAuthenticationError(int errorCode, @NonNull CharSequence errString);
+
+ /**
+ *
+ */
+ public abstract void onAuthenticationFailed();
+
+ /**
+ * @param message the message to send
+ */
+ public abstract void onMessage(@NonNull String message);
+
+ /**
+ * @param cipher The cipher to authenticate
+ * @param listener The listener to call back
+ */
+ public abstract void authenticateKey(@NonNull Cipher cipher,
+ @NonNull SecureCipher.SecureAuthListener listener);
+
+ /**
+ * @param signature The signature to authenticate
+ * @param listener The listener to call back
+ */
+ public abstract void authenticateKey(@NonNull Signature signature,
+ @NonNull SecureCipher.SecureAuthListener listener);
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/config/TldConstants.java b/security/crypto/src/main/java/androidx/security/config/TldConstants.java
new file mode 100644
index 0000000..d118cb5c
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/config/TldConstants.java
@@ -0,0 +1,1575 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.config;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class that contains all known TLDs.
+ */
+public final class TldConstants {
+
+ private TldConstants() {
+ }
+
+ @NonNull
+ public static final List<String> VALID_TLDS = Arrays.asList(
+ "*.AAA",
+ "*.AARP",
+ "*.ABARTH",
+ "*.ABB",
+ "*.ABBOTT",
+ "*.ABBVIE",
+ "*.ABC",
+ "*.ABLE",
+ "*.ABOGADO",
+ "*.ABUDHABI",
+ "*.AC",
+ "*.ACADEMY",
+ "*.ACCENTURE",
+ "*.ACCOUNTANT",
+ "*.ACCOUNTANTS",
+ "*.ACO",
+ "*.ACTIVE",
+ "*.ACTOR",
+ "*.AD",
+ "*.ADAC",
+ "*.ADS",
+ "*.ADULT",
+ "*.AE",
+ "*.AEG",
+ "*.AERO",
+ "*.AETNA",
+ "*.AF",
+ "*.AFAMILYCOMPANY",
+ "*.AFL",
+ "*.AFRICA",
+ "*.AG",
+ "*.AGAKHAN",
+ "*.AGENCY",
+ "*.AI",
+ "*.AIG",
+ "*.AIGO",
+ "*.AIRBUS",
+ "*.AIRFORCE",
+ "*.AIRTEL",
+ "*.AKDN",
+ "*.AL",
+ "*.ALFAROMEO",
+ "*.ALIBABA",
+ "*.ALIPAY",
+ "*.ALLFINANZ",
+ "*.ALLSTATE",
+ "*.ALLY",
+ "*.ALSACE",
+ "*.ALSTOM",
+ "*.AM",
+ "*.AMERICANEXPRESS",
+ "*.AMERICANFAMILY",
+ "*.AMEX",
+ "*.AMFAM",
+ "*.AMICA",
+ "*.AMSTERDAM",
+ "*.ANALYTICS",
+ "*.ANDROID",
+ "*.ANQUAN",
+ "*.ANZ",
+ "*.AO",
+ "*.AOL",
+ "*.APARTMENTS",
+ "*.APP",
+ "*.APPLE",
+ "*.AQ",
+ "*.AQUARELLE",
+ "*.AR",
+ "*.ARAB",
+ "*.ARAMCO",
+ "*.ARCHI",
+ "*.ARMY",
+ "*.ARPA",
+ "*.ART",
+ "*.ARTE",
+ "*.AS",
+ "*.ASDA",
+ "*.ASIA",
+ "*.ASSOCIATES",
+ "*.AT",
+ "*.ATHLETA",
+ "*.ATTORNEY",
+ "*.AU",
+ "*.AUCTION",
+ "*.AUDI",
+ "*.AUDIBLE",
+ "*.AUDIO",
+ "*.AUSPOST",
+ "*.AUTHOR",
+ "*.AUTO",
+ "*.AUTOS",
+ "*.AVIANCA",
+ "*.AW",
+ "*.AWS",
+ "*.AX",
+ "*.AXA",
+ "*.AZ",
+ "*.AZURE",
+ "*.BA",
+ "*.BABY",
+ "*.BAIDU",
+ "*.BANAMEX",
+ "*.BANANAREPUBLIC",
+ "*.BAND",
+ "*.BANK",
+ "*.BAR",
+ "*.BARCELONA",
+ "*.BARCLAYCARD",
+ "*.BARCLAYS",
+ "*.BAREFOOT",
+ "*.BARGAINS",
+ "*.BASEBALL",
+ "*.BASKETBALL",
+ "*.BAUHAUS",
+ "*.BAYERN",
+ "*.BB",
+ "*.BBC",
+ "*.BBT",
+ "*.BBVA",
+ "*.BCG",
+ "*.BCN",
+ "*.BD",
+ "*.BE",
+ "*.BEATS",
+ "*.BEAUTY",
+ "*.BEER",
+ "*.BENTLEY",
+ "*.BERLIN",
+ "*.BEST",
+ "*.BESTBUY",
+ "*.BET",
+ "*.BF",
+ "*.BG",
+ "*.BH",
+ "*.BHARTI",
+ "*.BI",
+ "*.BIBLE",
+ "*.BID",
+ "*.BIKE",
+ "*.BING",
+ "*.BINGO",
+ "*.BIO",
+ "*.BIZ",
+ "*.BJ",
+ "*.BLACK",
+ "*.BLACKFRIDAY",
+ "*.BLANCO",
+ "*.BLOCKBUSTER",
+ "*.BLOG",
+ "*.BLOOMBERG",
+ "*.BLUE",
+ "*.BM",
+ "*.BMS",
+ "*.BMW",
+ "*.BN",
+ "*.BNL",
+ "*.BNPPARIBAS",
+ "*.BO",
+ "*.BOATS",
+ "*.BOEHRINGER",
+ "*.BOFA",
+ "*.BOM",
+ "*.BOND",
+ "*.BOO",
+ "*.BOOK",
+ "*.BOOKING",
+ "*.BOSCH",
+ "*.BOSTIK",
+ "*.BOSTON",
+ "*.BOT",
+ "*.BOUTIQUE",
+ "*.BOX",
+ "*.BR",
+ "*.BRADESCO",
+ "*.BRIDGESTONE",
+ "*.BROADWAY",
+ "*.BROKER",
+ "*.BROTHER",
+ "*.BRUSSELS",
+ "*.BS",
+ "*.BT",
+ "*.BUDAPEST",
+ "*.BUGATTI",
+ "*.BUILD",
+ "*.BUILDERS",
+ "*.BUSINESS",
+ "*.BUY",
+ "*.BUZZ",
+ "*.BV",
+ "*.BW",
+ "*.BY",
+ "*.BZ",
+ "*.BZH",
+ "*.CA",
+ "*.CAB",
+ "*.CAFE",
+ "*.CAL",
+ "*.CALL",
+ "*.CALVINKLEIN",
+ "*.CAM",
+ "*.CAMERA",
+ "*.CAMP",
+ "*.CANCERRESEARCH",
+ "*.CANON",
+ "*.CAPETOWN",
+ "*.CAPITAL",
+ "*.CAPITALONE",
+ "*.CAR",
+ "*.CARAVAN",
+ "*.CARDS",
+ "*.CARE",
+ "*.CAREER",
+ "*.CAREERS",
+ "*.CARS",
+ "*.CARTIER",
+ "*.CASA",
+ "*.CASE",
+ "*.CASEIH",
+ "*.CASH",
+ "*.CASINO",
+ "*.CAT",
+ "*.CATERING",
+ "*.CATHOLIC",
+ "*.CBA",
+ "*.CBN",
+ "*.CBRE",
+ "*.CBS",
+ "*.CC",
+ "*.CD",
+ "*.CEB",
+ "*.CENTER",
+ "*.CEO",
+ "*.CERN",
+ "*.CF",
+ "*.CFA",
+ "*.CFD",
+ "*.CG",
+ "*.CH",
+ "*.CHANEL",
+ "*.CHANNEL",
+ "*.CHARITY",
+ "*.CHASE",
+ "*.CHAT",
+ "*.CHEAP",
+ "*.CHINTAI",
+ "*.CHRISTMAS",
+ "*.CHROME",
+ "*.CHRYSLER",
+ "*.CHURCH",
+ "*.CI",
+ "*.CIPRIANI",
+ "*.CIRCLE",
+ "*.CISCO",
+ "*.CITADEL",
+ "*.CITI",
+ "*.CITIC",
+ "*.CITY",
+ "*.CITYEATS",
+ "*.CK",
+ "*.CL",
+ "*.CLAIMS",
+ "*.CLEANING",
+ "*.CLICK",
+ "*.CLINIC",
+ "*.CLINIQUE",
+ "*.CLOTHING",
+ "*.CLOUD",
+ "*.CLUB",
+ "*.CLUBMED",
+ "*.CM",
+ "*.CN",
+ "*.CO",
+ "*.COACH",
+ "*.CODES",
+ "*.COFFEE",
+ "*.COLLEGE",
+ "*.COLOGNE",
+ "*.COM",
+ "*.COMCAST",
+ "*.COMMBANK",
+ "*.COMMUNITY",
+ "*.COMPANY",
+ "*.COMPARE",
+ "*.COMPUTER",
+ "*.COMSEC",
+ "*.CONDOS",
+ "*.CONSTRUCTION",
+ "*.CONSULTING",
+ "*.CONTACT",
+ "*.CONTRACTORS",
+ "*.COOKING",
+ "*.COOKINGCHANNEL",
+ "*.COOL",
+ "*.COOP",
+ "*.CORSICA",
+ "*.COUNTRY",
+ "*.COUPON",
+ "*.COUPONS",
+ "*.COURSES",
+ "*.CR",
+ "*.CREDIT",
+ "*.CREDITCARD",
+ "*.CREDITUNION",
+ "*.CRICKET",
+ "*.CROWN",
+ "*.CRS",
+ "*.CRUISE",
+ "*.CRUISES",
+ "*.CSC",
+ "*.CU",
+ "*.CUISINELLA",
+ "*.CV",
+ "*.CW",
+ "*.CX",
+ "*.CY",
+ "*.CYMRU",
+ "*.CYOU",
+ "*.CZ",
+ "*.DABUR",
+ "*.DAD",
+ "*.DANCE",
+ "*.DATA",
+ "*.DATE",
+ "*.DATING",
+ "*.DATSUN",
+ "*.DAY",
+ "*.DCLK",
+ "*.DDS",
+ "*.DE",
+ "*.DEAL",
+ "*.DEALER",
+ "*.DEALS",
+ "*.DEGREE",
+ "*.DELIVERY",
+ "*.DELL",
+ "*.DELOITTE",
+ "*.DELTA",
+ "*.DEMOCRAT",
+ "*.DENTAL",
+ "*.DENTIST",
+ "*.DESI",
+ "*.DESIGN",
+ "*.DEV",
+ "*.DHL",
+ "*.DIAMONDS",
+ "*.DIET",
+ "*.DIGITAL",
+ "*.DIRECT",
+ "*.DIRECTORY",
+ "*.DISCOUNT",
+ "*.DISCOVER",
+ "*.DISH",
+ "*.DIY",
+ "*.DJ",
+ "*.DK",
+ "*.DM",
+ "*.DNP",
+ "*.DO",
+ "*.DOCS",
+ "*.DOCTOR",
+ "*.DODGE",
+ "*.DOG",
+ "*.DOHA",
+ "*.DOMAINS",
+ "*.DOT",
+ "*.DOWNLOAD",
+ "*.DRIVE",
+ "*.DTV",
+ "*.DUBAI",
+ "*.DUCK",
+ "*.DUNLOP",
+ "*.DUNS",
+ "*.DUPONT",
+ "*.DURBAN",
+ "*.DVAG",
+ "*.DVR",
+ "*.DZ",
+ "*.EARTH",
+ "*.EAT",
+ "*.EC",
+ "*.ECO",
+ "*.EDEKA",
+ "*.EDU",
+ "*.EDUCATION",
+ "*.EE",
+ "*.EG",
+ "*.EMAIL",
+ "*.EMERCK",
+ "*.ENERGY",
+ "*.ENGINEER",
+ "*.ENGINEERING",
+ "*.ENTERPRISES",
+ "*.EPOST",
+ "*.EPSON",
+ "*.EQUIPMENT",
+ "*.ER",
+ "*.ERICSSON",
+ "*.ERNI",
+ "*.ES",
+ "*.ESQ",
+ "*.ESTATE",
+ "*.ESURANCE",
+ "*.ET",
+ "*.ETISALAT",
+ "*.EU",
+ "*.EUROVISION",
+ "*.EUS",
+ "*.EVENTS",
+ "*.EVERBANK",
+ "*.EXCHANGE",
+ "*.EXPERT",
+ "*.EXPOSED",
+ "*.EXPRESS",
+ "*.EXTRASPACE",
+ "*.FAGE",
+ "*.FAIL",
+ "*.FAIRWINDS",
+ "*.FAITH",
+ "*.FAMILY",
+ "*.FAN",
+ "*.FANS",
+ "*.FARM",
+ "*.FARMERS",
+ "*.FASHION",
+ "*.FAST",
+ "*.FEDEX",
+ "*.FEEDBACK",
+ "*.FERRARI",
+ "*.FERRERO",
+ "*.FI",
+ "*.FIAT",
+ "*.FIDELITY",
+ "*.FIDO",
+ "*.FILM",
+ "*.FINAL",
+ "*.FINANCE",
+ "*.FINANCIAL",
+ "*.FIRE",
+ "*.FIRESTONE",
+ "*.FIRMDALE",
+ "*.FISH",
+ "*.FISHING",
+ "*.FIT",
+ "*.FITNESS",
+ "*.FJ",
+ "*.FK",
+ "*.FLICKR",
+ "*.FLIGHTS",
+ "*.FLIR",
+ "*.FLORIST",
+ "*.FLOWERS",
+ "*.FLY",
+ "*.FM",
+ "*.FO",
+ "*.FOO",
+ "*.FOOD",
+ "*.FOODNETWORK",
+ "*.FOOTBALL",
+ "*.FORD",
+ "*.FOREX",
+ "*.FORSALE",
+ "*.FORUM",
+ "*.FOUNDATION",
+ "*.FOX",
+ "*.FR",
+ "*.FREE",
+ "*.FRESENIUS",
+ "*.FRL",
+ "*.FROGANS",
+ "*.FRONTDOOR",
+ "*.FRONTIER",
+ "*.FTR",
+ "*.FUJITSU",
+ "*.FUJIXEROX",
+ "*.FUN",
+ "*.FUND",
+ "*.FURNITURE",
+ "*.FUTBOL",
+ "*.FYI",
+ "*.GA",
+ "*.GAL",
+ "*.GALLERY",
+ "*.GALLO",
+ "*.GALLUP",
+ "*.GAME",
+ "*.GAMES",
+ "*.GAP",
+ "*.GARDEN",
+ "*.GB",
+ "*.GBIZ",
+ "*.GD",
+ "*.GDN",
+ "*.GE",
+ "*.GEA",
+ "*.GENT",
+ "*.GENTING",
+ "*.GEORGE",
+ "*.GF",
+ "*.GG",
+ "*.GGEE",
+ "*.GH",
+ "*.GI",
+ "*.GIFT",
+ "*.GIFTS",
+ "*.GIVES",
+ "*.GIVING",
+ "*.GL",
+ "*.GLADE",
+ "*.GLASS",
+ "*.GLE",
+ "*.GLOBAL",
+ "*.GLOBO",
+ "*.GM",
+ "*.GMAIL",
+ "*.GMBH",
+ "*.GMO",
+ "*.GMX",
+ "*.GN",
+ "*.GODADDY",
+ "*.GOLD",
+ "*.GOLDPOINT",
+ "*.GOLF",
+ "*.GOO",
+ "*.GOODHANDS",
+ "*.GOODYEAR",
+ "*.GOOG",
+ "*.GOOGLE",
+ "*.GOP",
+ "*.GOT",
+ "*.GOV",
+ "*.GP",
+ "*.GQ",
+ "*.GR",
+ "*.GRAINGER",
+ "*.GRAPHICS",
+ "*.GRATIS",
+ "*.GREEN",
+ "*.GRIPE",
+ "*.GROCERY",
+ "*.GROUP",
+ "*.GS",
+ "*.GT",
+ "*.GU",
+ "*.GUARDIAN",
+ "*.GUCCI",
+ "*.GUGE",
+ "*.GUIDE",
+ "*.GUITARS",
+ "*.GURU",
+ "*.GW",
+ "*.GY",
+ "*.HAIR",
+ "*.HAMBURG",
+ "*.HANGOUT",
+ "*.HAUS",
+ "*.HBO",
+ "*.HDFC",
+ "*.HDFCBANK",
+ "*.HEALTH",
+ "*.HEALTHCARE",
+ "*.HELP",
+ "*.HELSINKI",
+ "*.HERE",
+ "*.HERMES",
+ "*.HGTV",
+ "*.HIPHOP",
+ "*.HISAMITSU",
+ "*.HITACHI",
+ "*.HIV",
+ "*.HK",
+ "*.HKT",
+ "*.HM",
+ "*.HN",
+ "*.HOCKEY",
+ "*.HOLDINGS",
+ "*.HOLIDAY",
+ "*.HOMEDEPOT",
+ "*.HOMEGOODS",
+ "*.HOMES",
+ "*.HOMESENSE",
+ "*.HONDA",
+ "*.HONEYWELL",
+ "*.HORSE",
+ "*.HOSPITAL",
+ "*.HOST",
+ "*.HOSTING",
+ "*.HOT",
+ "*.HOTELES",
+ "*.HOTELS",
+ "*.HOTMAIL",
+ "*.HOUSE",
+ "*.HOW",
+ "*.HR",
+ "*.HSBC",
+ "*.HT",
+ "*.HU",
+ "*.HUGHES",
+ "*.HYATT",
+ "*.HYUNDAI",
+ "*.IBM",
+ "*.ICBC",
+ "*.ICE",
+ "*.ICU",
+ "*.ID",
+ "*.IE",
+ "*.IEEE",
+ "*.IFM",
+ "*.IKANO",
+ "*.IL",
+ "*.IM",
+ "*.IMAMAT",
+ "*.IMDB",
+ "*.IMMO",
+ "*.IMMOBILIEN",
+ "*.IN",
+ "*.INC",
+ "*.INDUSTRIES",
+ "*.INFINITI",
+ "*.INFO",
+ "*.ING",
+ "*.INK",
+ "*.INSTITUTE",
+ "*.INSURANCE",
+ "*.INSURE",
+ "*.INT",
+ "*.INTEL",
+ "*.INTERNATIONAL",
+ "*.INTUIT",
+ "*.INVESTMENTS",
+ "*.IO",
+ "*.IPIRANGA",
+ "*.IQ",
+ "*.IR",
+ "*.IRISH",
+ "*.IS",
+ "*.ISELECT",
+ "*.ISMAILI",
+ "*.IST",
+ "*.ISTANBUL",
+ "*.IT",
+ "*.ITAU",
+ "*.ITV",
+ "*.IVECO",
+ "*.JAGUAR",
+ "*.JAVA",
+ "*.JCB",
+ "*.JCP",
+ "*.JE",
+ "*.JEEP",
+ "*.JETZT",
+ "*.JEWELRY",
+ "*.JIO",
+ "*.JLC",
+ "*.JLL",
+ "*.JM",
+ "*.JMP",
+ "*.JNJ",
+ "*.JO",
+ "*.JOBS",
+ "*.JOBURG",
+ "*.JOT",
+ "*.JOY",
+ "*.JP",
+ "*.JPMORGAN",
+ "*.JPRS",
+ "*.JUEGOS",
+ "*.JUNIPER",
+ "*.KAUFEN",
+ "*.KDDI",
+ "*.KE",
+ "*.KERRYHOTELS",
+ "*.KERRYLOGISTICS",
+ "*.KERRYPROPERTIES",
+ "*.KFH",
+ "*.KG",
+ "*.KH",
+ "*.KI",
+ "*.KIA",
+ "*.KIM",
+ "*.KINDER",
+ "*.KINDLE",
+ "*.KITCHEN",
+ "*.KIWI",
+ "*.KM",
+ "*.KN",
+ "*.KOELN",
+ "*.KOMATSU",
+ "*.KOSHER",
+ "*.KP",
+ "*.KPMG",
+ "*.KPN",
+ "*.KR",
+ "*.KRD",
+ "*.KRED",
+ "*.KUOKGROUP",
+ "*.KW",
+ "*.KY",
+ "*.KYOTO",
+ "*.KZ",
+ "*.LA",
+ "*.LACAIXA",
+ "*.LADBROKES",
+ "*.LAMBORGHINI",
+ "*.LAMER",
+ "*.LANCASTER",
+ "*.LANCIA",
+ "*.LANCOME",
+ "*.LAND",
+ "*.LANDROVER",
+ "*.LANXESS",
+ "*.LASALLE",
+ "*.LAT",
+ "*.LATINO",
+ "*.LATROBE",
+ "*.LAW",
+ "*.LAWYER",
+ "*.LB",
+ "*.LC",
+ "*.LDS",
+ "*.LEASE",
+ "*.LECLERC",
+ "*.LEFRAK",
+ "*.LEGAL",
+ "*.LEGO",
+ "*.LEXUS",
+ "*.LGBT",
+ "*.LI",
+ "*.LIAISON",
+ "*.LIDL",
+ "*.LIFE",
+ "*.LIFEINSURANCE",
+ "*.LIFESTYLE",
+ "*.LIGHTING",
+ "*.LIKE",
+ "*.LILLY",
+ "*.LIMITED",
+ "*.LIMO",
+ "*.LINCOLN",
+ "*.LINDE",
+ "*.LINK",
+ "*.LIPSY",
+ "*.LIVE",
+ "*.LIVING",
+ "*.LIXIL",
+ "*.LK",
+ "*.LLC",
+ "*.LOAN",
+ "*.LOANS",
+ "*.LOCKER",
+ "*.LOCUS",
+ "*.LOFT",
+ "*.LOL",
+ "*.LONDON",
+ "*.LOTTE",
+ "*.LOTTO",
+ "*.LOVE",
+ "*.LPL",
+ "*.LPLFINANCIAL",
+ "*.LR",
+ "*.LS",
+ "*.LT",
+ "*.LTD",
+ "*.LTDA",
+ "*.LU",
+ "*.LUNDBECK",
+ "*.LUPIN",
+ "*.LUXE",
+ "*.LUXURY",
+ "*.LV",
+ "*.LY",
+ "*.MA",
+ "*.MACYS",
+ "*.MADRID",
+ "*.MAIF",
+ "*.MAISON",
+ "*.MAKEUP",
+ "*.MAN",
+ "*.MANAGEMENT",
+ "*.MANGO",
+ "*.MAP",
+ "*.MARKET",
+ "*.MARKETING",
+ "*.MARKETS",
+ "*.MARRIOTT",
+ "*.MARSHALLS",
+ "*.MASERATI",
+ "*.MATTEL",
+ "*.MBA",
+ "*.MC",
+ "*.MCKINSEY",
+ "*.MD",
+ "*.ME",
+ "*.MED",
+ "*.MEDIA",
+ "*.MEET",
+ "*.MELBOURNE",
+ "*.MEME",
+ "*.MEMORIAL",
+ "*.MEN",
+ "*.MENU",
+ "*.MERCKMSD",
+ "*.METLIFE",
+ "*.MG",
+ "*.MH",
+ "*.MIAMI",
+ "*.MICROSOFT",
+ "*.MIL",
+ "*.MINI",
+ "*.MINT",
+ "*.MIT",
+ "*.MITSUBISHI",
+ "*.MK",
+ "*.ML",
+ "*.MLB",
+ "*.MLS",
+ "*.MM",
+ "*.MMA",
+ "*.MN",
+ "*.MO",
+ "*.MOBI",
+ "*.MOBILE",
+ "*.MOBILY",
+ "*.MODA",
+ "*.MOE",
+ "*.MOI",
+ "*.MOM",
+ "*.MONASH",
+ "*.MONEY",
+ "*.MONSTER",
+ "*.MOPAR",
+ "*.MORMON",
+ "*.MORTGAGE",
+ "*.MOSCOW",
+ "*.MOTO",
+ "*.MOTORCYCLES",
+ "*.MOV",
+ "*.MOVIE",
+ "*.MOVISTAR",
+ "*.MP",
+ "*.MQ",
+ "*.MR",
+ "*.MS",
+ "*.MSD",
+ "*.MT",
+ "*.MTN",
+ "*.MTR",
+ "*.MU",
+ "*.MUSEUM",
+ "*.MUTUAL",
+ "*.MV",
+ "*.MW",
+ "*.MX",
+ "*.MY",
+ "*.MZ",
+ "*.NA",
+ "*.NAB",
+ "*.NADEX",
+ "*.NAGOYA",
+ "*.NAME",
+ "*.NATIONWIDE",
+ "*.NATURA",
+ "*.NAVY",
+ "*.NBA",
+ "*.NC",
+ "*.NE",
+ "*.NEC",
+ "*.NET",
+ "*.NETBANK",
+ "*.NETFLIX",
+ "*.NETWORK",
+ "*.NEUSTAR",
+ "*.NEW",
+ "*.NEWHOLLAND",
+ "*.NEWS",
+ "*.NEXT",
+ "*.NEXTDIRECT",
+ "*.NEXUS",
+ "*.NF",
+ "*.NFL",
+ "*.NG",
+ "*.NGO",
+ "*.NHK",
+ "*.NI",
+ "*.NICO",
+ "*.NIKE",
+ "*.NIKON",
+ "*.NINJA",
+ "*.NISSAN",
+ "*.NISSAY",
+ "*.NL",
+ "*.NO",
+ "*.NOKIA",
+ "*.NORTHWESTERNMUTUAL",
+ "*.NORTON",
+ "*.NOW",
+ "*.NOWRUZ",
+ "*.NOWTV",
+ "*.NP",
+ "*.NR",
+ "*.NRA",
+ "*.NRW",
+ "*.NTT",
+ "*.NU",
+ "*.NYC",
+ "*.NZ",
+ "*.OBI",
+ "*.OBSERVER",
+ "*.OFF",
+ "*.OFFICE",
+ "*.OKINAWA",
+ "*.OLAYAN",
+ "*.OLAYANGROUP",
+ "*.OLDNAVY",
+ "*.OLLO",
+ "*.OM",
+ "*.OMEGA",
+ "*.ONE",
+ "*.ONG",
+ "*.ONL",
+ "*.ONLINE",
+ "*.ONYOURSIDE",
+ "*.OOO",
+ "*.OPEN",
+ "*.ORACLE",
+ "*.ORANGE",
+ "*.ORG",
+ "*.ORGANIC",
+ "*.ORIGINS",
+ "*.OSAKA",
+ "*.OTSUKA",
+ "*.OTT",
+ "*.OVH",
+ "*.PA",
+ "*.PAGE",
+ "*.PANASONIC",
+ "*.PANERAI",
+ "*.PARIS",
+ "*.PARS",
+ "*.PARTNERS",
+ "*.PARTS",
+ "*.PARTY",
+ "*.PASSAGENS",
+ "*.PAY",
+ "*.PCCW",
+ "*.PE",
+ "*.PET",
+ "*.PF",
+ "*.PFIZER",
+ "*.PG",
+ "*.PH",
+ "*.PHARMACY",
+ "*.PHD",
+ "*.PHILIPS",
+ "*.PHONE",
+ "*.PHOTO",
+ "*.PHOTOGRAPHY",
+ "*.PHOTOS",
+ "*.PHYSIO",
+ "*.PIAGET",
+ "*.PICS",
+ "*.PICTET",
+ "*.PICTURES",
+ "*.PID",
+ "*.PIN",
+ "*.PING",
+ "*.PINK",
+ "*.PIONEER",
+ "*.PIZZA",
+ "*.PK",
+ "*.PL",
+ "*.PLACE",
+ "*.PLAY",
+ "*.PLAYSTATION",
+ "*.PLUMBING",
+ "*.PLUS",
+ "*.PM",
+ "*.PN",
+ "*.PNC",
+ "*.POHL",
+ "*.POKER",
+ "*.POLITIE",
+ "*.PORN",
+ "*.POST",
+ "*.PR",
+ "*.PRAMERICA",
+ "*.PRAXI",
+ "*.PRESS",
+ "*.PRIME",
+ "*.PRO",
+ "*.PROD",
+ "*.PRODUCTIONS",
+ "*.PROF",
+ "*.PROGRESSIVE",
+ "*.PROMO",
+ "*.PROPERTIES",
+ "*.PROPERTY",
+ "*.PROTECTION",
+ "*.PRU",
+ "*.PRUDENTIAL",
+ "*.PS",
+ "*.PT",
+ "*.PUB",
+ "*.PW",
+ "*.PWC",
+ "*.PY",
+ "*.QA",
+ "*.QPON",
+ "*.QUEBEC",
+ "*.QUEST",
+ "*.QVC",
+ "*.RACING",
+ "*.RADIO",
+ "*.RAID",
+ "*.RE",
+ "*.READ",
+ "*.REALESTATE",
+ "*.REALTOR",
+ "*.REALTY",
+ "*.RECIPES",
+ "*.RED",
+ "*.REDSTONE",
+ "*.REDUMBRELLA",
+ "*.REHAB",
+ "*.REISE",
+ "*.REISEN",
+ "*.REIT",
+ "*.RELIANCE",
+ "*.REN",
+ "*.RENT",
+ "*.RENTALS",
+ "*.REPAIR",
+ "*.REPORT",
+ "*.REPUBLICAN",
+ "*.REST",
+ "*.RESTAURANT",
+ "*.REVIEW",
+ "*.REVIEWS",
+ "*.REXROTH",
+ "*.RICH",
+ "*.RICHARDLI",
+ "*.RICOH",
+ "*.RIGHTATHOME",
+ "*.RIL",
+ "*.RIO",
+ "*.RIP",
+ "*.RMIT",
+ "*.RO",
+ "*.ROCHER",
+ "*.ROCKS",
+ "*.RODEO",
+ "*.ROGERS",
+ "*.ROOM",
+ "*.RS",
+ "*.RSVP",
+ "*.RU",
+ "*.RUGBY",
+ "*.RUHR",
+ "*.RUN",
+ "*.RW",
+ "*.RWE",
+ "*.RYUKYU",
+ "*.SA",
+ "*.SAARLAND",
+ "*.SAFE",
+ "*.SAFETY",
+ "*.SAKURA",
+ "*.SALE",
+ "*.SALON",
+ "*.SAMSCLUB",
+ "*.SAMSUNG",
+ "*.SANDVIK",
+ "*.SANDVIKCOROMANT",
+ "*.SANOFI",
+ "*.SAP",
+ "*.SARL",
+ "*.SAS",
+ "*.SAVE",
+ "*.SAXO",
+ "*.SB",
+ "*.SBI",
+ "*.SBS",
+ "*.SC",
+ "*.SCA",
+ "*.SCB",
+ "*.SCHAEFFLER",
+ "*.SCHMIDT",
+ "*.SCHOLARSHIPS",
+ "*.SCHOOL",
+ "*.SCHULE",
+ "*.SCHWARZ",
+ "*.SCIENCE",
+ "*.SCJOHNSON",
+ "*.SCOR",
+ "*.SCOT",
+ "*.SD",
+ "*.SE",
+ "*.SEARCH",
+ "*.SEAT",
+ "*.SECURE",
+ "*.SECURITY",
+ "*.SEEK",
+ "*.SELECT",
+ "*.SENER",
+ "*.SERVICES",
+ "*.SES",
+ "*.SEVEN",
+ "*.SEW",
+ "*.SEX",
+ "*.SEXY",
+ "*.SFR",
+ "*.SG",
+ "*.SH",
+ "*.SHANGRILA",
+ "*.SHARP",
+ "*.SHAW",
+ "*.SHELL",
+ "*.SHIA",
+ "*.SHIKSHA",
+ "*.SHOES",
+ "*.SHOP",
+ "*.SHOPPING",
+ "*.SHOUJI",
+ "*.SHOW",
+ "*.SHOWTIME",
+ "*.SHRIRAM",
+ "*.SI",
+ "*.SILK",
+ "*.SINA",
+ "*.SINGLES",
+ "*.SITE",
+ "*.SJ",
+ "*.SK",
+ "*.SKI",
+ "*.SKIN",
+ "*.SKY",
+ "*.SKYPE",
+ "*.SL",
+ "*.SLING",
+ "*.SM",
+ "*.SMART",
+ "*.SMILE",
+ "*.SN",
+ "*.SNCF",
+ "*.SO",
+ "*.SOCCER",
+ "*.SOCIAL",
+ "*.SOFTBANK",
+ "*.SOFTWARE",
+ "*.SOHU",
+ "*.SOLAR",
+ "*.SOLUTIONS",
+ "*.SONG",
+ "*.SONY",
+ "*.SOY",
+ "*.SPACE",
+ "*.SPIEGEL",
+ "*.SPORT",
+ "*.SPOT",
+ "*.SPREADBETTING",
+ "*.SR",
+ "*.SRL",
+ "*.SRT",
+ "*.ST",
+ "*.STADA",
+ "*.STAPLES",
+ "*.STAR",
+ "*.STARHUB",
+ "*.STATEBANK",
+ "*.STATEFARM",
+ "*.STATOIL",
+ "*.STC",
+ "*.STCGROUP",
+ "*.STOCKHOLM",
+ "*.STORAGE",
+ "*.STORE",
+ "*.STREAM",
+ "*.STUDIO",
+ "*.STUDY",
+ "*.STYLE",
+ "*.SU",
+ "*.SUCKS",
+ "*.SUPPLIES",
+ "*.SUPPLY",
+ "*.SUPPORT",
+ "*.SURF",
+ "*.SURGERY",
+ "*.SUZUKI",
+ "*.SV",
+ "*.SWATCH",
+ "*.SWIFTCOVER",
+ "*.SWISS",
+ "*.SX",
+ "*.SY",
+ "*.SYDNEY",
+ "*.SYMANTEC",
+ "*.SYSTEMS",
+ "*.SZ",
+ "*.TAB",
+ "*.TAIPEI",
+ "*.TALK",
+ "*.TAOBAO",
+ "*.TARGET",
+ "*.TATAMOTORS",
+ "*.TATAR",
+ "*.TATTOO",
+ "*.TAX",
+ "*.TAXI",
+ "*.TC",
+ "*.TCI",
+ "*.TD",
+ "*.TDK",
+ "*.TEAM",
+ "*.TECH",
+ "*.TECHNOLOGY",
+ "*.TEL",
+ "*.TELEFONICA",
+ "*.TEMASEK",
+ "*.TENNIS",
+ "*.TEVA",
+ "*.TF",
+ "*.TG",
+ "*.TH",
+ "*.THD",
+ "*.THEATER",
+ "*.THEATRE",
+ "*.TIAA",
+ "*.TICKETS",
+ "*.TIENDA",
+ "*.TIFFANY",
+ "*.TIPS",
+ "*.TIRES",
+ "*.TIROL",
+ "*.TJ",
+ "*.TJMAXX",
+ "*.TJX",
+ "*.TK",
+ "*.TKMAXX",
+ "*.TL",
+ "*.TM",
+ "*.TMALL",
+ "*.TN",
+ "*.TO",
+ "*.TODAY",
+ "*.TOKYO",
+ "*.TOOLS",
+ "*.TOP",
+ "*.TORAY",
+ "*.TOSHIBA",
+ "*.TOTAL",
+ "*.TOURS",
+ "*.TOWN",
+ "*.TOYOTA",
+ "*.TOYS",
+ "*.TR",
+ "*.TRADE",
+ "*.TRADING",
+ "*.TRAINING",
+ "*.TRAVEL",
+ "*.TRAVELCHANNEL",
+ "*.TRAVELERS",
+ "*.TRAVELERSINSURANCE",
+ "*.TRUST",
+ "*.TRV",
+ "*.TT",
+ "*.TUBE",
+ "*.TUI",
+ "*.TUNES",
+ "*.TUSHU",
+ "*.TV",
+ "*.TVS",
+ "*.TW",
+ "*.TZ",
+ "*.UA",
+ "*.UBANK",
+ "*.UBS",
+ "*.UCONNECT",
+ "*.UG",
+ "*.UK",
+ "*.UNICOM",
+ "*.UNIVERSITY",
+ "*.UNO",
+ "*.UOL",
+ "*.UPS",
+ "*.US",
+ "*.UY",
+ "*.UZ",
+ "*.VA",
+ "*.VACATIONS",
+ "*.VANA",
+ "*.VANGUARD",
+ "*.VC",
+ "*.VE",
+ "*.VEGAS",
+ "*.VENTURES",
+ "*.VERISIGN",
+ "*.VERSICHERUNG",
+ "*.VET",
+ "*.VG",
+ "*.VI",
+ "*.VIAJES",
+ "*.VIDEO",
+ "*.VIG",
+ "*.VIKING",
+ "*.VILLAS",
+ "*.VIN",
+ "*.VIP",
+ "*.VIRGIN",
+ "*.VISA",
+ "*.VISION",
+ "*.VISTAPRINT",
+ "*.VIVA",
+ "*.VIVO",
+ "*.VLAANDEREN",
+ "*.VN",
+ "*.VODKA",
+ "*.VOLKSWAGEN",
+ "*.VOLVO",
+ "*.VOTE",
+ "*.VOTING",
+ "*.VOTO",
+ "*.VOYAGE",
+ "*.VU",
+ "*.VUELOS",
+ "*.WALES",
+ "*.WALMART",
+ "*.WALTER",
+ "*.WANG",
+ "*.WANGGOU",
+ "*.WARMAN",
+ "*.WATCH",
+ "*.WATCHES",
+ "*.WEATHER",
+ "*.WEATHERCHANNEL",
+ "*.WEBCAM",
+ "*.WEBER",
+ "*.WEBSITE",
+ "*.WED",
+ "*.WEDDING",
+ "*.WEIBO",
+ "*.WEIR",
+ "*.WF",
+ "*.WHOSWHO",
+ "*.WIEN",
+ "*.WIKI",
+ "*.WILLIAMHILL",
+ "*.WIN",
+ "*.WINDOWS",
+ "*.WINE",
+ "*.WINNERS",
+ "*.WME",
+ "*.WOLTERSKLUWER",
+ "*.WOODSIDE",
+ "*.WORK",
+ "*.WORKS",
+ "*.WORLD",
+ "*.WOW",
+ "*.WS",
+ "*.WTC",
+ "*.WTF",
+ "*.XBOX",
+ "*.XEROX",
+ "*.XFINITY",
+ "*.XIHUAN",
+ "*.XIN",
+ "*.XN--11B4C3D",
+ "*.XN--1CK2E1B",
+ "*.XN--1QQW23A",
+ "*.XN--2SCRJ9C",
+ "*.XN--30RR7Y",
+ "*.XN--3BST00M",
+ "*.XN--3DS443G",
+ "*.XN--3E0B707E",
+ "*.XN--3HCRJ9C",
+ "*.XN--3OQ18VL8PN36A",
+ "*.XN--3PXU8K",
+ "*.XN--42C2D9A",
+ "*.XN--45BR5CYL",
+ "*.XN--45BRJ9C",
+ "*.XN--45Q11C",
+ "*.XN--4GBRIM",
+ "*.XN--54B7FTA0CC",
+ "*.XN--55QW42G",
+ "*.XN--55QX5D",
+ "*.XN--5SU34J936BGSG",
+ "*.XN--5TZM5G",
+ "*.XN--6FRZ82G",
+ "*.XN--6QQ986B3XL",
+ "*.XN--80ADXHKS",
+ "*.XN--80AO21A",
+ "*.XN--80AQECDR1A",
+ "*.XN--80ASEHDB",
+ "*.XN--80ASWG",
+ "*.XN--8Y0A063A",
+ "*.XN--90A3AC",
+ "*.XN--90AE",
+ "*.XN--90AIS",
+ "*.XN--9DBQ2A",
+ "*.XN--9ET52U",
+ "*.XN--9KRT00A",
+ "*.XN--B4W605FERD",
+ "*.XN--BCK1B9A5DRE4C",
+ "*.XN--C1AVG",
+ "*.XN--C2BR7G",
+ "*.XN--CCK2B3B",
+ "*.XN--CG4BKI",
+ "*.XN--CLCHC0EA0B2G2A9GCD",
+ "*.XN--CZR694B",
+ "*.XN--CZRS0T",
+ "*.XN--CZRU2D",
+ "*.XN--D1ACJ3B",
+ "*.XN--D1ALF",
+ "*.XN--E1A4C",
+ "*.XN--ECKVDTC9D",
+ "*.XN--EFVY88H",
+ "*.XN--ESTV75G",
+ "*.XN--FCT429K",
+ "*.XN--FHBEI",
+ "*.XN--FIQ228C5HS",
+ "*.XN--FIQ64B",
+ "*.XN--FIQS8S",
+ "*.XN--FIQZ9S",
+ "*.XN--FJQ720A",
+ "*.XN--FLW351E",
+ "*.XN--FPCRJ9C3D",
+ "*.XN--FZC2C9E2C",
+ "*.XN--FZYS8D69UVGM",
+ "*.XN--G2XX48C",
+ "*.XN--GCKR3F0F",
+ "*.XN--GECRJ9C",
+ "*.XN--GK3AT1E",
+ "*.XN--H2BREG3EVE",
+ "*.XN--H2BRJ9C",
+ "*.XN--H2BRJ9C8C",
+ "*.XN--HXT814E",
+ "*.XN--I1B6B1A6A2E",
+ "*.XN--IMR513N",
+ "*.XN--IO0A7I",
+ "*.XN--J1AEF",
+ "*.XN--J1AMH",
+ "*.XN--J6W193G",
+ "*.XN--JLQ61U9W7B",
+ "*.XN--JVR189M",
+ "*.XN--KCRX77D1X4A",
+ "*.XN--KPRW13D",
+ "*.XN--KPRY57D",
+ "*.XN--KPU716F",
+ "*.XN--KPUT3I",
+ "*.XN--L1ACC",
+ "*.XN--LGBBAT1AD8J",
+ "*.XN--MGB9AWBF",
+ "*.XN--MGBA3A3EJT",
+ "*.XN--MGBA3A4F16A",
+ "*.XN--MGBA7C0BBN0A",
+ "*.XN--MGBAAKC7DVF",
+ "*.XN--MGBAAM7A8H",
+ "*.XN--MGBAB2BD",
+ "*.XN--MGBAI9AZGQP6J",
+ "*.XN--MGBAYH7GPA",
+ "*.XN--MGBB9FBPOB",
+ "*.XN--MGBBH1A",
+ "*.XN--MGBBH1A71E",
+ "*.XN--MGBC0A9AZCG",
+ "*.XN--MGBCA7DZDO",
+ "*.XN--MGBERP4A5D4AR",
+ "*.XN--MGBGU82A",
+ "*.XN--MGBI4ECEXP",
+ "*.XN--MGBPL2FH",
+ "*.XN--MGBT3DHD",
+ "*.XN--MGBTX2B",
+ "*.XN--MGBX4CD0AB",
+ "*.XN--MIX891F",
+ "*.XN--MK1BU44C",
+ "*.XN--MXTQ1M",
+ "*.XN--NGBC5AZD",
+ "*.XN--NGBE9E0A",
+ "*.XN--NGBRX",
+ "*.XN--NODE",
+ "*.XN--NQV7F",
+ "*.XN--NQV7FS00EMA",
+ "*.XN--NYQY26A",
+ "*.XN--O3CW4H",
+ "*.XN--OGBPF8FL",
+ "*.XN--OTU796D",
+ "*.XN--P1ACF",
+ "*.XN--P1AI",
+ "*.XN--PBT977C",
+ "*.XN--PGBS0DH",
+ "*.XN--PSSY2U",
+ "*.XN--Q9JYB4C",
+ "*.XN--QCKA1PMC",
+ "*.XN--QXAM",
+ "*.XN--RHQV96G",
+ "*.XN--ROVU88B",
+ "*.XN--RVC1E0AM3E",
+ "*.XN--S9BRJ9C",
+ "*.XN--SES554G",
+ "*.XN--T60B56A",
+ "*.XN--TCKWE",
+ "*.XN--TIQ49XQYJ",
+ "*.XN--UNUP4Y",
+ "*.XN--VERMGENSBERATER-CTB",
+ "*.XN--VERMGENSBERATUNG-PWB",
+ "*.XN--VHQUV",
+ "*.XN--VUQ861B",
+ "*.XN--W4R85EL8FHU5DNRA",
+ "*.XN--W4RS40L",
+ "*.XN--WGBH1C",
+ "*.XN--WGBL6A",
+ "*.XN--XHQ521B",
+ "*.XN--XKC2AL3HYE2A",
+ "*.XN--XKC2DL3A5EE0H",
+ "*.XN--Y9A3AQ",
+ "*.XN--YFRO4I67O",
+ "*.XN--YGBI2AMMX",
+ "*.XN--ZFR164B",
+ "*.XXX",
+ "*.XYZ",
+ "*.YACHTS",
+ "*.YAHOO",
+ "*.YAMAXUN",
+ "*.YANDEX",
+ "*.YE",
+ "*.YODOBASHI",
+ "*.YOGA",
+ "*.YOKOHAMA",
+ "*.YOU",
+ "*.YOUTUBE",
+ "*.YT",
+ "*.YUN",
+ "*.ZA",
+ "*.ZAPPOS",
+ "*.ZARA",
+ "*.ZERO",
+ "*.ZIP",
+ "*.ZIPPO",
+ "*.ZM",
+ "*.ZONE",
+ "*.ZUERICH",
+ "*.ZW"
+ );
+}
diff --git a/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java b/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java
new file mode 100644
index 0000000..2136b76
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/config/TrustAnchorOptions.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.config;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Enum that enumerates valid trust anchors
+ */
+public enum TrustAnchorOptions {
+ USER_SYSTEM(0),
+ SYSTEM_ONLY(1),
+ USER_ONLY(2),
+ LIMITED_SYSTEM(3);
+
+ private final int mType;
+
+ TrustAnchorOptions(int type) {
+ this.mType = type;
+ }
+
+ /**
+ * @return the mType value
+ */
+ public int getType() {
+ return this.mType;
+ }
+
+ /**
+ * @param id the id of the trust anchor
+ * @return the mType
+ */
+ @NonNull
+ public static TrustAnchorOptions fromId(int id) {
+ switch (id) {
+ case 0:
+ return USER_SYSTEM;
+ case 1:
+ return SYSTEM_ONLY;
+ case 2:
+ return USER_ONLY;
+ case 3:
+ return LIMITED_SYSTEM;
+ }
+ return USER_SYSTEM;
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java b/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java
new file mode 100644
index 0000000..20888ee
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/context/SecureContextCompat.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.context;
+
+import android.annotation.TargetApi;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+import androidx.security.crypto.FileCipher;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.Executor;
+
+/**
+ * Class that provides access to an encrypted file input and output streams, as well as encrypted
+ * SharedPreferences.
+ */
+public class SecureContextCompat {
+
+ private static final String TAG = "SecureContextCompat";
+
+ private Context mContext;
+ private SecureConfig mSecureConfig;
+
+ private static final String DEFAULT_FILE_ENCRYPTION_KEY = "default_encryption_key";
+
+ /**
+ * A listener for getting access to encrypted files. Required when the key requires a
+ * Biometric Prompt.
+ */
+ public interface EncryptedFileInputStreamListener {
+ /**
+ * @param inputStream
+ */
+ void onEncryptedFileInput(@NonNull FileInputStream inputStream);
+ }
+
+ public SecureContextCompat(@NonNull Context context) {
+ this(context, SecureConfig.getDefault());
+ }
+
+ public SecureContextCompat(@NonNull Context context, @NonNull SecureConfig secureConfig) {
+ mContext = context;
+ mSecureConfig = secureConfig;
+ }
+
+ /**
+ * Open an encrypted private file associated with this Context's application
+ * package for reading.
+ *
+ * @param name The name of the file to open; can not contain path separators.
+ * @throws IOException
+ */
+ public void openEncryptedFileInput(@NonNull String name,
+ @NonNull Executor executor,
+ @NonNull EncryptedFileInputStreamListener listener) throws IOException {
+ new FileCipher(name, mContext.openFileInput(name), mSecureConfig, executor, listener);
+ }
+
+ /**
+ * Open a private encrypted file associated with this Context's application package for
+ * writing. Creates the file if it doesn't already exist.
+ * <p>
+ * The written file will be encrypted with the default keyPairAlias.
+ *
+ * @param name The name of the file to open; can not contain path separators.
+ * @param mode Operating mode.
+ * @return The resulting {@link FileOutputStream}.
+ * @throws IOException
+ */
+ @NonNull
+ public FileOutputStream openEncryptedFileOutput(@NonNull String name, int mode)
+ throws IOException {
+ return openEncryptedFileOutput(name, mode, DEFAULT_FILE_ENCRYPTION_KEY);
+ }
+
+ /**
+ * Open a private encrypted file associated with this Context's application package for
+ * writing. Creates the file if it doesn't already exist.
+ * <p>
+ * The written file will be encrypted with the specified keyPairAlias.
+ *
+ * @param name The name of the file to open; can not contain path separators.
+ * @param mode Operating mode.
+ * @param keyPairAlias The alias of the KeyPair used for encryption, the KeyPair will be
+ * created if it does not exist.
+ * @return The resulting {@link FileOutputStream}.
+ * @throws IOException
+ */
+ @NonNull
+ public FileOutputStream openEncryptedFileOutput(@NonNull String name, int mode,
+ @NonNull String keyPairAlias)
+ throws IOException {
+ FileCipher fileCipher = new FileCipher(keyPairAlias,
+ mContext.openFileOutput(name, mode), mSecureConfig);
+ return fileCipher.getFileOutputStream();
+ }
+
+
+ /**
+ * Checks to see if the device is locked.
+ *
+ * @return true if the device is locked, false otherwise.
+ */
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
+ public boolean deviceLocked() {
+ KeyguardManager keyGuardManager =
+ (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ return keyGuardManager.isDeviceLocked();
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java b/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java
new file mode 100644
index 0000000..da2633a
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/EphemeralSecretKey.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.crypto;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.spec.KeySpec;
+import java.util.Arrays;
+import java.util.Locale;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.spec.GCMParameterSpec;
+
+/**
+ * A destroyable SecretKey. Can be completely evicted from memory on deletion.
+ */
+public class EphemeralSecretKey implements KeySpec, SecretKey {
+
+ private static final long serialVersionUID = 7177238317307289223L;
+ private static final String TAG = "EphemeralSecretKey";
+
+ private byte[] mKey;
+
+ private String mAlgorithm;
+
+ private boolean mKeyDestroyed;
+
+ private SecureConfig mSecureConfig;
+
+ public EphemeralSecretKey(@NonNull byte[] key) {
+ this(key, KeyProperties.KEY_ALGORITHM_AES, SecureConfig.getDefault());
+ }
+
+ public EphemeralSecretKey(@NonNull byte[] key, @NonNull String algorithm) {
+ this(key, algorithm, SecureConfig.getDefault());
+ }
+
+ public EphemeralSecretKey(@NonNull byte[] key, @NonNull SecureConfig secureConfig) {
+ this(key, KeyProperties.KEY_ALGORITHM_AES, secureConfig);
+ }
+
+ /**
+ * Constructs a secret mKey from the given byte array.
+ *
+ * <p>This constructor does not check if the given bytes indeed specify a
+ * secret mKey of the specified mAlgorithm. For example, if the mAlgorithm is
+ * DES, this constructor does not check if <code>mKey</code> is 8 bytes
+ * long, and also does not check for weak or semi-weak keys.
+ * In order for those checks to be performed, an mAlgorithm-specific
+ * <i>mKey specification</i> class (in this case:
+ * {@link DESKeySpec DESKeySpec})
+ * should be used.
+ *
+ * @param key the mKey material of the secret mKey. The contents of
+ * the array are copied to protect against subsequent modification.
+ * @param algorithm the name of the secret-mKey mAlgorithm to be associated
+ * with the given mKey material.
+ * @throws IllegalArgumentException if <code>mAlgorithm</code>
+ * is null or <code>mKey</code> is null or empty.
+ */
+ public EphemeralSecretKey(@NonNull byte[] key, @NonNull String algorithm,
+ @NonNull SecureConfig secureConfig) {
+ if (key == null || algorithm == null) {
+ throw new IllegalArgumentException("Missing argument");
+ }
+ if (key.length == 0) {
+ throw new IllegalArgumentException("Empty mKey");
+ }
+ this.mKey = key;
+ this.mAlgorithm = algorithm;
+ this.mKeyDestroyed = false;
+ this.mSecureConfig = secureConfig;
+ }
+
+ /**
+ * Constructs a secret mKey from the given byte array, using the first
+ * <code>len</code> bytes of <code>mKey</code>, starting at
+ * <code>offset</code> inclusive.
+ *
+ * <p> The bytes that constitute the secret mKey are
+ * those between <code>mKey[offset]</code> and
+ * <code>mKey[offset+len-1]</code> inclusive.
+ *
+ * <p>This constructor does not check if the given bytes indeed specify a
+ * secret mKey of the specified mAlgorithm. For example, if the mAlgorithm is
+ * DES, this constructor does not check if <code>mKey</code> is 8 bytes
+ * long, and also does not check for weak or semi-weak keys.
+ * In order for those checks to be performed, an mAlgorithm-specific mKey
+ * specification class (in this case:
+ * {@link DESKeySpec DESKeySpec})
+ * must be used.
+ *
+ * @param key the mKey material of the secret mKey. The first
+ * <code>len</code> bytes of the array beginning at
+ * <code>offset</code> inclusive are copied to protect
+ * against subsequent modification.
+ * @param offset the offset in <code>mKey</code> where the mKey material
+ * starts.
+ * @param len the length of the mKey material.
+ * @param algorithm the name of the secret-mKey mAlgorithm to be associated
+ * with the given mKey material.
+ * @throws IllegalArgumentException if <code>mAlgorithm</code>
+ * is null or <code>mKey</code> is null,
+ * empty, or too short,
+ * i.e. {@code mKey.length-offset<len}.
+ * @throws ArrayIndexOutOfBoundsException is thrown if
+ * <code>offset</code> or <code>len</code>
+ * index bytes outside the
+ * <code>mKey</code>.
+ */
+ public EphemeralSecretKey(@NonNull byte[] key, int offset, int len,
+ @NonNull String algorithm) {
+ if (key == null || algorithm == null) {
+ throw new IllegalArgumentException("Missing argument");
+ }
+ if (key.length == 0) {
+ throw new IllegalArgumentException("Empty mKey");
+ }
+ if (key.length - offset < len) {
+ throw new IllegalArgumentException("Invalid offset/length combination");
+ }
+ if (len < 0) {
+ throw new ArrayIndexOutOfBoundsException("len is negative");
+ }
+ this.mKey = new byte[len];
+ System.arraycopy(key, offset, this.mKey, 0, len);
+ this.mAlgorithm = algorithm;
+ this.mKeyDestroyed = false;
+ }
+
+ /**
+ * Returns the name of the mAlgorithm associated with this secret mKey.
+ *
+ * @return the secret mKey mAlgorithm.
+ */
+ @NonNull
+ public String getAlgorithm() {
+ return this.mAlgorithm;
+ }
+
+ /**
+ * Returns the name of the encoding format for this secret mKey.
+ *
+ * @return the string "RAW".
+ */
+ @NonNull
+ public String getFormat() {
+ return "RAW";
+ }
+
+ /**
+ * Returns the mKey material of this secret mKey.
+ *
+ * @return the mKey material. Returns a new array
+ * each time this method is called.
+ */
+ @NonNull
+ public byte[] getEncoded() {
+ return this.mKey;
+ }
+
+ /**
+ * Calculates a hash code value for the object.
+ * Objects that are equal will also have the same hashcode.
+ */
+ public int hashCode() {
+ int retval = 0;
+ for (int i = 1; i < this.mKey.length; i++) {
+ retval += this.mKey[i] * i;
+ }
+ if (this.mAlgorithm.equalsIgnoreCase("TripleDES")) {
+ return (retval ^= "desede".hashCode());
+ } else {
+ return (retval ^= this.mAlgorithm.toLowerCase(Locale.ENGLISH).hashCode());
+ }
+ }
+
+ /**
+ * Tests for equality between the specified object and this
+ * object. Two SecretKeySpec objects are considered equal if
+ * they are both SecretKey instances which have the
+ * same case-insensitive mAlgorithm name and mKey encoding.
+ *
+ * @param obj the object to test for equality with this object.
+ * @return true if the objects are considered equal, false if
+ * <code>obj</code> is null or otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof SecretKey)) {
+ return false;
+ }
+
+ String thatAlg = ((SecretKey) obj).getAlgorithm();
+ if (!(thatAlg.equalsIgnoreCase(this.mAlgorithm))) {
+ if ((!(thatAlg.equalsIgnoreCase("DESede"))
+ || !(this.mAlgorithm.equalsIgnoreCase("TripleDES")))
+ && (!(thatAlg.equalsIgnoreCase("TripleDES"))
+ || !(this.mAlgorithm.equalsIgnoreCase("DESede")))) {
+ return false;
+ }
+ }
+
+ byte[] thatKey = ((SecretKey) obj).getEncoded();
+
+ return MessageDigest.isEqual(this.mKey, thatKey);
+ }
+
+ @Override
+ public void destroy() {
+ if (!mKeyDestroyed) {
+ Arrays.fill(mKey, (byte) 0);
+ this.mKey = null;
+ mKeyDestroyed = true;
+ }
+ }
+
+
+ /**
+ * Manually destroy the cipher by zeroing out all instances of the mKey.
+ *
+ * @param cipher The Cipher used with this mKey.
+ * @param opmode The opmode of the cipher.
+ */
+ public void destroyCipherKey(@NonNull Cipher cipher, int opmode) {
+ try {
+ byte[] blankKey = new byte[mSecureConfig.getSymmetricKeySize() / 8];
+ byte[] iv = new byte[SecureConfig.AES_IV_SIZE_BYTES];
+ Arrays.fill(blankKey, (byte) 0);
+ Arrays.fill(iv, (byte) 0);
+ EphemeralSecretKey blankSecretKey =
+ new EphemeralSecretKey(blankKey, mSecureConfig.getSymmetricKeyAlgorithm());
+ cipher.init(opmode, blankSecretKey,
+ new GCMParameterSpec(mSecureConfig.getSymmetricGcmTagLength(), iv));
+ } catch (GeneralSecurityException e) {
+ throw new SecurityException("Could not destroy mKey.");
+ }
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mKeyDestroyed;
+ }
+}
+
diff --git a/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java b/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java
new file mode 100644
index 0000000..19b36d3
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/FileCipher.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.security.SecureConfig;
+import androidx.security.context.SecureContextCompat;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * Class used to create and read encrypted files. Provides implementations
+ * of EncryptedFileInput/Output Streams.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class FileCipher {
+
+ private String mFileName;
+ private String mKeyPairAlias;
+ private FileInputStream mFileInputStream;
+ private FileOutputStream mFileOutputStream;
+
+ private SecureContextCompat.EncryptedFileInputStreamListener mListener;
+
+ SecureConfig mSecureConfig;
+
+ public FileCipher(@NonNull String fileName, @NonNull FileInputStream fileInputStream,
+ @NonNull SecureConfig secureConfig, @NonNull Executor executor,
+ @NonNull SecureContextCompat.EncryptedFileInputStreamListener listener)
+ throws IOException {
+ mFileName = fileName;
+ mFileInputStream = fileInputStream;
+ mSecureConfig = secureConfig;
+ EncryptedFileInputStream encryptedFileInputStream =
+ new EncryptedFileInputStream(mFileInputStream);
+ setEncryptedFileInputStreamListener(listener);
+ encryptedFileInputStream.decrypt(listener);
+ }
+
+ public FileCipher(@NonNull String keyPairAlias, @NonNull FileOutputStream fileOutputStream,
+ @NonNull SecureConfig secureConfig) {
+ mKeyPairAlias = keyPairAlias;
+ mFileOutputStream = new EncryptedFileOutputStream(mFileName, mKeyPairAlias,
+ fileOutputStream);
+ mSecureConfig = secureConfig;
+ }
+
+ /**
+ * @param listener the listener to call back on
+ */
+ public void setEncryptedFileInputStreamListener(
+ @NonNull SecureContextCompat.EncryptedFileInputStreamListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * @return the file output stream
+ */
+ @NonNull
+ public FileOutputStream getFileOutputStream() {
+ return mFileOutputStream;
+ }
+
+ /**
+ * @return the file input stream
+ */
+ @NonNull
+ public FileInputStream getFileInputStream() {
+ return mFileInputStream;
+ }
+
+ /**
+ * Encrypted file output stream
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ class EncryptedFileOutputStream extends FileOutputStream {
+
+ private static final String TAG = "EncryptedFOS";
+
+ FileOutputStream mFileOutputStream;
+ private String mKeyPairAlias;
+
+ EncryptedFileOutputStream(String name, String keyPairAlias,
+ FileOutputStream fileOutputStream) {
+ super(new FileDescriptor());
+ this.mKeyPairAlias = keyPairAlias;
+ this.mFileOutputStream = fileOutputStream;
+ }
+
+ String getAsymKeyPairAlias() {
+ return this.mKeyPairAlias;
+ }
+
+ @Override
+ public void write(@NonNull byte[] b) {
+ SecureKeyStore secureKeyStore = SecureKeyStore.getDefault();
+ if (!secureKeyStore.keyExists(getAsymKeyPairAlias())) {
+ SecureKeyGenerator keyGenerator = SecureKeyGenerator.getDefault();
+ keyGenerator.generateAsymmetricKeyPair(getAsymKeyPairAlias());
+ }
+ SecureKeyGenerator secureKeyGenerator = SecureKeyGenerator.getDefault();
+ final EphemeralSecretKey secretKey = secureKeyGenerator.generateEphemeralDataKey();
+ final SecureCipher secureCipher = SecureCipher
+ .getDefault(mSecureConfig.getBiometricKeyAuthCallback());
+ final Pair<byte[], byte[]> encryptedData =
+ secureCipher.encryptEphemeralData(secretKey, b);
+ secureCipher.encryptAsymmetric(getAsymKeyPairAlias(),
+ secretKey.getEncoded(),
+ new SecureCipher.SecureAsymmetricEncryptionListener() {
+ public void encryptionComplete(byte[] encryptedEphemeralKey) {
+ byte[] encodedData = secureCipher.encodeEphemeralData(
+ getAsymKeyPairAlias().getBytes(), encryptedEphemeralKey,
+ encryptedData.first, encryptedData.second);
+ secretKey.destroy();
+ try {
+ mFileOutputStream.write(encodedData);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write secure file.");
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must write all data "
+ + "simultaneously. Call #write(byte[]).");
+ }
+
+ @Override
+ public void write(@NonNull byte[] b, int off, int len) throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must write all data "
+ + "simultaneously. Call #write(byte[]).");
+ }
+
+ @Override
+ public void close() throws IOException {
+ mFileOutputStream.close();
+ }
+
+ @NonNull
+ @Override
+ public FileChannel getChannel() {
+ throw new UnsupportedOperationException("For encrypted files, you must write all data "
+ + "simultaneously. Call #write(byte[]).");
+ }
+
+ @Override
+ protected void finalize() throws IOException {
+ super.finalize();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ mFileOutputStream.flush();
+ }
+ }
+
+
+ /**
+ * Encrypted file input stream
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ class EncryptedFileInputStream extends FileInputStream {
+
+ // Was 25 characters, truncating to fix compile error
+ private static final String TAG = "EncryptedFIS";
+
+ private FileInputStream mFileInputStream;
+ byte[] mDecryptedData;
+ private int mReadStatus = 0;
+
+ EncryptedFileInputStream(FileInputStream fileInputStream) {
+ super(new FileDescriptor());
+ this.mFileInputStream = fileInputStream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ void decrypt(final SecureContextCompat.EncryptedFileInputStreamListener listener)
+ throws IOException {
+ final EncryptedFileInputStream thisStream = this;
+ if (this.mDecryptedData == null) {
+ try {
+ byte[] encodedData = new byte[mFileInputStream.available()];
+ mReadStatus = mFileInputStream.read(encodedData);
+ SecureCipher secureCipher = SecureCipher.getDefault(
+ mSecureConfig.getBiometricKeyAuthCallback());
+ secureCipher.decryptEncodedData(encodedData,
+ new SecureCipher.SecureDecryptionListener() {
+ public void decryptionComplete(byte[] clearText) {
+ thisStream.mDecryptedData = clearText;
+ //Binder.clearCallingIdentity();
+ listener.onEncryptedFileInput(thisStream);
+ }
+ });
+ } catch (IOException ex) {
+ throw ex;
+ }
+ }
+ }
+
+ private void destroyCache() {
+ if (mDecryptedData != null) {
+ Arrays.fill(mDecryptedData, (byte) 0);
+ mDecryptedData = null;
+ }
+ }
+
+ @Override
+ public int read(@NonNull byte[] b) {
+ System.arraycopy(mDecryptedData, 0, b, 0, mDecryptedData.length);
+ return mReadStatus;
+ }
+
+ @Override
+ public int read(@NonNull byte[] b, int off, int len) throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ @Override
+ public int available() {
+ return mDecryptedData.length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ destroyCache();
+ mFileInputStream.close();
+ }
+
+ @Override
+ public FileChannel getChannel() {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ @Override
+ protected void finalize() throws IOException {
+ destroyCache();
+ super.finalize();
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new UnsupportedOperationException("For encrypted files, you must read all data "
+ + "simultaneously. Call #read(byte[]).");
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java b/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java
new file mode 100644
index 0000000..5cd7af0
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/SecureCipher.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import android.os.Build;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+import androidx.security.biometric.BiometricKeyAuthCallback;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.security.spec.MGF1ParameterSpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+
+/**
+ * Class that helps encrypt and decrypt data.
+ */
+public class SecureCipher {
+
+ private static final String TAG = "SecureCipher";
+ private static final int FILE_ENCODING_VERSION = 1;
+
+ private SecureConfig mSecureConfig;
+
+ /**
+ * Listener for encryption that requires biometric prompt
+ */
+ public interface SecureAuthListener {
+ /**
+ * @param status the status of auth
+ */
+ void authComplete(@NonNull BiometricKeyAuthCallback.BiometricStatus status);
+ }
+
+ /**
+ * Listener for encrypting symmetric data
+ */
+ public interface SecureSymmetricEncryptionListener {
+ /**
+ * @param cipherText the encrypted cipher text
+ * @param iv the initialization vector used
+ */
+ void encryptionComplete(@NonNull byte[] cipherText, @NonNull byte[] iv);
+ }
+
+ /**
+ * Listener for encrypting asymmetric data
+ */
+ public interface SecureAsymmetricEncryptionListener {
+ /**
+ * @param cipherText the encrypted cipher text
+ */
+ void encryptionComplete(@NonNull byte[] cipherText);
+ }
+
+ /**
+ * The listener for decryption
+ */
+ public interface SecureDecryptionListener {
+ /**
+ * @param clearText the decrypted text
+ */
+ void decryptionComplete(@NonNull byte[] clearText);
+ }
+
+ /**
+ * The listener for signing data
+ */
+ public interface SecureSignListener {
+ /**
+ * @param signature the signature
+ */
+ void signComplete(@NonNull byte[] signature);
+ }
+
+ /**
+ * @return The secure cipher
+ */
+ @NonNull
+ public static SecureCipher getDefault() {
+ return new SecureCipher(SecureConfig.getDefault());
+ }
+
+ /**
+ * @param biometricKeyAuthCallback The callback to use when authenticating a key
+ * @return the cipher using biometric prompt
+ */
+ @NonNull
+ public static SecureCipher getDefault(
+ @NonNull BiometricKeyAuthCallback biometricKeyAuthCallback) {
+ SecureConfig secureConfig = SecureConfig.getDefault();
+ secureConfig.setSymmetricRequireUserAuth(true);
+ secureConfig.setAsymmetricRequireUserAuth(true);
+ secureConfig.setBiometricKeyAuthCallback(biometricKeyAuthCallback);
+ return new SecureCipher(secureConfig);
+ }
+
+ /**
+ * Gets an instance using the specified configuration
+ *
+ * @param secureConfig the config
+ * @return the secure cipher
+ */
+ @NonNull
+ public static SecureCipher getInstance(@NonNull SecureConfig secureConfig) {
+ return new SecureCipher(secureConfig);
+ }
+
+
+ public SecureCipher(@NonNull SecureConfig secureConfig) {
+ this.mSecureConfig = secureConfig;
+ }
+
+ /**
+ * The encoding mType for files
+ */
+ public enum SecureFileEncodingType {
+ SYMMETRIC(0),
+ ASYMMETRIC(1),
+ EPHEMERAL(2),
+ NOT_ENCRYPTED(1000);
+
+ private final int mType;
+
+ SecureFileEncodingType(int type) {
+ this.mType = type;
+ }
+
+ /**
+ * @return the id
+ */
+ public int getType() {
+ return this.mType;
+ }
+
+ /**
+ * @param id the id
+ * @return the encoding mType
+ */
+ @NonNull
+ public static SecureFileEncodingType fromId(int id) {
+ switch (id) {
+ case 0:
+ return SYMMETRIC;
+ case 1:
+ return ASYMMETRIC;
+ case 2:
+ return EPHEMERAL;
+ }
+ return NOT_ENCRYPTED;
+ }
+
+ }
+
+
+ /**
+ * Encrypts data with an existing key alias from the AndroidKeyStore.
+ *
+ * @param keyAlias The name of the existing SecretKey to retrieve from the AndroidKeyStore.
+ * @param clearData The unencrypted data to encrypt
+ */
+ public void encrypt(@NonNull String keyAlias,
+ @NonNull final byte[] clearData,
+ @NonNull final SecureSymmetricEncryptionListener callback) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ SecretKey key = (SecretKey) keyStore.getKey(keyAlias, null);
+ final Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getSymmetricCipherTransformation());
+ cipher.init(Cipher.ENCRYPT_MODE, key);
+ byte[] iv = cipher.getIV();
+ if (mSecureConfig.getSymmetricRequireUserAuthEnabled()) {
+ mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
+ new SecureAuthListener() {
+ public void authComplete(
+ BiometricKeyAuthCallback.BiometricStatus status) {
+
+ switch (status) {
+ case SUCCESS:
+ try {
+ callback.encryptionComplete(cipher.doFinal(clearData),
+ cipher.getIV());
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ break;
+ default:
+ Log.i(TAG, "Failure");
+ callback.encryptionComplete(null, null);
+ }
+ }
+ });
+ } else {
+ callback.encryptionComplete(cipher.doFinal(clearData), cipher.getIV());
+ }
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ } catch (IOException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Signs data based on the specific key that has been generated
+ *
+ * Uses SecureConfig.getSignatureAlgorithm for algorithm type
+ *
+ * @param keyAlias The key to use for signing
+ * @param clearData The data to sign
+ * @param callback The listener to call back with the signature
+ */
+ public void sign(@NonNull String keyAlias,
+ @NonNull final byte[] clearData,
+ @NonNull final SecureSignListener callback) {
+ byte[] dataSignature = new byte[0];
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, null);
+ final Signature signature = Signature.getInstance(
+ mSecureConfig.getSignatureAlgorithm());
+ signature.initSign(key);
+ signature.update(clearData);
+ if (mSecureConfig.getAsymmetricRequireUserAuthEnabled()) {
+ mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(signature,
+ new SecureAuthListener() {
+ public void authComplete(
+ BiometricKeyAuthCallback.BiometricStatus status) {
+ Log.i(TAG, "Finished success auth!");
+ switch (status) {
+ case SUCCESS:
+ try {
+ byte[] sig = signature.sign();
+ Log.i(TAG, "Signed " + clearData.length
+ + " bytes");
+ callback.signComplete(sig);
+
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ break;
+ default:
+ Log.i(TAG, "Failure");
+ callback.signComplete(null);
+ }
+ }
+ });
+ } else {
+ dataSignature = signature.sign();
+ callback.signComplete(dataSignature);
+ }
+ } catch (GeneralSecurityException ex) {
+ ex.printStackTrace();
+ //Log.e(TAG, ex.getMessage());
+ } catch (IOException ex) {
+ Log.e(TAG, ex.getMessage());
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Verifies signed data
+ *
+ * Uses SecureConfig.getSignatureAlgorithm for algorithm type
+ *
+ * @param keyAlias The key to use for signing
+ * @param clearData The signed data
+ * @param signature The signature
+ * @return true if the provided signature is valid, false otherwise
+ */
+ public boolean verify(@NonNull String keyAlias,
+ @NonNull final byte[] clearData,
+ @NonNull final byte[] signature) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
+ final Signature signatureObject = Signature.getInstance(
+ mSecureConfig.getSignatureAlgorithm());
+ signatureObject.initVerify(publicKey);
+ signatureObject.update(clearData);
+ return signatureObject.verify(signature);
+ } catch (GeneralSecurityException ex) {
+ ex.printStackTrace();
+ //Log.e(TAG, ex.getMessage());
+ } catch (IOException ex) {
+ Log.e(TAG, ex.getMessage());
+ ex.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Encrypts data with a public key from the cert in the AndroidKeyStore.
+ *
+ * @param keyAlias The name of the existing KeyPair to retrieve the
+ * PublicKey from the AndroidKeyStore.
+ * @param clearData The unencrypted data to encrypt
+ */
+ public void encryptAsymmetric(@NonNull String keyAlias,
+ @NonNull byte[] clearData, @NonNull SecureAsymmetricEncryptionListener callback) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
+ Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getAsymmetricCipherTransformation());
+ // Check to see if there are other padding types with a complex config.
+ if (mSecureConfig.getAsymmetricPaddings().equals(
+ KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)) {
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey,
+ new OAEPParameterSpec("SHA-256",
+ "MGF1", new MGF1ParameterSpec("SHA-1"),
+ PSource.PSpecified.DEFAULT));
+ } else {
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ }
+ byte[] clearText = cipher.doFinal(clearData);
+ callback.encryptionComplete(clearText);
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ } catch (IOException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Encrypts data using an Ephemeral key, destroying any trace of the key from the Cipher used.
+ *
+ * @param ephemeralSecretKey The generated Ephemeral key
+ * @param clearData The unencrypted data to encrypt
+ * @return A Pair of byte[]'s, first is the encrypted data, second is the IV
+ * (initialization vector)
+ * used to encrypt which is required for decryption
+ */
+ @NonNull
+ public Pair<byte[], byte[]> encryptEphemeralData(
+ @NonNull EphemeralSecretKey ephemeralSecretKey, @NonNull byte[] clearData) {
+ try {
+ SecureRandom secureRandom = null;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ secureRandom = SecureRandom.getInstanceStrong();
+ } else {
+ secureRandom = new SecureRandom();
+ }
+ byte[] iv = new byte[SecureConfig.AES_IV_SIZE_BYTES];
+ secureRandom.nextBytes(iv);
+ GCMParameterSpec parameterSpec = new GCMParameterSpec(
+ mSecureConfig.getSymmetricGcmTagLength(), iv);
+ final Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getSymmetricCipherTransformation());
+ cipher.init(Cipher.ENCRYPT_MODE, ephemeralSecretKey, parameterSpec);
+ byte[] encryptedData = cipher.doFinal(clearData);
+ ephemeralSecretKey.destroyCipherKey(cipher, Cipher.ENCRYPT_MODE);
+ return new Pair<>(encryptedData, iv);
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Decrypts a previously encrypted byte[]
+ * <p>
+ * Destroys all traces of the key data in the Cipher.
+ *
+ * @param ephemeralSecretKey The generated Ephemeral key
+ * @param encryptedData The byte[] of encrypted data
+ * @param initializationVector The IV of which the encrypted data was encrypted with
+ * @return The byte[] of data that has been decrypted
+ */
+ @NonNull
+ public byte[] decryptEphemeralData(@NonNull EphemeralSecretKey ephemeralSecretKey,
+ @NonNull byte[] encryptedData, @NonNull byte[] initializationVector) {
+ try {
+ final Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getSymmetricCipherTransformation());
+ cipher.init(Cipher.DECRYPT_MODE, ephemeralSecretKey,
+ new GCMParameterSpec(
+ mSecureConfig.getSymmetricGcmTagLength(), initializationVector));
+ byte[] decryptedData = cipher.doFinal(encryptedData);
+ ephemeralSecretKey.destroyCipherKey(cipher, Cipher.DECRYPT_MODE);
+ return decryptedData;
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Decrypts a previously encrypted byte[]
+ *
+ * @param keyAlias The name of the existing SecretKey to retrieve from the
+ * AndroidKeyStore.
+ * @param encryptedData The byte[] of encrypted data
+ * @param initializationVector The IV of which the encrypted data was encrypted with
+ */
+ public void decrypt(@NonNull String keyAlias,
+ @NonNull final byte[] encryptedData, @NonNull byte[] initializationVector,
+ @NonNull final SecureDecryptionListener callback) {
+ byte[] decryptedData = new byte[0];
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ Key key = keyStore.getKey(keyAlias, null);
+ final Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getSymmetricCipherTransformation());
+ GCMParameterSpec spec = new GCMParameterSpec(
+ mSecureConfig.getSymmetricGcmTagLength(), initializationVector);
+ cipher.init(Cipher.DECRYPT_MODE, key, spec);
+ if (mSecureConfig.getSymmetricRequireUserAuthEnabled()) {
+ mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
+ new SecureAuthListener() {
+ public void authComplete(
+ BiometricKeyAuthCallback.BiometricStatus status) {
+ switch (status) {
+ case SUCCESS:
+ try {
+ callback.decryptionComplete(
+ cipher.doFinal(encryptedData));
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ break;
+ default:
+ Log.i(TAG, "Failure");
+ callback.decryptionComplete(null);
+ }
+ }
+ });
+ } else {
+ callback.decryptionComplete(cipher.doFinal(encryptedData));
+ }
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ } catch (IOException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Decrypts a previously encrypted byte[] with the PrivateKey
+ *
+ * @param keyAlias The name of the existing KeyPair to retrieve from the AndroidKeyStore.
+ * @param encryptedData The byte[] of encrypted data
+ */
+ public void decryptAsymmetric(@NonNull String keyAlias,
+ @NonNull final byte[] encryptedData,
+ @NonNull final SecureDecryptionListener callback) {
+ byte[] decryptedData = new byte[0];
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, null);
+ final Cipher cipher = Cipher.getInstance(
+ mSecureConfig.getAsymmetricCipherTransformation());
+ if (mSecureConfig.getAsymmetricPaddings().equals(
+ KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)) {
+ cipher.init(Cipher.DECRYPT_MODE, key, new OAEPParameterSpec("SHA-256",
+ "MGF1",
+ new MGF1ParameterSpec("SHA-1"), PSource.PSpecified.DEFAULT));
+ } else {
+ cipher.init(Cipher.DECRYPT_MODE, key);
+ }
+ if (mSecureConfig.getAsymmetricRequireUserAuthEnabled()) {
+ mSecureConfig.getBiometricKeyAuthCallback().authenticateKey(cipher,
+ new SecureAuthListener() {
+ public void authComplete(
+ BiometricKeyAuthCallback.BiometricStatus status) {
+ Log.i(TAG, "Finished success auth!");
+ switch (status) {
+ case SUCCESS:
+ try {
+ byte[] clearData = cipher.doFinal(encryptedData);
+ Log.i(TAG, "Decrypted " + new String(clearData));
+ callback.decryptionComplete(clearData);
+
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ break;
+ default:
+ Log.i(TAG, "Failure");
+ callback.decryptionComplete(null);
+ }
+ }
+ });
+ } else {
+ decryptedData = cipher.doFinal(encryptedData);
+ callback.decryptionComplete(decryptedData);
+ }
+ } catch (GeneralSecurityException ex) {
+ ex.printStackTrace();
+ //Log.e(TAG, ex.getMessage());
+ } catch (IOException ex) {
+ Log.e(TAG, ex.getMessage());
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * @param keyPairAlias
+ * @param encryptedKey
+ * @param cipherText
+ * @param iv
+ * @return
+ */
+ @NonNull
+ public byte[] encodeEphemeralData(@NonNull byte[] keyPairAlias, @NonNull byte[] encryptedKey,
+ @NonNull byte[] cipherText, @NonNull byte[] iv) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 5) + iv.length
+ + keyPairAlias.length + encryptedKey.length + cipherText.length);
+ byteBuffer.putInt(SecureFileEncodingType.EPHEMERAL.getType());
+ byteBuffer.putInt(FILE_ENCODING_VERSION);
+ byteBuffer.putInt(encryptedKey.length);
+ byteBuffer.put(encryptedKey);
+ byteBuffer.putInt(iv.length);
+ byteBuffer.put(iv);
+ byteBuffer.putInt(keyPairAlias.length);
+ byteBuffer.put(keyPairAlias);
+ byteBuffer.put(cipherText);
+ return byteBuffer.array();
+ }
+
+ /**
+ * @param keyAlias
+ * @param cipherText
+ * @param iv
+ * @return
+ */
+ @NonNull
+ public byte[] encodeSymmetricData(@NonNull byte[] keyAlias, @NonNull byte[] cipherText,
+ @NonNull byte[] iv) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 4) + iv.length
+ + keyAlias.length + cipherText.length);
+ byteBuffer.putInt(SecureFileEncodingType.SYMMETRIC.getType());
+ byteBuffer.putInt(FILE_ENCODING_VERSION);
+ byteBuffer.putInt(iv.length);
+ byteBuffer.put(iv);
+ byteBuffer.putInt(keyAlias.length);
+ byteBuffer.put(keyAlias);
+ byteBuffer.put(cipherText);
+ return byteBuffer.array();
+ }
+
+ /**
+ * @param keyPairAlias
+ * @param cipherText
+ * @return
+ */
+ @NonNull
+ public byte[] encodeAsymmetricData(@NonNull byte[] keyPairAlias,
+ @NonNull byte[] cipherText) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(((Integer.SIZE / 8) * 3)
+ + keyPairAlias.length + cipherText.length);
+ byteBuffer.putInt(SecureFileEncodingType.ASYMMETRIC.getType());
+ byteBuffer.putInt(FILE_ENCODING_VERSION);
+ byteBuffer.putInt(keyPairAlias.length);
+ byteBuffer.put(keyPairAlias);
+ byteBuffer.put(cipherText);
+ return byteBuffer.array();
+ }
+
+ /**
+ * @param encodedCipherText
+ * @param callback
+ */
+ @SuppressWarnings("fallthrough")
+ public void decryptEncodedData(@NonNull byte[] encodedCipherText,
+ @NonNull final SecureDecryptionListener callback) {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(encodedCipherText);
+ int encodingTypeVal = byteBuffer.getInt();
+ SecureFileEncodingType encodingType = SecureFileEncodingType.fromId(encodingTypeVal);
+ int encodingVersion = byteBuffer.getInt();
+ byte[] encodedEphKey = null;
+ byte[] iv = null;
+ String keyAlias = null;
+ byte[] cipherText = null;
+
+ switch (encodingType) {
+ case EPHEMERAL:
+ int encodedEphKeyLength = byteBuffer.getInt();
+ encodedEphKey = new byte[encodedEphKeyLength];
+ byteBuffer.get(encodedEphKey);
+ // fall through
+ case SYMMETRIC:
+ int ivLength = byteBuffer.getInt();
+ iv = new byte[ivLength];
+ byteBuffer.get(iv);
+ // fall through
+ case ASYMMETRIC:
+ int keyAliasLength = byteBuffer.getInt();
+ byte[] keyAliasBytes = new byte[keyAliasLength];
+ byteBuffer.get(keyAliasBytes);
+ keyAlias = new String(keyAliasBytes);
+ cipherText = new byte[byteBuffer.remaining()];
+ byteBuffer.get(cipherText);
+ break;
+ case NOT_ENCRYPTED:
+ throw new SecurityException("Cannot determine file mType.");
+ }
+ switch (encodingType) {
+ case EPHEMERAL:
+ final byte[] ephemeralCipherText = cipherText;
+ final byte[] ephemeralIv = iv;
+ decryptAsymmetric(keyAlias, encodedEphKey,
+ new SecureDecryptionListener() {
+ @java.lang.Override
+ public void decryptionComplete(byte[] clearText) {
+ EphemeralSecretKey ephemeralSecretKey =
+ new EphemeralSecretKey(clearText);
+ byte[] decrypted = decryptEphemeralData(
+ ephemeralSecretKey,
+ ephemeralCipherText, ephemeralIv);
+ callback.decryptionComplete(decrypted);
+ ephemeralSecretKey.destroy();
+ }
+ });
+
+ break;
+ case SYMMETRIC:
+ decrypt(
+ keyAlias,
+ cipherText, iv, callback);
+ break;
+ case ASYMMETRIC:
+ decryptAsymmetric(
+ keyAlias,
+ cipherText, callback);
+ break;
+ case NOT_ENCRYPTED:
+ throw new SecurityException("File not encrypted.");
+ }
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java
new file mode 100644
index 0000000..fa698e5
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyGenerator.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.crypto;
+
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+
+import javax.crypto.KeyGenerator;
+
+/**
+ * Class that provides easy and reliable key generation for both symmetric and asymmetric crypto.
+ */
+public class SecureKeyGenerator {
+
+ private SecureConfig mSecureConfig;
+
+ @NonNull
+ public static SecureKeyGenerator getDefault() {
+ return new SecureKeyGenerator(SecureConfig.getDefault());
+ }
+
+ /**
+ * @param secureConfig The config
+ * @return The key generator
+ */
+ @NonNull
+ public static SecureKeyGenerator getInstance(@NonNull SecureConfig secureConfig) {
+ return new SecureKeyGenerator(secureConfig);
+ }
+
+ private SecureKeyGenerator(SecureConfig secureConfig) {
+ this.mSecureConfig = secureConfig;
+ }
+
+ /**
+ * <p>
+ * Generates a sensitive data key and adds the SecretKey to the AndroidKeyStore.
+ * Utilizes UnlockedDeviceProtection to ensure that the device must be unlocked in order to
+ * use the generated key.
+ * </p>
+ *
+ * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
+ * @return true if the key was generated, false otherwise
+ */
+ //@TargetApi(Build.VERSION_CODES.P)
+ public boolean generateKey(@NonNull String keyAlias) {
+ boolean created = false;
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ mSecureConfig.getSymmetricKeyAlgorithm(), mSecureConfig.getAndroidKeyStore());
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+ keyAlias, mSecureConfig.getSymmetricKeyPurposes())
+ .setBlockModes(mSecureConfig.getSymmetricBlockModes())
+ .setEncryptionPaddings(mSecureConfig.getSymmetricPaddings())
+ .setKeySize(mSecureConfig.getSymmetricKeySize());
+ builder = builder.setUserAuthenticationRequired(
+ mSecureConfig.getSymmetricRequireUserAuthEnabled());
+ builder = builder.setUserAuthenticationValidityDurationSeconds(
+ mSecureConfig.getSymmetricRequireUserValiditySeconds());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ builder = builder.setUnlockedDeviceRequired(
+ mSecureConfig.getSymmetricSensitiveDataProtectionEnabled());
+ }
+ keyGenerator.init(builder.build());
+ keyGenerator.generateKey();
+ created = true;
+ } catch (NoSuchAlgorithmException ex) {
+ throw new SecurityException(ex);
+ } catch (InvalidAlgorithmParameterException ex) {
+ throw new SecurityException(ex);
+ } catch (NoSuchProviderException ex) {
+ throw new SecurityException(ex);
+ }
+ return created;
+ }
+
+ /**
+ * <p>
+ * Generates a sensitive data public/private key pair and adds the KeyPair to the
+ * AndroidKeyStore. Utilizes UnlockedDeviceProtection to ensure that the device
+ * must be unlocked in order to use the generated key.
+ * </p>
+ * <p>
+ * ANDROID P ONLY (API LEVEL 28>)
+ * </p>
+ *
+ * @param keyPairAlias The name of the generated SecretKey to save into the AndroidKeyStore.
+ * @return true if the key was generated, false otherwise
+ */
+ public boolean generateAsymmetricKeyPair(@NonNull String keyPairAlias) {
+ boolean created = false;
+ try {
+ KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance(
+ mSecureConfig.getAsymmetricKeyPairAlgorithm(),
+ mSecureConfig.getAndroidKeyStore());
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+ keyPairAlias, mSecureConfig.getAsymmetricKeyPurposes())
+ .setEncryptionPaddings(mSecureConfig.getAsymmetricPaddings())
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
+ .setBlockModes(mSecureConfig.getAsymmetricBlockModes())
+ .setKeySize(mSecureConfig.getAsymmetricKeySize());
+ builder = builder.setUserAuthenticationRequired(
+ mSecureConfig.getAsymmetricRequireUserAuthEnabled());
+ builder = builder.setUserAuthenticationValidityDurationSeconds(
+ mSecureConfig.getAsymmetricRequireUserValiditySeconds());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ builder = builder.setUnlockedDeviceRequired(
+ mSecureConfig.getAsymmetricSensitiveDataProtectionEnabled());
+ }
+ keyGenerator.initialize(builder.build());
+ keyGenerator.generateKeyPair();
+ created = true;
+ } catch (NoSuchProviderException | InvalidAlgorithmParameterException
+ | NoSuchAlgorithmException ex) {
+ throw new SecurityException(ex);
+ }
+ return created;
+ }
+
+ /**
+ * <p>
+ * Generates an Ephemeral symmetric key that can be fully destroyed and removed from memory.
+ * </p>
+ *
+ * @return The EphemeralSecretKey generated
+ */
+ @NonNull
+ public EphemeralSecretKey generateEphemeralDataKey() {
+ try {
+ SecureRandom secureRandom;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ secureRandom = SecureRandom.getInstanceStrong();
+ } else {
+ // Not best practices, TODO update this as per this SO thread
+ // https://stackoverflow.com/questions/36813098/securerandom-provider-crypto-
+ // unavailable-in-android-n-for-deterministially-gen
+ secureRandom = new SecureRandom();
+ }
+ byte[] key = new byte[mSecureConfig.getSymmetricKeySize() / 8];
+ secureRandom.nextBytes(key);
+ return new EphemeralSecretKey(key, mSecureConfig.getSymmetricKeyAlgorithm());
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java
new file mode 100644
index 0000000..b3a0681
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/SecureKeyStore.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import android.security.keystore.KeyInfo;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+
+/**
+ * A class that provides simpler access to the AndroidKeyStore including commonly used utilities.
+ */
+public class SecureKeyStore {
+
+ private static final String TAG = "SecureKeyStore";
+
+ private SecureConfig mSecureConfig;
+
+ @NonNull
+ public static SecureKeyStore getDefault() {
+ return new SecureKeyStore(SecureConfig.getDefault());
+ }
+
+ /**
+ * Gets an instance of SecureKeyStore based on the provided SecureConfig.
+ *
+ * @param secureConfig The SecureConfig to use the KeyStore.
+ * @return A SecureKeyStore that has been configured.
+ */
+ @NonNull
+ public static SecureKeyStore getInstance(@NonNull SecureConfig secureConfig) {
+ return new SecureKeyStore(secureConfig);
+ }
+
+ private SecureKeyStore(@NonNull SecureConfig secureConfig) {
+ this.mSecureConfig = secureConfig;
+ }
+
+ /**
+ * Checks to see if the specified key exists in the AndroidKeyStore
+ *
+ * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
+ * @return true if the key is stored in secure hardware
+ */
+ public boolean keyExists(@NonNull String keyAlias) {
+ boolean exists = false;
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ exists = keyStore.containsAlias(keyAlias);
+ /*Certificate cert = keyStore.getCertificate(keyAlias);
+ if (cert != null) {
+ exists = cert.getPublicKey() != null;
+ }*/
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ } catch (IOException ex) {
+ throw new SecurityException(ex);
+ }
+ return exists;
+ }
+
+
+ /**
+ * Delete a key from the specified keystore.
+ *
+ * @param keyAlias The key to delete from the KeyStore
+ */
+ public void deleteKey(@NonNull String keyAlias) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ keyStore.deleteEntry(keyAlias);
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException(ex);
+ } catch (IOException ex) {
+ throw new SecurityException(ex);
+ }
+ }
+
+ /**
+ * Checks to see if the specified key is stored in secure hardware
+ *
+ * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
+ * @return true if the key is stored in secure hardware
+ */
+ public boolean checkKeyInsideSecureHardware(@NonNull String keyAlias) {
+ boolean inHardware = false;
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ SecretKey key = (SecretKey) keyStore.getKey(keyAlias, null);
+ SecretKeyFactory factory = SecretKeyFactory.getInstance(key.getAlgorithm(),
+ mSecureConfig.getAndroidKeyStore());
+ KeyInfo keyInfo;
+ keyInfo = (KeyInfo) factory.getKeySpec(key, KeyInfo.class);
+ inHardware = keyInfo.isInsideSecureHardware();
+ return inHardware;
+ } catch (GeneralSecurityException e) {
+ return inHardware;
+ } catch (IOException e) {
+ return inHardware;
+ }
+ }
+
+ /**
+ * Checks to see if the specified private key is stored in secure hardware
+ *
+ * @param keyAlias The name of the generated SecretKey to save into the AndroidKeyStore.
+ * @return true if the key is stored in secure hardware
+ */
+ public boolean checkKeyInsideSecureHardwareAsymmetric(@NonNull String keyAlias) {
+ boolean inHardware = false;
+ try {
+ KeyStore keyStore = KeyStore.getInstance(mSecureConfig.getAndroidKeyStore());
+ keyStore.load(null);
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, null);
+
+ KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(),
+ mSecureConfig.getAndroidKeyStore());
+ KeyInfo keyInfo;
+
+ keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
+ inHardware = keyInfo.isInsideSecureHardware();
+ return inHardware;
+
+ } catch (GeneralSecurityException e) {
+ return inHardware;
+ } catch (IOException e) {
+ return inHardware;
+ }
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java b/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java
new file mode 100644
index 0000000..77c10b2
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/net/SecureKeyManager.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.net;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.security.KeyChainException;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+/**
+ * Class that helps generate and manage crypto keys
+ */
+public class SecureKeyManager implements X509KeyManager, KeyChainAliasCallback {
+ private static final String TAG = "SecureKeyManager";
+
+ private final String mAlias;
+ private X509Certificate[] mCertChain;
+ private PrivateKey mPrivateKey;
+ private static Activity sActivity;
+ private SecureConfig mSecureConfig;
+
+ public static void setContext(@NonNull Activity activity) {
+ sActivity = activity;
+ }
+
+ public enum CertType {
+ X509(0),
+ PKCS12(1),
+ NOT_SUPPORTED(1000);
+
+ private final int mType;
+
+ CertType(int type) {
+ this.mType = type;
+ }
+
+ /**
+ * @return the type as an int
+ */
+ public int getType() {
+ return this.mType;
+ }
+
+ /**
+ * @param id the id of the cert type
+ * @return the cert type
+ */
+ @NonNull
+ public static CertType fromId(int id) {
+ switch (id) {
+ case 0:
+ return X509;
+ case 1:
+ return PKCS12;
+ }
+ return NOT_SUPPORTED;
+ }
+ }
+
+
+ /**
+ * @param alias the key alias
+ * @return the key manager
+ */
+ @NonNull
+ public static SecureKeyManager getDefault(@NonNull String alias) {
+ return getDefault(alias, SecureConfig.getDefault());
+ }
+
+ /**
+ * @param alias the key alias
+ * @param secureConfig the configuration
+ * @return the key manager
+ */
+ @NonNull
+ public static SecureKeyManager getDefault(@NonNull String alias,
+ @NonNull SecureConfig secureConfig) {
+ SecureKeyManager keyManager = new SecureKeyManager(alias, secureConfig);
+ try {
+ KeyChain.choosePrivateKeyAlias(sActivity, keyManager,
+ secureConfig.getClientCertAlgorithms(),
+ null, null, -1, alias);
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return keyManager;
+ }
+
+ /**
+ * @param certType cert mType to install
+ * @param certData the cert data in byte[] format
+ * @param keyAlias the alias of they key to use
+ * @param secureConfig the crypto config
+ * @return the secure key manager instance
+ */
+ @NonNull
+ public static SecureKeyManager installCertManually(@NonNull CertType certType,
+ @NonNull byte[] certData, @NonNull String keyAlias,
+ @NonNull SecureConfig secureConfig) {
+ SecureKeyManager keyManager = new SecureKeyManager(keyAlias, secureConfig);
+ Intent intent = KeyChain.createInstallIntent();
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ switch (certType) {
+ case X509:
+ intent.putExtra(KeyChain.EXTRA_CERTIFICATE, certData);
+ break;
+ case PKCS12:
+ intent.putExtra(KeyChain.EXTRA_PKCS12, certData);
+ break;
+ default:
+ throw new SecurityException("Cert mType not supported.");
+ }
+ sActivity.startActivity(intent);
+ return keyManager;
+ }
+
+ public SecureKeyManager(@NonNull String alias, @NonNull SecureConfig secureConfig) {
+ this.mAlias = alias;
+ this.mSecureConfig = secureConfig;
+ }
+
+ @Override
+ @NonNull
+ public String chooseClientAlias(@NonNull String[] arg0,
+ @NonNull Principal[] arg1, @NonNull Socket arg2) {
+ return mAlias;
+ }
+
+ @Override
+ @NonNull
+ public X509Certificate[] getCertificateChain(@NonNull String alias) {
+ if (this.mAlias.equals(alias)) return mCertChain;
+ return null;
+ }
+
+ public void setCertChain(@NonNull X509Certificate[] certChain) {
+ this.mCertChain = certChain;
+ }
+
+ @Override
+ @NonNull
+ public PrivateKey getPrivateKey(@NonNull String alias) {
+ if (this.mAlias.equals(alias)) return mPrivateKey;
+ return null;
+ }
+
+ public void setPrivateKey(@NonNull PrivateKey privateKey) {
+ this.mPrivateKey = privateKey;
+ }
+
+ @Override
+ @NonNull
+ public final String chooseServerAlias(@NonNull String keyType,
+ @NonNull Principal[] issuers, @NonNull Socket socket) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @NonNull
+ public final String[] getClientAliases(@NonNull String keyType, @NonNull Principal[] issuers) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @NonNull
+ public final String[] getServerAliases(@NonNull String keyType, @NonNull Principal[] issuers) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void alias(@NonNull String alias) {
+ try {
+ mCertChain = KeyChain.getCertificateChain(sActivity.getApplicationContext(), alias);
+ mPrivateKey = KeyChain.getPrivateKey(sActivity.getApplicationContext(), alias);
+ if (mCertChain == null || mPrivateKey == null) {
+ throw new SecurityException("Could not retrieve the cert chain and private key"
+ + " from client cert.");
+ }
+ this.setCertChain(mCertChain);
+ this.setPrivateKey(mPrivateKey);
+ } catch (KeyChainException ex) {
+ throw new SecurityException("Could not retrieve the cert chain and private key from"
+ + " client cert.");
+ } catch (InterruptedException ex) {
+ throw new SecurityException("Could not retrieve the cert chain and private key from"
+ + " client cert.");
+ }
+ }
+}
diff --git a/security/crypto/src/main/java/androidx/security/net/SecureURL.java b/security/crypto/src/main/java/androidx/security/net/SecureURL.java
new file mode 100644
index 0000000..f93c8a8
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/net/SecureURL.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.net;
+
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.security.SecureConfig;
+import androidx.security.config.TldConstants;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.PKIXParameters;
+import java.security.cert.PKIXRevocationChecker;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * A URL that provides TLS, certification validity checks, and TLD verification automatically.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class SecureURL {
+
+ private static final String TAG = "SecureURL";
+
+ private URL mUrl;
+ private SecureConfig mSecureConfig;
+ private String mClientCertAlias;
+
+ public SecureURL(@NonNull String spec)
+ throws MalformedURLException {
+ this(spec, null, SecureConfig.getDefault());
+ }
+
+ public SecureURL(@NonNull String spec, @NonNull String clientCertAlias)
+ throws MalformedURLException {
+ this(spec, clientCertAlias, SecureConfig.getDefault());
+ }
+
+ public SecureURL(@NonNull String spec, @NonNull String clientCertAlias,
+ @NonNull SecureConfig secureConfig)
+ throws MalformedURLException {
+ this.mUrl = new URL(addProtocol(spec));
+ this.mClientCertAlias = clientCertAlias;
+ this.mSecureConfig = secureConfig;
+ }
+
+
+ /**
+ * Gets the hostname used to construct the underlying URL.
+ *
+ * @return the hostname associated with the Url.
+ */
+ @NonNull
+ public String getHostname() {
+ return this.mUrl.getHost();
+ }
+
+ /**
+ * Gets the port used to construct the underlying URL.
+ *
+ * @return the port associated with the Url.
+ */
+ public int getPort() {
+ int port = this.mUrl.getPort();
+ if (port == -1) {
+ port = this.mUrl.getDefaultPort();
+ }
+ return port;
+ }
+
+ private String addProtocol(@NonNull String spec) {
+ if (!spec.toLowerCase().startsWith("http://")
+ && !spec.toLowerCase().startsWith("https://")) {
+ return "https://" + spec;
+ }
+ return spec;
+ }
+
+
+ /**
+ * Gets the client cert alias.
+ *
+ * @return The client cert alias.
+ */
+ @NonNull
+ public String getClientCertAlias() {
+ return this.mClientCertAlias;
+ }
+
+
+ /**
+ * Opens a connection using default certs with a custom SSLSocketFactory.
+ *
+ * @return the UrlConnection of the newly opened connection.
+ * @throws IOException
+ */
+ @NonNull
+ public URLConnection openConnection() throws IOException {
+ HttpsURLConnection urlConnection = (HttpsURLConnection) this.mUrl.openConnection();
+ urlConnection.setSSLSocketFactory(new ValidatableSSLSocketFactory(this));
+ return urlConnection;
+ }
+
+ /**
+ * Opens a connection using the provided trusted list of CAs.
+ *
+ * @param trustedCAs list of CAs to be trusted by this connection
+ * @return The opened connection
+ * @throws IOException
+ */
+ @NonNull
+ public URLConnection openUserTrustedCertConnection(
+ @NonNull Map<String, InputStream> trustedCAs)
+ throws IOException {
+ HttpsURLConnection urlConnection = (HttpsURLConnection) this.mUrl.openConnection();
+ urlConnection.setSSLSocketFactory(new ValidatableSSLSocketFactory(this,
+ trustedCAs, mSecureConfig));
+ return urlConnection;
+ }
+
+ /**
+ * Checks the hostname against an open SSLSocket connect to the hostname for validity for certs
+ * and hostname validity. Only used internally by ValidatableSSLSocket.
+ * <p>
+ * Example Code:
+ * SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault();
+ * SSLSocket socket = (SSLSocket) sf.createSocket("https://"+hostname, 443);
+ * socket.startHandshake();
+ * boolean valid = SecurityExt.isValid(hostname, socket);
+ * </p>
+ *
+ * @param hostname The host name to check
+ * @param socket The SSLSocket that is open to the URL of the host to check
+ * @return true if the SSLSocket has a valid cert and if the hostname is valid, false otherwise.
+ */
+ boolean isValid(@NonNull String hostname, @NonNull SSLSocket socket) {
+ try {
+ Log.i(TAG, "Hostname verifier: " + HttpsURLConnection
+ .getDefaultHostnameVerifier().verify(hostname, socket.getSession()));
+ Log.i(TAG, "isValid Peer Certs: "
+ + isValid(Arrays.asList(socket.getSession().getPeerCertificates())));
+ return HttpsURLConnection.getDefaultHostnameVerifier()
+ .verify(hostname, socket.getSession())
+ && isValid(Arrays.asList(socket.getSession().getPeerCertificates()))
+ && validTldWildcards(Arrays.asList(socket.getSession().getPeerCertificates()));
+ } catch (SSLPeerUnverifiedException e) {
+ Log.i(TAG, "Valid Check failed: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Checks the HttpsUrlConnection certificates for validity.
+ * <p>
+ * Example Code:
+ * SecureURL mUrl = new SecureURL("https://" + host);
+ * conn = (HttpsURLConnection) mUrl.openConnection();
+ * boolean valid = SecurityExt.isValid(conn);
+ * </p>
+ *
+ * @param conn The connection to check the certificates of
+ * @return true if the certificates for the HttpsUrlConnection are valid, false otherwise
+ */
+ public boolean isValid(@NonNull HttpsURLConnection conn) {
+ try {
+ return isValid(Arrays.asList(conn.getServerCertificates()))
+ && validTldWildcards(Arrays.asList(conn.getServerCertificates()));
+ } catch (SSLPeerUnverifiedException e) {
+ Log.i(TAG, "Valid Check failed: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+
+ /**
+ * Internal method to check a list of certificates for validity.
+ *
+ * @param certs list of certs to check
+ * @return true if the certs are valid, false otherwise
+ */
+ private boolean isValid(@NonNull List<? extends Certificate> certs) {
+ try {
+ List<Certificate> leafCerts = new ArrayList<>();
+ for (Certificate cert : certs) {
+ if (!isRootCA(cert)) {
+ leafCerts.add(cert);
+ }
+ }
+ CertPath path = CertificateFactory.getInstance(mSecureConfig.getCertPath())
+ .generateCertPath(leafCerts);
+ KeyStore ks = KeyStore.getInstance(mSecureConfig.getAndroidCAStore());
+ try {
+ ks.load(null, null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new AssertionError(e);
+ }
+ CertPathValidator cpv = CertPathValidator.getInstance(mSecureConfig
+ .getCertPathValidator());
+ PKIXParameters params = new PKIXParameters(ks);
+ PKIXRevocationChecker checker = (PKIXRevocationChecker) cpv.getRevocationChecker();
+ checker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.NO_FALLBACK));
+ params.addCertPathChecker(checker);
+ cpv.validate(path, params);
+ return true;
+ } catch (CertPathValidatorException e) {
+ // If this message prints out "Unable to determine revocation status due to
+ // network error"
+ // Make sure your network security config allows for clear text access of the relevant
+ // OCSP mUrl.
+ e.printStackTrace();
+ return false;
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Internal method to check if a cert is a CA.
+ *
+ * @param cert The cert to check
+ * @return true if the cert is a RootCA, false otherwise
+ */
+ private boolean isRootCA(@NonNull Certificate cert) {
+ boolean rootCA = false;
+ if (cert instanceof X509Certificate) {
+ X509Certificate x509Certificate = (X509Certificate) cert;
+ if (x509Certificate.getSubjectDN().getName().equals(
+ x509Certificate.getIssuerDN().getName())) {
+ rootCA = true;
+ }
+ }
+ return rootCA;
+ }
+
+
+ private boolean validTldWildcards(@NonNull List<? extends Certificate> certs) {
+ // For a more complete list https://publicsuffix.org/list/public_suffix_list.dat
+ for (Certificate cert : certs) {
+ if (cert instanceof X509Certificate) {
+ X509Certificate x509Cert = (X509Certificate) cert;
+ try {
+ Collection<List<?>> subAltNames = x509Cert.getSubjectAlternativeNames();
+ if (subAltNames != null) {
+ List<String> dnsNames = new ArrayList<>();
+ for (List<?> tldList : subAltNames) {
+ if (tldList.size() >= 2) {
+ dnsNames.add(tldList.get(1).toString().toUpperCase());
+ }
+ }
+ // Populate DNS NAMES, make sure they are lower case
+ for (String dnsName : dnsNames) {
+ if (TldConstants.VALID_TLDS.contains(dnsName)) {
+ Log.i(TAG, "FAILED WILDCARD TldConstants CHECK: " + dnsName);
+ return false;
+ }
+ }
+ }
+ } catch (CertificateParsingException ex) {
+ Log.i(TAG, "Cert Parsing Issue: " + ex.getMessage());
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java
new file mode 100644
index 0000000..e7be287
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocket.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.net;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+
+import androidx.annotation.RestrictTo;
+import androidx.security.SecureConfig;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * A custom implementation of SSLSocket which forces TLS, and handles automatically doing
+ * certificate validity checks.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class ValidatableSSLSocket extends SSLSocket {
+
+ private static final String TAG = "ValidatableSSLSocket";
+
+ private SSLSocket mSslSocket;
+ private String mHostname;
+ private SecureURL mSecureURL;
+ private boolean mHandshakeStarted = false;
+ private SecureConfig mSecureConfig;
+
+ ValidatableSSLSocket(SecureURL secureURL, Socket sslSocket, SecureConfig secureConfig)
+ throws IOException {
+ this.mSecureURL = secureURL;
+ this.mHostname = secureURL.getHostname();
+ this.mSslSocket = (SSLSocket) sslSocket;
+ this.mSecureConfig = secureConfig;
+ setSecureCiphers();
+ isValid();
+ }
+
+ private void setSecureCiphers() {
+ if (mSecureConfig.getUseStrongSSLCiphersEnabled()) {
+ this.mSslSocket.setEnabledCipherSuites(mSecureConfig.getStrongSSLCiphers());
+ }
+ }
+
+ private void isValid() throws IOException {
+ startHandshake();
+ try {
+ if (!mSecureURL.isValid(this.mHostname, this.mSslSocket)) {
+ throw new IOException("Found invalid certificate");
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ throw new IOException("Found invalid certificate");
+ }
+ }
+
+ @Override
+ public void startHandshake() throws IOException {
+ if (!mHandshakeStarted) {
+ mSslSocket.startHandshake();
+ mHandshakeStarted = true;
+ }
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return mSslSocket.getSupportedCipherSuites();
+ }
+
+ @Override
+ public String[] getEnabledCipherSuites() {
+ return mSslSocket.getEnabledCipherSuites();
+ }
+
+ @Override
+ public void setEnabledCipherSuites(String[] suites) {
+ mSslSocket.setEnabledCipherSuites(suites);
+ }
+
+ @Override
+ public String[] getSupportedProtocols() {
+ return mSslSocket.getSupportedProtocols();
+ }
+
+ @Override
+ public String[] getEnabledProtocols() {
+ return mSslSocket.getEnabledProtocols();
+ }
+
+ @Override
+ public void setEnabledProtocols(String[] protocols) {
+ mSslSocket.setEnabledProtocols(protocols);
+ }
+
+ @Override
+ public SSLSession getSession() {
+ return mSslSocket.getSession();
+ }
+
+ @Override
+ public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
+ mSslSocket.addHandshakeCompletedListener(listener);
+ }
+
+ @Override
+ public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
+ mSslSocket.removeHandshakeCompletedListener(listener);
+ }
+
+ @Override
+ public void setUseClientMode(boolean mode) {
+ mSslSocket.setUseClientMode(mode);
+ }
+
+ @Override
+ public boolean getUseClientMode() {
+ return mSslSocket.getUseClientMode();
+ }
+
+ @Override
+ public void setNeedClientAuth(boolean need) {
+ mSslSocket.setNeedClientAuth(need);
+ }
+
+ @Override
+ public boolean getNeedClientAuth() {
+ return mSslSocket.getNeedClientAuth();
+ }
+
+ @Override
+ public void setWantClientAuth(boolean want) {
+ mSslSocket.setWantClientAuth(want);
+ }
+
+ @Override
+ public boolean getWantClientAuth() {
+ return mSslSocket.getWantClientAuth();
+ }
+
+ @Override
+ public void setEnableSessionCreation(boolean flag) {
+ mSslSocket.setEnableSessionCreation(flag);
+ }
+
+ @Override
+ public boolean getEnableSessionCreation() {
+ return mSslSocket.getEnableSessionCreation();
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.N)
+ public SSLSession getHandshakeSession() {
+ return mSslSocket.getHandshakeSession();
+ }
+
+ @Override
+ public SSLParameters getSSLParameters() {
+ return mSslSocket.getSSLParameters();
+ }
+
+ @Override
+ public void setSSLParameters(SSLParameters params) {
+ mSslSocket.setSSLParameters(params);
+ }
+
+ @Override
+ public String toString() {
+ return mSslSocket.toString();
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint) throws IOException {
+ mSslSocket.connect(endpoint);
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint, int timeout) throws IOException {
+ mSslSocket.connect(endpoint, timeout);
+ }
+
+ @Override
+ public void bind(SocketAddress bindpoint) throws IOException {
+ mSslSocket.bind(bindpoint);
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ return mSslSocket.getInetAddress();
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ return mSslSocket.getLocalAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return mSslSocket.getPort();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return mSslSocket.getLocalPort();
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return mSslSocket.getRemoteSocketAddress();
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ return mSslSocket.getLocalSocketAddress();
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ return mSslSocket.getChannel();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return mSslSocket.getInputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return mSslSocket.getOutputStream();
+ }
+
+ @Override
+ public void setTcpNoDelay(boolean on) throws SocketException {
+ mSslSocket.setTcpNoDelay(on);
+ }
+
+ @Override
+ public boolean getTcpNoDelay() throws SocketException {
+ return mSslSocket.getTcpNoDelay();
+ }
+
+ @Override
+ public void setSoLinger(boolean on, int linger) throws SocketException {
+ mSslSocket.setSoLinger(on, linger);
+ }
+
+ @Override
+ public int getSoLinger() throws SocketException {
+ return mSslSocket.getSoLinger();
+ }
+
+ @Override
+ public void sendUrgentData(int data) throws IOException {
+ mSslSocket.sendUrgentData(data);
+ }
+
+ @Override
+ public void setOOBInline(boolean on) throws SocketException {
+ mSslSocket.setOOBInline(on);
+ }
+
+ @Override
+ public boolean getOOBInline() throws SocketException {
+ return mSslSocket.getOOBInline();
+ }
+
+ @Override
+ public synchronized void setSoTimeout(int timeout) throws SocketException {
+ mSslSocket.setSoTimeout(timeout);
+ }
+
+ @Override
+ public synchronized int getSoTimeout() throws SocketException {
+ return mSslSocket.getSoTimeout();
+ }
+
+ @Override
+ public synchronized void setSendBufferSize(int size) throws SocketException {
+ mSslSocket.setSendBufferSize(size);
+ }
+
+ @Override
+ public synchronized int getSendBufferSize() throws SocketException {
+ return mSslSocket.getSendBufferSize();
+ }
+
+ @Override
+ public synchronized void setReceiveBufferSize(int size) throws SocketException {
+ mSslSocket.setReceiveBufferSize(size);
+ }
+
+ @Override
+ public synchronized int getReceiveBufferSize() throws SocketException {
+ return mSslSocket.getReceiveBufferSize();
+ }
+
+ @Override
+ public void setKeepAlive(boolean on) throws SocketException {
+ mSslSocket.setKeepAlive(on);
+ }
+
+ @Override
+ public boolean getKeepAlive() throws SocketException {
+ return mSslSocket.getKeepAlive();
+ }
+
+ @Override
+ public void setTrafficClass(int tc) throws SocketException {
+ mSslSocket.setTrafficClass(tc);
+ }
+
+ @Override
+ public int getTrafficClass() throws SocketException {
+ return mSslSocket.getTrafficClass();
+ }
+
+ @Override
+ public void setReuseAddress(boolean on) throws SocketException {
+ mSslSocket.setReuseAddress(on);
+ }
+
+ @Override
+ public boolean getReuseAddress() throws SocketException {
+ return mSslSocket.getReuseAddress();
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ mSslSocket.close();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ mSslSocket.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ mSslSocket.shutdownOutput();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return mSslSocket.isConnected();
+ }
+
+ @Override
+ public boolean isBound() {
+ return mSslSocket.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return mSslSocket.isClosed();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return mSslSocket.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return mSslSocket.isOutputShutdown();
+ }
+
+ @Override
+ public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
+ mSslSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
+ }
+}
diff --git a/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java
new file mode 100644
index 0000000..0c678b2
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/net/ValidatableSSLSocketFactory.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package androidx.security.net;
+
+import static androidx.security.SecureConfig.SSL_TLS;
+
+import androidx.annotation.RestrictTo;
+import androidx.security.SecureConfig;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Enumeration;
+import java.util.Map;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * A custom implementation of SSLSocketFactory which handles the creation of custom SSLSockets
+ * that handle extra functionality and do validity checking.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class ValidatableSSLSocketFactory extends SSLSocketFactory {
+
+ private static final String TAG = "ValidatableSSLSocketFactory";
+
+ private SSLSocketFactory mSslSocketFactory;
+ private SecureURL mSecureURL;
+ private Socket mSocket;
+ private SecureConfig mSecureConfig;
+
+ ValidatableSSLSocketFactory(SecureURL secureURL, SSLSocketFactory sslSocketFactory,
+ SecureConfig secureConfig) throws IOException {
+ this.mSecureURL = secureURL;
+ this.mSslSocketFactory = sslSocketFactory;
+ this.mSecureConfig = secureConfig;
+ this.mSocket = new ValidatableSSLSocket(secureURL,
+ mSslSocketFactory.createSocket(mSecureURL.getHostname(), mSecureURL.getPort()),
+ mSecureConfig);
+ }
+
+ ValidatableSSLSocketFactory(SecureURL secureURL, SSLSocketFactory sslSocketFactory)
+ throws IOException {
+ this(secureURL, sslSocketFactory, SecureConfig.getDefault());
+ }
+
+ ValidatableSSLSocketFactory(SecureURL secureURL) throws IOException {
+ this(secureURL, (SSLSocketFactory) SSLSocketFactory.getDefault(),
+ SecureConfig.getDefault());
+ }
+
+ ValidatableSSLSocketFactory(SecureURL secureURL,
+ Map<String, InputStream> trustedCAs, SecureConfig secureConfig) throws IOException {
+ this(secureURL, createUserTrustSSLSocketFactory(trustedCAs, secureConfig, secureURL),
+ secureConfig);
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return mSslSocketFactory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return mSslSocketFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ if (mSocket == null) {
+ mSocket = new ValidatableSSLSocket(
+ mSecureURL, mSslSocketFactory.createSocket(s, host, port, autoClose),
+ mSecureConfig);
+ }
+ return mSocket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ if (mSocket == null) {
+ mSocket = new ValidatableSSLSocket(mSecureURL,
+ mSslSocketFactory.createSocket(host, port),
+ mSecureConfig);
+ }
+ return mSocket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
+ throws IOException, UnknownHostException {
+ if (mSocket == null) {
+ mSocket = new ValidatableSSLSocket(
+ mSecureURL, mSslSocketFactory.createSocket(host, port, localHost, localPort),
+ mSecureConfig);
+ }
+ return mSocket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ if (mSocket == null) {
+ mSocket = new ValidatableSSLSocket(mSecureURL, mSslSocketFactory
+ .createSocket(host, port),
+ mSecureConfig);
+ }
+ return mSocket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
+ int localPort) throws IOException {
+ if (mSocket == null) {
+ mSocket = new ValidatableSSLSocket(
+ mSecureURL, mSslSocketFactory.createSocket(address, port, localAddress,
+ localPort),
+ mSecureConfig);
+ }
+ return mSocket;
+ }
+
+ // TODO Evaluate the need for all of these options
+ private static SSLSocketFactory createUserTrustSSLSocketFactory(Map<String, InputStream>
+ trustAnchors, SecureConfig secureConfig, SecureURL secureURL) {
+ try {
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ KeyStore clientStore = KeyStore.getInstance(secureConfig.getKeystoreType());
+ clientStore.load(null, null);
+
+ KeyStore trustStore = null;
+ switch (secureConfig.getTrustAnchorOptions()) {
+ case USER_ONLY:
+ case USER_SYSTEM:
+ case LIMITED_SYSTEM:
+ trustStore = KeyStore.getInstance(secureConfig.getKeystoreType());
+ trustStore.load(null, null);
+ break;
+ }
+
+ switch (secureConfig.getTrustAnchorOptions()) {
+ case USER_SYSTEM:
+ KeyStore caStore = KeyStore.getInstance(secureConfig.getAndroidCAStore());
+ caStore.load(null, null);
+ Enumeration<String> caAliases = caStore.aliases();
+ while (caAliases.hasMoreElements()) {
+ String alias = caAliases.nextElement();
+ trustStore.setCertificateEntry(alias, caStore.getCertificate(alias));
+ }
+ break;
+ case USER_ONLY:
+ case LIMITED_SYSTEM:
+ for (Map.Entry<String, InputStream> ca : trustAnchors.entrySet()) {
+ CertificateFactory cf = CertificateFactory
+ .getInstance(secureConfig.getCertPath());
+ Certificate userCert = cf.generateCertificate(ca.getValue());
+ trustStore.setCertificateEntry(ca.getKey(), userCert);
+ }
+ break;
+ }
+
+ tmf.init(trustStore);
+ SSLContext sslContext = SSLContext.getInstance(SSL_TLS);
+
+ KeyManager[] keyManagersArray = new KeyManager[1];
+ keyManagersArray[0] = SecureKeyManager.getDefault(
+ secureURL.getClientCertAlias(), secureConfig);
+ sslContext.init(keyManagersArray, tmf.getTrustManagers(), new SecureRandom());
+ return sslContext.getSocketFactory();
+ } catch (GeneralSecurityException ex) {
+ throw new SecurityException("Issue creating User SSLSocketFactory.");
+ } catch (IOException ex) {
+ throw new SecurityException("Issue creating User SSLSocketFactory.");
+ }
+ }
+
+}
diff --git a/settings.gradle b/settings.gradle
index 31e96e2..523bfb4b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -47,6 +47,7 @@
includeProject(":arch:core-runtime", "arch/core-runtime")
includeProject(":asynclayoutinflater", "asynclayoutinflater")
includeProject(":benchmark", "benchmark")
+includeProject(":benchmark:benchmark-gradle-plugin", "benchmark/gradle-plugin")
includeProject(":biometric", "biometric")
includeProject(":browser", "browser")
includeProject(":car", "car/core")
@@ -95,10 +96,12 @@
includeProject(":lifecycle:lifecycle-livedata-core-ktx", "lifecycle/livedata-core/ktx")
includeProject(":lifecycle:lifecycle-livedata", "lifecycle/livedata")
includeProject(":lifecycle:lifecycle-livedata-ktx", "lifecycle/livedata/ktx")
+includeProject(":lifecycle:lifecycle-livedata-eap", "lifecycle/livedata/eap")
includeProject(":lifecycle:lifecycle-process", "lifecycle/process")
includeProject(":lifecycle:lifecycle-reactivestreams", "lifecycle/reactivestreams")
includeProject(":lifecycle:lifecycle-reactivestreams-ktx", "lifecycle/reactivestreams/ktx")
includeProject(":lifecycle:lifecycle-runtime", "lifecycle/runtime")
+includeProject(":lifecycle:lifecycle-runtime-eap", "lifecycle/runtime/eap") // temporary
includeProject(":lifecycle:lifecycle-service", "lifecycle/service")
includeProject(":lifecycle:lifecycle-viewmodel", "lifecycle/viewmodel")
includeProject(":lifecycle:lifecycle-viewmodel-ktx", "lifecycle/viewmodel/ktx")
@@ -146,6 +149,7 @@
includeProject(":room:integration-tests:room-testapp-kotlin", "room/integration-tests/kotlintestapp")
includeProject(":room:room-benchmark", "room/benchmark")
includeProject(":room:room-common", "room/common")
+includeProject(":room:room-common-java8", "room/common-java8")
includeProject(":room:room-compiler", "room/compiler")
includeProject(":room:room-guava", "room/guava")
includeProject(":room:room-ktx", "room/ktx")
@@ -271,6 +275,7 @@
// we need real android project to generate R.java, aidl etc files that mentioned in sources
if (!startParameter.projectProperties.containsKey('android.injected.invoked.from.ide')) {
// we don't need it in ide, so we don't configure it there
+ includeProject(":docs-fake", "docs-fake")
includeProject(":docs-runner", "docs-runner")
}
diff --git a/slices/OWNERS b/slices/OWNERS
index 3c245e9..f498537 100644
--- a/slices/OWNERS
+++ b/slices/OWNERS
@@ -1,3 +1,4 @@
jmonk@android.com
jmonk@google.com
madym@google.com
+sunnygoyal@google.com
diff --git a/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java b/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
index 080b552..43143e1 100644
--- a/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
+++ b/slices/benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
@@ -20,7 +20,6 @@
import android.content.Context;
import android.net.Uri;
-import androidx.annotation.NonNull;
import androidx.benchmark.BenchmarkRule;
import androidx.benchmark.BenchmarkState;
import androidx.slice.widget.SliceView;
@@ -32,12 +31,9 @@
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.junit.runners.model.Statement;
-import java.lang.annotation.Annotation;
import java.util.Arrays;
@RunWith(Parameterized.class)
@@ -47,38 +43,21 @@
private final int mMode;
- @Parameterized.Parameters
+ @Parameterized.Parameters(name = "{1}")
public static Iterable<? extends Object[]> data() {
- return Arrays.asList(new Object[][]{{SliceView.MODE_SHORTCUT}, {SliceView.MODE_SMALL},
- {SliceView.MODE_LARGE}});
+ return Arrays.asList(new Object[][]{
+ {SliceView.MODE_SHORTCUT, "shortcut"},
+ {SliceView.MODE_SMALL, "small"},
+ {SliceView.MODE_LARGE, "large"}
+ });
}
- public SliceViewMetrics(int mode) {
+ public SliceViewMetrics(int mode, @SuppressWarnings("unused") String ignored) {
mMode = mode;
}
@Rule
- public BenchmarkRule mBenchmarkRule = new BenchmarkRule() {
- @NonNull
- @Override
- public Statement apply(@NonNull Statement base, @NonNull Description description) {
- return super.apply(base, fixDescription(description));
- }
-
- private Description fixDescription(Description description) {
- // Copies the Description and modifies the method to be compatible with BenchmarkRule.
- return Description.createTestDescription(description.getClassName(),
- fixMethodName(description.getMethodName()),
- description.getAnnotations().toArray(new Annotation[0]));
- }
-
- private String fixMethodName(String methodName) {
- // Replace [int] with [string] for BenchmarkRule and readability.
- return methodName.replace("[0]", "[shortcut]")
- .replace("[1]", "[small]")
- .replace("[2]", "[large]");
- }
- };
+ public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
diff --git a/slices/core/api/1.1.0-alpha01.txt b/slices/core/api/1.1.0-alpha01.txt
index b1999c8..090e3a3 100644
--- a/slices/core/api/1.1.0-alpha01.txt
+++ b/slices/core/api/1.1.0-alpha01.txt
@@ -49,6 +49,7 @@
method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
method @RequiresApi(19) public abstract androidx.slice.Slice! onBindSlice(android.net.Uri!);
method public final boolean onCreate();
+ method public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
method @RequiresApi(19) public abstract boolean onCreateSliceProvider();
method @RequiresApi(19) public java.util.Collection<android.net.Uri>! onGetSliceDescendants(android.net.Uri!);
method @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent!);
diff --git a/slices/core/api/current.txt b/slices/core/api/current.txt
index b1999c8..090e3a3 100644
--- a/slices/core/api/current.txt
+++ b/slices/core/api/current.txt
@@ -49,6 +49,7 @@
method public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
method @RequiresApi(19) public abstract androidx.slice.Slice! onBindSlice(android.net.Uri!);
method public final boolean onCreate();
+ method public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
method @RequiresApi(19) public abstract boolean onCreateSliceProvider();
method @RequiresApi(19) public java.util.Collection<android.net.Uri>! onGetSliceDescendants(android.net.Uri!);
method @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent!);
diff --git a/slices/core/api/restricted_1.1.0-alpha01.ignore b/slices/core/api/restricted_1.1.0-alpha01.ignore
index af1addb..9843ccf 100644
--- a/slices/core/api/restricted_1.1.0-alpha01.ignore
+++ b/slices/core/api/restricted_1.1.0-alpha01.ignore
@@ -13,6 +13,12 @@
Removed method androidx.slice.SliceItem.getTimestamp()
RemovedMethod: androidx.slice.SliceItemHolder#SliceItemHolder():
Removed constructor androidx.slice.SliceItemHolder()
+RemovedMethod: androidx.slice.SliceProvider#createPermissionIntent(android.content.Context, android.net.Uri, String):
+ Removed method androidx.slice.SliceProvider.createPermissionIntent(android.content.Context,android.net.Uri,String)
+RemovedMethod: androidx.slice.SliceProvider#createPermissionSlice(android.content.Context, android.net.Uri, String):
+ Removed method androidx.slice.SliceProvider.createPermissionSlice(android.content.Context,android.net.Uri,String)
+RemovedMethod: androidx.slice.SliceProvider#getPermissionString(android.content.Context, String):
+ Removed method androidx.slice.SliceProvider.getPermissionString(android.content.Context,String)
RemovedMethod: androidx.slice.core.SliceQuery#stream(androidx.slice.Slice):
Removed method androidx.slice.core.SliceQuery.stream(androidx.slice.Slice)
RemovedMethod: androidx.slice.core.SliceQuery#stream(androidx.slice.SliceItem):
diff --git a/slices/core/api/restricted_1.1.0-alpha01.txt b/slices/core/api/restricted_1.1.0-alpha01.txt
index 135d4e8..e1209e1 100644
--- a/slices/core/api/restricted_1.1.0-alpha01.txt
+++ b/slices/core/api/restricted_1.1.0-alpha01.txt
@@ -75,11 +75,8 @@
}
public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static android.app.PendingIntent! createPermissionIntent(android.content.Context!, android.net.Uri!, String!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static androidx.slice.Slice! createPermissionSlice(android.content.Context!, android.net.Uri!, String!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @RequiresApi(19) public static androidx.slice.Clock! getClock();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static java.util.Set<androidx.slice.SliceSpec>! getCurrentSpecs();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static CharSequence! getPermissionString(android.content.Context!, String!);
}
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
diff --git a/slices/core/api/restricted_current.txt b/slices/core/api/restricted_current.txt
index 135d4e8..e1209e1 100644
--- a/slices/core/api/restricted_current.txt
+++ b/slices/core/api/restricted_current.txt
@@ -75,11 +75,8 @@
}
public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static android.app.PendingIntent! createPermissionIntent(android.content.Context!, android.net.Uri!, String!);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static androidx.slice.Slice! createPermissionSlice(android.content.Context!, android.net.Uri!, String!);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @RequiresApi(19) public static androidx.slice.Clock! getClock();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static java.util.Set<androidx.slice.SliceSpec>! getCurrentSpecs();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @RequiresApi(19) public static CharSequence! getPermissionString(android.content.Context!, String!);
}
public abstract class SliceProviderWithCallbacks<T extends androidx.slice.SliceProviderWithCallbacks> extends androidx.slice.SliceProvider implements androidx.remotecallback.CallbackBase<T> androidx.remotecallback.CallbackReceiver<T> {
diff --git a/slices/core/src/androidTest/java/androidx/slice/SliceTest.java b/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
index a313ccb..31f39d5 100644
--- a/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
+++ b/slices/core/src/androidTest/java/androidx/slice/SliceTest.java
@@ -19,6 +19,7 @@
import static android.app.slice.Slice.HINT_LARGE;
import static android.app.slice.Slice.HINT_LIST;
import static android.app.slice.Slice.HINT_NO_TINT;
+import static android.app.slice.Slice.HINT_PERMISSION_REQUEST;
import static android.app.slice.Slice.HINT_TITLE;
import static android.app.slice.SliceItem.FORMAT_ACTION;
import static android.app.slice.SliceItem.FORMAT_IMAGE;
@@ -45,6 +46,7 @@
import android.text.style.AbsoluteSizeSpan;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.core.SliceQuery;
import androidx.slice.core.test.R;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -263,4 +265,36 @@
assertEquals(Arrays.asList(HINT_NO_TINT, HINT_LARGE),
s.getItems().get(1).getHints());
}
+
+ @Test
+ public void testOnCreatePermissionRequest() {
+ sFlag = false;
+ final CountDownLatch latch = new CountDownLatch(1);
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sFlag = true;
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(receiver,
+ new IntentFilter(mContext.getPackageName() + ".permission"));
+ Uri uri = BASE_URI.buildUpon().appendPath("permission").build();
+ Slice s = Slice.bindSlice(mContext, uri, Collections.<SliceSpec>emptySet());
+ assertEquals(uri, s.getUri());
+ assertEquals(1, s.getItems().size());
+ assertTrue(s.getHints().contains(HINT_PERMISSION_REQUEST));
+
+ try {
+ SliceQuery.find(s, FORMAT_ACTION).getAction().send();
+ } catch (CanceledException e) {
+ }
+ try {
+ latch.await(100, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ assertTrue(sFlag);
+ mContext.unregisterReceiver(receiver);
+ }
}
diff --git a/slices/core/src/androidTest/java/androidx/slice/SliceTestProvider.java b/slices/core/src/androidTest/java/androidx/slice/SliceTestProvider.java
index c03dd24..53e2388 100644
--- a/slices/core/src/androidTest/java/androidx/slice/SliceTestProvider.java
+++ b/slices/core/src/androidTest/java/androidx/slice/SliceTestProvider.java
@@ -29,6 +29,8 @@
import android.text.SpannedString;
import android.text.style.AbsoluteSizeSpan;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice.Builder;
import androidx.slice.core.test.R;
@@ -96,8 +98,20 @@
.addIcon(IconCompat.createWithResource(getContext(), R.drawable.size_48x48),
null, HINT_NO_TINT, HINT_LARGE)
.build();
+ case "/permission":
+ return createPermissionSlice(sliceUri, getContext().getPackageName());
}
return new Slice.Builder(sliceUri).build();
}
+ @Nullable
+ @Override
+ public PendingIntent onCreatePermissionRequest(@NonNull Uri sliceUri,
+ @NonNull String callingPackage) {
+ if (getContext().getPackageName().equals(callingPackage)) {
+ return PendingIntent.getBroadcast(getContext(), 0,
+ new Intent(getContext().getPackageName() + ".permission"), 0);
+ }
+ return super.onCreatePermissionRequest(sliceUri, callingPackage);
+ }
}
diff --git a/slices/core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java b/slices/core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
index deecabc..d7384e7 100644
--- a/slices/core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
+++ b/slices/core/src/androidTest/java/androidx/slice/compat/SliceProviderCompatTest.java
@@ -67,6 +67,7 @@
.build();
SliceProvider provider = spy(new SliceProviderImpl());
+ provider.attachInfo(mContext, null);
CompatPermissionManager permissions = mock(CompatPermissionManager.class);
when(permissions.checkSlicePermission(any(Uri.class), anyInt(), anyInt()))
.thenReturn(PERMISSION_GRANTED);
@@ -100,6 +101,7 @@
.build();
SliceProvider provider = spy(new SliceProviderImpl());
+ provider.attachInfo(mContext, null);
CompatPermissionManager permissions = mock(CompatPermissionManager.class);
when(permissions.checkSlicePermission(any(Uri.class), anyInt(), anyInt()))
.thenReturn(PERMISSION_DENIED);
diff --git a/slices/core/src/main/java/androidx/slice/SliceProvider.java b/slices/core/src/main/java/androidx/slice/SliceProvider.java
index 397db68..7c9bb73 100644
--- a/slices/core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slices/core/src/main/java/androidx/slice/SliceProvider.java
@@ -143,6 +143,7 @@
private SliceProviderCompat mCompat;
+ private final Object mPinnedSliceUrisLock = new Object();
private List<Uri> mPinnedSliceUris;
/**
@@ -200,8 +201,6 @@
@Override
public final boolean onCreate() {
if (Build.VERSION.SDK_INT < 19) return false;
- mPinnedSliceUris = new ArrayList<>(SliceManager.getInstance(
- getContext()).getPinnedSlices());
if (Build.VERSION.SDK_INT < 28) {
mCompat = new SliceProviderCompat(this,
onCreatePermissionManager(mAutoGrantPermissions), getContext());
@@ -236,14 +235,35 @@
}
/**
+ * Called when an app requests a slice it does not have write permission
+ * to the uri for.
+ * <p>
+ * The return value will be the action on a slice that prompts the user that
+ * the calling app wants to show slices from this app. Returning null will use the default
+ * implementation that launches a dialog that allows the user to grant access to this slice.
+ * Apps that do not want to allow this user grant, can override this and instead
+ * launch their own dialog with different behavior.
+ *
+ * @param sliceUri the Uri of the slice attempting to be bound.
+ * @param callingPackage the packageName of the app requesting the slice
+ */
+ public @Nullable PendingIntent onCreatePermissionRequest(@NonNull Uri sliceUri,
+ @NonNull String callingPackage) {
+ return null;
+ }
+
+ /**
* Generate a slice that contains a permission request.
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
- public static Slice createPermissionSlice(Context context, Uri sliceUri,
- String callingPackage) {
- PendingIntent action = createPermissionIntent(context, sliceUri, callingPackage);
+ public Slice createPermissionSlice(Uri sliceUri, String callingPackage) {
+ Context context = getContext();
+ PendingIntent action = onCreatePermissionRequest(sliceUri, callingPackage);
+ if (action == null) {
+ action = createPermissionIntent(context, sliceUri, callingPackage);
+ }
Slice.Builder parent = new Slice.Builder(sliceUri);
Slice.Builder childAction = new Slice.Builder(parent)
@@ -269,11 +289,9 @@
/**
* Create a PendingIntent pointing at the permission dialog.
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
- public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
+ private static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
String callingPackage) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(context.getPackageName(),
@@ -290,11 +308,9 @@
/**
* Get string describing permission request.
- * @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
- public static CharSequence getPermissionString(Context context, String callingPackage) {
+ private static CharSequence getPermissionString(Context context, String callingPackage) {
PackageManager pm = context.getPackageManager();
try {
return context.getString(R.string.abc_slices_permission_request,
@@ -361,8 +377,9 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public void handleSlicePinned(Uri sliceUri) {
- if (!mPinnedSliceUris.contains(sliceUri)) {
- mPinnedSliceUris.add(sliceUri);
+ List<Uri> pinnedSlices = getPinnedSlices();
+ if (!pinnedSlices.contains(sliceUri)) {
+ pinnedSlices.add(sliceUri);
}
}
@@ -372,8 +389,9 @@
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public void handleSliceUnpinned(Uri sliceUri) {
- if (mPinnedSliceUris.contains(sliceUri)) {
- mPinnedSliceUris.remove(sliceUri);
+ List<Uri> pinnedSlices = getPinnedSlices();
+ if (pinnedSlices.contains(sliceUri)) {
+ pinnedSlices.remove(sliceUri);
}
}
@@ -413,6 +431,12 @@
*/
@RequiresApi(19)
@NonNull public List<Uri> getPinnedSlices() {
+ synchronized (mPinnedSliceUrisLock) {
+ if (mPinnedSliceUris == null) {
+ mPinnedSliceUris = new ArrayList<>(SliceManager.getInstance(getContext())
+ .getPinnedSlices());
+ }
+ }
return mPinnedSliceUris;
}
diff --git a/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index 630b212..987b2ed 100644
--- a/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slices/core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -269,7 +269,7 @@
: getContext().getPackageManager().getNameForUid(Binder.getCallingUid());
if (mPermissionManager.checkSlicePermission(sliceUri, Binder.getCallingPid(),
Binder.getCallingUid()) != PERMISSION_GRANTED) {
- return mProvider.createPermissionSlice(getContext(), sliceUri, pkg);
+ return mProvider.createPermissionSlice(sliceUri, pkg);
}
return onBindSliceStrict(sliceUri, specs);
}
diff --git a/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java b/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
index 732e11d..25afdbf 100644
--- a/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
+++ b/slices/core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
@@ -89,6 +89,11 @@
if (mAutoGrantPermissions != null) {
checkPermissions(sliceUri);
}
+ PendingIntent action = mSliceProvider.onCreatePermissionRequest(
+ sliceUri, getCallingPackage());
+ if (action != null) {
+ return action;
+ }
return super.onCreatePermissionRequest(sliceUri);
}
diff --git a/slices/test/src/main/java/androidx/slice/test/SampleSliceProvider.java b/slices/test/src/main/java/androidx/slice/test/SampleSliceProvider.java
index efe7436..0a792dc 100644
--- a/slices/test/src/main/java/androidx/slice/test/SampleSliceProvider.java
+++ b/slices/test/src/main/java/androidx/slice/test/SampleSliceProvider.java
@@ -106,7 +106,6 @@
"rtlgrid",
"slices",
"cat",
- "permission",
"longtext",
"loading",
"selection",
@@ -201,8 +200,6 @@
return createFoodOptionsSlice(sliceUri);
case "/cat":
return createBigPicSlice(sliceUri);
- case "/permission":
- return createPermissionSlice(getContext(), sliceUri, getContext().getPackageName());
case "/loading":
return createLoadingSlice(sliceUri);
case "/selection":
diff --git a/slices/view/src/androidTest/java/androidx/slice/SliceMetadataTest.java b/slices/view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
index c31eff0f..d0a02ca 100644
--- a/slices/view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/SliceMetadataTest.java
@@ -792,8 +792,10 @@
@Test
public void testIsPermissionSlice() {
Uri uri = Uri.parse("content://pkg/slice");
- Slice permissionSlice =
- SliceProvider.createPermissionSlice(mContext, uri, mContext.getPackageName());
+ SliceProvider provider = new SliceViewManagerTest.TestSliceProvider();
+ provider.attachInfo(mContext, null);
+ Slice permissionSlice = provider.createPermissionSlice(
+ uri, mContext.getPackageName());
SliceMetadata metadata = SliceMetadata.from(mContext, permissionSlice);
assertEquals(true, metadata.isPermissionSlice());
diff --git a/slices/view/src/main/java/androidx/slice/widget/GridRowView.java b/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
index 32270a9..fd0cf13 100644
--- a/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/GridRowView.java
@@ -149,7 +149,8 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int height = mGridContent.getHeight(mSliceStyle, mViewPolicy);
+ int height = mGridContent.getHeight(mSliceStyle, mViewPolicy)
+ + mInsetTop + mInsetBottom;
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
mViewContainer.getLayoutParams().height = height;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/slices/view/src/main/java/androidx/slice/widget/RowView.java b/slices/view/src/main/java/androidx/slice/widget/RowView.java
index a710afb0..76ea8c1 100644
--- a/slices/view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/RowView.java
@@ -144,10 +144,15 @@
Handler mHandler;
@SuppressWarnings("WeakerAccess") /* synthetic access */
long mLastSentRangeUpdate;
+ // TODO: mRangeValue is in 0..(mRangeMaxValue-mRangeMinValue) at initialization, and in
+ // mRangeMinValue..mRangeMaxValue after user interaction. As far as I know, this doesn't
+ // cause any incorrect behavior, but it is confusing and error-prone.
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRangeValue;
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mRangeMinValue;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ int mRangeMaxValue;
private SliceItem mRangeItem;
private int mImageSize;
@@ -235,10 +240,7 @@
@Override
public void setInsets(int l, int t, int r, int b) {
super.setInsets(l, t, r, b);
- mRootView.setPadding(l, t, r, b);
- if (mRangeBar != null) {
- updateRangePadding();
- }
+ setPadding(l, t, r, b);
}
/**
@@ -290,18 +292,21 @@
}
}
+ private void measureChildWithExactHeight(View child, int widthMeasureSpec, int childHeight) {
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight + mInsetTop + mInsetBottom,
+ MeasureSpec.EXACTLY);
+ measureChild(child, widthMeasureSpec, heightMeasureSpec);
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int totalHeight = mRowContent != null
- ? mRowContent.getHeight(mSliceStyle, mViewPolicy) : 0;
int childWidth = 0;
int rowHeight = getRowContentHeight();
if (rowHeight != 0) {
// Might be gone if we have range / progress but nothing else
mRootView.setVisibility(View.VISIBLE);
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(rowHeight, MeasureSpec.EXACTLY);
- measureChild(mRootView, widthMeasureSpec, heightMeasureSpec);
+ measureChildWithExactHeight(mRootView, widthMeasureSpec, rowHeight);
childWidth = mRootView.getMeasuredWidth();
} else {
@@ -310,32 +315,37 @@
if (mRangeBar != null) {
// If we're on a platform where SeekBar can't be stretched vertically, find out the
// exact size it would like to be so we can honor that in onLayout.
- int rangeMeasureSpec = sCanSpecifyLargerRangeBarHeight
- ? MeasureSpec.makeMeasureSpec(mIdealRangeHeight, MeasureSpec.EXACTLY)
- : MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- measureChild(mRangeBar, widthMeasureSpec, rangeMeasureSpec);
+ if (sCanSpecifyLargerRangeBarHeight) {
+ measureChildWithExactHeight(mRangeBar, widthMeasureSpec, mIdealRangeHeight);
+ } else {
+ measureChild(mRangeBar, widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ }
// Remember the measured height later for onLayout, since super.onMeasure will overwrite
// it.
mMeasuredRangeHeight = mRangeBar.getMeasuredHeight();
childWidth = Math.max(childWidth, mRangeBar.getMeasuredWidth());
}
- childWidth = Math.max(childWidth, getSuggestedMinimumWidth());
- setMeasuredDimension(resolveSizeAndState(childWidth, widthMeasureSpec, 0), totalHeight);
+ childWidth = Math.max(childWidth + mInsetStart + mInsetEnd, getSuggestedMinimumWidth());
+ int totalHeight = mRowContent != null ? mRowContent.getHeight(mSliceStyle, mViewPolicy) : 0;
+ setMeasuredDimension(resolveSizeAndState(childWidth, widthMeasureSpec, 0),
+ totalHeight + mInsetTop + mInsetBottom);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int insets = mInsetStart + mInsetEnd;
- mRootView.layout(0, 0, mRootView.getMeasuredWidth() + insets, getRowContentHeight());
+ int leftPadding = getPaddingLeft();
+ mRootView.layout(leftPadding, mInsetTop, mRootView.getMeasuredWidth() + leftPadding,
+ getRowContentHeight() + mInsetTop);
if (mRangeBar != null) {
// If we're on aa platform where SeekBar can't be stretched vertically, then
// mMeasuredRangeHeight can (and probably will) be smaller than mIdealRangeHeight, so we
// need to add some padding to make mRangeBar look like it's the larger size.
int verticalPadding = (mIdealRangeHeight - mMeasuredRangeHeight) / 2;
- int top = getRowContentHeight() + verticalPadding;
+ int top = getRowContentHeight() + verticalPadding + mInsetTop;
int bottom = top + mMeasuredRangeHeight;
- mRangeBar.layout(0, top, mRangeBar.getMeasuredWidth(), bottom);
+ mRangeBar.layout(leftPadding, top, mRangeBar.getMeasuredWidth() + leftPadding, bottom);
}
}
@@ -422,12 +432,10 @@
if (mRowAction != null) {
setViewClickable(mRootView, true);
}
+ mRangeItem = range;
if (!skipSliderUpdate) {
- determineRangeValues(range);
- addRange(range);
- } else {
- // Even if we're skipping the update, we should still update the range item
- mRangeItem = range;
+ setRangeBounds();
+ addRange();
}
return;
}
@@ -597,14 +605,7 @@
}
}
- private void determineRangeValues(SliceItem rangeItem) {
- if (rangeItem == null) {
- mRangeMinValue = 0;
- mRangeValue = 0;
- return;
- }
- mRangeItem = rangeItem;
-
+ private void setRangeBounds() {
SliceItem min = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MIN);
int minValue = 0;
if (min != null) {
@@ -612,18 +613,27 @@
}
mRangeMinValue = minValue;
- SliceItem progress = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_VALUE);
- if (progress != null) {
- mRangeValue = progress.getInt() - minValue;
+ SliceItem max = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_MAX);
+ int maxValue = 100; // TODO: This default shouldn't be hardcoded here.
+ if (max != null) {
+ maxValue = max.getInt();
}
+ mRangeMaxValue = maxValue;
+
+ SliceItem progress = SliceQuery.findSubtype(mRangeItem, FORMAT_INT, SUBTYPE_VALUE);
+ int progressValue = 0;
+ if (progress != null) {
+ progressValue = progress.getInt() - minValue;
+ }
+ mRangeValue = progressValue;
}
- private void addRange(final SliceItem range) {
+ private void addRange() {
if (mHandler == null) {
mHandler = new Handler();
}
- final boolean isSeekBar = FORMAT_ACTION.equals(range.getFormat());
+ final boolean isSeekBar = FORMAT_ACTION.equals(mRangeItem.getFormat());
final ProgressBar progressBar = isSeekBar
? new SeekBar(getContext())
: new ProgressBar(getContext(), null, android.R.attr.progressBarStyleHorizontal);
@@ -632,10 +642,9 @@
DrawableCompat.setTint(progressDrawable, mTintColor);
progressBar.setProgressDrawable(progressDrawable);
}
- SliceItem max = SliceQuery.findSubtype(range, FORMAT_INT, SUBTYPE_MAX);
- if (max != null) {
- progressBar.setMax(max.getInt() - mRangeMinValue);
- }
+ // N.B. We don't use progressBar.setMin because it doesn't work properly in backcompat
+ // and/or sliders.
+ progressBar.setMax(mRangeMaxValue - mRangeMinValue);
progressBar.setProgress(mRangeValue);
progressBar.setVisibility(View.VISIBLE);
addView(progressBar);
@@ -656,46 +665,26 @@
}
seekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
}
- updateRangePadding();
- }
-
- private void updateRangePadding() {
- if (mRangeBar != null) {
- int thumbSize = mRangeBar instanceof SeekBar
- ? ((SeekBar) mRangeBar).getThumb().getIntrinsicWidth() : 0;
- int topInsetPadding = mRowContent != null
- ? mRowContent.getLineCount() > 0 ? 0 : mInsetTop
- : mInsetTop;
- // Check if the app defined inset is large enough for the drawable
- if (thumbSize != 0 && mInsetStart >= thumbSize / 2 && mInsetEnd >= thumbSize / 2) {
- // If row content has text then the top inset gets applied to mContent layout
- // not the range layout.
- mRangeBar.setPadding(mInsetStart, topInsetPadding, mInsetEnd, mInsetBottom);
- } else {
- // App defined inset not bug enough; we need to apply one
- int thumbPadding = thumbSize != 0 ? thumbSize / 2 : 0;
- mRangeBar.setPadding(mInsetStart + thumbPadding, topInsetPadding,
- mInsetEnd + thumbPadding, mInsetBottom);
- }
- }
}
void sendSliderValue() {
- if (mRangeItem != null) {
- try {
- mLastSentRangeUpdate = System.currentTimeMillis();
- mRangeItem.fireAction(getContext(),
- new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_RANGE_VALUE, mRangeValue));
- if (mObserver != null) {
- EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SLIDER, ROW_TYPE_SLIDER,
- mRowIndex);
- info.state = mRangeValue;
- mObserver.onSliceAction(info, mRangeItem);
- }
- } catch (CanceledException e) {
- Log.e(TAG, "PendingIntent for slice cannot be sent", e);
+ if (mRangeItem == null) {
+ return;
+ }
+
+ try {
+ mLastSentRangeUpdate = System.currentTimeMillis();
+ mRangeItem.fireAction(getContext(),
+ new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ .putExtra(EXTRA_RANGE_VALUE, mRangeValue));
+ if (mObserver != null) {
+ EventInfo info = new EventInfo(getMode(), ACTION_TYPE_SLIDER, ROW_TYPE_SLIDER,
+ mRowIndex);
+ info.state = mRangeValue;
+ mObserver.onSliceAction(info, mRangeItem);
}
+ } catch (CanceledException e) {
+ Log.e(TAG, "PendingIntent for slice cannot be sent", e);
}
}
@@ -863,6 +852,11 @@
try {
mShowActionSpinner =
mRowAction.getActionItem().fireActionInternal(getContext(), null);
+ if (mObserver != null) {
+ EventInfo info = new EventInfo(getMode(), EventInfo.ACTION_TYPE_CONTENT,
+ EventInfo.ROW_TYPE_LIST, mRowIndex);
+ mObserver.onSliceAction(info, mRowAction.getSliceItem());
+ }
if (mShowActionSpinner && mLoadingListener != null) {
mLoadingListener.onSliceActionLoading(mRowAction.getSliceItem(), mRowIndex);
mLoadingActions.add(mRowAction.getSliceItem());
@@ -916,6 +910,7 @@
mRangeHasPendingUpdate = false;
mRangeItem = null;
mRangeMinValue = 0;
+ mRangeMaxValue = 0;
mRangeValue = 0;
mLastSentRangeUpdate = 0;
mHandler = null;
diff --git a/slices/view/src/main/java/androidx/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
index 54060f6..1e1c45d 100644
--- a/slices/view/src/main/java/androidx/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/slice/widget/SliceView.java
@@ -18,6 +18,9 @@
import static android.app.slice.Slice.SUBTYPE_COLOR;
import static android.app.slice.SliceItem.FORMAT_INT;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
import android.app.PendingIntent;
import android.content.Context;
@@ -227,10 +230,7 @@
mTouchSlopSquared = slop * slop;
mHandler = new Handler();
- mCurrentView.setInsets(getPaddingStart(), getPaddingTop(), getPaddingEnd(),
- getPaddingBottom());
setClipToPadding(false);
-
super.setOnClickListener(this);
}
@@ -344,33 +344,25 @@
}
}
- private int getHeightForMode(int maxHeight) {
- if (mListContent == null || !mListContent.isValid()) {
- return 0;
- }
- int mode = getMode();
- if (mode == MODE_SHORTCUT) {
- return mShortcutSize;
- }
- if (maxHeight > 0 && maxHeight < mSliceStyle.getRowMaxHeight()) {
- if (maxHeight <= mMinTemplateHeight) {
- maxHeight = mMinTemplateHeight;
+ private void configureViewPolicy(int maxHeight) {
+ if (mListContent != null && mListContent.isValid() && getMode() != MODE_SHORTCUT) {
+ if (maxHeight > 0 && maxHeight < mSliceStyle.getRowMaxHeight()) {
+ if (maxHeight <= mMinTemplateHeight) {
+ maxHeight = mMinTemplateHeight;
+ }
+ mViewPolicy.setMaxSmallHeight(maxHeight);
+ } else {
+ mViewPolicy.setMaxSmallHeight(0);
}
- mViewPolicy.setMaxSmallHeight(maxHeight);
- } else {
- mViewPolicy.setMaxSmallHeight(0);
+ mViewPolicy.setMaxHeight(maxHeight);
}
- mViewPolicy.setMaxHeight(maxHeight);
- return mListContent.getHeight(mSliceStyle, mViewPolicy);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
- int childWidth = MeasureSpec.getSize(widthMeasureSpec);
if (MODE_SHORTCUT == getMode()) {
// TODO: consider scaling the shortcut to fit if too small
- childWidth = mShortcutSize;
width = mShortcutSize + getPaddingLeft() + getPaddingRight();
}
final int actionHeight = mActionRow.getVisibility() != View.GONE
@@ -380,60 +372,60 @@
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
LayoutParams lp = getLayoutParams();
final int maxHeight = (lp != null && lp.height == LayoutParams.WRAP_CONTENT)
- || heightMode == MeasureSpec.UNSPECIFIED
+ || heightMode == UNSPECIFIED
? -1 // no max, be default sizes
: heightAvailable;
- final int sliceHeight = getHeightForMode(maxHeight);
+ configureViewPolicy(maxHeight);
// Remove the padding from our available height
- int height = heightAvailable - getPaddingTop() - getPaddingBottom();
- if (heightAvailable >= sliceHeight + actionHeight
- || heightMode == MeasureSpec.UNSPECIFIED) {
- // Available space is larger than the slice or we be what we want
- if (heightMode == MeasureSpec.EXACTLY) {
- height = Math.min(sliceHeight, height);
- } else {
- height = sliceHeight;
- }
- } else {
- // Not enough space available for slice in current mode
- if (getMode() == MODE_LARGE
- && heightAvailable >= mLargeHeight + actionHeight) {
- height = sliceHeight;
+ int childrenHeight = heightAvailable - getPaddingTop() - getPaddingBottom();
+
+ // never change the height if set to exactly
+ if (heightMode != EXACTLY) {
+ if (mListContent == null || !mListContent.isValid()) {
+ childrenHeight = actionHeight;
} else if (getMode() == MODE_SHORTCUT) {
- // TODO: consider scaling the shortcut to fit if too small
- height = mShortcutSize;
- } else if (height <= mMinTemplateHeight) {
- height = mMinTemplateHeight;
+ // No compromise in case of shortcut
+ childrenHeight = mShortcutSize + actionHeight;
+ } else {
+ int requiredHeight =
+ mListContent.getHeight(mSliceStyle, mViewPolicy) + actionHeight;
+ if (childrenHeight > requiredHeight || heightMode == UNSPECIFIED) {
+ // Available space is larger than what the slice wants
+ childrenHeight = requiredHeight;
+ } else {
+ // Not enough space available for slice in current mode
+ if (getMode() == MODE_LARGE
+ && childrenHeight >= mLargeHeight + actionHeight) {
+ childrenHeight = mLargeHeight + actionHeight;
+ } else if (childrenHeight <= mMinTemplateHeight) {
+ childrenHeight = mMinTemplateHeight;
+ }
+ }
}
}
- int childHeight = height + getPaddingTop() + getPaddingBottom();
- childWidth = childWidth + getPaddingLeft() + getPaddingRight();
- int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
- int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
- measureChild(mCurrentView, childWidthMeasureSpec, childHeightMeasureSpec);
+ // Measure directly instead of calling measureChild as the later substracts padding
+ // from the provided size
+ int childWidthSpec = makeMeasureSpec(width, EXACTLY);
+ int actionRowHeight = actionHeight > 0 ? (actionHeight + getPaddingBottom()) : 0;
+ mActionRow.measure(childWidthSpec, makeMeasureSpec(actionRowHeight, EXACTLY));
- int actionPaddedHeight = actionHeight + getPaddingTop() + getPaddingBottom();
- int actionHeightSpec = MeasureSpec.makeMeasureSpec(actionPaddedHeight, MeasureSpec.EXACTLY);
- measureChild(mActionRow, childWidthMeasureSpec, actionHeightSpec);
-
- // Total height should include action row and our padding
- height += actionHeight + getPaddingTop() + getPaddingBottom();
- setMeasuredDimension(width, height);
+ // Include the bottom padding for currentView only if action row is invisible
+ int currentViewHeight = childrenHeight + getPaddingTop()
+ + (actionHeight > 0 ? 0 : getPaddingBottom());
+ mCurrentView.measure(childWidthSpec, makeMeasureSpec(currentViewHeight, EXACTLY));
+ setMeasuredDimension(width,
+ mCurrentView.getMeasuredHeight() + mActionRow.getMeasuredHeight());
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View v = mCurrentView;
- final int left = 0;
- final int top = 0;
- v.layout(left, top, left + v.getMeasuredWidth() + getPaddingRight() + getPaddingLeft(),
- top + v.getMeasuredHeight());
+ v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
if (mActionRow.getVisibility() != View.GONE) {
- mActionRow.layout(left,
- top + v.getMeasuredHeight(),
- left + mActionRow.getMeasuredWidth(),
- top + v.getMeasuredHeight() + mActionRow.getMeasuredHeight());
+ int top = v.getMeasuredHeight();
+ mActionRow.layout(0, top, mActionRow.getMeasuredWidth(),
+ mActionRow.getMeasuredHeight() + top);
}
}
@@ -713,8 +705,6 @@
// If the view changes we should apply any configurations to it
if (newView) {
mCurrentView.setPolicy(mViewPolicy);
- mCurrentView.setInsets(getPaddingStart(), getPaddingTop(), getPaddingEnd(),
- getPaddingBottom());
applyConfigurations();
if (mListContent != null && mListContent.isValid()) {
mCurrentView.setSliceContent(mListContent);
@@ -741,6 +731,8 @@
// No actions, hide the row, clear out the view
mActionRow.setVisibility(View.GONE);
mCurrentView.setSliceActions(null);
+ mCurrentView.setInsets(getPaddingStart(), getPaddingTop(), getPaddingEnd(),
+ getPaddingBottom());
return;
}
// Sort actions based on priority and set them in action rows.
@@ -750,11 +742,20 @@
// Show in action row if available
mActionRow.setActions(sortedActions, getTintColor());
mActionRow.setVisibility(View.VISIBLE);
+
// Hide them on the template
mCurrentView.setSliceActions(null);
+
+ mCurrentView.setInsets(getPaddingStart(), getPaddingTop(), getPaddingEnd(), 0);
+ mActionRow.setPaddingRelative(getPaddingStart(), 0, getPaddingEnd(),
+ getPaddingBottom());
+
} else {
// Otherwise set them on the template
mCurrentView.setSliceActions(sortedActions);
+ mCurrentView.setInsets(getPaddingStart(), getPaddingTop(), getPaddingEnd(),
+ getPaddingBottom());
+
mActionRow.setVisibility(View.GONE);
}
}
diff --git a/studiow b/studiow
index 2ba8f59..ddff3a9 100755
--- a/studiow
+++ b/studiow
@@ -6,9 +6,10 @@
function getStudioUrl() {
- version="3.4.0.12"
- ideaMajorVersion="183"
- buildNumber="5256591"
+ propertiesFile="${scriptDir}/buildSrc/studio_versions.properties"
+ version="$(grep "studio_version=" ${propertiesFile} | sed 's/[^=]*=//')"
+ ideaMajorVersion="$(grep "idea_major_version=" ${propertiesFile} | sed 's/[^=]*=//')"
+ buildNumber="$(grep "studio_build_number=" ${propertiesFile} | sed 's/[^=]*=//')"
osName="$1"
studioUrl="https://dl.google.com/dl/android/studio/ide-zips/${version}/android-studio-ide-${ideaMajorVersion}.${buildNumber}-${osName}.zip"
echo "${studioUrl}"
diff --git a/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
index 22d0edd..615d1a2 100644
--- a/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
+++ b/testutils/src/main/java/androidx/testutils/FragmentActivityUtils.java
@@ -90,8 +90,8 @@
ActivityTestRule<? extends RecreatedActivity> rule, final T activity)
throws InterruptedException {
// Now switch the orientation
- RecreatedActivity.sResumed = new CountDownLatch(1);
- RecreatedActivity.sDestroyed = new CountDownLatch(1);
+ RecreatedActivity.setResumedLatch(new CountDownLatch(1));
+ RecreatedActivity.setDestroyedLatch(new CountDownLatch(1));
runOnUiThreadRethrow(rule, new Runnable() {
@Override
@@ -99,9 +99,9 @@
activity.recreate();
}
});
- assertTrue(RecreatedActivity.sResumed.await(1, TimeUnit.SECONDS));
- assertTrue(RecreatedActivity.sDestroyed.await(1, TimeUnit.SECONDS));
- T newActivity = (T) RecreatedActivity.sActivity;
+ assertTrue(RecreatedActivity.getResumedLatch().await(1, TimeUnit.SECONDS));
+ assertTrue(RecreatedActivity.getDestroyedLatch().await(1, TimeUnit.SECONDS));
+ T newActivity = (T) RecreatedActivity.getActivity();
waitForExecution(rule);
diff --git a/testutils/src/main/java/androidx/testutils/RecreatedActivity.java b/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
deleted file mode 100644
index 34326a48..0000000
--- a/testutils/src/main/java/androidx/testutils/RecreatedActivity.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.testutils;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-import androidx.test.rule.ActivityTestRule;
-
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Extension of {@link FragmentActivity} that keeps track of when it is recreated.
- * In order to use this class, have your activity extend it and call
- * {@link FragmentActivityUtils#recreateActivity(ActivityTestRule, RecreatedActivity)} API.
- */
-public class RecreatedActivity extends FragmentActivity {
- // These must be cleared after each test using clearState()
- public static RecreatedActivity sActivity;
- public static CountDownLatch sResumed;
- public static CountDownLatch sDestroyed;
-
- static void clearState() {
- sActivity = null;
- sResumed = null;
- sDestroyed = null;
- }
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- sActivity = this;
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (sResumed != null) {
- sResumed.countDown();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (sDestroyed != null) {
- sDestroyed.countDown();
- }
- }
-}
diff --git a/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt
new file mode 100644
index 0000000..1463395
--- /dev/null
+++ b/testutils/src/main/java/androidx/testutils/RecreatedActivity.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.testutils
+
+import android.os.Bundle
+import androidx.annotation.LayoutRes
+import androidx.fragment.app.FragmentActivity
+import java.util.concurrent.CountDownLatch
+
+/**
+ * Extension of [FragmentActivity] that keeps track of when it is recreated.
+ * In order to use this class, have your activity extend it and call
+ * [FragmentActivityUtils.recreateActivity] API.
+ */
+open class RecreatedActivity(
+ @LayoutRes contentLayoutId: Int = 0
+) : FragmentActivity(contentLayoutId) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ activity = this
+ }
+
+ override fun onResume() {
+ super.onResume()
+ resumedLatch?.countDown()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ destroyedLatch?.countDown()
+ }
+
+ companion object {
+ // These must be cleared after each test using clearState()
+ @JvmStatic
+ var activity: RecreatedActivity? = null
+ @JvmStatic
+ var resumedLatch: CountDownLatch? = null
+ @JvmStatic
+ var destroyedLatch: CountDownLatch? = null
+
+ @JvmStatic
+ fun clearState() {
+ activity = null
+ resumedLatch = null
+ destroyedLatch = null
+ }
+ }
+}
diff --git a/textclassifier/api/1.0.0-alpha03.txt b/textclassifier/api/1.0.0-alpha03.txt
index 043c858..c37c7d2 100644
--- a/textclassifier/api/1.0.0-alpha03.txt
+++ b/textclassifier/api/1.0.0-alpha03.txt
@@ -1,6 +1,10 @@
// Signature format: 3.0
package androidx.textclassifier {
+ public final class ExtrasUtils {
+ method public static java.util.Locale? getTopLanguage(android.content.Intent?);
+ }
+
public final class TextClassification {
method public static androidx.textclassifier.TextClassification createFromBundle(android.os.Bundle);
method public java.util.List<androidx.core.app.RemoteActionCompat> getActions();
diff --git a/textclassifier/api/current.txt b/textclassifier/api/current.txt
index 043c858..c37c7d2 100644
--- a/textclassifier/api/current.txt
+++ b/textclassifier/api/current.txt
@@ -1,6 +1,10 @@
// Signature format: 3.0
package androidx.textclassifier {
+ public final class ExtrasUtils {
+ method public static java.util.Locale? getTopLanguage(android.content.Intent?);
+ }
+
public final class TextClassification {
method public static androidx.textclassifier.TextClassification createFromBundle(android.os.Bundle);
method public java.util.List<androidx.core.app.RemoteActionCompat> getActions();
diff --git a/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java b/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java
new file mode 100644
index 0000000..17511e0
--- /dev/null
+++ b/textclassifier/src/androidTest/java/androidx/textclassifier/ExtrasUtilsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link ExtrasUtils}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtrasUtilsTest {
+
+ @Test
+ public void testGetTopLanguage() {
+ final Intent intent = ExtrasUtils.buildFakeTextClassifierIntent("ja", "en");
+ assertThat(ExtrasUtils.getTopLanguage(intent).getLanguage()).isEqualTo("ja");
+ }
+
+ @Test
+ public void testGetTopLanguage_differentLanguage() {
+ final Intent intent = ExtrasUtils.buildFakeTextClassifierIntent("de");
+ assertThat(ExtrasUtils.getTopLanguage(intent).getLanguage()).isEqualTo("de");
+ }
+
+ @Test
+ public void testGetTopLanguage_nullLanguageBundle() {
+ assertThat(ExtrasUtils.getTopLanguage(new Intent())).isNull();
+ }
+
+ @Test
+ public void testGetTopLanguage_null() {
+ assertThat(ExtrasUtils.getTopLanguage(null)).isNull();
+ }
+}
diff --git a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
index 1906b4c..52704ec 100644
--- a/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
+++ b/textclassifier/src/main/java/androidx/textclassifier/BundleUtils.java
@@ -25,6 +25,7 @@
import androidx.collection.ArrayMap;
import androidx.core.app.RemoteActionCompat;
import androidx.core.os.LocaleListCompat;
+import androidx.versionedparcelable.ParcelUtils;
import java.util.ArrayList;
import java.util.List;
@@ -80,30 +81,13 @@
/** Serializes a list of actions to a bundle, or clears it if null is passed. */
static void putRemoteActionList(
@NonNull Bundle bundle, @NonNull String key,
- @Nullable List<RemoteActionCompat> actions) {
- if (actions == null) {
- bundle.remove(key);
- return;
- }
- final ArrayList<Bundle> actionBundles = new ArrayList<>(actions.size());
- for (RemoteActionCompat action : actions) {
- actionBundles.add(action.toBundle());
- }
- bundle.putParcelableArrayList(key, actionBundles);
+ @NonNull List<RemoteActionCompat> actions) {
+ ParcelUtils.putVersionedParcelableList(bundle, key, actions);
}
- /** @throws IllegalArgumentException if key can't be found in the bundle */
static List<RemoteActionCompat> getRemoteActionListOrThrow(
@NonNull Bundle bundle, @NonNull String key) {
- final List<Bundle> actionBundles = bundle.getParcelableArrayList(key);
- if (actionBundles == null) {
- throw new IllegalArgumentException("Missing " + key);
- }
- final List<RemoteActionCompat> actions = new ArrayList<>(actionBundles.size());
- for (Bundle actionBundle : actionBundles) {
- actions.add(RemoteActionCompat.createFromBundle(actionBundle));
- }
- return actions;
+ return ParcelUtils.getVersionedParcelableList(bundle, key);
}
/** Serializes a list of TextLinks to a bundle, or clears it if null is passed. */
diff --git a/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java b/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java
new file mode 100644
index 0000000..26c45c6
--- /dev/null
+++ b/textclassifier/src/main/java/androidx/textclassifier/ExtrasUtils.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.textclassifier;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.os.LocaleListCompat;
+
+import java.util.Locale;
+
+/**
+ * Utilities for inserting/retrieving data into/from textclassifier related results and intents.
+ */
+public final class ExtrasUtils {
+
+ private static final String TAG = "ExtrasUtils";
+
+ private static final String EXTRA_FROM_TEXT_CLASSIFIER =
+ "android.view.textclassifier.extra.FROM_TEXT_CLASSIFIER";
+ private static final String ENTITY_TYPE = "entity-type";
+ private static final String SCORE = "score";
+ private static final String TEXT_LANGUAGES = "text-languages";
+
+ private ExtrasUtils() {}
+
+ /**
+ * Returns the highest scoring language found in the textclassifier extras in the intent.
+ * This may return null if the data could not be found.
+ *
+ * @param intent the intent used to start the activity.
+ * @see android.app.Activity#getIntent()
+ */
+ @Nullable
+ public static Locale getTopLanguage(@Nullable Intent intent) {
+ try {
+ // NOTE: This is (and should be) a copy of the related platform code.
+ // It is hard to test this code returns something on a given platform because we can't
+ // guarantee the TextClassifier implementation that will be used to send the intent.
+ // Depend on the platform tests instead and avoid this code running out of sync with
+ // what is expected of each platform. Note that the code may differ from platform to
+ // platform but that will be a bad idea as it will be hard to manage.
+ // TODO: Include a "put" counterpart of this method so that other TextClassifier
+ // implementations may use it to put language data into the generated intent in a way
+ // that this method can retrieve it.
+ if (intent == null) {
+ return null;
+ }
+ final Bundle tcBundle = intent.getBundleExtra(EXTRA_FROM_TEXT_CLASSIFIER);
+ if (tcBundle == null) {
+ return null;
+ }
+ final Bundle textLanguagesExtra = tcBundle.getBundle(TEXT_LANGUAGES);
+ if (textLanguagesExtra == null) {
+ return null;
+ }
+ final String[] languages = textLanguagesExtra.getStringArray(ENTITY_TYPE);
+ final float[] scores = textLanguagesExtra.getFloatArray(SCORE);
+ if (languages == null || scores == null
+ || languages.length == 0 || languages.length != scores.length) {
+ return null;
+ }
+ int highestScoringIndex = 0;
+ for (int i = 1; i < languages.length; i++) {
+ if (scores[highestScoringIndex] < scores[i]) {
+ highestScoringIndex = i;
+ }
+ }
+ final LocaleListCompat localeList =
+ LocaleListCompat.forLanguageTags(languages[highestScoringIndex]);
+ return localeList.isEmpty() ? null : localeList.get(0);
+ } catch (Throwable t) {
+ // Prevent this method from crashing the process.
+ Log.e(TAG, "Error retrieving language information from textclassifier intent", t);
+ return null;
+ }
+ }
+
+ /**
+ * Returns a fake TextClassifier generated intent for testing purposes.
+ * @param languages ordered list of languages for the classified text
+ */
+ @VisibleForTesting
+ static Intent buildFakeTextClassifierIntent(String... languages) {
+ final float[] scores = new float[languages.length];
+ float scoresLeft = 1f;
+ for (int i = 0; i < scores.length; i++) {
+ scores[i] = scoresLeft /= 2;
+ }
+ final Bundle textLanguagesExtra = new Bundle();
+ textLanguagesExtra.putStringArray(ENTITY_TYPE, languages);
+ textLanguagesExtra.putFloatArray(SCORE, scores);
+ final Bundle tcBundle = new Bundle();
+ tcBundle.putBundle(TEXT_LANGUAGES, textLanguagesExtra);
+ return new Intent(Intent.ACTION_VIEW).putExtra(EXTRA_FROM_TEXT_CLASSIFIER, tcBundle);
+ }
+}
diff --git a/transition/api/1.1.0-beta01.txt b/transition/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..86a584c
--- /dev/null
+++ b/transition/api/1.1.0-beta01.txt
@@ -0,0 +1,283 @@
+// Signature format: 3.0
+package androidx.transition {
+
+ public class ArcMotion extends androidx.transition.PathMotion {
+ ctor public ArcMotion();
+ ctor public ArcMotion(android.content.Context!, android.util.AttributeSet!);
+ method public float getMaximumAngle();
+ method public float getMinimumHorizontalAngle();
+ method public float getMinimumVerticalAngle();
+ method public android.graphics.Path! getPath(float, float, float, float);
+ method public void setMaximumAngle(float);
+ method public void setMinimumHorizontalAngle(float);
+ method public void setMinimumVerticalAngle(float);
+ }
+
+ public class AutoTransition extends androidx.transition.TransitionSet {
+ ctor public AutoTransition();
+ ctor public AutoTransition(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public class ChangeBounds extends androidx.transition.Transition {
+ ctor public ChangeBounds();
+ ctor public ChangeBounds(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public boolean getResizeClip();
+ method public void setResizeClip(boolean);
+ }
+
+ public class ChangeClipBounds extends androidx.transition.Transition {
+ ctor public ChangeClipBounds();
+ ctor public ChangeClipBounds(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeImageTransform extends androidx.transition.Transition {
+ ctor public ChangeImageTransform();
+ ctor public ChangeImageTransform(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeScroll extends androidx.transition.Transition {
+ ctor public ChangeScroll();
+ ctor public ChangeScroll(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ }
+
+ public class ChangeTransform extends androidx.transition.Transition {
+ ctor public ChangeTransform();
+ ctor public ChangeTransform(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public boolean getReparent();
+ method public boolean getReparentWithOverlay();
+ method public void setReparent(boolean);
+ method public void setReparentWithOverlay(boolean);
+ }
+
+ public class CircularPropagation extends androidx.transition.VisibilityPropagation {
+ ctor public CircularPropagation();
+ method public long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setPropagationSpeed(float);
+ }
+
+ public class Explode extends androidx.transition.Visibility {
+ ctor public Explode();
+ ctor public Explode(android.content.Context!, android.util.AttributeSet!);
+ }
+
+ public class Fade extends androidx.transition.Visibility {
+ ctor public Fade(int);
+ ctor public Fade();
+ ctor public Fade(android.content.Context!, android.util.AttributeSet!);
+ field public static final int IN = 1; // 0x1
+ field public static final int OUT = 2; // 0x2
+ }
+
+ public abstract class PathMotion {
+ ctor public PathMotion();
+ ctor public PathMotion(android.content.Context!, android.util.AttributeSet!);
+ method public abstract android.graphics.Path! getPath(float, float, float, float);
+ }
+
+ public class PatternPathMotion extends androidx.transition.PathMotion {
+ ctor public PatternPathMotion();
+ ctor public PatternPathMotion(android.content.Context!, android.util.AttributeSet!);
+ ctor public PatternPathMotion(android.graphics.Path!);
+ method public android.graphics.Path! getPath(float, float, float, float);
+ method public android.graphics.Path! getPatternPath();
+ method public void setPatternPath(android.graphics.Path!);
+ }
+
+ public class Scene {
+ ctor public Scene(android.view.ViewGroup);
+ ctor public Scene(android.view.ViewGroup, android.view.View);
+ method public void enter();
+ method public void exit();
+ method public static androidx.transition.Scene? getCurrentScene(android.view.ViewGroup);
+ method public static androidx.transition.Scene getSceneForLayout(android.view.ViewGroup, @LayoutRes int, android.content.Context);
+ method public android.view.ViewGroup getSceneRoot();
+ method public void setEnterAction(Runnable?);
+ method public void setExitAction(Runnable?);
+ }
+
+ public class SidePropagation extends androidx.transition.VisibilityPropagation {
+ ctor public SidePropagation();
+ method public long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setPropagationSpeed(float);
+ method public void setSide(int);
+ }
+
+ public class Slide extends androidx.transition.Visibility {
+ ctor public Slide();
+ ctor public Slide(int);
+ ctor public Slide(android.content.Context!, android.util.AttributeSet!);
+ method public int getSlideEdge();
+ method public void setSlideEdge(int);
+ }
+
+ public abstract class Transition implements java.lang.Cloneable {
+ ctor public Transition();
+ ctor public Transition(android.content.Context!, android.util.AttributeSet!);
+ method public androidx.transition.Transition addListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.Transition addTarget(android.view.View);
+ method public androidx.transition.Transition addTarget(@IdRes int);
+ method public androidx.transition.Transition addTarget(String);
+ method public androidx.transition.Transition addTarget(Class);
+ method public abstract void captureEndValues(androidx.transition.TransitionValues);
+ method public abstract void captureStartValues(androidx.transition.TransitionValues);
+ method public androidx.transition.Transition! clone();
+ method public android.animation.Animator? createAnimator(android.view.ViewGroup, androidx.transition.TransitionValues?, androidx.transition.TransitionValues?);
+ method public androidx.transition.Transition excludeChildren(android.view.View, boolean);
+ method public androidx.transition.Transition excludeChildren(@IdRes int, boolean);
+ method public androidx.transition.Transition excludeChildren(Class, boolean);
+ method public androidx.transition.Transition excludeTarget(android.view.View, boolean);
+ method public androidx.transition.Transition excludeTarget(@IdRes int, boolean);
+ method public androidx.transition.Transition excludeTarget(String, boolean);
+ method public androidx.transition.Transition excludeTarget(Class, boolean);
+ method public long getDuration();
+ method public android.graphics.Rect? getEpicenter();
+ method public androidx.transition.Transition.EpicenterCallback? getEpicenterCallback();
+ method public android.animation.TimeInterpolator? getInterpolator();
+ method public String getName();
+ method public androidx.transition.PathMotion getPathMotion();
+ method public androidx.transition.TransitionPropagation? getPropagation();
+ method public long getStartDelay();
+ method public java.util.List<java.lang.Integer> getTargetIds();
+ method public java.util.List<java.lang.String>? getTargetNames();
+ method public java.util.List<java.lang.Class>? getTargetTypes();
+ method public java.util.List<android.view.View> getTargets();
+ method public String[]? getTransitionProperties();
+ method public androidx.transition.TransitionValues? getTransitionValues(android.view.View, boolean);
+ method public boolean isTransitionRequired(androidx.transition.TransitionValues?, androidx.transition.TransitionValues?);
+ method public androidx.transition.Transition removeListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.Transition removeTarget(android.view.View);
+ method public androidx.transition.Transition removeTarget(@IdRes int);
+ method public androidx.transition.Transition removeTarget(String);
+ method public androidx.transition.Transition removeTarget(Class);
+ method public androidx.transition.Transition setDuration(long);
+ method public void setEpicenterCallback(androidx.transition.Transition.EpicenterCallback?);
+ method public androidx.transition.Transition setInterpolator(android.animation.TimeInterpolator?);
+ method public void setMatchOrder(int...!);
+ method public void setPathMotion(androidx.transition.PathMotion?);
+ method public void setPropagation(androidx.transition.TransitionPropagation?);
+ method public androidx.transition.Transition setStartDelay(long);
+ field public static final int MATCH_ID = 3; // 0x3
+ field public static final int MATCH_INSTANCE = 1; // 0x1
+ field public static final int MATCH_ITEM_ID = 4; // 0x4
+ field public static final int MATCH_NAME = 2; // 0x2
+ }
+
+ public abstract static class Transition.EpicenterCallback {
+ ctor public Transition.EpicenterCallback();
+ method public abstract android.graphics.Rect! onGetEpicenter(androidx.transition.Transition);
+ }
+
+ public static interface Transition.TransitionListener {
+ method public void onTransitionCancel(androidx.transition.Transition);
+ method public void onTransitionEnd(androidx.transition.Transition);
+ method public void onTransitionPause(androidx.transition.Transition);
+ method public void onTransitionResume(androidx.transition.Transition);
+ method public void onTransitionStart(androidx.transition.Transition);
+ }
+
+ public class TransitionInflater {
+ method public static androidx.transition.TransitionInflater! from(android.content.Context!);
+ method public androidx.transition.Transition! inflateTransition(int);
+ method public androidx.transition.TransitionManager! inflateTransitionManager(int, android.view.ViewGroup!);
+ }
+
+ public class TransitionListenerAdapter implements androidx.transition.Transition.TransitionListener {
+ ctor public TransitionListenerAdapter();
+ method public void onTransitionCancel(androidx.transition.Transition);
+ method public void onTransitionEnd(androidx.transition.Transition);
+ method public void onTransitionPause(androidx.transition.Transition);
+ method public void onTransitionResume(androidx.transition.Transition);
+ method public void onTransitionStart(androidx.transition.Transition);
+ }
+
+ public class TransitionManager {
+ ctor public TransitionManager();
+ method public static void beginDelayedTransition(android.view.ViewGroup);
+ method public static void beginDelayedTransition(android.view.ViewGroup, androidx.transition.Transition?);
+ method public static void endTransitions(android.view.ViewGroup!);
+ method public static void go(androidx.transition.Scene);
+ method public static void go(androidx.transition.Scene, androidx.transition.Transition?);
+ method public void setTransition(androidx.transition.Scene, androidx.transition.Transition?);
+ method public void setTransition(androidx.transition.Scene, androidx.transition.Scene, androidx.transition.Transition?);
+ method public void transitionTo(androidx.transition.Scene);
+ }
+
+ public abstract class TransitionPropagation {
+ ctor public TransitionPropagation();
+ method public abstract void captureValues(androidx.transition.TransitionValues!);
+ method public abstract String[]! getPropagationProperties();
+ method public abstract long getStartDelay(android.view.ViewGroup!, androidx.transition.Transition!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ }
+
+ public class TransitionSet extends androidx.transition.Transition {
+ ctor public TransitionSet();
+ ctor public TransitionSet(android.content.Context!, android.util.AttributeSet!);
+ method public androidx.transition.TransitionSet addListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.TransitionSet addTarget(android.view.View);
+ method public androidx.transition.TransitionSet addTarget(@IdRes int);
+ method public androidx.transition.TransitionSet addTarget(String);
+ method public androidx.transition.TransitionSet addTarget(Class);
+ method public androidx.transition.TransitionSet addTransition(androidx.transition.Transition);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public int getOrdering();
+ method public androidx.transition.Transition? getTransitionAt(int);
+ method public int getTransitionCount();
+ method public androidx.transition.TransitionSet removeListener(androidx.transition.Transition.TransitionListener);
+ method public androidx.transition.TransitionSet removeTarget(@IdRes int);
+ method public androidx.transition.TransitionSet removeTarget(android.view.View);
+ method public androidx.transition.TransitionSet removeTarget(Class);
+ method public androidx.transition.TransitionSet removeTarget(String);
+ method public androidx.transition.TransitionSet removeTransition(androidx.transition.Transition);
+ method public androidx.transition.TransitionSet setDuration(long);
+ method public androidx.transition.TransitionSet setInterpolator(android.animation.TimeInterpolator?);
+ method public androidx.transition.TransitionSet setOrdering(int);
+ method public androidx.transition.TransitionSet setStartDelay(long);
+ field public static final int ORDERING_SEQUENTIAL = 1; // 0x1
+ field public static final int ORDERING_TOGETHER = 0; // 0x0
+ }
+
+ public class TransitionValues {
+ ctor @Deprecated public TransitionValues();
+ ctor public TransitionValues(android.view.View);
+ field public final java.util.Map<java.lang.String,java.lang.Object>! values;
+ field public android.view.View! view;
+ }
+
+ public abstract class Visibility extends androidx.transition.Transition {
+ ctor public Visibility();
+ ctor public Visibility(android.content.Context!, android.util.AttributeSet!);
+ method public void captureEndValues(androidx.transition.TransitionValues);
+ method public void captureStartValues(androidx.transition.TransitionValues);
+ method public int getMode();
+ method public boolean isVisible(androidx.transition.TransitionValues!);
+ method public android.animation.Animator! onAppear(android.view.ViewGroup!, androidx.transition.TransitionValues!, int, androidx.transition.TransitionValues!, int);
+ method public android.animation.Animator! onAppear(android.view.ViewGroup!, android.view.View!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public android.animation.Animator! onDisappear(android.view.ViewGroup!, androidx.transition.TransitionValues!, int, androidx.transition.TransitionValues!, int);
+ method public android.animation.Animator! onDisappear(android.view.ViewGroup!, android.view.View!, androidx.transition.TransitionValues!, androidx.transition.TransitionValues!);
+ method public void setMode(int);
+ field public static final int MODE_IN = 1; // 0x1
+ field public static final int MODE_OUT = 2; // 0x2
+ }
+
+ public abstract class VisibilityPropagation extends androidx.transition.TransitionPropagation {
+ ctor public VisibilityPropagation();
+ method public void captureValues(androidx.transition.TransitionValues!);
+ method public String[]! getPropagationProperties();
+ method public int getViewVisibility(androidx.transition.TransitionValues!);
+ method public int getViewX(androidx.transition.TransitionValues!);
+ method public int getViewY(androidx.transition.TransitionValues!);
+ }
+
+}
+
diff --git a/transition/api/res-1.1.0-beta01.txt b/transition/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/transition/api/res-1.1.0-beta01.txt
diff --git a/transition/build.gradle b/transition/build.gradle
index 04e5dfa..7b37abe 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -1,16 +1,17 @@
-import static androidx.build.dependencies.DependenciesKt.*
import androidx.build.LibraryGroups
import androidx.build.LibraryVersions
+import static androidx.build.dependencies.DependenciesKt.*
+
plugins {
id("SupportAndroidLibraryPlugin")
}
dependencies {
api(project(":annotation"))
- api(project(":core"))
- implementation(project(":collection"))
- compileOnly project(':fragment')
+ api("androidx.core:core:1.0.1")
+ implementation("androidx.collection:collection:1.0.0")
+ compileOnly("androidx.fragment:fragment:1.0.0")
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
diff --git a/transition/src/main/java/androidx/transition/ArcMotion.java b/transition/src/main/java/androidx/transition/ArcMotion.java
index 7953389..3f97d53 100644
--- a/transition/src/main/java/androidx/transition/ArcMotion.java
+++ b/transition/src/main/java/androidx/transition/ArcMotion.java
@@ -16,6 +16,7 @@
package androidx.transition;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Path;
@@ -64,6 +65,8 @@
public ArcMotion() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public ArcMotion(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.ARC_MOTION);
diff --git a/transition/src/main/java/androidx/transition/ChangeBounds.java b/transition/src/main/java/androidx/transition/ChangeBounds.java
index fb621ed..79fefdf 100644
--- a/transition/src/main/java/androidx/transition/ChangeBounds.java
+++ b/transition/src/main/java/androidx/transition/ChangeBounds.java
@@ -21,6 +21,7 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -169,6 +170,8 @@
public ChangeBounds() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public ChangeBounds(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/transition/src/main/java/androidx/transition/ChangeTransform.java b/transition/src/main/java/androidx/transition/ChangeTransform.java
index dd7acbf..426ac64 100644
--- a/transition/src/main/java/androidx/transition/ChangeTransform.java
+++ b/transition/src/main/java/androidx/transition/ChangeTransform.java
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
@@ -107,6 +108,8 @@
public ChangeTransform() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public ChangeTransform(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.CHANGE_TRANSFORM);
diff --git a/transition/src/main/java/androidx/transition/Fade.java b/transition/src/main/java/androidx/transition/Fade.java
index 9c4512f..e048166 100644
--- a/transition/src/main/java/androidx/transition/Fade.java
+++ b/transition/src/main/java/androidx/transition/Fade.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -97,6 +98,8 @@
public Fade() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public Fade(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.FADE);
diff --git a/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index 41c3e69..f37b133 100644
--- a/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -18,6 +18,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
@@ -36,6 +37,8 @@
// This is instantiated in androidx.fragment.app.FragmentTransition
@SuppressWarnings("unused")
@RestrictTo(LIBRARY_GROUP_PREFIX)
+@SuppressLint("RestrictedApi") // remove once fragment lib would be released with the new
+// LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public class FragmentTransitionSupport extends FragmentTransitionImpl {
@Override
diff --git a/transition/src/main/java/androidx/transition/PatternPathMotion.java b/transition/src/main/java/androidx/transition/PatternPathMotion.java
index 7f6153d..a7fd84c 100644
--- a/transition/src/main/java/androidx/transition/PatternPathMotion.java
+++ b/transition/src/main/java/androidx/transition/PatternPathMotion.java
@@ -16,6 +16,7 @@
package androidx.transition;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
@@ -55,6 +56,8 @@
mOriginalPatternPath = mPatternPath;
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public PatternPathMotion(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.PATTERN_PATH_MOTION);
try {
diff --git a/transition/src/main/java/androidx/transition/Slide.java b/transition/src/main/java/androidx/transition/Slide.java
index f4458a2..fa75a89 100644
--- a/transition/src/main/java/androidx/transition/Slide.java
+++ b/transition/src/main/java/androidx/transition/Slide.java
@@ -20,6 +20,7 @@
import android.animation.Animator;
import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
@@ -162,6 +163,8 @@
setSlideEdge(slideEdge);
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public Slide(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.SLIDE);
diff --git a/transition/src/main/java/androidx/transition/Transition.java b/transition/src/main/java/androidx/transition/Transition.java
index 285570e..7fe90fe 100644
--- a/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/src/main/java/androidx/transition/Transition.java
@@ -21,6 +21,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -271,6 +272,8 @@
* access the current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the transition.
*/
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public Transition(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION);
XmlResourceParser parser = (XmlResourceParser) attrs;
diff --git a/transition/src/main/java/androidx/transition/TransitionInflater.java b/transition/src/main/java/androidx/transition/TransitionInflater.java
index 4570fe9..d01d199 100644
--- a/transition/src/main/java/androidx/transition/TransitionInflater.java
+++ b/transition/src/main/java/androidx/transition/TransitionInflater.java
@@ -16,6 +16,7 @@
package androidx.transition;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -218,6 +219,8 @@
}
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
private void getTargetIds(XmlPullParser parser,
AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
@@ -304,6 +307,8 @@
return transitionManager;
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
private void loadTransition(AttributeSet attrs, XmlPullParser parser, ViewGroup sceneRoot,
TransitionManager transitionManager) throws Resources.NotFoundException {
diff --git a/transition/src/main/java/androidx/transition/TransitionSet.java b/transition/src/main/java/androidx/transition/TransitionSet.java
index d82b832..f0f07fc 100644
--- a/transition/src/main/java/androidx/transition/TransitionSet.java
+++ b/transition/src/main/java/androidx/transition/TransitionSet.java
@@ -19,6 +19,7 @@
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.animation.TimeInterpolator;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -109,6 +110,8 @@
public TransitionSet() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public TransitionSet(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION_SET);
diff --git a/transition/src/main/java/androidx/transition/Visibility.java b/transition/src/main/java/androidx/transition/Visibility.java
index c27d93e..ea2180eb 100644
--- a/transition/src/main/java/androidx/transition/Visibility.java
+++ b/transition/src/main/java/androidx/transition/Visibility.java
@@ -100,6 +100,8 @@
public Visibility() {
}
+ @SuppressLint("RestrictedApi") // remove once core lib would be released with the new
+ // LIBRARY_GROUP_PREFIX restriction. tracking in b/127286008
public Visibility(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, Styleable.VISIBILITY_TRANSITION);
diff --git a/versionedparcelable/annotation/build.gradle b/versionedparcelable/annotation/build.gradle
index 076f4e0..1d9c857 100644
--- a/versionedparcelable/annotation/build.gradle
+++ b/versionedparcelable/annotation/build.gradle
@@ -21,3 +21,7 @@
dependencies {
compile(JAVAPOET)
}
+
+// The "javadoc" task is unused so we don't want it to appear in the output of `./gradlew tasks`
+// So, we set the group to null
+tasks["javadoc"].group = null
diff --git a/versionedparcelable/api/1.1.0-alpha02.txt b/versionedparcelable/api/1.1.0-alpha02.txt
index a53654a..0ca14c11 100644
--- a/versionedparcelable/api/1.1.0-alpha02.txt
+++ b/versionedparcelable/api/1.1.0-alpha02.txt
@@ -3,7 +3,9 @@
public class ParcelUtils {
method public static <T extends androidx.versionedparcelable.VersionedParcelable> T? getVersionedParcelable(android.os.Bundle!, String!);
+ method public static <T extends androidx.versionedparcelable.VersionedParcelable> java.util.List<T>? getVersionedParcelableList(android.os.Bundle!, String!);
method public static void putVersionedParcelable(android.os.Bundle, String, androidx.versionedparcelable.VersionedParcelable);
+ method public static void putVersionedParcelableList(android.os.Bundle, String, java.util.List<? extends androidx.versionedparcelable.VersionedParcelable>);
}
public interface VersionedParcelable {
diff --git a/versionedparcelable/api/current.txt b/versionedparcelable/api/current.txt
index a53654a..0ca14c11 100644
--- a/versionedparcelable/api/current.txt
+++ b/versionedparcelable/api/current.txt
@@ -3,7 +3,9 @@
public class ParcelUtils {
method public static <T extends androidx.versionedparcelable.VersionedParcelable> T? getVersionedParcelable(android.os.Bundle!, String!);
+ method public static <T extends androidx.versionedparcelable.VersionedParcelable> java.util.List<T>? getVersionedParcelableList(android.os.Bundle!, String!);
method public static void putVersionedParcelable(android.os.Bundle, String, androidx.versionedparcelable.VersionedParcelable);
+ method public static void putVersionedParcelableList(android.os.Bundle, String, java.util.List<? extends androidx.versionedparcelable.VersionedParcelable>);
}
public interface VersionedParcelable {
diff --git a/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java b/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
index b9b3b04..c2d530b 100644
--- a/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
+++ b/versionedparcelable/src/main/java/androidx/versionedparcelable/ParcelUtils.java
@@ -27,6 +27,8 @@
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
/**
* Utilities for managing {@link VersionedParcelable}s.
@@ -91,7 +93,6 @@
b.putParcelable(key, innerBundle);
}
-
/**
* Get a VersionedParcelable from a Bundle.
*
@@ -109,4 +110,43 @@
return null;
}
}
+
+ /**
+ * Add a list of VersionedParcelable to an existing Bundle.
+ */
+ public static void putVersionedParcelableList(@NonNull Bundle b, @NonNull String key,
+ @NonNull List<? extends VersionedParcelable> list) {
+ Bundle innerBundle = new Bundle();
+ ArrayList<Parcelable> toWrite = new ArrayList<>();
+ for (VersionedParcelable obj : list) {
+ toWrite.add(toParcelable(obj));
+ }
+ innerBundle.putParcelableArrayList(INNER_BUNDLE_KEY, toWrite);
+ b.putParcelable(key, innerBundle);
+ }
+
+ /**
+ * Get a list of VersionedParcelable from a Bundle.
+ *
+ * Returns null if the bundle isn't present or ClassLoader issues occur.
+ */
+ @SuppressWarnings("TypeParameterUnusedInFormals")
+ @Nullable
+ public static <T extends VersionedParcelable> List<T> getVersionedParcelableList(
+ Bundle bundle, String key) {
+ List<T> resultList = new ArrayList<>();
+ try {
+ Bundle innerBundle = bundle.getParcelable(key);
+ innerBundle.setClassLoader(ParcelUtils.class.getClassLoader());
+ ArrayList<Parcelable> parcelableArrayList =
+ innerBundle.getParcelableArrayList(INNER_BUNDLE_KEY);
+ for (Parcelable parcelable : parcelableArrayList) {
+ resultList.add((T) fromParcelable(parcelable));
+ }
+ return resultList;
+ } catch (RuntimeException e) {
+ // There may be new classes or such in the bundle, make sure not to crash the caller.
+ }
+ return null;
+ }
}
diff --git a/viewpager2/api/1.0.0-alpha03.txt b/viewpager2/api/1.0.0-alpha03.txt
new file mode 100644
index 0000000..4b1a6aa
--- /dev/null
+++ b/viewpager2/api/1.0.0-alpha03.txt
@@ -0,0 +1,77 @@
+// Signature format: 3.0
+package androidx.viewpager2.adapter {
+
+ public abstract class FragmentStateAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.viewpager2.adapter.FragmentViewHolder> implements androidx.viewpager2.adapter.StatefulAdapter {
+ ctor public FragmentStateAdapter(androidx.fragment.app.FragmentManager);
+ method public boolean containsItem(long);
+ method public abstract androidx.fragment.app.Fragment getItem(int);
+ method public final void onBindViewHolder(androidx.viewpager2.adapter.FragmentViewHolder, int);
+ method public final androidx.viewpager2.adapter.FragmentViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public final boolean onFailedToRecycleView(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewAttachedToWindow(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public final void onViewRecycled(androidx.viewpager2.adapter.FragmentViewHolder);
+ method public void restoreState(android.os.Parcelable);
+ method public android.os.Parcelable saveState();
+ method public final void setHasStableIds(boolean);
+ }
+
+ public final class FragmentViewHolder extends androidx.recyclerview.widget.RecyclerView.ViewHolder {
+ }
+
+ public interface StatefulAdapter {
+ method public void restoreState(android.os.Parcelable);
+ method public android.os.Parcelable saveState();
+ }
+
+}
+
+package androidx.viewpager2.widget {
+
+ public final class ViewPager2 extends android.view.ViewGroup {
+ ctor public ViewPager2(android.content.Context);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?);
+ ctor public ViewPager2(android.content.Context, android.util.AttributeSet?, int);
+ ctor @RequiresApi(21) public ViewPager2(android.content.Context, android.util.AttributeSet?, int, int);
+ method public boolean beginFakeDrag();
+ method public boolean endFakeDrag();
+ method public boolean fakeDragBy(float);
+ method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
+ method public int getCurrentItem();
+ method @androidx.viewpager2.widget.ViewPager2.Orientation public int getOrientation();
+ method @androidx.viewpager2.widget.ViewPager2.ScrollState public int getScrollState();
+ method public boolean isFakeDragging();
+ method public boolean isUserInputEnabled();
+ method public void registerOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
+ method public void setCurrentItem(int);
+ method public void setCurrentItem(int, boolean);
+ method public void setOrientation(@androidx.viewpager2.widget.ViewPager2.Orientation int);
+ method public void setPageTransformer(androidx.viewpager2.widget.ViewPager2.PageTransformer?);
+ method public void setUserInputEnabled(boolean);
+ method public void unregisterOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
+ field public static final int ORIENTATION_HORIZONTAL = 0; // 0x0
+ field public static final int ORIENTATION_VERTICAL = 1; // 0x1
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ }
+
+ public abstract static class ViewPager2.OnPageChangeCallback {
+ ctor public ViewPager2.OnPageChangeCallback();
+ method public void onPageScrollStateChanged(@androidx.viewpager2.widget.ViewPager2.ScrollState int);
+ method public void onPageScrolled(int, float, @Px int);
+ method public void onPageSelected(int);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL, androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL}) public static @interface ViewPager2.Orientation {
+ }
+
+ public static interface ViewPager2.PageTransformer {
+ method public void transformPage(android.view.View, float);
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef({androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE, androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING, androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING}) public static @interface ViewPager2.ScrollState {
+ }
+
+}
+
diff --git a/viewpager2/api/current.txt b/viewpager2/api/current.txt
index 5607352..4b1a6aa 100644
--- a/viewpager2/api/current.txt
+++ b/viewpager2/api/current.txt
@@ -32,9 +32,14 @@
ctor public ViewPager2(android.content.Context, android.util.AttributeSet?);
ctor public ViewPager2(android.content.Context, android.util.AttributeSet?, int);
ctor @RequiresApi(21) public ViewPager2(android.content.Context, android.util.AttributeSet?, int, int);
+ method public boolean beginFakeDrag();
+ method public boolean endFakeDrag();
+ method public boolean fakeDragBy(float);
method public androidx.recyclerview.widget.RecyclerView.Adapter? getAdapter();
method public int getCurrentItem();
method @androidx.viewpager2.widget.ViewPager2.Orientation public int getOrientation();
+ method @androidx.viewpager2.widget.ViewPager2.ScrollState public int getScrollState();
+ method public boolean isFakeDragging();
method public boolean isUserInputEnabled();
method public void registerOnPageChangeCallback(androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback);
method public void setAdapter(androidx.recyclerview.widget.RecyclerView.Adapter?);
diff --git a/viewpager2/api/res-1.0.0-alpha03.txt b/viewpager2/api/res-1.0.0-alpha03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/viewpager2/api/res-1.0.0-alpha03.txt
diff --git a/viewpager2/api/restricted_1.0.0-alpha03.txt b/viewpager2/api/restricted_1.0.0-alpha03.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/viewpager2/api/restricted_1.0.0-alpha03.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/viewpager2/build.gradle b/viewpager2/build.gradle
index e068aa8..2a0fe2f 100644
--- a/viewpager2/build.gradle
+++ b/viewpager2/build.gradle
@@ -25,10 +25,11 @@
dependencies {
api(project(":fragment"))
- api(project(":recyclerview"))
- implementation(project(":appcompat"))
+ // TODO: Change back to api(project(":recyclerview")) after april 3rd release
+ api("androidx.recyclerview:recyclerview:1.1.0-alpha03")
implementation(project(":collection"))
+ androidTestImplementation(project(":appcompat"))
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
androidTestImplementation(TEST_RUNNER)
diff --git a/viewpager2/integration-tests/testapp/build.gradle b/viewpager2/integration-tests/testapp/build.gradle
index 2b3a756..3affaad 100644
--- a/viewpager2/integration-tests/testapp/build.gradle
+++ b/viewpager2/integration-tests/testapp/build.gradle
@@ -17,7 +17,7 @@
import static androidx.build.dependencies.DependenciesKt.ARCH_LIFECYCLE_EXTENSIONS
import static androidx.build.dependencies.DependenciesKt.ESPRESSO_CORE
import static androidx.build.dependencies.DependenciesKt.KOTLIN_STDLIB
-import static androidx.build.dependencies.DependenciesKt.SUPPORT_DESIGN
+import static androidx.build.dependencies.DependenciesKt.MATERIAL
import static androidx.build.dependencies.DependenciesKt.TEST_EXT_JUNIT
import static androidx.build.dependencies.DependenciesKt.TEST_RULES
@@ -32,7 +32,8 @@
api(KOTLIN_STDLIB)
implementation(project(":viewpager2"))
implementation(ARCH_LIFECYCLE_EXTENSIONS)
- implementation(SUPPORT_DESIGN)
+ implementation(MATERIAL)
+ implementation(project(":coordinatorlayout"))
implementation(project(":cardview"))
androidTestImplementation(TEST_RULES)
diff --git a/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/BaseTest.kt b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/BaseTest.kt
index c6f68d6..ab76cb1 100644
--- a/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/BaseTest.kt
+++ b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/BaseTest.kt
@@ -19,17 +19,24 @@
import android.view.View
import androidx.annotation.LayoutRes
import androidx.fragment.app.FragmentActivity
+import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onIdle
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
+import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
import androidx.viewpager2.widget.ViewPager2
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
import com.example.androidx.viewpager2.test.ViewPagerIdleWatcher
import com.example.androidx.viewpager2.test.onCurrentPage
import com.example.androidx.viewpager2.test.onViewPager
import com.example.androidx.viewpager2.test.swipeNext
import com.example.androidx.viewpager2.test.swipePrevious
+import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
@@ -65,6 +72,17 @@
idleWatcher.unregister()
}
+ fun selectOrientation(@ViewPager2.Orientation orientation: Int) {
+ onView(withId(R.id.orientation_spinner)).perform(click())
+ onData(equalTo(
+ when (orientation) {
+ ORIENTATION_HORIZONTAL -> "horizontal"
+ ORIENTATION_VERTICAL -> "vertical"
+ else -> throw IllegalArgumentException("Orientation $orientation doesn't exist")
+ }
+ )).perform(click())
+ }
+
fun swipeToNextPage() {
onViewPager().perform(swipeNext())
idleWatcher.waitForIdle()
diff --git a/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/FakeDragTest.kt b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/FakeDragTest.kt
new file mode 100644
index 0000000..825f04c
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/FakeDragTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.viewpager2
+
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.content.res.Configuration.ORIENTATION_PORTRAIT
+import androidx.test.espresso.Espresso.onIdle
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.ViewAction
+import androidx.test.espresso.ViewInteraction
+import androidx.test.espresso.action.ViewActions.swipeDown
+import androidx.test.espresso.action.ViewActions.swipeLeft
+import androidx.test.espresso.action.ViewActions.swipeRight
+import androidx.test.espresso.action.ViewActions.swipeUp
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import androidx.viewpager2.widget.ViewPager2
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class FakeDragTest(private val config: TestConfig) :
+ BaseTest<FakeDragActivity>(FakeDragActivity::class.java) {
+ data class TestConfig(
+ @ViewPager2.Orientation val orientation: Int
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun spec(): List<TestConfig> {
+ return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).map { orientation ->
+ TestConfig(orientation)
+ }
+ }
+ }
+
+ private val twoOfSpadesPage = "2\n♣"
+ private val threeOfSpadesPage = "3\n♣"
+
+ override val layoutId get() = R.id.viewPager
+
+ private val phoneOrientation
+ get() = getInstrumentation().targetContext.resources.configuration.orientation
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ selectOrientation(config.orientation)
+ }
+
+ @Test
+ fun testFakeDragging() {
+ // test if ViewPager2 goes to the next page when fake dragging
+ fakeDragForward()
+ verifyCurrentPage(threeOfSpadesPage)
+
+ // test if ViewPager2 goes back to the first page when fake dragging the other way
+ fakeDragBackward()
+ verifyCurrentPage(twoOfSpadesPage)
+ }
+
+ private fun fakeDragForward() {
+ onTouchpad().perform(swipeNext())
+ idleWatcher.waitForIdle()
+ onIdle()
+ }
+
+ private fun fakeDragBackward() {
+ onTouchpad().perform(swipePrevious())
+ idleWatcher.waitForIdle()
+ onIdle()
+ }
+
+ private fun onTouchpad(): ViewInteraction {
+ return onView(withId(R.id.touchpad))
+ }
+
+ private fun swipeNext(): ViewAction {
+ return when (phoneOrientation) {
+ ORIENTATION_LANDSCAPE -> swipeDown()
+ ORIENTATION_PORTRAIT -> swipeLeft()
+ else -> throw RuntimeException("Orientation should be landscape or portrait")
+ }
+ }
+
+ private fun swipePrevious(): ViewAction {
+ return when (phoneOrientation) {
+ ORIENTATION_LANDSCAPE -> swipeUp()
+ ORIENTATION_PORTRAIT -> swipeRight()
+ else -> throw RuntimeException("Orientation should be landscape or portrait")
+ }
+ }
+}
diff --git a/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/ViewPagerBaseTest.kt b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/ViewPagerBaseTest.kt
index 38cf9f2..3fdb56f 100644
--- a/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/ViewPagerBaseTest.kt
+++ b/viewpager2/integration-tests/testapp/src/androidTest/java/com/example/androidx/viewpager2/ViewPagerBaseTest.kt
@@ -16,7 +16,6 @@
package com.example.androidx.viewpager2
-import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
@@ -26,7 +25,6 @@
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
import com.example.androidx.viewpager2.test.AnimationVerifier
import org.hamcrest.CoreMatchers.allOf
-import org.hamcrest.CoreMatchers.equalTo
import org.junit.Before
import org.junit.Test
import org.junit.runners.Parameterized
@@ -73,7 +71,7 @@
@Before
override fun setUp() {
super.setUp()
- selectOrientation()
+ selectOrientation(config.orientation)
if (config.animateRotate) check(R.id.rotate_checkbox)
if (config.animateTranslate) check(R.id.translate_checkbox)
if (config.animateScale) check(R.id.scale_checkbox)
@@ -96,17 +94,6 @@
verifyCurrentPage(twoOfSpades)
}
- private fun selectOrientation() {
- onView(withId(R.id.orientation_spinner)).perform(click())
- onData(equalTo(
- when (config.orientation) {
- ORIENTATION_HORIZONTAL -> "horizontal"
- ORIENTATION_VERTICAL -> "vertical"
- else -> "unknown"
- }
- )).perform(click())
- }
-
private fun check(id: Int) {
onView(allOf(withId(id), isNotChecked())).perform(click())
}
diff --git a/viewpager2/integration-tests/testapp/src/main/AndroidManifest.xml b/viewpager2/integration-tests/testapp/src/main/AndroidManifest.xml
index c7c6848..a6fea0b 100644
--- a/viewpager2/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/viewpager2/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -59,6 +59,13 @@
</intent-filter>
</activity>
+ <activity android:name=".FakeDragActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.SAMPLE_CODE"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".BrowseActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BaseCardActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BaseCardActivity.kt
index f90425b..291cdda3f 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BaseCardActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BaseCardActivity.kt
@@ -17,8 +17,6 @@
package com.example.androidx.viewpager2
import android.os.Bundle
-import android.view.View
-import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.CheckBox
@@ -36,20 +34,17 @@
*/
abstract class BaseCardActivity : FragmentActivity() {
- lateinit var viewPager: ViewPager2
+ protected lateinit var viewPager: ViewPager2
private lateinit var cardSelector: Spinner
private lateinit var smoothScrollCheckBox: CheckBox
private lateinit var rotateCheckBox: CheckBox
private lateinit var translateCheckBox: CheckBox
private lateinit var scaleCheckBox: CheckBox
private lateinit var gotoPage: Button
- private lateinit var orientationSelector: Spinner
- private lateinit var disableUserInputCheckBox: CheckBox
- private var orientation: Int = ORIENTATION_HORIZONTAL
- private val translateX get() = orientation == ORIENTATION_VERTICAL &&
+ private val translateX get() = viewPager.orientation == ORIENTATION_VERTICAL &&
translateCheckBox.isChecked
- private val translateY get() = orientation == ORIENTATION_HORIZONTAL &&
+ private val translateY get() = viewPager.orientation == ORIENTATION_HORIZONTAL &&
translateCheckBox.isChecked
protected open val layoutId: Int = R.layout.activity_no_tablayout
@@ -76,8 +71,6 @@
setContentView(layoutId)
viewPager = findViewById(R.id.view_pager)
- orientationSelector = findViewById(R.id.orientation_spinner)
- disableUserInputCheckBox = findViewById(R.id.disable_user_input_checkbox)
cardSelector = findViewById(R.id.card_spinner)
smoothScrollCheckBox = findViewById(R.id.smooth_scroll_checkbox)
rotateCheckBox = findViewById(R.id.rotate_checkbox)
@@ -85,32 +78,12 @@
scaleCheckBox = findViewById(R.id.scale_checkbox)
gotoPage = findViewById(R.id.jump_button)
- disableUserInputCheckBox.setOnCheckedChangeListener { _, isDisabled ->
- viewPager.isUserInputEnabled = !isDisabled
- }
-
- orientationSelector.adapter = createOrientationAdapter()
+ UserInputController(viewPager, findViewById(R.id.disable_user_input_checkbox)).setup()
+ OrientationController(viewPager, findViewById(R.id.orientation_spinner)).setup()
cardSelector.adapter = createCardAdapter()
viewPager.setPageTransformer(mAnimator)
- orientationSelector.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
- override fun onItemSelected(
- parent: AdapterView<*>,
- view: View?,
- position: Int,
- id: Long
- ) {
- when (parent.selectedItem.toString()) {
- HORIZONTAL -> orientation = ORIENTATION_HORIZONTAL
- VERTICAL -> orientation = ORIENTATION_VERTICAL
- }
- viewPager.orientation = orientation
- }
-
- override fun onNothingSelected(adapterView: AdapterView<*>) {}
- }
-
gotoPage.setOnClickListener {
val card = cardSelector.selectedItemPosition
val smoothScroll = smoothScrollCheckBox.isChecked
@@ -123,17 +96,4 @@
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
return adapter
}
-
- private fun createOrientationAdapter(): SpinnerAdapter {
- val adapter = ArrayAdapter(this,
- android.R.layout.simple_spinner_item, arrayOf(HORIZONTAL, VERTICAL))
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
- return adapter
- }
-
- companion object {
- val cards = Card.DECK
- private const val HORIZONTAL = "horizontal"
- private const val VERTICAL = "vertical"
- }
}
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BrowseActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BrowseActivity.kt
index ca544d3..b98a912 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BrowseActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/BrowseActivity.kt
@@ -49,6 +49,8 @@
"intent" to activityToIntent(MutableCollectionFragmentActivity::class.java.name)))
myData.add(mapOf("title" to "ViewPager2 with a TabLayout (Views)",
"intent" to activityToIntent(CardViewTabLayoutActivity::class.java.name)))
+ myData.add(mapOf("title" to "ViewPager2 with Fake Dragging",
+ "intent" to activityToIntent(FakeDragActivity::class.java.name)))
return myData
}
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.kt
index 4049be5..794104d 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardFragmentActivity.kt
@@ -40,11 +40,11 @@
viewPager.adapter = object : FragmentStateAdapter(supportFragmentManager) {
override fun getItem(position: Int): Fragment {
- return CardFragment.create(cards[position])
+ return CardFragment.create(Card.DECK[position])
}
override fun getItemCount(): Int {
- return cards.size
+ return Card.DECK.size
}
}
}
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewActivity.kt
index 71fc7d3..2508f92 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewActivity.kt
@@ -17,12 +17,8 @@
package com.example.androidx.viewpager2
import android.os.Bundle
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
-
-import com.example.androidx.viewpager2.cards.Card
-import com.example.androidx.viewpager2.cards.CardView
+import com.example.androidx.viewpager2.cards.CardViewAdapter
/**
* Shows how to use [ViewPager2.setAdapter] with Views.
@@ -30,34 +26,8 @@
* @see CardFragmentActivity for an example of using {@link ViewPager2} with Fragments.
*/
open class CardViewActivity : BaseCardActivity() {
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- viewPager.adapter = object : RecyclerView.Adapter<CardViewHolder>() {
- override fun onCreateViewHolder(
- parent: ViewGroup,
- viewType: Int
- ): CardViewHolder {
- return CardViewHolder(CardView(layoutInflater, parent))
- }
-
- override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
- holder.bind(cards[position])
- }
-
- override fun getItemCount(): Int {
- return cards.size
- }
- }
- }
-
- class CardViewHolder internal constructor(
- private val cardView: CardView
- ) : RecyclerView.ViewHolder(cardView.view) {
-
- internal fun bind(card: Card) {
- cardView.bind(card)
- }
+ viewPager.adapter = CardViewAdapter()
}
}
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewTabLayoutActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewTabLayoutActivity.kt
index a34b936..75d3a82 100644
--- a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewTabLayoutActivity.kt
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/CardViewTabLayoutActivity.kt
@@ -17,6 +17,7 @@
package com.example.androidx.viewpager2
import android.os.Bundle
+import com.example.androidx.viewpager2.cards.Card
import com.google.android.material.tabs.TabLayout
class CardViewTabLayoutActivity : CardViewActivity() {
@@ -30,7 +31,7 @@
tabLayout = findViewById(R.id.tabs)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
- tab.text = cards[position].toString()
+ tab.text = Card.DECK[position].toString()
}.attach()
}
}
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/FakeDragActivity.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/FakeDragActivity.kt
new file mode 100644
index 0000000..a1b27b5
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/FakeDragActivity.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.viewpager2
+
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.MotionEvent
+import android.view.View
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager2.widget.ViewPager2
+import com.example.androidx.viewpager2.cards.CardViewAdapter
+
+class FakeDragActivity : FragmentActivity() {
+
+ private lateinit var viewPager: ViewPager2
+ private var landscape = false
+ private var lastValue: Float = 0f
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_fakedrag)
+ landscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+
+ viewPager = findViewById(R.id.viewPager)
+ viewPager.adapter = CardViewAdapter()
+ viewPager.isUserInputEnabled = false
+ UserInputController(viewPager, findViewById(R.id.disable_user_input_checkbox)).setup()
+ OrientationController(viewPager, findViewById(R.id.orientation_spinner)).setup()
+
+ findViewById<View>(R.id.touchpad).setOnTouchListener { _, event ->
+ handleOnTouchEvent(event)
+ }
+ }
+
+ private fun getValue(event: MotionEvent): Float {
+ return if (landscape) event.y else event.x
+ }
+
+ private fun handleOnTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastValue = getValue(event)
+ viewPager.beginFakeDrag()
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ val value = getValue(event)
+ val delta = value - lastValue
+ viewPager.fakeDragBy(delta)
+ lastValue = value
+ }
+
+ MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
+ viewPager.endFakeDrag()
+ }
+ }
+ return true
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/OrientationController.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/OrientationController.kt
new file mode 100644
index 0000000..4f7e295
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/OrientationController.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.viewpager2
+
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import androidx.viewpager2.widget.ViewPager2
+
+/**
+ * It configures a spinner to show orientations and sets the orientation of a ViewPager2 when an orientation is selected
+ */
+class OrientationController(private val viewPager: ViewPager2, private val spinner: Spinner) {
+ fun setup() {
+ val orientation = viewPager.orientation
+ val adapter = ArrayAdapter(spinner.context, android.R.layout.simple_spinner_item,
+ arrayOf(HORIZONTAL, VERTICAL))
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ spinner.adapter = adapter
+
+ val initialPosition = adapter.getPosition(orientationToString(orientation))
+ if (initialPosition >= 0) {
+ spinner.setSelection(initialPosition)
+ }
+
+ spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>,
+ view: View?,
+ position: Int,
+ id: Long
+ ) {
+ viewPager.orientation = stringToOrientation(parent.selectedItem.toString())
+ }
+
+ override fun onNothingSelected(adapterView: AdapterView<*>) {}
+ }
+ }
+
+ private fun orientationToString(@ViewPager2.Orientation orientation: Int): String {
+ return when (orientation) {
+ ViewPager2.ORIENTATION_HORIZONTAL -> HORIZONTAL
+ ViewPager2.ORIENTATION_VERTICAL -> VERTICAL
+ else -> throw IllegalArgumentException("Orientation $orientation doesn't exist")
+ }
+ }
+
+ @ViewPager2.Orientation
+ internal fun stringToOrientation(string: String): Int {
+ return when (string) {
+ HORIZONTAL -> ViewPager2.ORIENTATION_HORIZONTAL
+ VERTICAL -> ViewPager2.ORIENTATION_VERTICAL
+ else -> throw IllegalArgumentException("Orientation $string doesn't exist")
+ }
+ }
+
+ companion object {
+ private const val HORIZONTAL = "horizontal"
+ private const val VERTICAL = "vertical"
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/UserInputController.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/UserInputController.kt
new file mode 100644
index 0000000..c4c1e26
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/UserInputController.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.viewpager2
+
+import android.widget.CheckBox
+import androidx.viewpager2.widget.ViewPager2
+
+class UserInputController(private val viewPager: ViewPager2, private val disableBox: CheckBox) {
+ fun setup() {
+ disableBox.isChecked = !viewPager.isUserInputEnabled
+ disableBox.setOnCheckedChangeListener { _, isDisabled ->
+ viewPager.isUserInputEnabled = !isDisabled
+ }
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/cards/CardViewAdapter.kt b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/cards/CardViewAdapter.kt
new file mode 100644
index 0000000..90739f0
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/java/com/example/androidx/viewpager2/cards/CardViewAdapter.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidx.viewpager2.cards
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+
+class CardViewAdapter : RecyclerView.Adapter<CardViewHolder>() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
+ return CardViewHolder(CardView(LayoutInflater.from(parent.context), parent))
+ }
+
+ override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
+ holder.bind(Card.DECK[position])
+ }
+
+ override fun getItemCount(): Int {
+ return Card.DECK.size
+ }
+}
+
+class CardViewHolder internal constructor(private val cardView: CardView) :
+ RecyclerView.ViewHolder(cardView.view) {
+ internal fun bind(card: Card) {
+ cardView.bind(card)
+ }
+}
diff --git a/viewpager2/integration-tests/testapp/src/main/res/layout-land/activity_fakedrag.xml b/viewpager2/integration-tests/testapp/src/main/res/layout-land/activity_fakedrag.xml
new file mode 100644
index 0000000..47b1d78
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/res/layout-land/activity_fakedrag.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ tools:background="#FFFFFF">
+
+ <include layout="@layout/controls_fakedrag" />
+
+ <androidx.viewpager2.widget.ViewPager2
+ android:id="@+id/viewPager"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/viewpager2/integration-tests/testapp/src/main/res/layout-land/controls_fakedrag.xml b/viewpager2/integration-tests/testapp/src/main/res/layout-land/controls_fakedrag.xml
new file mode 100644
index 0000000..0934069
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/res/layout-land/controls_fakedrag.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_margin="16dp"
+ android:gravity="center_horizontal"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_orientation"
+ android:textAppearance="@android:style/TextAppearance.Medium" />
+
+ <Spinner
+ android:id="@+id/orientation_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <CheckBox
+ android:id="@+id/disable_user_input_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/disable_user_input"
+ android:textAppearance="@android:style/TextAppearance.Medium" />
+
+ <View
+ android:id="@+id/touchpad"
+ android:layout_width="64dp"
+ android:layout_height="0dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="16dp"
+ android:layout_weight="1"
+ android:background="#EBEBEB" />
+
+ </LinearLayout>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="match_parent"
+ android:background="#000000" />
+
+</merge>
diff --git a/viewpager2/integration-tests/testapp/src/main/res/layout/activity_fakedrag.xml b/viewpager2/integration-tests/testapp/src/main/res/layout/activity_fakedrag.xml
new file mode 100644
index 0000000..6f943ee
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/res/layout/activity_fakedrag.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:background="#FFFFFF">
+
+ <include layout="@layout/controls_fakedrag" />
+
+ <androidx.viewpager2.widget.ViewPager2
+ android:id="@+id/viewPager"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+</LinearLayout>
diff --git a/viewpager2/integration-tests/testapp/src/main/res/layout/controls_fakedrag.xml b/viewpager2/integration-tests/testapp/src/main/res/layout/controls_fakedrag.xml
new file mode 100644
index 0000000..78db760
--- /dev/null
+++ b/viewpager2/integration-tests/testapp/src/main/res/layout/controls_fakedrag.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:layout_marginTop="16dp"
+ android:gravity="center_vertical|start"
+ android:orientation="horizontal">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/label_orientation"
+ android:textAppearance="@android:style/TextAppearance.Medium" />
+
+ <Spinner
+ android:id="@+id/orientation_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ <CheckBox
+ android:id="@+id/disable_user_input_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginLeft="16dp"
+ android:text="@string/disable_user_input"
+ android:textAppearance="@android:style/TextAppearance.Medium" />
+
+ <View
+ android:id="@+id/touchpad"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:layout_margin="16dp"
+ android:background="#EBEBEB" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#000000" />
+
+</merge>
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
index 9b308b1..54f92c2 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/BaseTest.kt
@@ -25,6 +25,7 @@
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.CoordinatesProvider
import androidx.test.espresso.action.GeneralLocation
import androidx.test.espresso.action.GeneralSwipeAction
@@ -35,18 +36,22 @@
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
-import androidx.testutils.FragmentActivityUtils
+import androidx.testutils.AppCompatActivityUtils
import androidx.testutils.FragmentActivityUtils.waitForActivityDrawn
import androidx.viewpager2.LocaleTestUtils
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.test.R
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING
import androidx.viewpager2.widget.swipe.FragmentAdapter
import androidx.viewpager2.widget.swipe.PageSwiper
import androidx.viewpager2.widget.swipe.PageSwiperEspresso
+import androidx.viewpager2.widget.swipe.PageSwiperFakeDrag
import androidx.viewpager2.widget.swipe.PageSwiperManual
import androidx.viewpager2.widget.swipe.SelfChecking
import androidx.viewpager2.widget.swipe.TestActivity
@@ -115,7 +120,7 @@
viewPager.adapter = adapterProvider(activity)
onCreateCallback(viewPager)
}
- activity = FragmentActivityUtils.recreateActivity(activityTestRule, activity)
+ activity = AppCompatActivityUtils.recreateActivity(activityTestRule, activity)
TestActivity.onCreateCallback = { }
waitForActivityDrawn(activity)
}
@@ -143,7 +148,8 @@
enum class SwipeMethod {
ESPRESSO,
- MANUAL
+ MANUAL,
+ FAKE_DRAG
}
fun swipe(currentPageIx: Int, nextPageIx: Int, method: SwipeMethod = SwipeMethod.ESPRESSO) {
@@ -190,11 +196,9 @@
private fun swiper(method: SwipeMethod = SwipeMethod.ESPRESSO): PageSwiper {
return when (method) {
- SwipeMethod.ESPRESSO -> PageSwiperEspresso(
- viewPager.orientation,
- isRtl
- )
+ SwipeMethod.ESPRESSO -> PageSwiperEspresso(viewPager.orientation, isRtl)
SwipeMethod.MANUAL -> PageSwiperManual(viewPager, isRtl)
+ SwipeMethod.FAKE_DRAG -> PageSwiperFakeDrag(viewPager)
}
}
@@ -279,11 +283,15 @@
}
fun ViewPager2.addWaitForIdleLatch(): CountDownLatch {
+ return addWaitForStateLatch(SCROLL_STATE_IDLE)
+ }
+
+ fun ViewPager2.addWaitForStateLatch(targetState: Int): CountDownLatch {
val latch = CountDownLatch(1)
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrollStateChanged(state: Int) {
- if (state == SCROLL_STATE_IDLE) {
+ if (state == targetState) {
latch.countDown()
post { unregisterOnPageChangeCallback(this) }
}
@@ -331,14 +339,16 @@
/**
* Checks:
* 1. Expected page is the current ViewPager2 page
- * 2. Expected text is displayed
- * 3. Internal activity state is valid (as per activity self-test)
+ * 2. Expected state is SCROLL_STATE_IDLE
+ * 3. Expected text is displayed
+ * 4. Internal activity state is valid (as per activity self-test)
*/
fun Context.assertBasicState(pageIx: Int, value: String = pageIx.toString()) {
assertThat<Int>(
"viewPager.getCurrentItem() should return $pageIx",
viewPager.currentItem, equalTo(pageIx)
)
+ assertThat(viewPager.scrollState, equalTo(SCROLL_STATE_IDLE))
onView(allOf<View>(withId(R.id.text_view), isDisplayed())).check(
matches(withText(value))
)
@@ -374,6 +384,13 @@
DESC(-1)
}
+ fun onPage(childMatcher: Matcher<View>): ViewInteraction {
+ return onView(allOf(
+ withParent(withParent(isAssignableFrom(ViewPager2::class.java))),
+ childMatcher
+ ))
+ }
+
fun <T, R : Comparable<R>> List<T>.assertSorted(selector: (T) -> R) {
assertThat(this, equalTo(this.sortedBy(selector)))
}
@@ -481,3 +498,19 @@
get() {
return this == fragmentAdapterProvider
}
+
+fun scrollStateToString(@ViewPager2.ScrollState state: Int): String {
+ return when (state) {
+ SCROLL_STATE_IDLE -> "IDLE"
+ SCROLL_STATE_DRAGGING -> "DRAGGING"
+ SCROLL_STATE_SETTLING -> "SETTLING"
+ else -> throw IllegalArgumentException("Scroll state $state doesn't exist")
+ }
+}
+
+fun scrollStateGlossary(): String {
+ return "Scroll states: " +
+ "$SCROLL_STATE_IDLE=${scrollStateToString(SCROLL_STATE_IDLE)}, " +
+ "$SCROLL_STATE_DRAGGING=${scrollStateToString(SCROLL_STATE_DRAGGING)}, " +
+ "$SCROLL_STATE_SETTLING=${scrollStateToString(SCROLL_STATE_SETTLING)})"
+}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
index d0a2831..5d2638c 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
@@ -16,7 +16,9 @@
package androidx.viewpager2.widget
+import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.filters.LargeTest
+import androidx.testutils.SwipeToLocation.flingToCenter
import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.DragWhileSmoothScrollTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.DragWhileSmoothScrollTest.Event.OnPageScrolledEvent
@@ -35,6 +37,8 @@
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.concurrent.TimeUnit.SECONDS
+import kotlin.math.ceil
+import kotlin.math.floor
import kotlin.math.max
/**
@@ -49,7 +53,8 @@
val startPage: Int = 0,
val targetPage: Int,
val dragInOppositeDirection: Boolean,
- val distanceToTargetWhenStartDrag: Float
+ val distanceToTargetWhenStartDrag: Float,
+ val endInSnappedPosition: Boolean = false
)
companion object {
@@ -78,7 +83,9 @@
waitTillCloseEnough.await(1, SECONDS)
// then perform a swipe
- if (dragInOppositeDirection == movingForward) {
+ if (endInSnappedPosition) {
+ onPage(withText("${pageToSnapTo(movingForward)}")).perform(flingToCenter())
+ } else if (dragInOppositeDirection == movingForward) {
swipeBackward(SwipeMethod.MANUAL)
} else {
swipeForward(SwipeMethod.MANUAL)
@@ -88,8 +95,7 @@
// and check the result
callback.apply {
assertThat(
- "Unexpected sequence of state changes (0=IDLE, 1=DRAGGING, 2=SETTLING)" +
- dumpEvents(),
+ "Unexpected sequence of state changes:" + dumpEvents(),
stateEvents.map { it.state },
equalTo(
if (expectIdleAfterDrag()) {
@@ -140,6 +146,19 @@
return RecordingCallback().also { registerOnPageChangeCallback(it) }
}
+ private fun TestConfig.pageToSnapTo(movingForward: Boolean): Int {
+ val positionToStartDragging = if (movingForward) {
+ targetPage - distanceToTargetWhenStartDrag
+ } else {
+ targetPage + distanceToTargetWhenStartDrag
+ }
+ return if (movingForward == dragInOppositeDirection) {
+ floor(positionToStartDragging).toInt()
+ } else {
+ ceil(positionToStartDragging).toInt()
+ }
+ }
+
private sealed class Event {
data class OnPageScrolledEvent(
val position: Int,
@@ -187,7 +206,7 @@
}
fun dumpEvents(): String {
- return events.joinToString("\n- ", "\n- ")
+ return events.joinToString("\n- ", "\n(${scrollStateGlossary()})\n- ")
}
}
}
@@ -198,7 +217,28 @@
return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
listOf(true, false).flatMap { dragInOppositeDirection ->
listOf(0.4f, 1.5f).flatMap { distanceToTarget ->
- createTestSet(orientation, dragInOppositeDirection, distanceToTarget)
+ listOf(true, false).flatMap { endInSnappedPosition ->
+ listOf(
+ TestConfig(
+ title = "forward",
+ orientation = orientation,
+ startPage = 0,
+ targetPage = 4,
+ dragInOppositeDirection = dragInOppositeDirection,
+ distanceToTargetWhenStartDrag = distanceToTarget,
+ endInSnappedPosition = endInSnappedPosition
+ ),
+ TestConfig(
+ title = "backward",
+ orientation = orientation,
+ startPage = 8,
+ targetPage = 4,
+ dragInOppositeDirection = dragInOppositeDirection,
+ distanceToTargetWhenStartDrag = distanceToTarget,
+ endInSnappedPosition = endInSnappedPosition
+ )
+ )
+ }
}
}.plus(listOf(
TestConfig(
@@ -213,29 +253,4 @@
}
}
-private fun createTestSet(
- orientation: Int,
- dragInOppositeDirection: Boolean,
- distanceToTarget: Float
-): List<TestConfig> {
- return listOf(
- TestConfig(
- title = "forward",
- orientation = orientation,
- startPage = 0,
- targetPage = 4,
- dragInOppositeDirection = dragInOppositeDirection,
- distanceToTargetWhenStartDrag = distanceToTarget
- ),
- TestConfig(
- title = "backward",
- orientation = orientation,
- startPage = 8,
- targetPage = 4,
- dragInOppositeDirection = dragInOppositeDirection,
- distanceToTargetWhenStartDrag = distanceToTarget
- )
- )
-}
-
// endregion
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
new file mode 100644
index 0000000..7b6e5df
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FakeDragTest.kt
@@ -0,0 +1,503 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.viewpager2.widget
+
+import android.graphics.Path
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.Interpolator
+import android.view.animation.LinearInterpolator
+import androidx.core.view.animation.PathInterpolatorCompat
+import androidx.test.filters.LargeTest
+import androidx.viewpager2.LocaleTestUtils
+import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
+import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrollStateChangedEvent
+import androidx.viewpager2.widget.FakeDragTest.Event.OnPageScrolledEvent
+import androidx.viewpager2.widget.FakeDragTest.Event.OnPageSelectedEvent
+import androidx.viewpager2.widget.FakeDragTest.TestConfig
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
+import androidx.viewpager2.widget.swipe.PageSwiperFakeDrag
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.CoreMatchers.notNullValue
+import org.hamcrest.Matchers.greaterThan
+import org.junit.Assert.assertThat
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors.newSingleThreadExecutor
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.TimeUnit.SECONDS
+import kotlin.math.roundToInt
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING as DRAGGING
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE as IDLE
+import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING as SETTLING
+
+@RunWith(Parameterized::class)
+@LargeTest
+class FakeDragTest(private val config: TestConfig) : BaseTest() {
+ data class TestConfig(
+ @ViewPager2.Orientation val orientation: Int,
+ val rtl: Boolean,
+ val enableUserInput: Boolean
+ )
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun spec(): List<TestConfig> = createTestSet()
+ }
+
+ private val pageCount = 10
+ private lateinit var test: Context
+ private lateinit var adapterProvider: AdapterProvider
+ private lateinit var fakeDragger: PageSwiperFakeDrag
+
+ override fun setUp() {
+ super.setUp()
+ if (config.rtl) {
+ localeUtil.resetLocale()
+ localeUtil.setLocale(LocaleTestUtils.RTL_LANGUAGE)
+ }
+ adapterProvider = viewAdapterProvider(stringSequence(pageCount))
+ test = setUpTest(config.orientation).also {
+ fakeDragger = PageSwiperFakeDrag(it.viewPager)
+ it.viewPager.isUserInputEnabled = config.enableUserInput
+ it.setAdapterSync(adapterProvider)
+ it.assertBasicState(0)
+ }
+ }
+
+ @Test
+ fun test_flingToNextPage() {
+ basicFakeDragTest(.2f, 100, 1)
+ }
+
+ @Test
+ fun test_peekNextPage() {
+ basicFakeDragTest(.1f, 200, 0, DecelerateInterpolator())
+ }
+
+ @Test
+ fun test_flingCompletelyToNextPage() {
+ basicFakeDragTest(1f, 100, 1)
+ }
+
+ @Test
+ fun test_peekNextAndMoveBack() {
+ // Roughly interpolates like this:
+ // |
+ // 3 | .-.
+ // | / ',
+ // 1 | / '-.___
+ // |/
+ // 0 +--------------
+ // 0 1
+ basicFakeDragTest(.2f, 500, 0, PathInterpolatorCompat.create(Path().also {
+ it.moveTo(0f, 0f)
+ it.cubicTo(.4f, 6f, .5f, 1f, .8f, 1f)
+ it.lineTo(1f, 1f)
+ }))
+ }
+
+ @Test
+ fun test_dragAlmostToNextPageAndFlingBack() {
+ // Roughly interpolates like this:
+ // |
+ // | .-.
+ // 1 | / '
+ // | /
+ // |/
+ // 0 +-------
+ // 0 1
+ basicFakeDragTest(.7f, 400, 0, PathInterpolatorCompat.create(Path().also {
+ it.moveTo(0f, 0f)
+ it.cubicTo(.4f, 1.3f, .7f, 1.5f, 1f, 1f)
+ }))
+ }
+
+ @Test
+ fun test_startFakeDragDuringManualDrag() {
+ // Skip tests where manual dragging is disabled
+ assumeThat(config.enableUserInput, equalTo(true))
+
+ // start manual drag
+ val latch = test.viewPager.addWaitForStateLatch(DRAGGING)
+ // Perform manual swipe in separate thread, because the SwipeMethod.MANUAL blocks while
+ // injecting events, and we need to interrupt it
+ newSingleThreadExecutor().execute { test.swipeForward(SwipeMethod.MANUAL) }
+ assertThat(latch.await(1, SECONDS), equalTo(true))
+
+ // start fake drag
+ assertThat(test.viewPager.beginFakeDrag(), equalTo(false))
+ }
+
+ @Test
+ fun test_startFakeDragToTargetPageWhileSettling() {
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val targetPage = test.viewPager.currentItem + 1
+ startFakeDragWhileSettling(targetPage, .2f, .2f, targetPage)
+ }
+ }
+
+ @Test
+ fun test_startFakeDragExactlyToTargetPageWhileSettling() {
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val tracker = PositionTracker().also { test.viewPager.registerOnPageChangeCallback(it) }
+ val targetPage = test.viewPager.currentItem + 1
+ startFakeDragWhileSettling(targetPage, .4f,
+ { targetPage - tracker.lastPosition }, targetPage, true)
+ test.viewPager.unregisterOnPageChangeCallback(tracker)
+ }
+ }
+
+ @Test
+ fun test_startFakeDragToNextPageWhileSettling() {
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val targetPage = test.viewPager.currentItem + 1
+ startFakeDragWhileSettling(targetPage, .5f, 1f, targetPage + 1)
+ }
+ }
+
+ @Test
+ fun test_startFakeDragExactlyToNextPageWhileSettling() {
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val tracker = PositionTracker().also { test.viewPager.registerOnPageChangeCallback(it) }
+ val targetPage = test.viewPager.currentItem + 1
+ val nextPage = targetPage + 1
+ startFakeDragWhileSettling(targetPage, .5f,
+ { nextPage - tracker.lastPosition }, nextPage, true)
+ test.viewPager.unregisterOnPageChangeCallback(tracker)
+ }
+ }
+
+ @Test
+ fun test_setCurrentItemDuringFakeDrag() {
+ setCurrentItemDuringFakeDrag(false)
+ }
+
+ @Test
+ fun test_smoothScrollDuringFakeDrag() {
+ setCurrentItemDuringFakeDrag(true)
+ }
+
+ @Test
+ fun test_startManualDragDuringFakeDrag() {
+ // Skip tests where manual dragging is disabled
+ assumeThat(config.enableUserInput, equalTo(true))
+
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val initialPage = test.viewPager.currentItem
+ val expectedFinalPage = initialPage + 1
+ val recorder = test.viewPager.addNewRecordingCallback()
+
+ // start fake drag
+ val fakeDragLatch = test.viewPager.addWaitForDistanceToTarget(expectedFinalPage, .9f)
+ val idleLatch = test.viewPager.addWaitForIdleLatch()
+ fakeDragger.fakeDrag(.5f, 500)
+ assertThat(fakeDragLatch.await(1, SECONDS), equalTo(true))
+
+ // start manual drag
+ test.swipeForward(SwipeMethod.MANUAL)
+ assertThat(idleLatch.await(2, SECONDS), equalTo(true))
+
+ // test assertions
+ test.assertBasicState(expectedFinalPage)
+ recorder.apply {
+ scrollEvents.assertValueSanity(0, pageCount - 1, test.viewPager.pageSize)
+ assertFirstEvents(DRAGGING)
+ assertLastEvents(expectedFinalPage)
+ assertPageSelectedEvents(initialPage, expectedFinalPage)
+ assertStateChanges(
+ listOf(DRAGGING, SETTLING, IDLE),
+ listOf(DRAGGING, IDLE)
+ )
+ }
+
+ test.viewPager.unregisterOnPageChangeCallback(recorder)
+ }
+ }
+
+ private fun basicFakeDragTest(
+ relativeDragDistance: Float,
+ duration: Long,
+ expectedFinalPage: Int,
+ interpolator: Interpolator = LinearInterpolator()
+ ) {
+ val startPage = test.viewPager.currentItem
+ // Run the test two times to verify that state doesn't linger
+ repeat(2) {
+ val initialPage = test.viewPager.currentItem
+ val expectedFinalPageWithOffset = expectedFinalPage + initialPage - startPage
+ val recorder = test.viewPager.addNewRecordingCallback()
+
+ val latch = test.viewPager.addWaitForIdleLatch()
+ fakeDragger.fakeDrag(relativeDragDistance, duration, interpolator)
+ latch.await(2000 + duration, MILLISECONDS)
+
+ // test assertions
+ test.assertBasicState(expectedFinalPageWithOffset)
+ recorder.apply {
+ scrollEvents.assertValueSanity(0, pageCount - 1, test.viewPager.pageSize)
+ assertFirstEvents(DRAGGING)
+ assertLastEvents(expectedFinalPageWithOffset)
+ assertPageSelectedEvents(initialPage, expectedFinalPageWithOffset)
+ assertStateChanges(
+ listOf(DRAGGING, SETTLING, IDLE),
+ listOf(DRAGGING, IDLE)
+ )
+ }
+
+ test.viewPager.unregisterOnPageChangeCallback(recorder)
+ }
+ }
+
+ private fun startFakeDragWhileSettling(
+ settleTarget: Int,
+ settleDistance: Float,
+ dragDistance: Float,
+ expectedFinalPage: Int
+ ) {
+ startFakeDragWhileSettling(settleTarget, settleDistance,
+ { dragDistance }, expectedFinalPage, false)
+ }
+
+ private fun startFakeDragWhileSettling(
+ settleTarget: Int,
+ settleDistance: Float,
+ dragDistance: () -> Float,
+ expectedFinalPage: Int,
+ fakeDragMustEndSnapped: Boolean
+ ) {
+ val initialPage = test.viewPager.currentItem
+ val recorder = test.viewPager.addNewRecordingCallback()
+
+ // start smooth scroll
+ val threshold = 1f - settleDistance
+ val scrollLatch = test.viewPager.addWaitForDistanceToTarget(settleTarget, threshold)
+ test.runOnUiThread { test.viewPager.setCurrentItem(settleTarget, true) }
+ assertThat(scrollLatch.await(1, SECONDS), equalTo(true))
+
+ // start fake drag
+ val idleLatch = test.viewPager.addWaitForIdleLatch()
+ fakeDragger.fakeDrag(dragDistance(), 200)
+ assertThat(idleLatch.await(2, SECONDS), equalTo(true))
+
+ // test assertions
+ test.assertBasicState(expectedFinalPage)
+ recorder.apply {
+ scrollEvents.assertValueSanity(0, pageCount - 1, test.viewPager.pageSize)
+ assertFirstEvents(SETTLING)
+ assertLastEvents(expectedFinalPage)
+ assertPageSelectedEvents(initialPage, settleTarget, expectedFinalPage)
+ if (fakeDragMustEndSnapped) {
+ assertThat("When a fake drag should end in a snapped position, we expect the last" +
+ " scroll event after the FAKE_DRAG event to be snapped. ${dumpEvents()}",
+ expectSettlingAfterState(DRAGGING), equalTo(false))
+ }
+ assertStateChanges(
+ listOf(SETTLING, DRAGGING, SETTLING, IDLE),
+ listOf(SETTLING, DRAGGING, IDLE)
+ )
+ }
+
+ test.viewPager.unregisterOnPageChangeCallback(recorder)
+ }
+
+ private fun setCurrentItemDuringFakeDrag(smoothScroll: Boolean) {
+ val initialPage = test.viewPager.currentItem
+ // start fake drag
+ val latch = test.viewPager.addWaitForStateLatch(DRAGGING)
+ fakeDragger.fakeDrag(.5f, 500)
+ assertThat(latch.await(1, SECONDS), equalTo(true))
+
+ // start smooth scroll
+ doIllegalAction("Cannot change current item when ViewPager2 is fake dragging") {
+ test.viewPager.setCurrentItem(initialPage + 1, smoothScroll)
+ }
+ }
+
+ private fun doIllegalAction(errorMessage: String, action: () -> Unit) {
+ val executionLatch = CountDownLatch(1)
+ var exception: IllegalStateException? = null
+ test.runOnUiThread {
+ try {
+ action()
+ } catch (e: IllegalStateException) {
+ exception = e
+ } finally {
+ executionLatch.countDown()
+ }
+ }
+ assertThat(executionLatch.await(1, SECONDS), equalTo(true))
+ assertThat(exception, notNullValue())
+ assertThat(exception!!.message, equalTo(errorMessage))
+ }
+
+ private fun ViewPager2.addNewRecordingCallback(): RecordingCallback {
+ return RecordingCallback().also { registerOnPageChangeCallback(it) }
+ }
+
+ private sealed class Event {
+ data class OnPageScrolledEvent(
+ val position: Int,
+ val positionOffset: Float,
+ val positionOffsetPixels: Int
+ ) : Event()
+ data class OnPageSelectedEvent(val position: Int) : Event()
+ data class OnPageScrollStateChangedEvent(val state: Int) : Event()
+ }
+
+ private class RecordingCallback : ViewPager2.OnPageChangeCallback() {
+ private val events = mutableListOf<Event>()
+
+ val allEvents get() = events.toList()
+ val scrollEvents get() = events.mapNotNull { it as? OnPageScrolledEvent }
+ val stateEvents get() = events.mapNotNull { it as? OnPageScrollStateChangedEvent }
+ val selectEvents get() = events.mapNotNull { it as? OnPageSelectedEvent }
+
+ val eventCount get() = events.size
+ val firstEvent get() = events.firstOrNull()
+ val lastEvent get() = events.lastOrNull()
+
+ override fun onPageScrolled(
+ position: Int,
+ positionOffset: Float,
+ positionOffsetPixels: Int
+ ) {
+ synchronized(events) {
+ events.add(OnPageScrolledEvent(position, positionOffset, positionOffsetPixels))
+ }
+ }
+
+ override fun onPageSelected(position: Int) {
+ synchronized(events) {
+ events.add(OnPageSelectedEvent(position))
+ }
+ }
+
+ override fun onPageScrollStateChanged(state: Int) {
+ synchronized(events) {
+ events.add(OnPageScrollStateChangedEvent(state))
+ }
+ }
+
+ fun expectSettlingAfterState(state: Int): Boolean {
+ val changeToStateEvent = OnPageScrollStateChangedEvent(state)
+ val lastScrollEvent = events
+ .dropWhile { it != changeToStateEvent }
+ .dropWhile { it !is OnPageScrolledEvent }
+ .takeWhile { it is OnPageScrolledEvent }
+ .lastOrNull() as? OnPageScrolledEvent
+ return lastScrollEvent?.let { it.positionOffsetPixels != 0 } ?: true
+ }
+
+ fun dumpEvents(): String {
+ return events.joinToString("\n- ", "\n(${scrollStateGlossary()})\n- ")
+ }
+ }
+
+ private fun RecordingCallback.assertFirstEvents(expectedFirstState: Int) {
+ assertThat("There should be events", eventCount, greaterThan(0))
+ assertThat("First event should be state change to " +
+ "${scrollStateToString(expectedFirstState)}: ${dumpEvents()}",
+ firstEvent, equalTo(OnPageScrollStateChangedEvent(expectedFirstState) as Event))
+ }
+
+ private fun RecordingCallback.assertLastEvents(expectedFinalPage: Int) {
+ assertThat("Last event should be state change to IDLE: ${dumpEvents()}",
+ lastEvent, equalTo(OnPageScrollStateChangedEvent(IDLE) as Event))
+ assertThat("Scroll events don't end in snapped position: ${dumpEvents()}",
+ scrollEvents.last().positionOffsetPixels, equalTo(0))
+ assertThat("Scroll events don't end at page $expectedFinalPage: ${dumpEvents()}",
+ scrollEvents.last().position, equalTo(expectedFinalPage))
+ }
+
+ private fun RecordingCallback.assertPageSelectedEvents(vararg visitedPages: Int) {
+ val expectedPageSelects = visitedPages.toList().zipWithNext().mapNotNull { pair ->
+ // If visited page is same as previous page, no page selected event should be fired
+ if (pair.first == pair.second) null else pair.second
+ }
+ assertThat("Sequence of selected pages should be $expectedPageSelects: ${dumpEvents()}",
+ selectEvents.map { it.position }, equalTo(expectedPageSelects))
+
+ val settleEvent = OnPageScrollStateChangedEvent(SETTLING)
+ val idleEvent = OnPageScrollStateChangedEvent(IDLE)
+ val events = allEvents
+ events.forEachIndexed { i, event ->
+ if (event is OnPageSelectedEvent) {
+ assertThat("OnPageSelectedEvents cannot be the first or last event: " +
+ dumpEvents(), i, isBetweenInEx(1, eventCount - 1))
+ val isAfterSettleEvent = events[i - 1] == settleEvent
+ val isBeforeIdleEvent = events[i + 1] == idleEvent
+ assertThat("OnPageSelectedEvent at index $i must follow a SETTLE event or precede" +
+ " an IDLE event, but not both: ${dumpEvents()}",
+ isAfterSettleEvent.xor(isBeforeIdleEvent), equalTo(true))
+ }
+ }
+ }
+
+ private fun RecordingCallback.assertStateChanges(
+ statesWithSettling: List<Int>,
+ statesWithoutSettling: List<Int>
+ ) {
+ assertThat(
+ "Unexpected sequence of state changes:" + dumpEvents(),
+ stateEvents.map { it.state },
+ equalTo(
+ if (expectSettlingAfterState(DRAGGING)) {
+ statesWithSettling
+ } else {
+ statesWithoutSettling
+ }
+ )
+ )
+ }
+
+ private fun List<OnPageScrolledEvent>.assertValueSanity(
+ initialPage: Int,
+ otherPage: Int,
+ pageSize: Int
+ ) = forEach {
+ assertThat(it.position, isBetweenInInMinMax(initialPage, otherPage))
+ assertThat(it.positionOffset, isBetweenInEx(0f, 1f))
+ assertThat((it.positionOffset * pageSize).roundToInt(), equalTo(it.positionOffsetPixels))
+ }
+
+ private class PositionTracker : ViewPager2.OnPageChangeCallback() {
+ var lastPosition = 0f
+ override fun onPageScrolled(position: Int, offset: Float, offsetPx: Int) {
+ lastPosition = position + offset
+ }
+ }
+}
+
+private fun createTestSet(): List<TestConfig> {
+ return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
+ listOf(false, true).flatMap { rtl ->
+ listOf(true, false).map { enableUserInput ->
+ TestConfig(orientation, rtl, enableUserInput)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index eae3849..e1e7909 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -25,6 +25,7 @@
import androidx.testutils.PollingCheck
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.LocaleTestUtils
+import androidx.viewpager2.widget.BaseTest.Context.SwipeMethod
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.MarkerEvent
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.OnPageScrollStateChangedEvent
import androidx.viewpager2.widget.PageChangeCallbackTest.Event.OnPageScrolledEvent
@@ -35,6 +36,7 @@
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE
import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_SETTLING
+import androidx.viewpager2.widget.swipe.ViewAdapter
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.CoreMatchers.not
import org.hamcrest.CoreMatchers.nullValue
@@ -45,6 +47,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
+import java.util.concurrent.Executors.newSingleThreadExecutor
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +58,8 @@
class PageChangeCallbackTest(private val config: TestConfig) : BaseTest() {
data class TestConfig(
@ViewPager2.Orientation val orientation: Int,
- val rtl: Boolean
+ val rtl: Boolean,
+ val pageMarginPx: Int
)
companion object {
@@ -72,6 +76,26 @@
}
}
+ private val adapterProvider: AdapterProviderForItems get() {
+ return if (config.pageMarginPx > 0) {
+ { items -> { MarginViewAdapter(config.pageMarginPx, items) } }
+ } else {
+ { items -> { ViewAdapter(items) } }
+ }
+ }
+
+ class MarginViewAdapter(private val margin: Int, items: List<String>) : ViewAdapter(items) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+ val viewHolder = super.onCreateViewHolder(parent, viewType)
+ val lp = viewHolder.itemView.layoutParams as ViewGroup.MarginLayoutParams
+ // Set unequal margins, to prevent symmetry from hiding bugs
+ // Similarly, make sure no margin is an exact multiple of another margin
+ lp.setMargins(margin * 2, margin * 3, margin * 7, margin * 5)
+ viewHolder.itemView.layoutParams = lp
+ return viewHolder
+ }
+ }
+
/*
Sample log to guide the test
@@ -105,7 +129,7 @@
@Test
fun test_swipeBetweenPages() {
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(4)))
+ setAdapterSync(adapterProvider(stringSequence(4)))
listOf(1, 2, 3, 2, 1, 0).forEach { targetPage ->
// given
val initialPage = viewPager.currentItem
@@ -168,7 +192,7 @@
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(totalPages)))
+ setAdapterSync(adapterProvider(stringSequence(totalPages)))
listOf(0, 0, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0).forEach { targetPage ->
// given
val initialPage = viewPager.currentItem
@@ -231,7 +255,7 @@
fun test_peekOnAdjacentPage_next() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(3)))
+ setAdapterSync(adapterProvider(stringSequence(3)))
val callback = viewPager.addNewRecordingCallback()
val latch = viewPager.addWaitForScrolledLatch(0)
@@ -290,7 +314,7 @@
fun test_peekOnAdjacentPage_previous() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(3)))
+ setAdapterSync(adapterProvider(stringSequence(3)))
viewPager.setCurrentItemSync(2, false, 200, MILLISECONDS)
@@ -367,7 +391,7 @@
fun test_selectItemProgrammatically_smoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(1000)))
+ setAdapterSync(adapterProvider(stringSequence(1000)))
// when
listOf(6, 5, 6, 3, 10, 0, 0, 999, 999, 0).forEach { targetPage ->
@@ -408,7 +432,7 @@
fun test_multiplePageChanges() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(10)))
+ setAdapterSync(adapterProvider(stringSequence(10)))
val targetPages = listOf(4, 9)
val callback = viewPager.addNewRecordingCallback()
val latch = viewPager.addWaitForScrolledLatch(targetPages.last(), true)
@@ -458,7 +482,7 @@
fun test_noSmoothScroll_after_smoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(6)))
+ setAdapterSync(adapterProvider(stringSequence(6)))
val targetPage = 4
val marker = 1
val callback = viewPager.addNewRecordingCallback()
@@ -574,7 +598,7 @@
// given
assertThat(targetPage, greaterThanOrEqualTo(4))
setUpTest(config.orientation).apply {
- val adapterProvider = viewAdapterProvider(stringSequence(5))
+ val adapterProvider = adapterProvider(stringSequence(5))
setAdapterSync(adapterProvider)
val marker = 1
val callback = viewPager.addNewRecordingCallback()
@@ -634,7 +658,7 @@
fun test_selectItemProgrammatically_noSmoothScroll() {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(3)))
+ setAdapterSync(adapterProvider(stringSequence(3)))
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -669,7 +693,7 @@
private fun test_selectItemProgrammatically_noCallback(smoothScroll: Boolean) {
// given
setUpTest(config.orientation).apply {
- setAdapterSync(viewAdapterProvider(stringSequence(3)))
+ setAdapterSync(adapterProvider(stringSequence(3)))
// when
listOf(2, 2, 0, 0, 1, 2, 1, 0).forEach { targetPage ->
@@ -709,6 +733,52 @@
}
@Test
+ fun test_getScrollState() {
+ val test = setUpTest(config.orientation)
+ test.setAdapterSync(viewAdapterProvider(stringSequence(5)))
+
+ // Test SCROLL_STATE_SETTLING
+ test_getScrollState(test, SCROLL_STATE_SETTLING, 1) {
+ test.runOnUiThread { test.viewPager.setCurrentItem(1, true) }
+ }
+
+ // Test SCROLL_STATE_DRAGGING (real drag)
+ test_getScrollState(test, SCROLL_STATE_DRAGGING, 2, true) {
+ // Perform manual swipe in separate thread, because the SwipeMethod.MANUAL blocks while
+ // injecting events, and we need to check getScrollState() during the swipe.
+ newSingleThreadExecutor().execute { test.swipeForward(SwipeMethod.MANUAL) }
+ }
+
+ // Test SCROLL_STATE_DRAGGING (fake drag)
+ test_getScrollState(test, SCROLL_STATE_DRAGGING, 3, true) {
+ test.swipeForward(SwipeMethod.FAKE_DRAG)
+ }
+ }
+
+ private fun test_getScrollState(
+ test: Context,
+ @ViewPager2.ScrollState state: Int,
+ expectedTargetPage: Int,
+ checkSettling: Boolean = false,
+ viewPagerAction: () -> Unit
+ ) {
+ val stateLatch = test.viewPager.addWaitForStateLatch(state)
+ val settlingLatch = test.viewPager.addWaitForStateLatch(SCROLL_STATE_SETTLING)
+ val idleLatch = test.viewPager.addWaitForIdleLatch()
+ viewPagerAction()
+ // Wait for onScrollStateChanged
+ assertThat(stateLatch.await(1, SECONDS), equalTo(true))
+ // Check scrollState
+ assertThat(test.viewPager.scrollState, equalTo(state))
+ if (checkSettling) {
+ assertThat(settlingLatch.await(2, SECONDS), equalTo(true))
+ }
+ // Let the animation finish
+ assertThat(idleLatch.await(2, SECONDS), equalTo(true))
+ test.assertBasicState(expectedTargetPage)
+ }
+
+ @Test
fun test_setCurrentItem_noAdapter() {
val test = setUpTest(config.orientation)
assertThat(test.viewPager.adapter, nullValue())
@@ -731,7 +801,7 @@
private fun test_setCurrentItem_outOfBounds(smoothScroll: Boolean) {
val test = setUpTest(config.orientation)
val n = 3
- test.setAdapterSync(viewAdapterProvider(stringSequence(n)))
+ test.setAdapterSync(adapterProvider(stringSequence(n)))
val adapterCount = test.viewPager.adapter!!.itemCount
listOf(-5, -1, n, n + 1, adapterCount, adapterCount + 1).forEach { targetPage ->
@@ -941,8 +1011,10 @@
private fun createTestSet(): List<TestConfig> {
return listOf(ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL).flatMap { orientation ->
- listOf(true, false).map { rtl ->
- TestConfig(orientation, rtl)
+ listOf(true, false).flatMap { rtl ->
+ listOf(0, 10, -10).map { margin ->
+ TestConfig(orientation, rtl, margin)
+ }
}
}
}
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ManualSwipeInjector.java b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ManualSwipeInjector.java
index 4a0f586..e041112 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ManualSwipeInjector.java
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/ManualSwipeInjector.java
@@ -29,9 +29,14 @@
import java.util.List;
/**
- * Performs a swipe on a view from the center of that view to on of its edges.
+ * Performs a swipe on a view from the center of that view to on of its edges. Mostly the same as
+ * Espresso's swipe ViewActions, but since this is not a ViewAction, it is not performed on the UI
+ * thread. It is still synchronous though, with sleeps between the injection of each MotionEvent. If
+ * you need asynchronous injection, run it in a separate thread. Another difference is that this
+ * injector swipes from the center of the targeted View to the center of an edge, instead of from
+ * the center of one edge to the center of another edge.
*
- * Obtain a new instance of this class for each swipe you want to perform, with one of the {@link
+ * <p>Obtain a new instance of this class for each swipe you want to perform, with one of the {@link
* #swipeLeft() swipe methods}. Inject the motion events by calling {@link #perform(Instrumentation,
* View)}.
*/
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt
new file mode 100644
index 0000000..c08b9cb
--- /dev/null
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/PageSwiperFakeDrag.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.viewpager2.widget.swipe
+
+import android.os.SystemClock
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.Interpolator
+import android.view.animation.LinearInterpolator
+import androidx.core.view.ViewCompat
+import androidx.viewpager2.widget.ViewPager2
+import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+class PageSwiperFakeDrag(private val viewPager: ViewPager2) : PageSwiper {
+ companion object {
+ // 60 fps
+ private const val FRAME_LENGTH_MS = 1000L / 60
+ private const val FLING_DURATION_MS = 100L
+ }
+
+ private val ViewPager2.pageSize: Int
+ get() {
+ return if (orientation == ORIENTATION_HORIZONTAL) {
+ measuredWidth - paddingLeft - paddingRight
+ } else {
+ measuredHeight - paddingTop - paddingBottom
+ }
+ }
+
+ private val needsRtlModifier
+ get() = viewPager.orientation == ORIENTATION_HORIZONTAL &&
+ ViewCompat.getLayoutDirection(viewPager) == ViewCompat.LAYOUT_DIRECTION_RTL
+
+ override fun swipeNext() {
+ fakeDrag(.5f, interpolator = AccelerateInterpolator())
+ }
+
+ override fun swipePrevious() {
+ fakeDrag(-.5f, interpolator = AccelerateInterpolator())
+ }
+
+ fun fakeDrag(
+ relativeDragDistance: Float,
+ duration: Long = FLING_DURATION_MS,
+ interpolator: Interpolator = LinearInterpolator()
+ ) {
+ // Generate the deltas to feed to fakeDragBy()
+ val rtlModifier = if (needsRtlModifier) -1 else 1
+ val steps = max(1, (duration / FRAME_LENGTH_MS.toFloat()).roundToInt())
+ val distancePx = viewPager.pageSize * -relativeDragDistance * rtlModifier
+ val deltas = List(steps) { i ->
+ val currDistance = interpolator.getInterpolation((i + 1f) / steps) * distancePx
+ val prevDistance = interpolator.getInterpolation((i + 0f) / steps) * distancePx
+ currDistance - prevDistance
+ }
+
+ // Send the fakeDrag events
+ var eventTime = SystemClock.uptimeMillis()
+ val delayMs = { eventTime - SystemClock.uptimeMillis() }
+ viewPager.post { viewPager.beginFakeDrag() }
+ for (delta in deltas) {
+ eventTime += FRAME_LENGTH_MS
+ viewPager.postDelayed({ viewPager.fakeDragBy(delta) }, delayMs())
+ }
+ eventTime++
+ viewPager.postDelayed({ viewPager.endFakeDrag() }, delayMs())
+ }
+}
\ No newline at end of file
diff --git a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
index f4ed3c6..7715091 100644
--- a/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
+++ b/viewpager2/src/androidTest/java/androidx/viewpager2/widget/swipe/TestActivity.kt
@@ -17,11 +17,11 @@
package androidx.viewpager2.widget.swipe
import android.os.Bundle
-import androidx.testutils.RecreatedActivity
+import androidx.testutils.RecreatedAppCompatActivity
import androidx.viewpager2.LocaleTestUtils
import androidx.viewpager2.test.R
-class TestActivity : RecreatedActivity() {
+class TestActivity : RecreatedAppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent?.hasExtra(EXTRA_LANGUAGE) == true) {
diff --git a/viewpager2/src/androidTest/res/values/styles.xml b/viewpager2/src/androidTest/res/values/styles.xml
index 94e0a86..cb1f73d 100644
--- a/viewpager2/src/androidTest/res/values/styles.xml
+++ b/viewpager2/src/androidTest/res/values/styles.xml
@@ -15,7 +15,7 @@
-->
<resources>
- <style name="TestActivityTheme" parent="android:Theme">
+ <style name="TestActivityTheme" parent="Theme.AppCompat">
<item name="android:windowAnimationStyle">@empty</item>
</style>
</resources>
diff --git a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
index 0af932fc..101a933 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentStateAdapter.java
@@ -20,6 +20,7 @@
import android.os.Parcelable;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
@@ -29,7 +30,6 @@
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.fragment.app.FragmentTransaction;
import androidx.recyclerview.widget.RecyclerView;
/**
@@ -56,6 +56,9 @@
private final LongSparseArray<Fragment> mFragments = new LongSparseArray<>();
private final LongSparseArray<Fragment.SavedState> mSavedStates = new LongSparseArray<>();
+ // Keeps track what ViewHolder was last bound to an item.
+ private final LongSparseArray<Integer> mItemViewHolderMap = new LongSparseArray<>();
+
private final FragmentManager mFragmentManager;
public FragmentStateAdapter(@NonNull FragmentManager fragmentManager) {
@@ -76,13 +79,16 @@
@Override
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
- Fragment fragment = getFragment(position);
- if (holder.mFragment != fragment) {
- /** There is no guarantee that {@link #onViewRecycled} happened since the last
- * {@link #onBindViewHolder}, so performing a clean-up here. */
- removeFragment(holder);
+ final long itemId = holder.getItemId();
+ final int viewHolderId = holder.getContainer().getId();
+ final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
+ if (boundItemId != null && boundItemId != itemId) {
+ removeFragment(boundItemId);
+ mItemViewHolderMap.remove(boundItemId);
}
- holder.mFragment = fragment;
+
+ mItemViewHolderMap.put(itemId, viewHolderId); // this might overwrite an existing entry
+ ensureFragment(position);
/** Special case when {@link RecyclerView} decides to keep the {@link container}
* attached to the window, but not to the view hierarchy (i.e. parent is null) */
@@ -104,22 +110,35 @@
}
}
- private Fragment getFragment(int position) {
- long itemId = getItemId(position);
- Fragment activeFragment = mFragments.get(itemId);
- if (activeFragment != null) {
- return activeFragment;
+ private Long itemForViewHolder(int viewHolderId) {
+ Long boundItemId = null;
+ for (int ix = 0; ix < mItemViewHolderMap.size(); ix++) {
+ if (mItemViewHolderMap.valueAt(ix) == viewHolderId) {
+ if (boundItemId != null) {
+ throw new IllegalStateException("Design assumption violated: "
+ + "a ViewHolder can only be bound to one item at a time.");
+ }
+ boundItemId = mItemViewHolderMap.keyAt(ix);
+ }
}
+ return boundItemId;
+ }
- Fragment newFragment = getItem(position);
- newFragment.setInitialSavedState(mSavedStates.get(itemId));
- mFragments.put(itemId, newFragment);
- return newFragment;
+ private void ensureFragment(int position) {
+ long itemId = getItemId(position);
+ if (!mFragments.containsKey(itemId)) {
+ Fragment newFragment = getItem(position);
+ newFragment.setInitialSavedState(mSavedStates.get(itemId));
+ mFragments.put(itemId, newFragment);
+ }
}
@Override
public final void onViewAttachedToWindow(@NonNull FragmentViewHolder holder) {
- Fragment fragment = holder.mFragment;
+ Fragment fragment = mFragments.get(holder.getItemId());
+ if (fragment == null) {
+ throw new IllegalStateException("Design assumption violated.");
+ }
FrameLayout container = holder.getContainer();
View view = fragment.getView();
@@ -214,7 +233,12 @@
@Override
public final void onViewRecycled(@NonNull FragmentViewHolder holder) {
- removeFragment(holder);
+ final int viewHolderId = holder.getContainer().getId();
+ final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VH
+ if (boundItemId != null) {
+ removeFragment(boundItemId);
+ mItemViewHolderMap.remove(boundItemId);
+ }
}
@Override
@@ -223,41 +247,30 @@
// animation). We don't have sufficient information on how to clear up what lead to
// the transient state, so we are throwing away the ViewHolder to stay on the
// conservative side.
- removeFragment(holder);
+ onViewRecycled(holder); // the same clean-up steps as when recycling a ViewHolder
return false; // don't recycle the view
}
- private void removeFragment(@NonNull FragmentViewHolder holder) {
- removeFragment(holder.mFragment, holder.getItemId());
- holder.getContainer().removeAllViews();
- holder.mFragment = null;
- }
+ private void removeFragment(long itemId) {
+ Fragment fragment = mFragments.get(itemId);
- /**
- * Removes a Fragment and commits the operation.
- */
- private void removeFragment(Fragment fragment, long itemId) {
- FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
- removeFragment(fragment, itemId, fragmentTransaction);
- // TODO(b/122669030): this call might fail, so address with recovery steps
- fragmentTransaction.commitNow();
- }
-
- /**
- * Adds a remove operation to the transaction, but does not commit.
- */
- private void removeFragment(Fragment fragment, long itemId,
- @NonNull FragmentTransaction fragmentTransaction) {
if (fragment == null) {
return;
}
+ if (fragment.getView() != null) {
+ ViewParent viewParent = fragment.getView().getParent();
+ if (viewParent != null) {
+ ((FrameLayout) viewParent).removeAllViews();
+ }
+ }
+
if (fragment.isAdded() && containsItem(itemId)) {
mSavedStates.put(itemId, mFragmentManager.saveFragmentInstanceState(fragment));
}
mFragments.remove(itemId);
- fragmentTransaction.remove(fragment);
+ mFragmentManager.beginTransaction().remove(fragment).commitNow();
}
/**
diff --git a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentViewHolder.java b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentViewHolder.java
index 53291e5..af77111 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentViewHolder.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/adapter/FragmentViewHolder.java
@@ -28,8 +28,6 @@
* {@link FragmentStateAdapter}.
*/
public final class FragmentViewHolder extends ViewHolder {
- Fragment mFragment;
-
private FragmentViewHolder(FrameLayout container) {
super(container);
}
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/FakeDrag.java b/viewpager2/src/main/java/androidx/viewpager2/widget/FakeDrag.java
new file mode 100644
index 0000000..ac9d3c4
--- /dev/null
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/FakeDrag.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.viewpager2.widget;
+
+import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
+
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.UiThread;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Provides fake dragging functionality to {@link ViewPager2}.
+ */
+final class FakeDrag {
+ private final ViewPager2 mViewPager;
+ private final ScrollEventAdapter mScrollEventAdapter;
+ private final RecyclerView mRecyclerView;
+
+ private VelocityTracker mVelocityTracker;
+ private int mMaximumVelocity;
+ private float mRequestedDragDistance;
+ private int mActualDraggedDistance;
+ private long mFakeDragBeginTime;
+
+ FakeDrag(ViewPager2 viewPager, ScrollEventAdapter scrollEventAdapter,
+ RecyclerView recyclerView) {
+ mViewPager = viewPager;
+ mScrollEventAdapter = scrollEventAdapter;
+ mRecyclerView = recyclerView;
+ }
+
+ boolean isFakeDragging() {
+ return mScrollEventAdapter.isFakeDragging();
+ }
+
+ @UiThread
+ boolean beginFakeDrag() {
+ if (mScrollEventAdapter.isDragging()) {
+ return false;
+ }
+ mRequestedDragDistance = mActualDraggedDistance = 0;
+ mFakeDragBeginTime = SystemClock.uptimeMillis();
+ beginFakeVelocityTracker();
+
+ mScrollEventAdapter.notifyBeginFakeDrag();
+ if (!mScrollEventAdapter.isIdle()) {
+ // Stop potentially running settling animation
+ mRecyclerView.stopScroll();
+ }
+ addFakeMotionEvent(mFakeDragBeginTime, MotionEvent.ACTION_DOWN, 0, 0);
+ return true;
+ }
+
+ @UiThread
+ boolean fakeDragBy(float offsetPxFloat) {
+ if (!mScrollEventAdapter.isFakeDragging()) {
+ // Can happen legitimately if user started dragging during fakeDrag and app is still
+ // sending fakeDragBy commands
+ return false;
+ }
+ // Subtract the offset, because content scrolls in the opposite direction of finger motion
+ mRequestedDragDistance -= offsetPxFloat;
+ // Calculate amount of pixels to scroll ...
+ int offsetPx = Math.round(mRequestedDragDistance - mActualDraggedDistance);
+ // ... and keep track of pixels scrolled so we don't get rounding errors
+ mActualDraggedDistance += offsetPx;
+ long time = SystemClock.uptimeMillis();
+
+ boolean isHorizontal = mViewPager.getOrientation() == ORIENTATION_HORIZONTAL;
+ // Scroll deltas use pixels:
+ final int offsetX = isHorizontal ? offsetPx : 0;
+ final int offsetY = isHorizontal ? 0 : offsetPx;
+ // Motion events get the raw float distance:
+ final float x = isHorizontal ? mRequestedDragDistance : 0;
+ final float y = isHorizontal ? 0 : mRequestedDragDistance;
+
+ mRecyclerView.scrollBy(offsetX, offsetY);
+ addFakeMotionEvent(time, MotionEvent.ACTION_MOVE, x, y);
+ return true;
+ }
+
+ @UiThread
+ boolean endFakeDrag() {
+ if (!mScrollEventAdapter.isFakeDragging()) {
+ // Happens legitimately if user started dragging during fakeDrag
+ return false;
+ }
+
+ mScrollEventAdapter.notifyEndFakeDrag();
+
+ // Compute the velocity of the fake drag
+ final int pixelsPerSecond = 1000;
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(pixelsPerSecond, mMaximumVelocity);
+ int xVelocity = (int) velocityTracker.getXVelocity();
+ int yVelocity = (int) velocityTracker.getYVelocity();
+ // And fling or snap the ViewPager2 to its destination
+ if (!mRecyclerView.fling(xVelocity, yVelocity)) {
+ // Velocity too low, trigger snap to page manually
+ mViewPager.snapToPage();
+ }
+ return true;
+ }
+
+ private void beginFakeVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ final ViewConfiguration configuration = ViewConfiguration.get(mViewPager.getContext());
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ } else {
+ mVelocityTracker.clear();
+ }
+ }
+
+ private void addFakeMotionEvent(long time, int action, float x, float y) {
+ final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, action, x, y, 0);
+ mVelocityTracker.addMovement(ev);
+ ev.recycle();
+ }
+}
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
index 5eadede..3e2b758 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ScrollEventAdapter.java
@@ -54,7 +54,7 @@
@Retention(SOURCE)
@IntDef({STATE_IDLE, STATE_IN_PROGRESS_MANUAL_DRAG, STATE_IN_PROGRESS_SMOOTH_SCROLL,
- STATE_IN_PROGRESS_IMMEDIATE_SCROLL})
+ STATE_IN_PROGRESS_IMMEDIATE_SCROLL, STATE_IN_PROGRESS_FAKE_DRAG})
private @interface AdapterState {
}
@@ -62,6 +62,7 @@
private static final int STATE_IN_PROGRESS_MANUAL_DRAG = 1;
private static final int STATE_IN_PROGRESS_SMOOTH_SCROLL = 2;
private static final int STATE_IN_PROGRESS_IMMEDIATE_SCROLL = 3;
+ private static final int STATE_IN_PROGRESS_FAKE_DRAG = 4;
private static final int NO_POSITION = -1;
@@ -76,6 +77,7 @@
private int mTarget;
private boolean mDispatchSelected;
private boolean mScrollHappened;
+ private boolean mFakeDragging;
ScrollEventAdapter(@NonNull LinearLayoutManager layoutManager) {
mLayoutManager = layoutManager;
@@ -91,6 +93,7 @@
mTarget = NO_POSITION;
mDispatchSelected = false;
mScrollHappened = false;
+ mFakeDragging = false;
}
/**
@@ -102,31 +105,15 @@
// User started a drag (not dragging -> dragging)
if (mAdapterState != STATE_IN_PROGRESS_MANUAL_DRAG
&& newState == RecyclerView.SCROLL_STATE_DRAGGING) {
- // Remember we're performing a drag
- mAdapterState = STATE_IN_PROGRESS_MANUAL_DRAG;
- if (mTarget != NO_POSITION) {
- // Target was set means programmatic scroll was in progress
- // Update "drag start page" to reflect the page that ViewPager2 thinks it is at
- mDragStartPosition = mTarget;
- // Reset target because drags have no target until released
- mTarget = NO_POSITION;
- } else {
- // ViewPager2 was at rest, set "drag start page" to current page
- mDragStartPosition = getPosition();
- }
- dispatchStateChanged(SCROLL_STATE_DRAGGING);
+ startDrag(false);
return;
}
// Drag is released, RecyclerView is snapping to page (dragging -> settling)
// Note that mAdapterState is not updated, to remember we were dragging when settling
- if (mAdapterState == STATE_IN_PROGRESS_MANUAL_DRAG
- && newState == RecyclerView.SCROLL_STATE_SETTLING) {
- if (!mScrollHappened) {
- // Pages didn't move during drag, so must be at the start or end of the list
- // ViewPager's contract requires at least one scroll event though
- dispatchScrolled(getPosition(), 0f, 0);
- } else {
+ if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_SETTLING) {
+ // Only go through the settling phase if the drag actually moved the page
+ if (mScrollHappened) {
dispatchStateChanged(SCROLL_STATE_SETTLING);
// Determine target page and dispatch onPageSelected on next scroll event
mDispatchSelected = true;
@@ -135,34 +122,29 @@
}
// Drag is finished (dragging || settling -> idle)
- if (mAdapterState == STATE_IN_PROGRESS_MANUAL_DRAG
- && newState == RecyclerView.SCROLL_STATE_IDLE) {
- if (mScrollState == SCROLL_STATE_DRAGGING && mScrollValues.mOffsetPx == 0) {
- // When going from dragging to idle, we skipped the settling phase.
- // Depending on whether mOffsetPx is 0 or not, PagerSnapHelper will kick in or not.
- // If it won't, do the necessary bookkeeping before going to idle.
- if (!mScrollHappened) {
- dispatchScrolled(getPosition(), 0f, 0);
- } else {
- // Don't dispatch settling event
- mDispatchSelected = true;
- }
- } else if (mScrollState == SCROLL_STATE_SETTLING && !mScrollHappened) {
- throw new IllegalStateException("RecyclerView sent SCROLL_STATE_SETTLING event "
- + "without scrolling any further before going to SCROLL_STATE_IDLE");
- }
- // Special case if we were snapped at a page when going from dragging to settling
- // Happens if there was no velocity or if it was the first or last page
- if (mDispatchSelected) {
- // Fire onPageSelected when snapped page is different from initial position
- // E.g.: smooth scroll from 0 to 1, interrupt with drag at 0.5, release at 0
- updateScrollEventValues();
+ if (isInAnyDraggingState() && newState == RecyclerView.SCROLL_STATE_IDLE) {
+ boolean dispatchIdle = false;
+ updateScrollEventValues();
+ if (!mScrollHappened) {
+ // Pages didn't move during drag, so must be at the start or end of the list
+ // ViewPager's contract requires at least one scroll event though
+ dispatchScrolled(getPosition(), 0f, 0);
+ dispatchIdle = true;
+ } else if (mScrollValues.mOffsetPx == 0) {
+ // Normally we dispatch the selected page and go to idle in onScrolled when
+ // mOffsetPx == 0, but in this case the drag was still ongoing when onScrolled was
+ // called, so that didn't happen. And since mOffsetPx == 0, there will be no further
+ // scroll events, so fire the onPageSelected event and go to idle now.
+ // Note that if we _did_ go to idle in that last onScrolled event, this code will
+ // not be executed because mAdapterState has been reset to STATE_IDLE.
+ dispatchIdle = true;
if (mDragStartPosition != mScrollValues.mPosition) {
dispatchSelected(mScrollValues.mPosition);
}
}
- if (!mScrollHappened || mDispatchSelected) {
- // Normally idle is fired in onScrolled, but scroll did not happen
+ if (dispatchIdle) {
+ // Normally idle is fired in last onScrolled call, but either onScrolled was never
+ // called, or we were still dragging when the last onScrolled was called
dispatchStateChanged(SCROLL_STATE_IDLE);
resetState();
}
@@ -176,7 +158,7 @@
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
mScrollHappened = true;
- ScrollEventValues values = updateScrollEventValues();
+ updateScrollEventValues();
if (mDispatchSelected) {
// Drag started settling, need to calculate target page and dispatch onPageSelected now
@@ -185,17 +167,19 @@
// "&& values.mOffsetPx != 0": filters special case where we're scrolling forward and
// the first scroll event after settling already got us at the target
- mTarget = scrollingForward && values.mOffsetPx != 0
- ? values.mPosition + 1 : values.mPosition;
+ mTarget = scrollingForward && mScrollValues.mOffsetPx != 0
+ ? mScrollValues.mPosition + 1 : mScrollValues.mPosition;
if (mDragStartPosition != mTarget) {
dispatchSelected(mTarget);
}
}
- dispatchScrolled(values.mPosition, values.mOffset, values.mOffsetPx);
+ dispatchScrolled(mScrollValues.mPosition, mScrollValues.mOffset, mScrollValues.mOffsetPx);
- if ((values.mPosition == mTarget || mTarget == NO_POSITION) && values.mOffsetPx == 0
- && mScrollState != SCROLL_STATE_DRAGGING) {
+ // Dispatch idle in onScrolled instead of in onScrollStateChanged because RecyclerView
+ // doesn't send IDLE event when using setCurrentItem(x, false)
+ if ((mScrollValues.mPosition == mTarget || mTarget == NO_POSITION)
+ && mScrollValues.mOffsetPx == 0 && !(mScrollState == SCROLL_STATE_DRAGGING)) {
// When the target page is reached and the user is not dragging anymore, we're settled,
// so go to idle.
// Special case and a bit of a hack when mTarget == NO_POSITION: RecyclerView is being
@@ -211,16 +195,18 @@
* Calculates the current position and the offset (as a percentage and in pixels) of that
* position from the center.
*/
- private ScrollEventValues updateScrollEventValues() {
+ private void updateScrollEventValues() {
ScrollEventValues values = mScrollValues;
values.mPosition = mLayoutManager.findFirstVisibleItemPosition();
if (values.mPosition == RecyclerView.NO_POSITION) {
- return values.reset();
+ values.reset();
+ return;
}
View firstVisibleView = mLayoutManager.findViewByPosition(values.mPosition);
if (firstVisibleView == null) {
- return values.reset();
+ values.reset();
+ return;
}
// TODO(123350297): automated test for this
@@ -232,14 +218,14 @@
boolean isHorizontal = mLayoutManager.getOrientation() == ORIENTATION_HORIZONTAL;
int start, sizePx;
if (isHorizontal) {
- sizePx = firstVisibleView.getWidth();
+ sizePx = firstVisibleView.getWidth() + margin.leftMargin + margin.rightMargin;
if (!isLayoutRTL()) {
start = firstVisibleView.getLeft() - margin.leftMargin;
} else {
- start = sizePx - firstVisibleView.getRight() + margin.rightMargin;
+ start = sizePx - firstVisibleView.getRight() - margin.rightMargin;
}
} else {
- sizePx = firstVisibleView.getHeight();
+ sizePx = firstVisibleView.getHeight() + margin.topMargin + margin.bottomMargin;
start = firstVisibleView.getTop() - margin.topMargin;
}
@@ -249,7 +235,22 @@
+ "positive amount, not by %d", values.mOffsetPx));
}
values.mOffset = sizePx == 0 ? 0 : (float) values.mOffsetPx / sizePx;
- return values;
+ }
+
+ private void startDrag(boolean isFakeDrag) {
+ mFakeDragging = isFakeDrag;
+ mAdapterState = isFakeDrag ? STATE_IN_PROGRESS_FAKE_DRAG : STATE_IN_PROGRESS_MANUAL_DRAG;
+ if (mTarget != NO_POSITION) {
+ // Target was set means programmatic scroll was in progress
+ // Update "drag start page" to reflect the page that ViewPager2 thinks it is at
+ mDragStartPosition = mTarget;
+ // Reset target because drags have no target until released
+ mTarget = NO_POSITION;
+ } else {
+ // ViewPager2 was at rest, set "drag start page" to current page
+ mDragStartPosition = getPosition();
+ }
+ dispatchStateChanged(SCROLL_STATE_DRAGGING);
}
/**
@@ -268,7 +269,7 @@
}
/**
- * Let the adapter know that mCurrentItem was restored in onRestoreInstanceState
+ * Let the adapter know that mCurrentItem was restored in onRestoreInstanceState.
*/
void notifyRestoreCurrentItem(int currentItem) {
// Don't send page selected event for page 0 for consistency with ViewPager
@@ -277,6 +278,37 @@
}
}
+ /**
+ * Let the adapter know that a fake drag has started.
+ */
+ void notifyBeginFakeDrag() {
+ mAdapterState = STATE_IN_PROGRESS_FAKE_DRAG;
+ startDrag(true);
+ }
+
+ /**
+ * Let the adapter know that a fake drag has ended.
+ */
+ void notifyEndFakeDrag() {
+ if (isDragging() && !mFakeDragging) {
+ // Real drag has already taken over, no need to post process the fake drag
+ return;
+ }
+ mFakeDragging = false;
+ updateScrollEventValues();
+ if (mScrollValues.mOffsetPx == 0) {
+ // We're snapped, so dispatch an IDLE event
+ if (mScrollValues.mPosition != mDragStartPosition) {
+ dispatchSelected(mScrollValues.mPosition);
+ }
+ dispatchStateChanged(SCROLL_STATE_IDLE);
+ resetState();
+ } else {
+ // We're not snapped, so dispatch a SETTLING event
+ dispatchStateChanged(SCROLL_STATE_SETTLING);
+ }
+ }
+
private boolean isLayoutRTL() {
return mLayoutManager.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@@ -285,11 +317,41 @@
mCallback = callback;
}
+ int getScrollState() {
+ return mScrollState;
+ }
+
/**
- * @return true if there is no known scroll in progress
+ * @return {@code true} if there is no known scroll in progress
*/
boolean isIdle() {
- return mAdapterState == STATE_IDLE;
+ return mScrollState == SCROLL_STATE_IDLE;
+ }
+
+ /**
+ * @return {@code true} if the ViewPager2 is being dragged. Returns {@code false} from the
+ * moment the ViewPager2 starts settling or goes idle.
+ */
+ boolean isDragging() {
+ return mScrollState == SCROLL_STATE_DRAGGING;
+ }
+
+ /**
+ * @return {@code true} if a fake drag is ongoing. Returns {@code false} from the moment the
+ * {@link ViewPager2#endFakeDrag()} is called.
+ */
+ boolean isFakeDragging() {
+ return mFakeDragging;
+ }
+
+ /**
+ * Checks if the adapter state (not the scroll state) is in the manual or fake dragging state.
+ * @return {@code true} if {@link #mAdapterState} is either {@link
+ * #STATE_IN_PROGRESS_MANUAL_DRAG} or {@link #STATE_IN_PROGRESS_FAKE_DRAG}
+ */
+ private boolean isInAnyDraggingState() {
+ return mAdapterState == STATE_IN_PROGRESS_MANUAL_DRAG
+ || mAdapterState == STATE_IN_PROGRESS_FAKE_DRAG;
}
/**
@@ -351,11 +413,10 @@
ScrollEventValues() {
}
- ScrollEventValues reset() {
+ void reset() {
mPosition = RecyclerView.NO_POSITION;
mOffset = 0f;
mOffsetPx = 0;
- return this;
}
}
}
diff --git a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
index 5c7fb00..13a3cfd 100644
--- a/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
+++ b/viewpager2/src/main/java/androidx/viewpager2/widget/ViewPager2.java
@@ -86,7 +86,9 @@
int mCurrentItem;
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
+ private PagerSnapHelper mPagerSnapHelper;
private ScrollEventAdapter mScrollEventAdapter;
+ private FakeDrag mFakeDragger;
private PageTransformerAdapter mPageTransformerAdapter;
private CompositeOnPageChangeCallback mPageChangeEventDispatcher;
private boolean mUserInputEnabled = true;
@@ -124,9 +126,16 @@
mRecyclerView.setLayoutParams(
new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());
- new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
+ // Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
+ // attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
mScrollEventAdapter = new ScrollEventAdapter(mLayoutManager);
+ // Create FakeDrag before attaching PagerSnapHelper, same reason as above
+ mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
+ mPagerSnapHelper = new PagerSnapHelperImpl();
+ mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
+ // Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
+ // don't want to respond on the events sent out during the attach process
mRecyclerView.addOnScrollListener(mScrollEventAdapter);
mPageChangeEventDispatcher = new CompositeOnPageChangeCallback(3);
@@ -388,7 +397,7 @@
}
/**
- * @param orientation @{link {@link ViewPager2.Orientation}}
+ * @param orientation {@link ViewPager2.Orientation}
*/
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
@@ -421,6 +430,10 @@
* @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
*/
public void setCurrentItem(int item, boolean smoothScroll) {
+ if (isFakeDragging()) {
+ throw new IllegalStateException("Cannot change current item when ViewPager2 is fake "
+ + "dragging");
+ }
Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0) {
return;
@@ -473,6 +486,109 @@
}
/**
+ * Returns the current scroll state of the ViewPager2. Returned value is one of can be one of
+ * {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
+ *
+ * @return The scroll state that was last dispatched to {@link
+ * OnPageChangeCallback#onPageScrollStateChanged(int)}
+ */
+ @ScrollState
+ public int getScrollState() {
+ return mScrollEventAdapter.getScrollState();
+ }
+
+ /**
+ * Start a fake drag of the pager.
+ *
+ * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager2 with the
+ * touch scrolling of another view, while still letting the ViewPager2 control the snapping
+ * motion and fling behavior. (e.g. parallax-scrolling tabs.) Call {@link #fakeDragBy(float)} to
+ * simulate the actual drag motion. Call {@link #endFakeDrag()} to complete the fake drag and
+ * fling as necessary.
+ *
+ * <p>A fake drag can be interrupted by a real drag. From that point on, all calls to {@code
+ * fakeDragBy} and {@code endFakeDrag} will be ignored until the next fake drag is started by
+ * calling {@code beginFakeDrag}. If you need the ViewPager2 to ignore touch events and other
+ * user input during a fake drag, use {@link #setUserInputEnabled(boolean)}. If a real or fake
+ * drag is already in progress, this method will return {@code false}.
+ *
+ * @return {@code true} if the fake drag began successfully, {@code false} if it could not be
+ * started
+ *
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ * @see #isFakeDragging()
+ */
+ public boolean beginFakeDrag() {
+ return mFakeDragger.beginFakeDrag();
+ }
+
+ /**
+ * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. Drag
+ * happens in the direction of the orientation. Positive offsets will drag to the previous page,
+ * negative values to the next page, with one exception: if layout direction is set to RTL and
+ * the ViewPager2's orientation is horizontal, then the behavior will be inverted. This matches
+ * the deltas of touch events that would cause the same real drag.
+ *
+ * <p>If the pager is not in the fake dragging state anymore, it ignores this call and returns
+ * {@code false}.
+ *
+ * @param offsetPxFloat Offset in pixels to drag by
+ * @return {@code true} if the fake drag was executed. If {@code false} is returned, it means
+ * there was no fake drag to end.
+ *
+ * @see #beginFakeDrag()
+ * @see #endFakeDrag()
+ * @see #isFakeDragging()
+ */
+ public boolean fakeDragBy(float offsetPxFloat) {
+ return mFakeDragger.fakeDragBy(offsetPxFloat);
+ }
+
+ /**
+ * End a fake drag of the pager.
+ *
+ * @return {@code true} if the fake drag was ended. If {@code false} is returned, it means there
+ * was no fake drag to end.
+ *
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ * @see #isFakeDragging()
+ */
+ public boolean endFakeDrag() {
+ return mFakeDragger.endFakeDrag();
+ }
+
+ /**
+ * Returns {@code true} if a fake drag is in progress.
+ *
+ * @return {@code true} if currently in a fake drag, {@code false} otherwise.
+ * @see #beginFakeDrag()
+ * @see #fakeDragBy(float)
+ * @see #endFakeDrag()
+ */
+ public boolean isFakeDragging() {
+ return mFakeDragger.isFakeDragging();
+ }
+
+ /**
+ * Snaps the ViewPager2 to the closest page
+ */
+ void snapToPage() {
+ // Method copied from PagerSnapHelper#snapToTargetExistingView
+ // When fixing something here, make sure to update that method as well
+ View view = mPagerSnapHelper.findSnapView(mLayoutManager);
+ if (view == null) {
+ return;
+ }
+ int[] snapDistance = mPagerSnapHelper.calculateDistanceToFinalSnap(mLayoutManager, view);
+ //noinspection ConstantConditions
+ if (snapDistance[0] != 0 || snapDistance[1] != 0) {
+ mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
+ }
+ }
+
+ /**
* Enable or disable user initiated scrolling. This includes touch input (scroll and fling
* gestures) and accessibility input. Disabling keyboard input is not yet supported. When user
* initiated scrolling is disabled, programmatic scrolls through {@link #setCurrentItem(int,
@@ -599,6 +715,21 @@
}
}
+ private class PagerSnapHelperImpl extends PagerSnapHelper {
+ PagerSnapHelperImpl() {
+ }
+
+ @Nullable
+ @Override
+ public View findSnapView(RecyclerView.LayoutManager layoutManager) {
+ // When interrupting a smooth scroll with a fake drag, we stop RecyclerView's scroll
+ // animation, which fires a scroll state change to IDLE. PagerSnapHelper then kicks in
+ // to snap to a page, which we need to prevent here.
+ // Simplifying that case: during a fake drag, no snapping should occur.
+ return isFakeDragging() ? null : super.findSnapView(layoutManager);
+ }
+ }
+
private static class SmoothScrollToPosition implements Runnable {
private final int mPosition;
private final RecyclerView mRecyclerView;
@@ -641,9 +772,10 @@
}
/**
- * Called when the scroll state changes. Useful for discovering when the user
- * begins dragging, when the pager is automatically settling to the current page,
- * or when it is fully stopped/idle.
+ * Called when the scroll state changes. Useful for discovering when the user begins
+ * dragging, when a fake drag is started, when the pager is automatically settling to the
+ * current page, or when it is fully stopped/idle. {@code state} can be one of {@link
+ * #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
*/
public void onPageScrollStateChanged(@ScrollState int state) {
}
diff --git a/wear/src/main/java/androidx/wear/widget/BoxInsetLayout.java b/wear/src/main/java/androidx/wear/widget/BoxInsetLayout.java
index a838ae3..e43e2bf 100644
--- a/wear/src/main/java/androidx/wear/widget/BoxInsetLayout.java
+++ b/wear/src/main/java/androidx/wear/widget/BoxInsetLayout.java
@@ -389,7 +389,7 @@
* See {@link R.styleable#BoxInsetLayout_Layout BoxInsetLayout Layout Attributes} for a list
* of all child view attributes that this class supports.
*
- * {@link R.attr#Layout_boxedEdges}
+ * {@link androidx.wear.R.attr#boxedEdges}
*/
public static class LayoutParams extends FrameLayout.LayoutParams {
diff --git a/wear/src/main/java/androidx/wear/widget/RoundedDrawable.java b/wear/src/main/java/androidx/wear/widget/RoundedDrawable.java
index 0c65ccf..52d7151 100644
--- a/wear/src/main/java/androidx/wear/widget/RoundedDrawable.java
+++ b/wear/src/main/java/androidx/wear/widget/RoundedDrawable.java
@@ -118,7 +118,7 @@
* Sets the drawable to be rendered.
*
* @param drawable {@link Drawable} to be rendered
- * {@link androidx.wear.R.attr#android_src}
+ * {@link android.R.attr#src}
*/
public void setDrawable(@Nullable Drawable drawable) {
if (Objects.equals(mDrawable, drawable)) {
diff --git a/webkit/api/1.1.0-alpha01.txt b/webkit/api/1.1.0-alpha01.txt
index 0febf69..8d18f7e 100644
--- a/webkit/api/1.1.0-alpha01.txt
+++ b/webkit/api/1.1.0-alpha01.txt
@@ -1,6 +1,30 @@
// Signature format: 3.0
package androidx.webkit {
+ public class ProxyConfig {
+ field public static final String DIRECT = "direct://";
+ field public static final String MATCH_ALL_SCHEMES = "*";
+ field public static final String MATCH_HTTP = "http";
+ field public static final String MATCH_HTTPS = "https";
+ }
+
+ public static final class ProxyConfig.Builder {
+ ctor public ProxyConfig.Builder();
+ ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+ method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+ method public androidx.webkit.ProxyConfig build();
+ method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+ method public androidx.webkit.ProxyConfig.Builder subtractImplicitRules();
+ }
+
+ public abstract class ProxyController {
+ method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+ method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+ }
+
public abstract class SafeBrowsingResponseCompat {
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
@@ -98,6 +122,25 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings!, boolean);
}
+ public class WebViewAssetLoader {
+ method public android.net.Uri? getAssetsHttpPrefix();
+ method public android.net.Uri getAssetsHttpsPrefix();
+ method public android.net.Uri? getResourcesHttpPrefix();
+ method public android.net.Uri getResourcesHttpsPrefix();
+ method @RequiresApi(21) public android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+ method public android.webkit.WebResourceResponse? shouldInterceptRequest(String);
+ field public static final String KNOWN_UNUSED_AUTHORITY = "appassets.androidplatform.net";
+ }
+
+ public static final class WebViewAssetLoader.Builder {
+ ctor public WebViewAssetLoader.Builder(android.content.Context);
+ method public androidx.webkit.WebViewAssetLoader.Builder allowHttp();
+ method public androidx.webkit.WebViewAssetLoader build();
+ method public androidx.webkit.WebViewAssetLoader.Builder setAssetsHostingPath(String);
+ method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+ method public androidx.webkit.WebViewAssetLoader.Builder setResourcesHostingPath(String);
+ }
+
public class WebViewClientCompat extends android.webkit.WebViewClient {
ctor public WebViewClientCompat();
method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
@@ -112,13 +155,13 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderer? getWebViewRenderer(android.webkit.WebView);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRendererClient? getWebViewRendererClient(android.webkit.WebView);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String>, android.webkit.ValueCallback<java.lang.Boolean>?);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRendererClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRendererClient);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRendererClient(android.webkit.WebView, androidx.webkit.WebViewRendererClient?);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>?);
}
@@ -135,6 +178,7 @@
field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+ field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
@@ -165,14 +209,15 @@
field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
}
- public abstract class WebViewRenderer {
+ public abstract class WebViewRenderProcess {
+ ctor public WebViewRenderProcess();
method public abstract boolean terminate();
}
- public abstract class WebViewRendererClient {
- ctor public WebViewRendererClient();
- method public abstract void onRendererResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderer?);
- method public abstract void onRendererUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderer?);
+ public abstract class WebViewRenderProcessClient {
+ ctor public WebViewRenderProcessClient();
+ method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+ method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
}
}
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
index 0febf69..8d18f7e 100644
--- a/webkit/api/current.txt
+++ b/webkit/api/current.txt
@@ -1,6 +1,30 @@
// Signature format: 3.0
package androidx.webkit {
+ public class ProxyConfig {
+ field public static final String DIRECT = "direct://";
+ field public static final String MATCH_ALL_SCHEMES = "*";
+ field public static final String MATCH_HTTP = "http";
+ field public static final String MATCH_HTTPS = "https";
+ }
+
+ public static final class ProxyConfig.Builder {
+ ctor public ProxyConfig.Builder();
+ ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+ method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+ method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+ method public androidx.webkit.ProxyConfig build();
+ method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+ method public androidx.webkit.ProxyConfig.Builder subtractImplicitRules();
+ }
+
+ public abstract class ProxyController {
+ method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+ method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+ }
+
public abstract class SafeBrowsingResponseCompat {
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
@@ -98,6 +122,25 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings!, boolean);
}
+ public class WebViewAssetLoader {
+ method public android.net.Uri? getAssetsHttpPrefix();
+ method public android.net.Uri getAssetsHttpsPrefix();
+ method public android.net.Uri? getResourcesHttpPrefix();
+ method public android.net.Uri getResourcesHttpsPrefix();
+ method @RequiresApi(21) public android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+ method public android.webkit.WebResourceResponse? shouldInterceptRequest(String);
+ field public static final String KNOWN_UNUSED_AUTHORITY = "appassets.androidplatform.net";
+ }
+
+ public static final class WebViewAssetLoader.Builder {
+ ctor public WebViewAssetLoader.Builder(android.content.Context);
+ method public androidx.webkit.WebViewAssetLoader.Builder allowHttp();
+ method public androidx.webkit.WebViewAssetLoader build();
+ method public androidx.webkit.WebViewAssetLoader.Builder setAssetsHostingPath(String);
+ method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+ method public androidx.webkit.WebViewAssetLoader.Builder setResourcesHostingPath(String);
+ }
+
public class WebViewClientCompat extends android.webkit.WebViewClient {
ctor public WebViewClientCompat();
method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
@@ -112,13 +155,13 @@
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderer? getWebViewRenderer(android.webkit.WebView);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRendererClient? getWebViewRendererClient(android.webkit.WebView);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String>, android.webkit.ValueCallback<java.lang.Boolean>?);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRendererClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRendererClient);
- method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRendererClient(android.webkit.WebView, androidx.webkit.WebViewRendererClient?);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+ method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>?);
}
@@ -135,6 +178,7 @@
field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+ field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
@@ -165,14 +209,15 @@
field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
}
- public abstract class WebViewRenderer {
+ public abstract class WebViewRenderProcess {
+ ctor public WebViewRenderProcess();
method public abstract boolean terminate();
}
- public abstract class WebViewRendererClient {
- ctor public WebViewRendererClient();
- method public abstract void onRendererResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderer?);
- method public abstract void onRendererUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderer?);
+ public abstract class WebViewRenderProcessClient {
+ ctor public WebViewRenderProcessClient();
+ method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+ method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
}
}
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
index 88d4368..279d00e 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
@@ -102,7 +102,8 @@
private void setProxyOverride(String proxyUrl) {
ProxyController proxyController = ProxyController.getInstance();
ProxyConfig proxyConfig = new ProxyConfig.Builder().addProxyRule(proxyUrl).build();
- proxyController.setProxyOverride(proxyConfig, () -> onProxyOverrideComplete());
+ proxyController.setProxyOverride(proxyConfig, (Runnable r) -> r.run(),
+ () -> onProxyOverrideComplete());
}
private void onProxyOverrideComplete() {
diff --git a/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
index 841a555..dd91e2a 100644
--- a/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
@@ -30,6 +30,7 @@
import org.junit.runner.RunWith;
import java.io.IOException;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import okhttp3.mockwebserver.MockWebServer;
@@ -103,7 +104,7 @@
// Localhost should use proxy with loopback rule
setProxyOverrideSync(new ProxyConfig.Builder()
.addProxyRule(proxyUrl)
- .doProxyLoopbackRequests()
+ .subtractImplicitRules()
.build());
mWebViewOnUiThread.loadUrl(contentUrl);
assertNotNull(mProxyServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS,
@@ -223,19 +224,20 @@
private void setProxyOverrideSync(final ProxyConfig proxyRules) {
final ResolvableFuture<Void> future = ResolvableFuture.create();
- ProxyController.getInstance().setProxyOverride(proxyRules, new Runnable() {
- @Override
- public void run() {
- future.set(null);
- }
- });
+ ProxyController.getInstance().setProxyOverride(proxyRules, new SynchronousExecutor(),
+ new Runnable() {
+ @Override
+ public void run() {
+ future.set(null);
+ }
+ });
// This future is used to ensure that setProxyOverride's callback was called
WebkitUtils.waitForFuture(future);
}
private void clearProxyOverrideSync() {
final ResolvableFuture<Void> future = ResolvableFuture.create();
- ProxyController.getInstance().clearProxyOverride(new Runnable() {
+ ProxyController.getInstance().clearProxyOverride(new SynchronousExecutor(), new Runnable() {
@Override
public void run() {
future.set(null);
@@ -244,4 +246,11 @@
// This future is used to ensure that clearProxyOverride's callback was called
WebkitUtils.waitForFuture(future);
}
+
+ static class SynchronousExecutor implements Executor {
+ @Override
+ public void execute(Runnable r) {
+ r.run();
+ }
+ }
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
index 763bcb1..3acd804 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebSettingsCompatTest.java
@@ -97,4 +97,26 @@
assertEquals(WebSettings.MENU_ITEM_PROCESS_TEXT | WebSettings.MENU_ITEM_WEB_SEARCH,
WebSettingsCompat.getDisabledActionModeMenuItems(mWebViewOnUiThread.getSettings()));
}
+
+ /**
+ * This should remain functionally equivalent to
+ * android.webkit.cts.WebSettingsTest#testSuppressedErrorPage. Modifications to this test should
+ * be reflected in that test as necessary. See http://go/modifying-webview-cts.
+ */
+ @Test
+ public void testSuppressedErrorPage() throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.SUPPRESS_ERROR_PAGE);
+
+ // default value should be false
+ assertFalse(WebSettingsCompat.willSuppressErrorPage(mWebViewOnUiThread.getSettings()));
+
+ WebSettingsCompat.setWillSuppressErrorPage(mWebViewOnUiThread.getSettings(), true);
+ assertTrue(WebSettingsCompat.willSuppressErrorPage(mWebViewOnUiThread.getSettings()));
+
+ // We could test that suppression actually happens, similar to #testWillSuppressErrorPage in
+ // org.chromium.android_webview.test.AwSettingsTest using only public WebView APIs.
+ // However, at the time of writing, that test is potentially flaky (waits 1000ms after a
+ // bad navigation and then checks).
+ }
+
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
index e1fe81f..2ec6943 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderIntegrationTest.java
@@ -17,25 +17,22 @@
package androidx.webkit;
import android.app.Activity;
-import android.net.Uri;
import android.os.Bundle;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
-import android.webkit.WebViewClient;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.After;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.Callable;
-
@RunWith(AndroidJUnit4.class)
public class WebViewAssetLoaderIntegrationTest {
private static final String TAG = "WebViewAssetLoaderIntegrationTest";
@@ -44,67 +41,58 @@
public final ActivityTestRule<TestActivity> mActivityRule =
new ActivityTestRule<>(TestActivity.class);
+ private WebViewOnUiThread mOnUiThread;
+ private WebViewAssetLoader mAssetLoader;
+
+ private static class AssetLoadingWebViewClient extends WebViewOnUiThread.WaitForLoadedClient {
+ private final WebViewAssetLoader mAssetLoader;
+ AssetLoadingWebViewClient(WebViewOnUiThread onUiThread,
+ WebViewAssetLoader assetLoader) {
+ super(onUiThread);
+ mAssetLoader = assetLoader;
+ }
+
+ @SuppressWarnings({"deprecated"})
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+ return mAssetLoader.shouldInterceptRequest(url);
+ }
+
+ @Override
+ public WebResourceResponse shouldInterceptRequest(WebView view,
+ WebResourceRequest request) {
+ return mAssetLoader.shouldInterceptRequest(request);
+ }
+ }
+
// An Activity for Integeration tests
public static class TestActivity extends Activity {
- private class MyWebViewClient extends WebViewClient {
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- return false;
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- mOnPageFinishedUrl.add(url);
- }
-
- @SuppressWarnings({"deprecated"})
- @Override
- public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
- return mAssetLoader.shouldInterceptRequest(url);
- }
-
- @Override
- public WebResourceResponse shouldInterceptRequest(WebView view,
- WebResourceRequest request) {
- return mAssetLoader.shouldInterceptRequest(request);
- }
- }
-
- private WebViewAssetLoader mAssetLoader;
private WebView mWebView;
- private ArrayBlockingQueue<String> mOnPageFinishedUrl = new ArrayBlockingQueue<String>(5);
-
- public WebViewAssetLoader getAssetLoader() {
- return mAssetLoader;
-
- }
public WebView getWebView() {
return mWebView;
}
- public ArrayBlockingQueue<String> getOnPageFinishedUrl() {
- return mOnPageFinishedUrl;
- }
-
- private void setUpWebView(WebView view) {
- view.setWebViewClient(new MyWebViewClient());
- }
-
+ // Runs before test suite's @Before.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mAssetLoader = (new WebViewAssetLoader.Builder(this)).build();
mWebView = new WebView(this);
- setUpWebView(mWebView);
setContentView(mWebView);
}
+ }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mWebView.destroy();
- mWebView = null;
+ @Before
+ public void setUp() {
+ mAssetLoader = (new WebViewAssetLoader.Builder(mActivityRule.getActivity())).build();
+ mOnUiThread = new WebViewOnUiThread(mActivityRule.getActivity().getWebView());
+ mOnUiThread.setWebViewClient(new AssetLoadingWebViewClient(mOnUiThread, mAssetLoader));
+ }
+
+ @After
+ public void tearDown() {
+ if (mOnUiThread != null) {
+ mOnUiThread.cleanUp();
}
}
@@ -112,66 +100,34 @@
@MediumTest
public void testAssetHosting() throws Exception {
final TestActivity activity = mActivityRule.getActivity();
- final String test_with_title_path = "www/test_with_title.html";
+ final String testWithTitlePath = "www/test_with_title.html";
- String url = WebkitUtils.onMainThreadSync(new Callable<String>() {
- @Override
- public String call() {
- WebViewAssetLoader assetLoader = activity.getAssetLoader();
- Uri.Builder testPath =
- assetLoader.getAssetsHttpsPrefix().buildUpon()
- .appendPath(test_with_title_path);
+ String url =
+ mAssetLoader.getAssetsHttpsPrefix().buildUpon()
+ .appendPath(testWithTitlePath)
+ .build()
+ .toString();
- String url = testPath.toString();
- activity.getWebView().loadUrl(url);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
- return url;
- }
- });
-
- String onPageFinishedUrl = activity.getOnPageFinishedUrl().take();
- Assert.assertEquals(url, onPageFinishedUrl);
-
- String title = WebkitUtils.onMainThreadSync(new Callable<String>() {
- @Override
- public String call() {
- return activity.getWebView().getTitle();
- }
- });
- Assert.assertEquals("WebViewAssetLoaderTest", title);
+ Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
}
@Test
@MediumTest
public void testResourcesHosting() throws Exception {
final TestActivity activity = mActivityRule.getActivity();
- final String test_with_title_path = "test_with_title.html";
+ final String testWithTitlePath = "test_with_title.html";
- String url = WebkitUtils.onMainThreadSync(new Callable<String>() {
- @Override
- public String call() {
- WebViewAssetLoader assetLoader = activity.getAssetLoader();
- Uri.Builder testPath =
- assetLoader.getResourcesHttpsPrefix().buildUpon()
- .appendPath("raw")
- .appendPath(test_with_title_path);
+ String url =
+ mAssetLoader.getResourcesHttpsPrefix().buildUpon()
+ .appendPath("raw")
+ .appendPath(testWithTitlePath)
+ .build()
+ .toString();
- String url = testPath.toString();
- activity.getWebView().loadUrl(url);
+ mOnUiThread.loadUrlAndWaitForCompletion(url);
- return url;
- }
- });
-
- String onPageFinishedUrl = activity.getOnPageFinishedUrl().take();
- Assert.assertEquals(url, onPageFinishedUrl);
-
- String title = WebkitUtils.onMainThreadSync(new Callable<String>() {
- @Override
- public String call() {
- return activity.getWebView().getTitle();
- }
- });
- Assert.assertEquals("WebViewAssetLoaderTest", title);
+ Assert.assertEquals("WebViewAssetLoaderTest", mOnUiThread.getTitle());
}
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
index ff62e4b..bf663db 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewAssetLoaderTest.java
@@ -18,7 +18,6 @@
import android.content.ContextWrapper;
import android.net.Uri;
-import android.util.Log;
import android.webkit.WebResourceResponse;
import androidx.test.core.app.ApplicationProvider;
@@ -95,9 +94,8 @@
try {
return new ByteArrayInputStream(contents.getBytes(encoding));
} catch (UnsupportedEncodingException e) {
- Log.e(TAG, "exception when creating response", e);
+ throw new RuntimeException(e);
}
- return null;
}
};
@@ -106,10 +104,11 @@
WebResourceResponse response =
assetLoader.shouldInterceptRequest("http://appassets.androidplatform.net/test/");
- Assert.assertNotNull(response);
+ Assert.assertNotNull("didn't match the exact registered URL", response);
Assert.assertEquals(contents, readAsString(response.getData(), encoding));
- Assert.assertNull(assetLoader.shouldInterceptRequest("http://foo.bar/"));
+ Assert.assertNull("opened a non-registered URL - should return null",
+ assetLoader.shouldInterceptRequest("http://foo.bar/"));
}
@Test
@@ -125,21 +124,23 @@
try {
return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
} catch (IOException e) {
- Log.e(TAG, "Unable to open asset URL: " + url);
- return null;
+ throw new RuntimeException(e);
}
}
return null;
}
});
- Assert.assertNull(assetLoader.getAssetsHttpPrefix());
+ Assert.assertNull("HTTP is not allowed - getAssetsHttpPrefix should return null",
+ assetLoader.getAssetsHttpPrefix());
Assert.assertEquals(assetLoader.getAssetsHttpsPrefix(),
Uri.parse("https://appassets.androidplatform.net/assets/"));
WebResourceResponse response =
assetLoader.shouldInterceptRequest("https://appassets.androidplatform.net/assets/www/test.html");
- Assert.assertNotNull(response);
+ Assert.assertNotNull("failed to match the URL and returned null response", response);
+ Assert.assertNotNull("matched the URL but not the file and returned a null InputStream",
+ response.getData());
Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
}
@@ -152,23 +153,27 @@
WebViewAssetLoader assetLoader = builder.buildForTest(new MockAssetHelper() {
@Override
public InputStream openResource(Uri uri) {
- try {
- if (uri.getPath().equals("raw/test.html")) {
+ if (uri.getPath().equals("raw/test.html")) {
+ try {
return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- } catch (IOException e) {
- Log.e(TAG, "exception when creating response", e);
}
return null;
}
});
- Assert.assertNull(assetLoader.getResourcesHttpPrefix());
- Assert.assertEquals(assetLoader.getResourcesHttpsPrefix(), Uri.parse("https://appassets.androidplatform.net/res/"));
+ Assert.assertNull("HTTP is not allowed - getResourcesHttpPrefix should return null",
+ assetLoader.getResourcesHttpPrefix());
+ Assert.assertEquals(assetLoader.getResourcesHttpsPrefix(),
+ Uri.parse("https://appassets.androidplatform.net/res/"));
WebResourceResponse response =
assetLoader.shouldInterceptRequest("https://appassets.androidplatform.net/res/raw/test.html");
- Assert.assertNotNull(response);
+ Assert.assertNotNull("failed to match the URL and returned null response", response);
+ Assert.assertNotNull("matched the prefix URL but not the file",
+ response.getData());
Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
}
@@ -188,8 +193,7 @@
try {
return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
} catch (IOException e) {
- Log.e(TAG, "Unable to open asset URL: " + url);
- return null;
+ throw new RuntimeException(e);
}
}
return null;
@@ -203,7 +207,9 @@
WebResourceResponse response =
assetLoader.shouldInterceptRequest("http://example.com/android_assets/www/test.html");
- Assert.assertNotNull(response);
+ Assert.assertNotNull("failed to match the URL and returned null response", response);
+ Assert.assertNotNull("matched the prefix URL but not the file",
+ response.getData());
Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
}
@@ -219,12 +225,12 @@
WebViewAssetLoader assetLoader = builder.buildForTest(new MockAssetHelper() {
@Override
public InputStream openResource(Uri uri) {
- try {
- if (uri.getPath().equals("raw/test.html")) {
+ if (uri.getPath().equals("raw/test.html")) {
+ try {
return new ByteArrayInputStream(testHtmlContents.getBytes("utf-8"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- } catch (IOException e) {
- Log.e(TAG, "exception when creating response", e);
}
return null;
}
@@ -237,7 +243,9 @@
WebResourceResponse response =
assetLoader.shouldInterceptRequest("http://example.com/android_res/raw/test.html");
- Assert.assertNotNull(response);
+ Assert.assertNotNull("failed to match the URL and returned null response", response);
+ Assert.assertNotNull("matched the prefix URL but not the file",
+ response.getData());
Assert.assertEquals(testHtmlContents, readAsString(response.getData(), "utf-8"));
}
}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index cdd8e27..21e687c 100644
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -30,6 +30,7 @@
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
@@ -45,7 +46,7 @@
* Modifications to this class should be reflected in that class as necessary. See
* http://go/modifying-webview-cts.
*/
-class WebViewOnUiThread {
+public class WebViewOnUiThread {
/**
* The maximum time, in milliseconds (10 seconds) to wait for a load
* to be triggered.
@@ -69,10 +70,22 @@
private WebView mWebView;
public WebViewOnUiThread() {
+ this(WebkitUtils.onMainThreadSync(new Callable<WebView>() {
+ @Override
+ public WebView call() {
+ return new WebView(ApplicationProvider.getApplicationContext());
+ }
+ }));
+ }
+
+ /**
+ * Create a new WebViewOnUiThread wrapping the provided {@link WebView}.
+ */
+ public WebViewOnUiThread(final WebView webView) {
WebkitUtils.onMainThreadSync(new Runnable() {
@Override
public void run() {
- mWebView = new WebView(ApplicationProvider.getApplicationContext());
+ mWebView = webView;
mWebView.setWebViewClient(new WaitForLoadedClient(WebViewOnUiThread.this));
mWebView.setWebChromeClient(new WaitForProgressClient(WebViewOnUiThread.this));
}
@@ -174,47 +187,49 @@
});
}
- public void setWebViewRendererClient(final WebViewRendererClient webViewRendererClient) {
- setWebViewRendererClient(mWebView, webViewRendererClient);
+ public void setWebViewRenderProcessClient(
+ final WebViewRenderProcessClient webViewRenderProcessClient) {
+ setWebViewRenderProcessClient(mWebView, webViewRenderProcessClient);
}
- public static void setWebViewRendererClient(
- final WebView webView, final WebViewRendererClient webViewRendererClient) {
+ public static void setWebViewRenderProcessClient(
+ final WebView webView, final WebViewRenderProcessClient webViewRenderProcessClient) {
WebkitUtils.onMainThreadSync(new Runnable() {
@Override
public void run() {
- WebViewCompat.setWebViewRendererClient(webView, webViewRendererClient);
+ WebViewCompat.setWebViewRenderProcessClient(webView, webViewRenderProcessClient);
}
});
}
- public void setWebViewRendererClient(
- final Executor executor, final WebViewRendererClient webViewRendererClient) {
- setWebViewRendererClient(mWebView, executor, webViewRendererClient);
+ public void setWebViewRenderProcessClient(
+ final Executor executor, final WebViewRenderProcessClient webViewRenderProcessClient) {
+ setWebViewRenderProcessClient(mWebView, executor, webViewRenderProcessClient);
}
- public static void setWebViewRendererClient(
+ public static void setWebViewRenderProcessClient(
final WebView webView,
final Executor executor,
- final WebViewRendererClient webViewRendererClient) {
+ final WebViewRenderProcessClient webViewRenderProcessClient) {
WebkitUtils.onMainThreadSync(new Runnable() {
@Override
public void run() {
- WebViewCompat.setWebViewRendererClient(webView, executor, webViewRendererClient);
+ WebViewCompat.setWebViewRenderProcessClient(
+ webView, executor, webViewRenderProcessClient);
}
});
}
- public WebViewRendererClient getWebViewRendererClient() {
- return getWebViewRendererClient(mWebView);
+ public WebViewRenderProcessClient getWebViewRenderProcessClient() {
+ return getWebViewRenderProcessClient(mWebView);
}
- public static WebViewRendererClient getWebViewRendererClient(
+ public static WebViewRenderProcessClient getWebViewRenderProcessClient(
final WebView webView) {
- return WebkitUtils.onMainThreadSync(new Callable<WebViewRendererClient>() {
+ return WebkitUtils.onMainThreadSync(new Callable<WebViewRenderProcessClient>() {
@Override
- public WebViewRendererClient call() {
- return WebViewCompat.getWebViewRendererClient(webView);
+ public WebViewRenderProcessClient call() {
+ return WebViewCompat.getWebViewRenderProcessClient(webView);
}
});
}
@@ -518,6 +533,7 @@
}
@Override
+ @CallSuper
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mOnUiThread.onProgressChanged(newProgress);
@@ -539,12 +555,14 @@
}
@Override
+ @CallSuper
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mOnUiThread.onPageFinished();
}
@Override
+ @CallSuper
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
mOnUiThread.onPageStarted();
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
new file mode 100644
index 0000000..3b57f1a
--- /dev/null
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessClientTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.view.KeyEvent;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+import androidx.concurrent.futures.ResolvableFuture;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class WebViewRenderProcessClientTest {
+ WebViewOnUiThread mWebViewOnUiThread;
+
+ @Before
+ public void setUp() {
+ mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread();
+ }
+
+ @After
+ public void tearDown() {
+ if (mWebViewOnUiThread != null) {
+ mWebViewOnUiThread.cleanUp();
+ }
+ }
+
+ private static class JSBlocker {
+ // A CoundDownLatch is used here, instead of a Future, because that makes it
+ // easier to support requiring variable numbers of releaseBlock() calls
+ // to unblock.
+ private CountDownLatch mLatch;
+ private ResolvableFuture<Void> mBecameBlocked;
+ JSBlocker(int requiredReleaseCount) {
+ mLatch = new CountDownLatch(requiredReleaseCount);
+ mBecameBlocked = ResolvableFuture.create();
+ }
+
+ JSBlocker() {
+ this(1);
+ }
+
+ public void releaseBlock() {
+ mLatch.countDown();
+ }
+
+ @JavascriptInterface
+ public void block() throws Exception {
+ // This blocks indefinitely (until signalled) on a background thread.
+ // The actual test timeout is not determined by this wait, but by other
+ // code waiting for the onRenderProcessUnresponsive() call.
+ mBecameBlocked.set(null);
+ mLatch.await();
+ }
+
+ public void waitForBlocked() {
+ WebkitUtils.waitForFuture(mBecameBlocked);
+ }
+ }
+
+ private void blockRenderProcess(final JSBlocker blocker) {
+ WebkitUtils.onMainThreadSync(new Runnable() {
+ @Override
+ public void run() {
+ WebView webView = mWebViewOnUiThread.getWebViewOnCurrentThread();
+ webView.evaluateJavascript("blocker.block();", null);
+ blocker.waitForBlocked();
+ // Sending an input event that does not get acknowledged will cause
+ // the unresponsive renderer event to fire.
+ webView.dispatchKeyEvent(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
+ }
+ });
+ }
+
+ private void addJsBlockerInterface(final JSBlocker blocker) {
+ WebkitUtils.onMainThreadSync(new Runnable() {
+ @Override
+ public void run() {
+ WebView webView = mWebViewOnUiThread.getWebViewOnCurrentThread();
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.addJavascriptInterface(blocker, "blocker");
+ }
+ });
+ }
+
+ private void testWebViewRenderProcessClientOnExecutor(Executor executor) throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
+ final JSBlocker blocker = new JSBlocker();
+ final ResolvableFuture<Void> rendererUnblocked = ResolvableFuture.create();
+
+ WebViewRenderProcessClient client = new WebViewRenderProcessClient() {
+ @Override
+ public void onRenderProcessUnresponsive(WebView view, WebViewRenderProcess renderer) {
+ // Let the renderer unblock.
+ blocker.releaseBlock();
+ }
+
+ @Override
+ public void onRenderProcessResponsive(WebView view, WebViewRenderProcess renderer) {
+ // Notify that the renderer has been unblocked.
+ rendererUnblocked.set(null);
+ }
+ };
+ if (executor == null) {
+ mWebViewOnUiThread.setWebViewRenderProcessClient(client);
+ } else {
+ mWebViewOnUiThread.setWebViewRenderProcessClient(executor, client);
+ }
+
+ addJsBlockerInterface(blocker);
+ mWebViewOnUiThread.loadUrlAndWaitForCompletion("about:blank");
+ blockRenderProcess(blocker);
+ WebkitUtils.waitForFuture(rendererUnblocked);
+ }
+
+ @Test
+ public void testWebViewRenderProcessClientWithoutExecutor() throws Throwable {
+ testWebViewRenderProcessClientOnExecutor(null);
+ }
+
+ @Test
+ public void testWebViewRenderProcessClientWithExecutor() throws Throwable {
+ final AtomicInteger executorCount = new AtomicInteger();
+ testWebViewRenderProcessClientOnExecutor(new Executor() {
+ @Override
+ public void execute(Runnable r) {
+ executorCount.incrementAndGet();
+ r.run();
+ }
+ });
+ Assert.assertEquals(2, executorCount.get());
+ }
+
+ @Test
+ public void testSetWebViewRenderProcessClient() throws Throwable {
+ WebkitUtils.checkFeature(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
+
+ Assert.assertNull("Initially the renderer client should be null",
+ mWebViewOnUiThread.getWebViewRenderProcessClient());
+
+ final WebViewRenderProcessClient client = new WebViewRenderProcessClient() {
+ @Override
+ public void onRenderProcessUnresponsive(WebView view, WebViewRenderProcess renderer) {}
+
+ @Override
+ public void onRenderProcessResponsive(WebView view, WebViewRenderProcess renderer) {}
+ };
+ mWebViewOnUiThread.setWebViewRenderProcessClient(client);
+
+ Assert.assertSame(
+ "After the renderer client is set, getting it should return the same object",
+ client, mWebViewOnUiThread.getWebViewRenderProcessClient());
+ }
+}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
new file mode 100644
index 0000000..fda6677
--- /dev/null
+++ b/webkit/src/androidTest/java/androidx/webkit/WebViewRenderProcessTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.annotation.SuppressLint;
+import android.os.Build;
+import android.webkit.RenderProcessGoneDetail;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.concurrent.futures.ResolvableFuture;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class WebViewRenderProcessTest {
+ private boolean terminateRenderProcessOnUiThread(
+ final WebViewRenderProcess renderer) {
+ return WebkitUtils.onMainThreadSync(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return renderer.terminate();
+ }
+ });
+ }
+
+ WebViewRenderProcess getRenderProcessOnUiThread(final WebView webView) {
+ return WebkitUtils.onMainThreadSync(new Callable<WebViewRenderProcess>() {
+ @Override
+ public WebViewRenderProcess call() {
+ return WebViewCompat.getWebViewRenderProcess(webView);
+ }
+ });
+ }
+
+ private ListenableFuture<WebViewRenderProcess> startAndGetRenderProcess(
+ final WebView webView) throws Throwable {
+ final ResolvableFuture<WebViewRenderProcess> future = ResolvableFuture.create();
+
+ WebkitUtils.onMainThread(new Runnable() {
+ @Override
+ public void run() {
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ WebViewRenderProcess result =
+ WebViewCompat.getWebViewRenderProcess(webView);
+ future.set(result);
+ }
+ });
+ webView.loadUrl("about:blank");
+ }
+ });
+
+ return future;
+ }
+
+ ListenableFuture<Boolean> catchRenderProcessTermination(final WebView webView) {
+ final ResolvableFuture<Boolean> future = ResolvableFuture.create();
+
+ WebkitUtils.onMainThread(new Runnable() {
+ @Override
+ public void run() {
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean onRenderProcessGone(
+ WebView view,
+ RenderProcessGoneDetail detail) {
+ view.destroy();
+ future.set(true);
+ return true;
+ }
+ });
+ }
+ });
+
+ return future;
+ }
+
+ @Before
+ public void setUp() {
+ WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
+
+ // Ensure that any existing renderer still alive after a previous test is terminated.
+ // TODO(tobiasjs): This assumes that WebView uses at most one renderer, which is true
+ // for now but may not remain so in future.
+ final WebView webView = WebViewOnUiThread.createWebView();
+ final WebViewRenderProcess renderProcess = getRenderProcessOnUiThread(webView);
+ WebViewOnUiThread.destroy(webView);
+ if (renderProcess != null) {
+ terminateRenderProcessOnUiThread(renderProcess);
+ }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
+ public void testGetWebViewRenderProcessPreO() throws Throwable {
+ // It should not be possible to get a renderer pre-O
+ WebView webView = WebViewOnUiThread.createWebView();
+ final WebViewRenderProcess renderer = startAndGetRenderProcess(webView).get();
+ Assert.assertNull(renderer);
+
+ WebViewOnUiThread.destroy(webView);
+ }
+
+ @LargeTest
+ @Test
+ @SuppressLint("NewApi")
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ public void testGetWebViewRenderProcess() throws Throwable {
+ // TODO(tobiasjs) some O devices are not multiprocess, and multiprocess can also be disabled
+ // manually. This test should handle those scenarios.
+
+ final WebView webView = WebViewOnUiThread.createWebView();
+
+ final WebViewRenderProcess preStartRenderProcess = getRenderProcessOnUiThread(webView);
+ Assert.assertNotNull(
+ "Should be possible to obtain a renderer handle before the renderer has started.",
+ preStartRenderProcess);
+ Assert.assertFalse(
+ "Should not be able to terminate an unstarted renderer.",
+ terminateRenderProcessOnUiThread(preStartRenderProcess));
+
+ final WebViewRenderProcess renderer = startAndGetRenderProcess(webView).get();
+ Assert.assertSame(
+ "The pre- and post-start renderer handles should be the same object.",
+ renderer, preStartRenderProcess);
+
+ Assert.assertSame(
+ "When getWebViewRender is called a second time, it should return the same object.",
+ renderer, startAndGetRenderProcess(webView).get());
+
+ ListenableFuture<Boolean> terminationFuture = catchRenderProcessTermination(webView);
+ Assert.assertTrue(
+ "A started renderer should be able to be terminated.",
+ terminateRenderProcessOnUiThread(renderer));
+ Assert.assertTrue(
+ "Terminating a renderer should result in onRenderProcessGone being called.",
+ terminationFuture.get());
+
+ Assert.assertFalse(
+ "It should not be possible to terminate a renderer that has already terminated.",
+ terminateRenderProcessOnUiThread(renderer));
+
+ final WebView webView2 = WebViewOnUiThread.createWebView();
+ Assert.assertNotSame(
+ "After a renderer restart, the new renderer handle object should be different.",
+ renderer, startAndGetRenderProcess(webView2).get());
+
+ // Ensure that we clean up webView2. webView has been destroyed by the WebViewClient
+ // installed by catchRenderProcessTermination
+ WebViewOnUiThread.destroy(webView2);
+ }
+}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewRendererClientTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewRendererClientTest.java
deleted file mode 100644
index e6fdc76..0000000
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewRendererClientTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit;
-
-import android.view.KeyEvent;
-import android.webkit.JavascriptInterface;
-import android.webkit.WebView;
-
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class WebViewRendererClientTest {
- WebViewOnUiThread mWebViewOnUiThread;
-
- @Before
- public void setUp() {
- mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread();
- }
-
- @After
- public void tearDown() {
- if (mWebViewOnUiThread != null) {
- mWebViewOnUiThread.cleanUp();
- }
- }
-
- private static class JSBlocker {
- // A CoundDownLatch is used here, instead of a Future, because that makes it
- // easier to support requiring variable numbers of releaseBlock() calls
- // to unblock.
- private CountDownLatch mLatch;
- private ResolvableFuture<Void> mBecameBlocked;
- JSBlocker(int requiredReleaseCount) {
- mLatch = new CountDownLatch(requiredReleaseCount);
- mBecameBlocked = ResolvableFuture.create();
- }
-
- JSBlocker() {
- this(1);
- }
-
- public void releaseBlock() {
- mLatch.countDown();
- }
-
- @JavascriptInterface
- public void block() throws Exception {
- // This blocks indefinitely (until signalled) on a background thread.
- // The actual test timeout is not determined by this wait, but by other
- // code waiting for the onRendererUnresponsive() call.
- mBecameBlocked.set(null);
- mLatch.await();
- }
-
- public void waitForBlocked() {
- WebkitUtils.waitForFuture(mBecameBlocked);
- }
- }
-
- private void blockRenderer(final JSBlocker blocker) {
- WebkitUtils.onMainThreadSync(new Runnable() {
- @Override
- public void run() {
- WebView webView = mWebViewOnUiThread.getWebViewOnCurrentThread();
- webView.evaluateJavascript("blocker.block();", null);
- blocker.waitForBlocked();
- // Sending an input event that does not get acknowledged will cause
- // the unresponsive renderer event to fire.
- webView.dispatchKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
- }
- });
- }
-
- private void addJsBlockerInterface(final JSBlocker blocker) {
- WebkitUtils.onMainThreadSync(new Runnable() {
- @Override
- public void run() {
- WebView webView = mWebViewOnUiThread.getWebViewOnCurrentThread();
- webView.getSettings().setJavaScriptEnabled(true);
- webView.addJavascriptInterface(blocker, "blocker");
- }
- });
- }
-
- private void testWebViewRendererClientOnExecutor(Executor executor) throws Throwable {
- WebkitUtils.checkFeature(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
- final JSBlocker blocker = new JSBlocker();
- final ResolvableFuture<Void> rendererUnblocked = ResolvableFuture.create();
-
- WebViewRendererClient client = new WebViewRendererClient() {
- @Override
- public void onRendererUnresponsive(WebView view, WebViewRenderer renderer) {
- // Let the renderer unblock.
- blocker.releaseBlock();
- }
-
- @Override
- public void onRendererResponsive(WebView view, WebViewRenderer renderer) {
- // Notify that the renderer has been unblocked.
- rendererUnblocked.set(null);
- }
- };
- if (executor == null) {
- mWebViewOnUiThread.setWebViewRendererClient(client);
- } else {
- mWebViewOnUiThread.setWebViewRendererClient(executor, client);
- }
-
- addJsBlockerInterface(blocker);
- mWebViewOnUiThread.loadUrlAndWaitForCompletion("about:blank");
- blockRenderer(blocker);
- WebkitUtils.waitForFuture(rendererUnblocked);
- }
-
- @Test
- public void testWebViewRendererClientWithoutExecutor() throws Throwable {
- testWebViewRendererClientOnExecutor(null);
- }
-
- @Test
- public void testWebViewRendererClientWithExecutor() throws Throwable {
- final AtomicInteger executorCount = new AtomicInteger();
- testWebViewRendererClientOnExecutor(new Executor() {
- @Override
- public void execute(Runnable r) {
- executorCount.incrementAndGet();
- r.run();
- }
- });
- Assert.assertEquals(2, executorCount.get());
- }
-
- @Test
- public void testSetWebViewRendererClient() throws Throwable {
- WebkitUtils.checkFeature(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
-
- Assert.assertNull("Initially the renderer client should be null",
- mWebViewOnUiThread.getWebViewRendererClient());
-
- final WebViewRendererClient webViewRendererClient = new WebViewRendererClient() {
- @Override
- public void onRendererUnresponsive(WebView view, WebViewRenderer renderer) {}
-
- @Override
- public void onRendererResponsive(WebView view, WebViewRenderer renderer) {}
- };
- mWebViewOnUiThread.setWebViewRendererClient(webViewRendererClient);
-
- Assert.assertSame(
- "After the renderer client is set, getting it should return the same object",
- webViewRendererClient, mWebViewOnUiThread.getWebViewRendererClient());
- }
-}
diff --git a/webkit/src/androidTest/java/androidx/webkit/WebViewRendererTest.java b/webkit/src/androidTest/java/androidx/webkit/WebViewRendererTest.java
deleted file mode 100644
index ef33f6f..0000000
--- a/webkit/src/androidTest/java/androidx/webkit/WebViewRendererTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.webkit.RenderProcessGoneDetail;
-import android.webkit.WebView;
-import android.webkit.WebViewClient;
-
-import androidx.concurrent.futures.ResolvableFuture;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SdkSuppress;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.Callable;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class WebViewRendererTest {
- private boolean terminateRendererOnUiThread(
- final WebViewRenderer renderer) {
- return WebkitUtils.onMainThreadSync(new Callable<Boolean>() {
- @Override
- public Boolean call() {
- return renderer.terminate();
- }
- });
- }
-
- WebViewRenderer getRendererOnUiThread(final WebView webView) {
- return WebkitUtils.onMainThreadSync(new Callable<WebViewRenderer>() {
- @Override
- public WebViewRenderer call() {
- return WebViewCompat.getWebViewRenderer(webView);
- }
- });
- }
-
- private ListenableFuture<WebViewRenderer> startAndGetRenderer(
- final WebView webView) throws Throwable {
- final ResolvableFuture<WebViewRenderer> future = ResolvableFuture.create();
-
- WebkitUtils.onMainThread(new Runnable() {
- @Override
- public void run() {
- webView.setWebViewClient(new WebViewClient() {
- @Override
- public void onPageFinished(WebView view, String url) {
- WebViewRenderer result = WebViewCompat.getWebViewRenderer(webView);
- future.set(result);
- }
- });
- webView.loadUrl("about:blank");
- }
- });
-
- return future;
- }
-
- ListenableFuture<Boolean> catchRendererTermination(final WebView webView) {
- final ResolvableFuture<Boolean> future = ResolvableFuture.create();
-
- WebkitUtils.onMainThread(new Runnable() {
- @Override
- public void run() {
- webView.setWebViewClient(new WebViewClient() {
- @Override
- public boolean onRenderProcessGone(
- WebView view,
- RenderProcessGoneDetail detail) {
- view.destroy();
- future.set(true);
- return true;
- }
- });
- }
- });
-
- return future;
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
- public void testGetWebViewRendererPreO() throws Throwable {
- WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
-
- // It should not be possible to get a renderer pre-O
- WebView webView = WebViewOnUiThread.createWebView();
- final WebViewRenderer renderer = startAndGetRenderer(webView).get();
- Assert.assertNull(renderer);
-
- WebViewOnUiThread.destroy(webView);
- }
-
- @LargeTest
- @Test
- @SuppressLint("NewApi")
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
- public void testGetWebViewRenderer() throws Throwable {
- WebkitUtils.checkFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
- // TODO(tobiasjs) some O devices are not multiprocess, and multiprocess can also be disabled
- // manually. This test should handle those scenarios.
-
- final WebView webView = WebViewOnUiThread.createWebView();
-
- final WebViewRenderer preStartRenderer = getRendererOnUiThread(webView);
- Assert.assertNotNull(
- "Should be possible to obtain a renderer handle before the renderer has started.",
- preStartRenderer);
- Assert.assertFalse(
- "Should not be able to terminate an unstarted renderer.",
- terminateRendererOnUiThread(preStartRenderer));
-
- final WebViewRenderer renderer = startAndGetRenderer(webView).get();
- Assert.assertSame(
- "The pre- and post-start renderer handles should be the same object.",
- renderer, preStartRenderer);
-
- Assert.assertSame(
- "When getWebViewRender is called a second time, it should return the same object.",
- renderer, startAndGetRenderer(webView).get());
-
- ListenableFuture<Boolean> terminationFuture = catchRendererTermination(webView);
- Assert.assertTrue(
- "A started renderer should be able to be terminated.",
- terminateRendererOnUiThread(renderer));
- Assert.assertTrue(
- "Terminating a renderer should result in onRenderProcessGone being called.",
- terminationFuture.get());
-
- Assert.assertFalse(
- "It should not be possible to terminate a renderer that has already terminated.",
- terminateRendererOnUiThread(renderer));
-
- final WebView webView2 = WebViewOnUiThread.createWebView();
- Assert.assertNotSame(
- "After a renderer restart, the new renderer handle object should be different.",
- renderer, startAndGetRenderer(webView2).get());
-
- // Ensure that we clean up webView2. webView has been destroyed by the WebViewClient
- // installed by catchRendererTermination
- WebViewOnUiThread.destroy(webView2);
- }
-}
diff --git a/webkit/src/main/java/androidx/webkit/ProxyConfig.java b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
index 4f42391..5585cb4 100644
--- a/webkit/src/main/java/androidx/webkit/ProxyConfig.java
+++ b/webkit/src/main/java/androidx/webkit/ProxyConfig.java
@@ -21,27 +21,26 @@
import androidx.annotation.StringDef;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Executor;
/**
* Config for {@link ProxyController#setProxyOverride(ProxyConfig, Executor, Runnable)}.
* <p>
* Proxy rules should be added using {@code addProxyRule} methods. Multiple rules can be used as
- * fallback if a proxy fails to respond (e.g. the proxy server is down). Bypass rules can be set
- * for URLs that should not use these settings.
+ * fallback if a proxy fails to respond (for example, the proxy server is down). Bypass rules can
+ * be set for URLs that should not use these settings.
* <p>
- * For instance, the following code means that WebView would first try to use proxy1.com for all
- * URLs, if that fails, proxy2.com, and if that fails, it would make a direct connection.
+ * For instance, the following code means that WebView would first try to use {@code proxy1.com}
+ * for all URLs, if that fails, {@code proxy2.com}, and if that fails, it would make a direct
+ * connection.
* <pre class="prettyprint">
* ProxyConfig proxyConfig = new ProxyConfig.Builder().addProxyRule("proxy1.com")
* .addProxyRule("proxy2.com")
* .addProxyRule(ProxyConfig.DIRECT)
* .build();
* </pre>
- * TODO(laisminchillo): unhide this when we're ready to expose this
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class ProxyConfig {
/**
* Connect to URLs directly instead of using a proxy server.
@@ -50,30 +49,30 @@
/**
* HTTP scheme.
*/
- public static final String HTTP = "http";
+ public static final String MATCH_HTTP = "http";
/**
* HTTPS scheme.
*/
- public static final String HTTPS = "https";
+ public static final String MATCH_HTTPS = "https";
/**
* Matches all schemes.
*/
public static final String MATCH_ALL_SCHEMES = "*";
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- @StringDef({HTTP, HTTPS, MATCH_ALL_SCHEMES})
+ @StringDef({MATCH_HTTP, MATCH_HTTPS, MATCH_ALL_SCHEMES})
public @interface ProxyScheme {}
- private static final String BYPASS_RULE_LOCAL = "<local>";
- private static final String BYPASS_RULE_LOOPBACK = "<-loopback>";
+ private static final String BYPASS_RULE_SIMPLE_NAMES = "<local>";
+ private static final String BYPASS_RULE_SUBTRACT_IMPLICIT = "<-loopback>";
- private String[][] mProxyRules;
- private String[] mBypassRules;
+ private List<String[]> mProxyRules;
+ private List<String> mBypassRules;
/**
* @hide Internal use only
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public ProxyConfig(String[][] proxyRules, String[] bypassRules) {
+ public ProxyConfig(List<String[]> proxyRules, List<String> bypassRules) {
mProxyRules = proxyRules;
mBypassRules = bypassRules;
}
@@ -83,7 +82,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@NonNull
- public String[][] proxyRules() {
+ public List<String[]> proxyRules() {
return mProxyRules;
}
@@ -92,54 +91,60 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@NonNull
- public String[] bypassRules() {
+ public List<String> bypassRules() {
return mBypassRules;
}
/**
* ProxyConfig builder. Use {@link Builder#addProxyRule(String)} or
- * {@link Builder#addProxyRule(String, String)} to add proxy rules. Note that if
- * you don't add any proxy rules, all connections will be made directly. Use
- * {@link Builder#addBypassRule(String)} to add bypass rules. Use
- * {@link Builder#build()} to build this into a {@link ProxyConfig} object.
+ * {@link Builder#addProxyRule(String, String)} to add proxy rules. Use
+ * {@link Builder#addBypassRule(String)} to add bypass rules. Use {@link Builder#build()} to
+ * build this into a {@link ProxyConfig} object.
+ *
+ * <p class="note"><b>Note:</b> applying a {@code ProxyConfig} with no rules will cause all
+ * connections to be made directly.
*/
- public static class Builder {
- private ArrayList<String[]> mProxyRules;
- private ArrayList<String> mBypassRules;
+ public static final class Builder {
+ private List<String[]> mProxyRules;
+ private List<String> mBypassRules;
+ /**
+ * Create an empty ProxyConfig Builder.
+ */
public Builder() {
mProxyRules = new ArrayList<>();
mBypassRules = new ArrayList<>();
}
/**
+ * Create a ProxyConfig Builder from an existing ProxyConfig object.
+ */
+ public Builder(@NonNull ProxyConfig proxyConfig) {
+ mProxyRules = proxyConfig.proxyRules();
+ mBypassRules = proxyConfig.bypassRules();
+ }
+
+ /**
* Builds the current rules into a ProxyConfig object.
*/
@NonNull
public ProxyConfig build() {
- return new ProxyConfig(buildProxyRules(), buildBypassRules());
+ return new ProxyConfig(proxyRules(), bypassRules());
}
/**
* Adds a proxy to be used for all URLs.
* <p>Proxy is either {@link ProxyConfig#DIRECT} or a string in the format
- * {@code [scheme://]host[:port]}. Scheme is optional and defaults to HTTP; host is one
- * of an IPv6 literal with brackets, an IPv4 literal or one or more labels separated by
- * a period; port number is optional and defaults to {@code 80} for {@code HTTP},
- * {@code 443} for {@code HTTPS} and {@code 1080} for {@code SOCKS}.
+ * {@code [scheme://]host[:port]}. Scheme is optional, if present must be {@code HTTP},
+ * {@code HTTPS} or <a href="https://tools.ietf.org/html/rfc1928">SOCKS</a> and defaults to
+ * {@code HTTP}. Host is one of an IPv6 literal with brackets, an IPv4 literal or one or
+ * more labels separated by a period. Port number is optional and defaults to {@code 80} for
+ * {@code HTTP}, {@code 443} for {@code HTTPS} and {@code 1080} for {@code SOCKS}.
* <p>
* The correct syntax for hosts is defined by
- * <a href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>
+ * <a href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>
* <p>
- * Host examples:
- * <table>
- * <tr><th> Type </th> <th> Example </th></tr>
- * <tr><td> IPv4 literal</td> <td> 192.168.1.1 </td></tr>
- * <tr><td> IPv6 literal with brackets</td> <td> [10:20:30:40:50:60:70:80] </td></tr>
- * <tr><td> Labels </td> <td> example.com </td></tr>
- * </table>
- * <p>
- * Proxy URL examples:
+ * Examples:
* <table>
* <tr><th> Scheme </th> <th> Host </th> <th> Port </th> <th> Proxy URL </th></tr>
* <tr><td></td> <td>example.com</td> <td></td> <td>example.com</td> </tr>
@@ -162,7 +167,7 @@
/**
* This does everything that {@link Builder#addProxyRule(String)} does,
* but only applies to URLs using {@code schemeFilter}. Scheme filter must be one of
- * {@link ProxyConfig#HTTP}, {@link ProxyConfig#HTTPS} or
+ * {@link ProxyConfig#MATCH_HTTP}, {@link ProxyConfig#MATCH_HTTPS} or
* {@link ProxyConfig#MATCH_ALL_SCHEMES}.
*
* @param proxyUrl Proxy URL
@@ -192,30 +197,45 @@
}
/**
- * Matches hostnames without a period in them (and are not IP literals).
+ * Hostnames without a period in them (and that are not IP literals) will skip proxy
+ * settings and be connected to directly instead. Examples: {@code "abc"}, {@code "local"},
+ * {@code "some-domain"}.
+ * <p>
+ * Hostnames with a trailing dot are not considered simple by this definition.
*/
@NonNull
- public Builder doNotProxyLocalNetworkRequests() {
- return addBypassRule(BYPASS_RULE_LOCAL);
+ public Builder bypassSimpleHostnames() {
+ return addBypassRule(BYPASS_RULE_SIMPLE_NAMES);
}
/**
- * Subtracts the implicit proxy bypass rules (localhost and link local addresses), so they
- * are no longer bypassed.
+ * By default, certain hostnames implicitly bypass the proxy if they are link-local IPs, or
+ * localhost addresses. For instance hostnames matching any of (non-exhaustive list):
+ * <ul>
+ * <li>localhost</li>
+ * <li>*.localhost</li>
+ * <li>[::1]</li>
+ * <li>127.0.0.1/8</li>
+ * <li>169.254/16</li>
+ * <li>[FE80::]/10</li>
+ * </ul>
+ * <p>
+ * Call this function to override the default behavior and force localhost and link-local
+ * URLs to be sent through the proxy.
*/
@NonNull
- public Builder doProxyLoopbackRequests() {
- return addBypassRule(BYPASS_RULE_LOOPBACK);
+ public Builder subtractImplicitRules() {
+ return addBypassRule(BYPASS_RULE_SUBTRACT_IMPLICIT);
}
@NonNull
- private String[][] buildProxyRules() {
- return mProxyRules.toArray(new String[0][]);
+ private List<String[]> proxyRules() {
+ return mProxyRules;
}
@NonNull
- private String[] buildBypassRules() {
- return mBypassRules.toArray(new String[mBypassRules.size()]);
+ private List<String> bypassRules() {
+ return mBypassRules;
}
}
}
diff --git a/webkit/src/main/java/androidx/webkit/ProxyController.java b/webkit/src/main/java/androidx/webkit/ProxyController.java
index 14892fe..cd6b76a 100644
--- a/webkit/src/main/java/androidx/webkit/ProxyController.java
+++ b/webkit/src/main/java/androidx/webkit/ProxyController.java
@@ -17,7 +17,6 @@
package androidx.webkit;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresFeature;
import androidx.annotation.RestrictTo;
import androidx.webkit.internal.ProxyControllerImpl;
@@ -49,11 +48,7 @@
* ...
* ProxyController.getInstance().clearProxyOverride(executor, listener);
* </pre>
- *
- * TODO(laisminchillo): unhide this when we're ready to expose this
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public abstract class ProxyController {
/**
* @hide Don't allow apps to sub-class this class.
@@ -83,38 +78,23 @@
}
/**
- * Does everything that
- * {@link ProxyController#setProxyOverride(ProxyConfig, Executor, Runnable)} does, but this
- * listener will be called in the same thread this method was called from.
- *
- * @param proxyConfig Proxy config to be applied
- * @param listener Optional listener called when the proxy setting change has been applied
- */
- public abstract void setProxyOverride(@NonNull ProxyConfig proxyConfig,
- @Nullable Runnable listener);
-
- /**
* Sets {@link ProxyConfig} which will be used by all WebViews in the app. URLs that match
* patterns in the bypass list will not be directed to any proxy. Instead, the request will be
* made directly to the origin specified by the URL. Network connections are not guaranteed to
* immediately use the new proxy setting; wait for the listener before loading a page. This
* listener will be called in the provided executor.
*
+ * <p class="note"><b>Note:</b> calling setProxyOverride will cause any existing system wide
+ * setting to be ignored.
+ *
* @param proxyConfig Proxy config to be applied
* @param executor Executor for the listener to be executed in
- * @param listener Optional listener called when the proxy setting change has been applied
+ * @param listener Listener called when the proxy setting change has been applied
+ *
+ * @throws IllegalArgumentException If the proxyConfig is invalid
*/
public abstract void setProxyOverride(@NonNull ProxyConfig proxyConfig,
- @NonNull Executor executor, @Nullable Runnable listener);
-
- /**
- * Does everything that {@link ProxyController#clearProxyOverride(Executor, Runnable)} does,
- * but this listener will be called in the same thread this method was called from.
- *
- * @param listener Optional listener called when the proxy setting change has been applied
- */
- public abstract void clearProxyOverride(@Nullable Runnable listener);
-
+ @NonNull Executor executor, @NonNull Runnable listener);
/**
* Clears the proxy settings. Network connections are not guaranteed to immediately use the
@@ -122,8 +102,8 @@
* in the provided executor.
*
* @param executor Executor for the listener to be executed in
- * @param listener Optional listener called when the proxy setting change has been applied
+ * @param listener Listener called when the proxy setting change has been applied
*/
public abstract void clearProxyOverride(@NonNull Executor executor,
- @Nullable Runnable listener);
+ @NonNull Runnable listener);
}
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index 59e2647..21835fb 100644
--- a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -223,6 +223,67 @@
}
}
+ /**
+ * Sets whether the WebView’s internal error page should be suppressed or displayed
+ * for bad navigations. True means suppressed (not shown), false means it will be
+ * displayed.
+ * The default value is false.
+ *
+ * <p>
+ * This method should only be called if
+ * {@link WebViewFeature#isFeatureSupported(String)}
+ * returns true for {@link WebViewFeature#SUPPRESS_ERROR_PAGE}.
+ *
+ * @param suppressed whether the WebView should suppress its internal error page
+ *
+ * TODO(cricke): unhide
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @SuppressLint("NewApi")
+ @RequiresFeature(name = WebViewFeature.SUPPRESS_ERROR_PAGE,
+ enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+ public static void setWillSuppressErrorPage(WebSettings webSettings, boolean suppressed) {
+ WebViewFeatureInternal webviewFeature =
+ WebViewFeatureInternal.getFeature(WebViewFeature.SUPPRESS_ERROR_PAGE);
+ if (webviewFeature.isSupportedByWebView()) {
+ getAdapter(webSettings).setWillSuppressErrorPage(suppressed);
+ } else {
+ throw WebViewFeatureInternal.getUnsupportedOperationException();
+ }
+ }
+
+
+ /**
+ * Gets whether the WebView’s internal error page will be suppressed or displayed
+ *
+ * <p>
+ * This method should only be called if
+ * {@link WebViewFeature#isFeatureSupported(String)}
+ * returns true for {@link WebViewFeature#SUPPRESS_ERROR_PAGE}.
+ *
+ * @return true if the WebView will suppress its internal error page
+ * @see #setWillSuppressErrorPage
+ *
+ * TODO(cricke): unhide
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @SuppressLint("NewApi")
+ @RequiresFeature(name = WebViewFeature.SUPPRESS_ERROR_PAGE,
+ enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
+ public static boolean willSuppressErrorPage(WebSettings webSettings) {
+ WebViewFeatureInternal webviewFeature =
+ WebViewFeatureInternal.getFeature(WebViewFeature.SUPPRESS_ERROR_PAGE);
+ if (webviewFeature.isSupportedByWebView()) {
+ return getAdapter(webSettings).willSuppressErrorPage();
+ } else {
+ throw WebViewFeatureInternal.getUnsupportedOperationException();
+ }
+ }
+
+
+
private static WebSettingsAdapter getAdapter(WebSettings webSettings) {
return WebViewGlueCommunicator.getCompatConverter().convertSettings(webSettings);
}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
index 7286c8f..923b271 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewAssetLoader.java
@@ -25,7 +25,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.webkit.internal.AssetHelper;
@@ -33,51 +32,60 @@
import java.net.URLConnection;
/**
- * Helper class meant to be used with the android.webkit.WebView class to enable hosting assets,
- * resources and other data on 'virtual' http(s):// URL.
- * Hosting assets and resources on http(s):// URLs is desirable as it is compatible with the
- * Same-Origin policy.
+ * Helper class to enable accessing the application's static assets and resources under an
+ * http(s):// URL to be loaded by {@link android.webkit.WebView} class.
+ * Hosting assets and resources this way is desirable as it is compatible with the Same-Origin
+ * policy.
*
- * This class is intended to be used from within the
- * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
- * android.webkit.WebResourceRequest)}
- * methods.
- * <pre>
- * WebViewAssetLoader assetLoader = new WebViewAssetLoader(this);
- * // For security WebViewAssetLoader uses a unique subdomain by default.
- * assetLoader.hostAssets();
+ * <p>
+ * For more context about application's assets and resources and how to normally access them please
+ * refer to <a href="https://developer.android.com/guide/topics/resources/providing-resources">
+ * Android Developer Docs: App resources overview</a>.
+ *
+ * <p class='note'>
+ * This class is expected to be used within
+ * {@link android.webkit.WebViewClient#shouldInterceptRequest}, which is not invoked on the
+ * application's main thread. Although instances are themselves thread-safe (and may be safely
+ * constructed on the application's main thread), exercise caution when accessing private data or
+ * the view system.
+ *
+ * <p>
+ * Using http(s):// URLs to access local resources may conflict with a real website. This means
+ * that local resources should only be hosted on domains your organization owns (at paths reserved
+ * for this purpose) or the default domain Google has reserved for this:
+ * {@code appassets.androidplatform.net}.
+ *
+ * <p>
+ * A typical usage would be like:
+ * <pre class="prettyprint">
+ * WebViewAssetLoader.Builder assetLoaderBuilder = new WebViewAssetLoader.Builder(this);
+ * final WebViewAssetLoader assetLoader = assetLoaderBuilder.build();
* webView.setWebViewClient(new WebViewClient() {
- * @Override
+ * {@literal @}Override
* public WebResourceResponse shouldInterceptRequest(WebView view,
* WebResourceRequest request) {
* return assetLoader.shouldInterceptRequest(request);
* }
* });
- * // If your application's assets are in the "main/assets" folder this will read the file
+ * // Assets are hosted under http(s)://appassets.androidplatform.net/assets/... by default.
+ * // If the application's assets are in the "main/assets" folder this will read the file
* // from "main/assets/www/index.html" and load it as if it were hosted on:
* // https://appassets.androidplatform.net/assets/www/index.html
- * assetLoader.hostAssets();
- * webview.loadUrl(assetLoader.getAssetsHttpsPrefix().buildUpon().appendPath("www/index.html")
- * .build().toString());
+ * webview.loadUrl(assetLoader.getAssetsHttpsPrefix().buildUpon()
+ * .appendPath("www")
+ * .appendPath("index.html")
+ * .build().toString());
*
* </pre>
- *
- * @hide
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class WebViewAssetLoader {
private static final String TAG = "WebViewAssetLoader";
/**
- * Using http(s):// URL to access local resources may conflict with a real website. This means
- * that local resources should only be hosted on domains that the user has control of or which
- * have been dedicated for this purpose.
- *
- * The androidplatform.net domain currently belongs to Google and has been reserved for the
- * purpose of Android applications intercepting navigations/requests directed there. It'll be
- * used by default unless the user specified a different domain.
- *
- * A subdomain "appassets" will be used to even make sure no such collisons would happen.
+ * An unused domain reserved by Google for Android applications to intercept requests
+ * for app assets.
+ * <p>
+ * It'll be used by default unless the user specified a different domain.
*/
public static final String KNOWN_UNUSED_AUTHORITY = "appassets.androidplatform.net";
@@ -90,9 +98,12 @@
/**
* A handler that produces responses for the registered paths.
*
+ * Matches URIs on the form: {@code "http(s)://authority/path/**"}, HTTPS is always enabled.
+ *
+ * <p>
* Methods of this handler will be invoked on a background thread and care must be taken to
* correctly synchronize access to any shared state.
- *
+ * <p>
* On Android KitKat and above these methods may be called on more than one thread. This thread
* may be different than the thread on which the shouldInterceptRequest method was invoked.
* This means that on Android KitKat and above it is possible to block in this method without
@@ -108,12 +119,9 @@
@NonNull final String mPath;
/**
- * Add a URI to match, and the handler to return when this URI is
- * matched. Matches URIs on the form: "scheme://authority/path/**"
- *
- * @param authority the authority to match (For example example.com)
- * @param path the prefix path to match. Should start and end with a slash "/".
- * @param httpEnabled whether to enable hosting using the http scheme.
+ * @param authority the authority to match (For instance {@code "example.com"})
+ * @param path the prefix path to match, it should start and end with a {@code "/"}.
+ * @param httpEnabled enable hosting under the HTTP scheme, HTTPS is always enabled.
*/
PathHandler(@NonNull final String authority, @NonNull final String path,
boolean httpEnabled) {
@@ -128,19 +136,33 @@
this.mHttpEnabled = httpEnabled;
}
+ /**
+ * Open an {@link InputStream} for the requested URL.
+ * <p>
+ * This method should be called if {@code match(Uri)} returns true in order to
+ * open the file requested by this URL.
+ *
+ * @param url path that has been matched.
+ * @return {@link InputStream} for the requested URL, {@code null} if an error happens
+ * while opening the file or file doesn't exist.
+ */
@Nullable
public abstract InputStream handle(@NonNull Uri url);
/**
- * Match happens when:
- * - Scheme is "https" or the scheme is "http" and http is enabled.
- * - AND authority exact matches the given URI's authority.
- * - AND path is a prefix of the given URI's path.
- * @param uri The URI whose path we will match against.
+ * Match against registered scheme, authority and path prefix.
*
- * @return true if match happens, false otherwise.
+ * Match happens when:
+ * <ul>
+ * <li>Scheme is "https" <b>or</b> the scheme is "http" and http is enabled.</li>
+ * <li>Authority exact matches the given URI's authority.</li>
+ * <li>Path is a prefix of the given URI's path.</li>
+ * </ul>
+ *
+ * @param uri the URI whose path we will match against.
+ *
+ * @return {@code true} if a match happens, {@code false} otherwise.
*/
- @Nullable
public boolean match(@NonNull Uri uri) {
// Only match HTTP_SCHEME if caller enabled HTTP matches.
if (uri.getScheme().equals(HTTP_SCHEME) && !mHttpEnabled) {
@@ -199,7 +221,7 @@
/**
- * A builder class for constructing WebViewAssetLoader objects.
+ * A builder class for constructing {@link WebViewAssetLoader} objects.
*/
public static final class Builder {
private final Context mContext;
@@ -208,6 +230,9 @@
@NonNull Uri mAssetsUri;
@NonNull Uri mResourcesUri;
+ /**
+ * @param context {@link Context} used to resolve resources/assets.
+ */
public Builder(@NonNull Context context) {
mContext = context;
mAllowHttp = false;
@@ -219,9 +244,10 @@
* Set the domain under which app assets and resources can be accessed.
* The default domain is {@code "appassets.androidplatform.net"}
*
- * @param domain the domain on which app assets are hosted.
- * @return builder object.
+ * @param domain the domain on which app assets should be hosted.
+ * @return {@link Builder} object.
*/
+ @NonNull
public Builder setDomain(@NonNull String domain) {
mAssetsUri = createUriPrefix(domain, mAssetsUri.getPath());
mResourcesUri = createUriPrefix(domain, mResourcesUri.getPath());
@@ -229,24 +255,34 @@
}
/**
- * Set the prefix path under which app assets are hosted.
- * The default path for assets is {@code "/assets/"}
+ * Set the prefix path under which app assets should be hosted.
+ * The default path for assets is {@code "/assets/"}. The path must start and end with
+ * {@code "/"}.
+ * <p>
+ * A custom prefix path can be used in conjunction with a custom domain, to
+ * avoid conflicts with real paths which may be hosted at that domain.
*
- * @param path the path under which app assets are hosted.
- * @return builder object.
+ * @param path the path under which app assets should be hosted.
+ * @return {@link Builder} object.
+ * @throws IllegalArgumentException if the path is invalid.
*/
+ @NonNull
public Builder setAssetsHostingPath(@NonNull String path) {
mAssetsUri = createUriPrefix(mAssetsUri.getAuthority(), path);
return this;
}
/**
- * Set the prefix path under which app resources are hosted.
- * the default path for resources is {@code "/res/"}
+ * Set the prefix path under which app resources should be hosted.
+ * The default path for resources is {@code "/res/"}. The path must start and end with
+ * {@code "/"}. A custom prefix path can be used in conjunction with a custom domain, to
+ * avoid conflicts with real paths which may be hosted at that domain.
*
- * @param path the path under which app resources are hosted.
- * @return builder object.
+ * @param path the path under which app resources should be hosted.
+ * @return {@link Builder} object.
+ * @throws IllegalArgumentException if the path is invalid.
*/
+ @NonNull
public Builder setResourcesHostingPath(@NonNull String path) {
mResourcesUri = createUriPrefix(mResourcesUri.getAuthority(), path);
return this;
@@ -256,20 +292,33 @@
* Allow using the HTTP scheme in addition to HTTPS.
* The default is to not allow HTTP.
*
- * @return builder object.
+ * @return {@link Builder} object.
*/
+ @NonNull
public Builder allowHttp() {
this.mAllowHttp = true;
return this;
}
/**
- * Build and return WebViewAssetLoader object.
+ * Build and return a {@link WebViewAssetLoader} object.
*
- * @return immutable WebViewAssetLoader object.
+ * @return immutable {@link WebViewAssetLoader} object.
+ * @throws IllegalArgumentException if the {@code Builder} received conflicting inputs.
*/
@NonNull
public WebViewAssetLoader build() {
+ String assetsPath = mAssetsUri.getPath();
+ String resourcesPath = mResourcesUri.getPath();
+ if (assetsPath.startsWith(resourcesPath)) {
+ throw new
+ IllegalArgumentException("Resources path cannot be prefix of assets path");
+ }
+ if (resourcesPath.startsWith(assetsPath)) {
+ throw new
+ IllegalArgumentException("Assets path cannot be prefix of resources path");
+ }
+
AssetHelper assetHelper = new AssetHelper(mContext);
PathHandler assetHandler = new AssetsPathHandler(mAssetsUri.getAuthority(),
mAssetsUri.getPath(), mAllowHttp, assetHelper);
@@ -324,12 +373,6 @@
}
}
- /**
- * Creates a new instance of the WebView asset loader.
- * Will use a default domain on the form of: appassets.androidplatform.net
- *
- * @param context context used to resolve resources/assets.
- */
/*package*/ WebViewAssetLoader(@NonNull PathHandler assetHandler,
@NonNull PathHandler resourceHandler) {
this.mAssetsHandler = assetHandler;
@@ -355,30 +398,51 @@
}
/**
- * Attempt to retrieve the WebResourceResponse associated with the given <code>request</code>.
+ * Attempt to resolve the {@link WebResourceRequest} to an application resource or
+ * asset, and return a {@link WebResourceResponse} for the content.
+ * <p>
+ * The prefix path used shouldn't be a prefix of a real web path. Thus, in case of having a URL
+ * that matches a registered prefix path but the requested asset cannot be found or opened a
+ * {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be returned
+ * instead of {@code null}. This saves the time of falling back to network and trying to
+ * resolve a path that doesn't exist. A {@link WebResourceResponse} with {@code null}
+ * {@link InputStream} will be received as an HTTP response with status code {@code 404} and
+ * no body.
+ * <p>
* This method should be invoked from within
- * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView,
- * android.webkit.WebResourceRequest)}.
+ * {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, WebResourceRequest)}.
*
- * @param request the request to process.
- * @return a response if the request URL had a matching registered url, null otherwise.
+ * @param request the {@link WebResourceRequest} to process.
+ * @return {@link WebResourceResponse} if the request URL matches a registered url,
+ * {@code null} otherwise.
*/
@RequiresApi(21)
@Nullable
- public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
+ public WebResourceResponse shouldInterceptRequest(@NonNull WebResourceRequest request) {
return shouldInterceptRequestImpl(request.getUrl());
}
/**
- * Attempt to retrieve the WebResourceResponse associated with the given <code>url</code>.
+ * Attempt to resolve the {@code url} to an application resource or asset, and return
+ * a {@link WebResourceResponse} for the content.
+ * <p>
+ * The prefix path used shouldn't be a prefix of a real web path. Thus, in case of having a URL
+ * that matches a registered prefix path but the requested asset cannot be found or opened a
+ * {@link WebResourceResponse} object with a {@code null} {@link InputStream} will be returned
+ * instead of {@code null}. This saves the time of falling back to network and trying to
+ * resolve a path that doesn't exist. A {@link WebResourceResponse} with {@code null}
+ * {@link InputStream} will be received as an HTTP response with status code {@code 404} and
+ * no body.
+ * <p>
* This method should be invoked from within
* {@link android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, String)}.
*
- * @param url the url to process.
- * @return a response if the request URL had a matching registered url, null otherwise.
+ * @param url the URL string to process.
+ * @return {@link WebResourceResponse} if the request URL matches a registered URL,
+ * {@code null} otherwise.
*/
@Nullable
- public WebResourceResponse shouldInterceptRequest(@Nullable String url) {
+ public WebResourceResponse shouldInterceptRequest(@NonNull String url) {
PathHandler handler = null;
Uri uri = parseAndVerifyUrl(url);
if (uri == null) {
@@ -405,8 +469,14 @@
}
/**
- * Gets the http: scheme prefix at which assets are hosted.
- * @return the http: scheme prefix at which assets are hosted. Can return null.
+ * Get the HTTP URL prefix under which assets are hosted.
+ * <p>
+ * If HTTP is allowed, the prefix will be on the format:
+ * {@code "http://<domain>/<prefix-path>/"}, for example:
+ * {@code "http://appassets.androidplatform.net/assets/"}.
+ *
+ * @return the HTTP URL prefix under which assets are hosted, or {@code null} if HTTP is not
+ * enabled.
*/
@Nullable
public Uri getAssetsHttpPrefix() {
@@ -423,8 +493,12 @@
}
/**
- * Gets the https: scheme prefix at which assets are hosted.
- * @return the https: scheme prefix at which assets are hosted. Can return null.
+ * Get the HTTPS URL prefix under which assets are hosted.
+ * <p>
+ * The prefix will be on the format: {@code "https://<domain>/<prefix-path>/"}, if the default
+ * values are used then it will be: {@code "https://appassets.androidplatform.net/assets/"}.
+ *
+ * @return the HTTPS URL prefix under which assets are hosted.
*/
@NonNull
public Uri getAssetsHttpsPrefix() {
@@ -437,8 +511,14 @@
}
/**
- * Gets the http: scheme prefix at which resources are hosted.
- * @return the http: scheme prefix at which resources are hosted. Can return null.
+ * Get the HTTP URL prefix under which resources are hosted.
+ * <p>
+ * If HTTP is allowed, the prefix will be on the format:
+ * {@code "http://<domain>/<prefix-path>/"}, for example
+ * {@code "http://appassets.androidplatform.net/res/"}.
+ *
+ * @return the HTTP URL prefix under which resources are hosted, or {@code null} if HTTP is not
+ * enabled.
*/
@Nullable
public Uri getResourcesHttpPrefix() {
@@ -455,8 +535,12 @@
}
/**
- * Gets the https: scheme prefix at which resources are hosted.
- * @return the https: scheme prefix at which resources are hosted. Can return null.
+ * Get the HTTPS URL prefix under which resources are hosted.
+ * <p>
+ * The prefix will be on the format: {@code "https://<domain>/<prefix-path>/"}, if the default
+ * values are used then it will be: {@code "https://appassets.androidplatform.net/res/"}.
+ *
+ * @return the HTTPs URL prefix under which resources are hosted.
*/
@NonNull
public Uri getResourcesHttpsPrefix() {
diff --git a/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 0904aee..9a1387c6 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -38,7 +38,6 @@
import androidx.webkit.internal.WebViewProviderFactory;
import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
-import org.chromium.support_lib_boundary.util.Features;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -441,7 +440,7 @@
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
public static @NonNull WebViewClient getWebViewClient(@NonNull WebView webview) {
final WebViewFeatureInternal feature =
- WebViewFeatureInternal.getFeature(Features.GET_WEB_VIEW_CLIENT);
+ WebViewFeatureInternal.getFeature(WebViewFeature.GET_WEB_VIEW_CLIENT);
if (feature.isSupportedByFramework()) {
return webview.getWebViewClient();
} else if (feature.isSupportedByWebView()) {
@@ -493,18 +492,18 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#GET_WEB_VIEW_RENDERER}.
*
- * @return the {@link WebViewRenderer} renderer handle associated
+ * @return the {@link WebViewRenderProcess} renderer handle associated
* with this {@link android.webkit.WebView}, or {@code null} if
* WebView is not runing in multiprocess mode.
*/
@SuppressLint("NewApi")
@RequiresFeature(name = WebViewFeature.GET_WEB_VIEW_RENDERER,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
- public static @Nullable WebViewRenderer getWebViewRenderer(@NonNull WebView webview) {
+ public static @Nullable WebViewRenderProcess getWebViewRenderProcess(@NonNull WebView webview) {
final WebViewFeatureInternal feature =
WebViewFeatureInternal.getFeature(WebViewFeature.GET_WEB_VIEW_RENDERER);
if (feature.isSupportedByWebView()) {
- return getProvider(webview).getWebViewRenderer();
+ return getProvider(webview).getWebViewRenderProcess();
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
@@ -514,20 +513,20 @@
* Sets the renderer client object associated with this WebView.
*
* <p>The renderer client encapsulates callbacks relevant to WebView renderer
- * state. See {@link WebViewRendererClient} for details.
+ * state. See {@link WebViewRenderProcessClient} for details.
*
* <p>Although many WebView instances may share a single underlying renderer, and renderers may
* live either in the application process, or in a sandboxed process that is isolated from
- * the application process, instances of {@link WebViewRendererClient} are set per-WebView.
+ * the application process, instances of {@link WebViewRenderProcessClient} are set per-WebView.
* Callbacks represent renderer events from the perspective of this WebView, and may or may
* not be correlated with renderer events affecting other WebViews.
*
* <p>The renderer client encapsulates callbacks relevant to WebView renderer
- * state. See {@link WebViewRendererClient} for details.
+ * state. See {@link WebViewRenderProcessClient} for details.
*
* <p>Although many WebView instances may share a single underlying renderer, and renderers may
* live either in the application process, or in a sandboxed process that is isolated from
- * the application process, instances of {@link WebViewRendererClient} are set per-WebView.
+ * the application process, instances of {@link WebViewRenderProcessClient} are set per-WebView.
* Callbacks represent renderer events from the perspective of this WebView, and may or may
* not be correlated with renderer events affecting other WebViews.
*
@@ -537,18 +536,20 @@
*
* @param webview the {@link WebView} on which to monitor responsiveness.
* @param executor the {@link Executor} that will be used to execute callbacks.
- * @param webViewRendererClient the {@link WebViewRendererClient} to set for callbacks.
+ * @param webViewRenderProcessClient the {@link WebViewRenderProcessClient} to set for
+ * callbacks.
*/
@RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
- public static void setWebViewRendererClient(
+ public static void setWebViewRenderProcessClient(
@NonNull WebView webview,
@NonNull /* @CallbackExecutor */ Executor executor,
- @NonNull WebViewRendererClient webViewRendererClient) {
+ @NonNull WebViewRenderProcessClient webViewRenderProcessClient) {
final WebViewFeatureInternal feature = WebViewFeatureInternal.getFeature(
WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
if (feature.isSupportedByWebView()) {
- getProvider(webview).setWebViewRendererClient(executor, webViewRendererClient);
+ getProvider(webview).setWebViewRenderProcessClient(
+ executor, webViewRenderProcessClient);
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
@@ -557,12 +558,12 @@
/**
* Sets the renderer client object associated with this WebView.
*
- * <p>See {@link #setWebViewRendererClient(WebView,Executor,WebViewRendererClient} for details,
- * with the following differences:
+ * <p>See {@link WebViewCompat#setWebViewRenderProcessClient(WebView,Executor,WebViewRenderProcessClient)} for
+ * details, with the following differences:
*
* <p>Callbacks will execute directly on the thread on which this WebView was instantiated.
*
- * <p>Passing {@code null} for {@code webViewRendererClien} will clear the renderer client
+ * <p>Passing {@code null} for {@code webViewRenderProcessClient} will clear the renderer client
* object for this WebView.
*
* <p>This method should only be called if
@@ -570,16 +571,18 @@
* returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE}.
*
* @param webview the {@link WebView} on which to monitor responsiveness.
- * @param webViewRendererClient the {@link WebViewRendererClient} to set for callbacks.
+ * @param webViewRenderProcessClient the {@link WebViewRenderProcessClient} to set for
+ * callbacks.
*/
@RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
- public static void setWebViewRendererClient(
- @NonNull WebView webview, @Nullable WebViewRendererClient webViewRendererClient) {
+ public static void setWebViewRenderProcessClient(
+ @NonNull WebView webview,
+ @Nullable WebViewRenderProcessClient webViewRenderProcessClient) {
final WebViewFeatureInternal feature = WebViewFeatureInternal.getFeature(
WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
if (feature.isSupportedByWebView()) {
- getProvider(webview).setWebViewRendererClient(null, webViewRendererClient);
+ getProvider(webview).setWebViewRenderProcessClient(null, webViewRenderProcessClient);
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
@@ -592,18 +595,19 @@
* {@link WebViewFeature#isFeatureSupported(String)}
* returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE}.
*
- * @return the {@link WebViewRendererClient} object associated with this WebView, if one has
- * been set via {@link #setWebViewRendererClient(WebView,WebViewRendererClient)} or {@code null}
+ * @return the {@link WebViewRenderProcessClient} object associated with this WebView, if
+ * one has been set via
+ * {@link #setWebViewRenderProcessClient(WebView,WebViewRenderProcessClient)} or {@code null}
* otherwise.
*/
@RequiresFeature(name = WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
- public static @Nullable WebViewRendererClient getWebViewRendererClient(
+ public static @Nullable WebViewRenderProcessClient getWebViewRenderProcessClient(
@NonNull WebView webview) {
final WebViewFeatureInternal feature = WebViewFeatureInternal.getFeature(
WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE);
if (feature.isSupportedByWebView()) {
- return getProvider(webview).getWebViewRendererClient();
+ return getProvider(webview).getWebViewRenderProcessClient();
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index 9fa1d36..c4bbc75 100644
--- a/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -30,8 +30,6 @@
import androidx.annotation.StringDef;
import androidx.webkit.internal.WebViewFeatureInternal;
-import org.chromium.support_lib_boundary.util.Features;
-
import java.io.OutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -88,6 +86,7 @@
WEB_VIEW_RENDERER_TERMINATE,
WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
PROXY_OVERRIDE,
+ SUPPRESS_ERROR_PAGE,
})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER, ElementType.METHOD})
@@ -101,7 +100,7 @@
* WebViewClientCompat#onPageCommitVisible(
* android.webkit.WebView, String)}.
*/
- public static final String VISUAL_STATE_CALLBACK = Features.VISUAL_STATE_CALLBACK;
+ public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -109,7 +108,7 @@
* {@link androidx.webkit.WebSettingsCompat#getOffscreenPreRaster(WebSettings)}, and
* {@link androidx.webkit.WebSettingsCompat#setOffscreenPreRaster(WebSettings, boolean)}.
*/
- public static final String OFF_SCREEN_PRERASTER = Features.OFF_SCREEN_PRERASTER;
+ public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -117,7 +116,7 @@
* {@link androidx.webkit.WebSettingsCompat#getSafeBrowsingEnabled(WebSettings)}, and
* {@link androidx.webkit.WebSettingsCompat#setSafeBrowsingEnabled(WebSettings, boolean)}.
*/
- public static final String SAFE_BROWSING_ENABLE = Features.SAFE_BROWSING_ENABLE;
+ public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -126,36 +125,36 @@
* {@link androidx.webkit.WebSettingsCompat#setDisabledActionModeMenuItems(WebSettings, int)}.
*/
public static final String DISABLED_ACTION_MODE_MENU_ITEMS =
- Features.DISABLED_ACTION_MODE_MENU_ITEMS;
+ "DISABLED_ACTION_MODE_MENU_ITEMS";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
* {@link androidx.webkit.WebViewCompat#startSafeBrowsing(Context, ValueCallback)}.
*/
- public static final String START_SAFE_BROWSING = Features.START_SAFE_BROWSING;
+ public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
* {@link androidx.webkit.WebViewCompat#setSafeBrowsingWhitelist(List, ValueCallback)}.
*/
- public static final String SAFE_BROWSING_WHITELIST = Features.SAFE_BROWSING_WHITELIST;
+ public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link WebViewCompat#getSafeBrowsingPrivacyPolicyUrl()}.
+ * {@link androidx.webkit.WebViewCompat#getSafeBrowsingPrivacyPolicyUrl()}.
*/
public static final String SAFE_BROWSING_PRIVACY_POLICY_URL =
- Features.SAFE_BROWSING_PRIVACY_POLICY_URL;
+ "SAFE_BROWSING_PRIVACY_POLICY_URL";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
* {@link ServiceWorkerControllerCompat#getInstance()}.
*/
- public static final String SERVICE_WORKER_BASIC_USAGE = Features.SERVICE_WORKER_BASIC_USAGE;
+ public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -163,7 +162,7 @@
* {@link ServiceWorkerWebSettingsCompat#getCacheMode()}, and
* {@link ServiceWorkerWebSettingsCompat#setCacheMode(int)}.
*/
- public static final String SERVICE_WORKER_CACHE_MODE = Features.SERVICE_WORKER_CACHE_MODE;
+ public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -172,7 +171,7 @@
* {@link ServiceWorkerWebSettingsCompat#setAllowContentAccess(boolean)}.
*/
public static final String SERVICE_WORKER_CONTENT_ACCESS =
- Features.SERVICE_WORKER_CONTENT_ACCESS;
+ "SERVICE_WORKER_CONTENT_ACCESS";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -180,7 +179,7 @@
* {@link ServiceWorkerWebSettingsCompat#getAllowFileAccess()}, and
* {@link ServiceWorkerWebSettingsCompat#setAllowFileAccess(boolean)}.
*/
- public static final String SERVICE_WORKER_FILE_ACCESS = Features.SERVICE_WORKER_FILE_ACCESS;
+ public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -189,7 +188,7 @@
* {@link ServiceWorkerWebSettingsCompat#setBlockNetworkLoads(boolean)}.
*/
public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS =
- Features.SERVICE_WORKER_BLOCK_NETWORK_LOADS;
+ "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -197,7 +196,7 @@
* {@link ServiceWorkerClientCompat#shouldInterceptRequest(WebResourceRequest)}.
*/
public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST =
- Features.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST;
+ "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -205,7 +204,7 @@
* {@link WebViewClientCompat#onReceivedError(android.webkit.WebView, WebResourceRequest,
* WebResourceErrorCompat)}.
*/
- public static final String RECEIVE_WEB_RESOURCE_ERROR = Features.RECEIVE_WEB_RESOURCE_ERROR;
+ public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -213,7 +212,7 @@
* {@link WebViewClientCompat#onReceivedHttpError(android.webkit.WebView, WebResourceRequest,
* WebResourceResponse)}.
*/
- public static final String RECEIVE_HTTP_ERROR = Features.RECEIVE_HTTP_ERROR;
+ public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -222,7 +221,7 @@
* WebResourceRequest)}.
*/
public static final String SHOULD_OVERRIDE_WITH_REDIRECTS =
- Features.SHOULD_OVERRIDE_WITH_REDIRECTS;
+ "SHOULD_OVERRIDE_WITH_REDIRECTS";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -230,7 +229,7 @@
* {@link WebViewClientCompat#onSafeBrowsingHit(android.webkit.WebView,
* WebResourceRequest, int, SafeBrowsingResponseCompat)}.
*/
- public static final String SAFE_BROWSING_HIT = Features.SAFE_BROWSING_HIT;
+ public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -241,7 +240,7 @@
* {@link TracingController#stop(OutputStream, Executor)}.
*/
public static final String TRACING_CONTROLLER_BASIC_USAGE =
- Features.TRACING_CONTROLLER_BASIC_USAGE;
+ "TRACING_CONTROLLER_BASIC_USAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -249,7 +248,7 @@
* {@link WebResourceRequestCompat#isRedirect(WebResourceRequest)}.
*/
public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT =
- Features.WEB_RESOURCE_REQUEST_IS_REDIRECT;
+ "WEB_RESOURCE_REQUEST_IS_REDIRECT";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -257,7 +256,7 @@
* {@link WebResourceErrorCompat#getDescription()}.
*/
public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION =
- Features.WEB_RESOURCE_ERROR_GET_DESCRIPTION;
+ "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -265,7 +264,7 @@
* {@link WebResourceErrorCompat#getErrorCode()}.
*/
public static final String WEB_RESOURCE_ERROR_GET_CODE =
- Features.WEB_RESOURCE_ERROR_GET_CODE;
+ "WEB_RESOURCE_ERROR_GET_CODE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -273,7 +272,7 @@
* {@link SafeBrowsingResponseCompat#backToSafety(boolean)}.
*/
public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY =
- Features.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY;
+ "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -281,7 +280,7 @@
* {@link SafeBrowsingResponseCompat#proceed(boolean)}.
*/
public static final String SAFE_BROWSING_RESPONSE_PROCEED =
- Features.SAFE_BROWSING_RESPONSE_PROCEED;
+ "SAFE_BROWSING_RESPONSE_PROCEED";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -289,7 +288,7 @@
* {@link SafeBrowsingResponseCompat#showInterstitial(boolean)}.
*/
public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL =
- Features.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL;
+ "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -297,14 +296,14 @@
* {@link androidx.webkit.WebMessagePortCompat#postMessage(WebMessageCompat)}.
*/
public static final String WEB_MESSAGE_PORT_POST_MESSAGE =
- Features.WEB_MESSAGE_PORT_POST_MESSAGE;
+ "WEB_MESSAGE_PORT_POST_MESSAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
* {@link androidx.webkit.WebMessagePortCompat#close()}.
*/
- public static final String WEB_MESSAGE_PORT_CLOSE = Features.WEB_MESSAGE_PORT_CLOSE;
+ public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -315,21 +314,21 @@
* WebMessagePortCompat.WebMessageCallbackCompat)}.
*/
public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK =
- Features.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK;
+ "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link WebViewCompat#createWebMessageChannel(WebView)}.
+ * {@link androidx.webkit.WebViewCompat#createWebMessageChannel(WebView)}.
*/
- public static final String CREATE_WEB_MESSAGE_CHANNEL = Features.CREATE_WEB_MESSAGE_CHANNEL;
+ public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link WebViewCompat#postWebMessage(WebView, WebMessageCompat, Uri)}.
+ * {@link androidx.webkit.WebViewCompat#postWebMessage(WebView, WebMessageCompat, Uri)}.
*/
- public static final String POST_WEB_MESSAGE = Features.POST_WEB_MESSAGE;
+ public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
@@ -338,55 +337,61 @@
* WebMessageCompat)}.
*/
public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE =
- Features.WEB_MESSAGE_CALLBACK_ON_MESSAGE;
+ "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
- * This feature covers {@link WebViewCompat#getWebViewClient(WebView)}
+ * This feature covers {@link androidx.webkit.WebViewCompat#getWebViewClient(WebView)}
*/
- public static final String GET_WEB_VIEW_CLIENT = Features.GET_WEB_VIEW_CLIENT;
+ public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
/**
* Feature for {@link #isFeatureSupported(String)}.
- * This feature covers {@link WebViewCompat#getWebChromeClient(WebView)}
+ * This feature covers {@link androidx.webkit.WebViewCompat#getWebChromeClient(WebView)}
*/
- public static final String GET_WEB_CHROME_CLIENT = Features.GET_WEB_CHROME_CLIENT;
+ public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
/**
* Feature for {@link #isFeatureSupported(String)}.
- * This feature covers {@link WebViewCompat#getWebViewRenderer(WebView)}
+ * This feature covers {@link androidx.webkit.WebViewCompat#getWebViewRenderProcess(WebView)}
*/
- public static final String GET_WEB_VIEW_RENDERER = Features.GET_WEB_VIEW_RENDERER;
+ public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
/**
* Feature for {@link #isFeatureSupported(String)}.
- * This feature covers {@link WebViewRenderer#terminate()}
+ * This feature covers {@link WebViewRenderProcess#terminate()}
*/
- public static final String WEB_VIEW_RENDERER_TERMINATE = Features.WEB_VIEW_RENDERER_TERMINATE;
+ public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
/**
- * Feature for {@link #isFeatureSupported(String)}.
+ i* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
- * {@link WebViewCompat#getWebViewRendererClient()},
- * {@link WebViewCompat#setWebViewRendererClient(WebViewRendererClient)},
- * {@link WebViewRendererClient#onRendererUnresponsive(WebView,WebViewRenderer)},
- * {@link WebViewRendererClient#onRendererResponsive(WebView,WebViewRenderer)}
+ * {@link androidx.webkit.WebViewCompat#getWebViewRenderProcessClient(WebView)},
+ * {@link androidx.webkit.WebViewCompat#setWebViewRenderProcessClient(WebView, WebViewRenderProcessClient)},
*/
public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE =
- Features.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE;
+ "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
/**
* Feature for {@link #isFeatureSupported(String)}.
* This feature covers
* {@link ProxyController#setProxyOverride(ProxyConfig, Executor, Runnable)},
- * {@link ProxyController#setProxyOverride(ProxyConfig, Runnable)},
* {@link ProxyController#clearProxyOverride(Executor, Runnable)}, and
- * {@link ProxyController#clearProxyOverride(Runnable)}.
- * TODO(laisminchillo): unhide this when we're ready to expose this
+ */
+ public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+
+ /**
+ * Feature for {@link #isFeatureSupported(String)}.
+ * This feature covers
+ * {@link WebSettingsCompat#willSuppressErrorPage(WebSettings)} and
+ * {@link WebSettingsCompat#setWillSuppressErrorPage(WebSettings, boolean)}.
+ *
+ * TODO(cricke): unhide
* @hide
*/
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public static final String PROXY_OVERRIDE = Features.PROXY_OVERRIDE;
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public static final String SUPPRESS_ERROR_PAGE = "SUPPRESS_ERROR_PAGE";
+
/**
* Return whether a feature is supported at run-time. On devices running Android version {@link
diff --git a/webkit/src/main/java/androidx/webkit/WebViewRenderProcess.java b/webkit/src/main/java/androidx/webkit/WebViewRenderProcess.java
new file mode 100644
index 0000000..734b540
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/WebViewRenderProcess.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+/**
+ * WebViewRenderProcess provides an opaque handle to a WebView renderer.
+ */
+public abstract class WebViewRenderProcess {
+ /**
+ * Cause this renderer to terminate.
+ *
+ * <p>Calling this on a not yet started, or an already terminated renderer will have no effect.
+ *
+ * <p>Terminating a renderer process may have an effect on multiple
+ * {@link android.webkit.WebView} instances.
+ *
+ * <p>RenderProcess termination must be handled by properly overriding
+ * {@link android.webkit.WebViewClient#onRenderProcessGone} for every WebView that shares this
+ * renderer. If termination is not handled by all associated WebViews, then the application
+ * process will also be terminated.
+ *
+ * <p>This method should only be called if
+ * {@link WebViewFeature#isFeatureSupported(String)}
+ * returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_TERMINATE}.
+ *
+ * @return {@code true} if it was possible to terminate this renderer, {@code false} otherwise.
+ */
+ public abstract boolean terminate();
+
+ public WebViewRenderProcess() {
+ }
+}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewRenderProcessClient.java b/webkit/src/main/java/androidx/webkit/WebViewRenderProcessClient.java
new file mode 100644
index 0000000..0344066
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/WebViewRenderProcessClient.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit;
+
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Used to receive callbacks on {@link WebView} renderer events.
+ *
+ * WebViewRenderProcessClient instances may be set or retrieved via {@link
+ * WebViewCompat#setWebViewRenderProcessClient(WebView,Executor,WebViewRenderProcessClient)}
+ * and {@link WebViewCompat#getWebViewRenderProcessClient(WebView)}.
+ *
+ * Instances may be attached to multiple WebViews, and thus a single renderer event may cause
+ * a callback to be called multiple times with different WebView parameters.
+ */
+public abstract class WebViewRenderProcessClient {
+ /**
+ * Called when the renderer currently associated with {@code view} becomes unresponsive as a
+ * result of a long running blocking task such as the execution of JavaScript.
+ *
+ * <p>If a WebView fails to process an input event, or successfully navigate to a new URL within
+ * a reasonable time frame, the renderer is considered to be unresponsive, and this callback
+ * will be called.
+ *
+ * <p>This callback will continue to be called at regular intervals as long as the renderer
+ * remains unresponsive. If the renderer becomes responsive again, {@link
+ * WebViewRenderProcessClient#onRenderProcessResponsive} will be called once, and this method
+ * will not subsequently be called unless another period of unresponsiveness is detected.
+ *
+ * <p>The minimum interval between successive calls to {@code onRenderProcessUnresponsive} is 5
+ * seconds.
+ *
+ * <p>No action is taken by WebView as a result of this method call. Applications may
+ * choose to terminate the associated renderer via the object that is passed to this callback,
+ * if in multiprocess mode, however this must be accompanied by correctly handling
+ * {@link android.webkit.WebViewClient#onRenderProcessGone} for this WebView, and all other
+ * WebViews associated with the same renderer. Failure to do so will result in application
+ * termination.
+ *
+ * @param view The {@link android.webkit.WebView} for which unresponsiveness was detected.
+ * @param renderer The {@link WebViewRenderProcess} that has become unresponsive, or
+ * {@code null} if WebView is running in single process mode.
+ */
+ public abstract void onRenderProcessUnresponsive(
+ @NonNull WebView view, @Nullable WebViewRenderProcess renderer);
+
+ /**
+ * Called once when an unresponsive renderer currently associated with {@code view} becomes
+ * responsive.
+ *
+ * <p>After a WebView renderer becomes unresponsive, which is notified to the application by
+ * {@link WebViewRenderProcessClient#onRenderProcessUnresponsive}, it is possible for the
+ * blocking renderer task to complete, returning the renderer to a responsive state. In that
+ * case, this method is called once to indicate responsiveness.
+ *
+ * <p>No action is taken by WebView as a result of this method call.
+ *
+ * @param view The {@link android.webkit.WebView} for which responsiveness was detected.
+ *
+ * @param renderer The {@link WebViewRenderProcess} that has become responsive, or {@code null}
+ * if WebView is running in single process mode.
+ */
+ public abstract void onRenderProcessResponsive(
+ @NonNull WebView view, @Nullable WebViewRenderProcess renderer);
+}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewRenderer.java b/webkit/src/main/java/androidx/webkit/WebViewRenderer.java
deleted file mode 100644
index 6b5d264..0000000
--- a/webkit/src/main/java/androidx/webkit/WebViewRenderer.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit;
-
-import androidx.annotation.RestrictTo;
-
-/**
- * WebViewRenderer provides an opaque handle to a WebView renderer.
- */
-public abstract class WebViewRenderer {
- /**
- * Cause this renderer to terminate.
- *
- * <p>Calling this on a not yet started, or an already terminated renderer will have no effect.
- *
- * <p>Terminating a renderer process may have an effect on multiple
- * {@link android.webkit.WebView} instances.
- *
- * <p>Renderer termination must be handled by properly overriding
- * {@link android.webkit.WebViewClient#onRenderProcessGone} for every WebView that shares this
- * renderer. If termination is not handled by all associated WebViews, then the application
- * process will also be terminated.
- *
- * <p>This method should only be called if
- * {@link WebViewFeature#isFeatureSupported(String)}
- * returns true for {@link WebViewFeature#WEB_VIEW_RENDERER_TERMINATE}.
- *
- * @return {@code true} if it was possible to terminate this renderer, {@code false} otherwise.
- */
- public abstract boolean terminate();
-
- /**
- * This class cannot be created by applications.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- public WebViewRenderer() {
- }
-}
diff --git a/webkit/src/main/java/androidx/webkit/WebViewRendererClient.java b/webkit/src/main/java/androidx/webkit/WebViewRendererClient.java
deleted file mode 100644
index 47e550f..0000000
--- a/webkit/src/main/java/androidx/webkit/WebViewRendererClient.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit;
-
-import android.webkit.WebView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Used to receive callbacks on {@link WebView} renderer events.
- *
- * WebViewRendererClient instances may be set or retrieved via {@link
- * WebViewCompat#setWebViewRendererClient(WebView,Executor,WebViewRendererClient)} and {@link
- * WebViewCompat#getWebViewRendererClient(WebView)}.
- *
- * Instances may be attached to multiple WebViews, and thus a single renderer event may cause
- * a callback to be called multiple times with different WebView parameters.
- */
-public abstract class WebViewRendererClient {
- /**
- * Called when the renderer currently associated with {@code view} becomes unresponsive as a
- * result of a long running blocking task such as the execution of JavaScript.
- *
- * <p>If a WebView fails to process an input event, or successfully navigate to a new URL within
- * a reasonable time frame, the renderer is considered to be unresponsive, and this callback
- * will be called.
- *
- * <p>This callback will continue to be called at regular intervals as long as the renderer
- * remains unresponsive. If the renderer becomes responsive again, {@link
- * WebViewRendererClient#onRendererResponsive} will be called once, and this method will not
- * subsequently be called unless another period of unresponsiveness is detected.
- *
- * <p>No action is taken by WebView as a result of this method call. Applications may
- * choose to terminate the associated renderer via the object that is passed to this callback,
- * if in multiprocess mode, however this must be accompanied by correctly handling
- * {@link android.webkit.WebViewClient#onRenderProcessGone} for this WebView, and all other
- * WebViews associated with the same renderer. Failure to do so will result in application
- * termination.
- *
- * @param view The {@link android.webkit.WebView} for which unresponsiveness was detected.
- * @param renderer The {@link WebViewRenderer} that has become unresponsive, or {@code null} if
- * WebView is running in single process mode.
- */
- public abstract void onRendererUnresponsive(
- @NonNull WebView view, @Nullable WebViewRenderer renderer);
-
- /**
- * Called once when an unresponsive renderer currently associated with {@code view} becomes
- * responsive.
- *
- * <p>After a WebView renderer becomes unresponsive, which is notified to the application by
- * {@link WebViewRendererClient#onRendererUnresponsive}, it is possible for the blocking
- * renderer task to complete, returning the renderer to a responsive state. In that case,
- * this method is called once to indicate responsiveness.
- *
- * <p>No action is taken by WebView as a result of this method call.
- *
- * @param view The {@link android.webkit.WebView} for which responsiveness was detected.
- *
- * @param renderer The {@link WebViewRenderer} that has become responsive, or {@code null} if
- * WebView is running in single process mode.
- */
- public abstract void onRendererResponsive(
- @NonNull WebView view, @Nullable WebViewRenderer renderer);
-}
diff --git a/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
index d5212d9..1b61f33 100644
--- a/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
+++ b/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
@@ -17,7 +17,6 @@
package androidx.webkit.internal;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.WebViewFeature;
@@ -33,30 +32,21 @@
private ProxyControllerBoundaryInterface mBoundaryInterface;
@Override
- public void setProxyOverride(@NonNull ProxyConfig proxyConfig, @Nullable Runnable listener) {
- setProxyOverride(proxyConfig, new SynchronousExecutor(), listener);
- }
-
- @Override
public void setProxyOverride(@NonNull ProxyConfig proxyConfig, @NonNull Executor executor,
- @Nullable Runnable listener) {
+ @NonNull Runnable listener) {
WebViewFeatureInternal webViewFeature =
WebViewFeatureInternal.getFeature(WebViewFeature.PROXY_OVERRIDE);
if (webViewFeature.isSupportedByWebView()) {
- getBoundaryInterface().setProxyOverride(proxyConfig.proxyRules(),
- proxyConfig.bypassRules(), listener, executor);
+ getBoundaryInterface().setProxyOverride(
+ proxyConfig.proxyRules().toArray(new String[0][]),
+ proxyConfig.bypassRules().toArray(new String[0]), listener, executor);
} else {
throw WebViewFeatureInternal.getUnsupportedOperationException();
}
}
@Override
- public void clearProxyOverride(@Nullable Runnable listener) {
- clearProxyOverride(new SynchronousExecutor(), listener);
- }
-
- @Override
- public void clearProxyOverride(@NonNull Executor executor, @Nullable Runnable listener) {
+ public void clearProxyOverride(@NonNull Executor executor, @NonNull Runnable listener) {
WebViewFeatureInternal webViewFeature =
WebViewFeatureInternal.getFeature(WebViewFeature.PROXY_OVERRIDE);
if (webViewFeature.isSupportedByWebView()) {
@@ -72,11 +62,4 @@
}
return mBoundaryInterface;
}
-
- static class SynchronousExecutor implements Executor {
- @Override
- public void execute(Runnable r) {
- r.run();
- }
- }
}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
index 091879c..672a594 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebSettingsAdapter.java
@@ -72,4 +72,18 @@
return mBoundaryInterface.getDisabledActionModeMenuItems();
}
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#setWillSuppressErrorPage}.
+ */
+ public void setWillSuppressErrorPage(boolean suppressed) {
+ mBoundaryInterface.setWillSuppressErrorPage(suppressed);
+ }
+
+ /**
+ * Adapter method for {@link androidx.webkit.WebSettingsCompat#willSuppressErrorPage}.
+ */
+ public boolean willSuppressErrorPage() {
+ return mBoundaryInterface.getWillSuppressErrorPage();
+ }
+
}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
index 768387a..9386406 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewFeatureInternal.java
@@ -26,6 +26,7 @@
import android.webkit.WebSettings;
import android.webkit.WebView;
+import androidx.annotation.NonNull;
import androidx.webkit.ProxyConfig;
import androidx.webkit.ProxyController;
import androidx.webkit.SafeBrowsingResponseCompat;
@@ -60,21 +61,24 @@
* androidx.webkit.WebViewCompat.VisualStateCallback)}, and
* {@link WebViewClientCompat#onPageCommitVisible(android.webkit.WebView, String)}.
*/
- VISUAL_STATE_CALLBACK_FEATURE(WebViewFeature.VISUAL_STATE_CALLBACK, Build.VERSION_CODES.M),
+ VISUAL_STATE_CALLBACK_FEATURE(WebViewFeature.VISUAL_STATE_CALLBACK,
+ Features.VISUAL_STATE_CALLBACK, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link androidx.webkit.WebSettingsCompat#getOffscreenPreRaster(WebSettings)}, and
* {@link androidx.webkit.WebSettingsCompat#setOffscreenPreRaster(WebSettings, boolean)}.
*/
- OFF_SCREEN_PRERASTER(WebViewFeature.OFF_SCREEN_PRERASTER, Build.VERSION_CODES.M),
+ OFF_SCREEN_PRERASTER(WebViewFeature.OFF_SCREEN_PRERASTER, Features.OFF_SCREEN_PRERASTER,
+ Build.VERSION_CODES.M),
/**
* This feature covers
* {@link androidx.webkit.WebSettingsCompat#getSafeBrowsingEnabled(WebSettings)}, and
* {@link androidx.webkit.WebSettingsCompat#setSafeBrowsingEnabled(WebSettings, boolean)}.
*/
- SAFE_BROWSING_ENABLE(WebViewFeature.SAFE_BROWSING_ENABLE, Build.VERSION_CODES.O),
+ SAFE_BROWSING_ENABLE(WebViewFeature.SAFE_BROWSING_ENABLE, Features.SAFE_BROWSING_ENABLE,
+ Build.VERSION_CODES.O),
/**
* This feature covers
@@ -82,39 +86,43 @@
* {@link androidx.webkit.WebSettingsCompat#setDisabledActionModeMenuItems(WebSettings, int)}.
*/
DISABLED_ACTION_MODE_MENU_ITEMS(WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS,
- Build.VERSION_CODES.N),
+ Features.DISABLED_ACTION_MODE_MENU_ITEMS, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link androidx.webkit.WebViewCompat#startSafeBrowsing(Context, ValueCallback)}.
*/
- START_SAFE_BROWSING(WebViewFeature.START_SAFE_BROWSING, Build.VERSION_CODES.O_MR1),
+ START_SAFE_BROWSING(WebViewFeature.START_SAFE_BROWSING, Features.START_SAFE_BROWSING,
+ Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link androidx.webkit.WebViewCompat#setSafeBrowsingWhitelist(List, ValueCallback)}.
*/
- SAFE_BROWSING_WHITELIST(WebViewFeature.SAFE_BROWSING_WHITELIST, Build.VERSION_CODES.O_MR1),
+ SAFE_BROWSING_WHITELIST(WebViewFeature.SAFE_BROWSING_WHITELIST,
+ Features.SAFE_BROWSING_WHITELIST, Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link WebViewCompat#getSafeBrowsingPrivacyPolicyUrl()}.
*/
SAFE_BROWSING_PRIVACY_POLICY_URL(WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL,
- Build.VERSION_CODES.O_MR1),
+ Features.SAFE_BROWSING_PRIVACY_POLICY_URL, Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link androidx.webkit.ServiceWorkerControllerCompat#getInstance()}.
*/
- SERVICE_WORKER_BASIC_USAGE(WebViewFeature.SERVICE_WORKER_BASIC_USAGE, Build.VERSION_CODES.N),
+ SERVICE_WORKER_BASIC_USAGE(WebViewFeature.SERVICE_WORKER_BASIC_USAGE,
+ Features.SERVICE_WORKER_BASIC_USAGE, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#getCacheMode()}, and
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setCacheMode(int)}.
*/
- SERVICE_WORKER_CACHE_MODE(WebViewFeature.SERVICE_WORKER_CACHE_MODE, Build.VERSION_CODES.N),
+ SERVICE_WORKER_CACHE_MODE(WebViewFeature.SERVICE_WORKER_CACHE_MODE,
+ Features.SERVICE_WORKER_CACHE_MODE, Build.VERSION_CODES.N),
/**
* This feature covers
@@ -122,14 +130,15 @@
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setAllowContentAccess(boolean)}.
*/
SERVICE_WORKER_CONTENT_ACCESS(WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS,
- Build.VERSION_CODES.N),
+ Features.SERVICE_WORKER_CONTENT_ACCESS, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#getAllowFileAccess()}, and
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setAllowFileAccess(boolean)}.
*/
- SERVICE_WORKER_FILE_ACCESS(WebViewFeature.SERVICE_WORKER_FILE_ACCESS, Build.VERSION_CODES.N),
+ SERVICE_WORKER_FILE_ACCESS(WebViewFeature.SERVICE_WORKER_FILE_ACCESS,
+ Features.SERVICE_WORKER_FILE_ACCESS, Build.VERSION_CODES.N),
/**
* This feature covers
@@ -137,28 +146,30 @@
* {@link androidx.webkit.ServiceWorkerWebSettingsCompat#setBlockNetworkLoads(boolean)}.
*/
SERVICE_WORKER_BLOCK_NETWORK_LOADS(WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS,
- Build.VERSION_CODES.N),
+ Features.SERVICE_WORKER_BLOCK_NETWORK_LOADS, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link ServiceWorkerClientCompat#shouldInterceptRequest(WebResourceRequest)}.
*/
SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST(WebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST,
- Build.VERSION_CODES.N),
+ Features.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link WebViewClientCompat#onReceivedError(android.webkit.WebView, WebResourceRequest,
* WebResourceErrorCompat)}.
*/
- RECEIVE_WEB_RESOURCE_ERROR(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR, Build.VERSION_CODES.M),
+ RECEIVE_WEB_RESOURCE_ERROR(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR,
+ Features.RECEIVE_WEB_RESOURCE_ERROR, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link WebViewClientCompat#onReceivedHttpError(android.webkit.WebView, WebResourceRequest,
* WebResourceResponse)}.
*/
- RECEIVE_HTTP_ERROR(WebViewFeature.RECEIVE_HTTP_ERROR, Build.VERSION_CODES.M),
+ RECEIVE_HTTP_ERROR(WebViewFeature.RECEIVE_HTTP_ERROR, Features.RECEIVE_HTTP_ERROR,
+ Build.VERSION_CODES.M),
/**
* This feature covers
@@ -166,49 +177,50 @@
* WebResourceRequest)}.
*/
SHOULD_OVERRIDE_WITH_REDIRECTS(WebViewFeature.SHOULD_OVERRIDE_WITH_REDIRECTS,
- Build.VERSION_CODES.N),
+ Features.SHOULD_OVERRIDE_WITH_REDIRECTS, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link WebViewClientCompat#onSafeBrowsingHit(android.webkit.WebView,
* WebResourceRequest, int, SafeBrowsingResponseCompat)}.
*/
- SAFE_BROWSING_HIT(WebViewFeature.SAFE_BROWSING_HIT, Build.VERSION_CODES.O_MR1),
+ SAFE_BROWSING_HIT(WebViewFeature.SAFE_BROWSING_HIT, Features.SAFE_BROWSING_HIT,
+ Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link WebResourceRequestCompat#isRedirect(WebResourceRequest)}.
*/
WEB_RESOURCE_REQUEST_IS_REDIRECT(WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT,
- Build.VERSION_CODES.N),
+ Features.WEB_RESOURCE_REQUEST_IS_REDIRECT, Build.VERSION_CODES.N),
/**
* This feature covers
* {@link WebResourceErrorCompat#getDescription()}.
*/
WEB_RESOURCE_ERROR_GET_DESCRIPTION(WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION,
- Build.VERSION_CODES.M),
+ Features.WEB_RESOURCE_ERROR_GET_DESCRIPTION, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link WebResourceErrorCompat#getErrorCode()}.
*/
WEB_RESOURCE_ERROR_GET_CODE(WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE,
- Build.VERSION_CODES.M),
+ Features.WEB_RESOURCE_ERROR_GET_CODE, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link SafeBrowsingResponseCompat#backToSafety(boolean)}.
*/
SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY(WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY,
- Build.VERSION_CODES.O_MR1),
+ Features.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link SafeBrowsingResponseCompat#proceed(boolean)}.
*/
SAFE_BROWSING_RESPONSE_PROCEED(WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED,
- Build.VERSION_CODES.O_MR1),
+ Features.SAFE_BROWSING_RESPONSE_PROCEED, Build.VERSION_CODES.O_MR1),
/**
* This feature covers
@@ -216,20 +228,20 @@
*/
SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL(
WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL,
- Build.VERSION_CODES.O_MR1),
+ Features.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, Build.VERSION_CODES.O_MR1),
/**
* This feature covers
* {@link WebMessagePortCompat#postMessage(WebMessageCompat)}.
*/
WEB_MESSAGE_PORT_POST_MESSAGE(WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE,
- Build.VERSION_CODES.M),
+ Features.WEB_MESSAGE_PORT_POST_MESSAGE, Build.VERSION_CODES.M),
/**
* * This feature covers
* {@link androidx.webkit.WebMessagePortCompat#close()}.
*/
- WEB_MESSAGE_PORT_CLOSE(WebViewFeature.WEB_MESSAGE_PORT_CLOSE,
+ WEB_MESSAGE_PORT_CLOSE(WebViewFeature.WEB_MESSAGE_PORT_CLOSE, Features.WEB_MESSAGE_PORT_CLOSE,
Build.VERSION_CODES.M),
/**
@@ -240,20 +252,20 @@
* WebMessagePortCompat.WebMessageCallbackCompat)}.
*/
WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK(WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK,
- Build.VERSION_CODES.M),
+ Features.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link WebViewCompat#createWebMessageChannel(WebView)}.
*/
CREATE_WEB_MESSAGE_CHANNEL(WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL,
- Build.VERSION_CODES.M),
+ Features.CREATE_WEB_MESSAGE_CHANNEL, Build.VERSION_CODES.M),
/**
* This feature covers
* {@link WebViewCompat#postWebMessage(WebView, WebMessageCompat, Uri)}.
*/
- POST_WEB_MESSAGE(WebViewFeature.POST_WEB_MESSAGE,
+ POST_WEB_MESSAGE(WebViewFeature.POST_WEB_MESSAGE, Features.POST_WEB_MESSAGE,
Build.VERSION_CODES.M),
/**
@@ -261,20 +273,23 @@
* {@link WebViewCompat#postWebMessage(WebView, WebMessageCompat, Uri)}.
*/
WEB_MESSAGE_CALLBACK_ON_MESSAGE(WebViewFeature.WEB_MESSAGE_CALLBACK_ON_MESSAGE,
- Build.VERSION_CODES.M),
+ Features.WEB_MESSAGE_CALLBACK_ON_MESSAGE, Build.VERSION_CODES.M),
/**
* This feature covers {@link WebViewCompat#getWebViewClient(WebView)}.
*/
- GET_WEB_VIEW_CLIENT(Features.GET_WEB_VIEW_CLIENT, Build.VERSION_CODES.O),
+ GET_WEB_VIEW_CLIENT(WebViewFeature.GET_WEB_VIEW_CLIENT, Features.GET_WEB_VIEW_CLIENT,
+ Build.VERSION_CODES.O),
/**
* This feature covers {@link WebViewCompat#getWebChromeClient(WebView)}.
*/
- GET_WEB_CHROME_CLIENT(WebViewFeature.GET_WEB_CHROME_CLIENT, Build.VERSION_CODES.O),
+ GET_WEB_CHROME_CLIENT(WebViewFeature.GET_WEB_CHROME_CLIENT, Features.GET_WEB_CHROME_CLIENT,
+ Build.VERSION_CODES.O),
- GET_WEB_VIEW_RENDERER(WebViewFeature.GET_WEB_VIEW_RENDERER),
- WEB_VIEW_RENDERER_TERMINATE(WebViewFeature.WEB_VIEW_RENDERER_TERMINATE),
+ GET_WEB_VIEW_RENDERER(WebViewFeature.GET_WEB_VIEW_RENDERER, Features.GET_WEB_VIEW_RENDERER),
+ WEB_VIEW_RENDERER_TERMINATE(WebViewFeature.WEB_VIEW_RENDERER_TERMINATE,
+ Features.WEB_VIEW_RENDERER_TERMINATE),
/**
* This feature covers
@@ -284,16 +299,17 @@
* {@link TracingController#stop(OutputStream, Executor)}.
*/
TRACING_CONTROLLER_BASIC_USAGE(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE,
- Build.VERSION_CODES.P),
+ Features.TRACING_CONTROLLER_BASIC_USAGE, Build.VERSION_CODES.P),
/**
* This feature covers
- * {@link WebViewCompat#getWebViewRendererClient()},
- * {@link WebViewCompat#setWebViewRendererClient(WebViewRendererClient)},
- * {@link WebViewRendererClient#onRendererUnresponsive(WebView,WebViewRenderer)},
- * {@link WebViewRendererClient#onRendererResponsive(WebView,WebViewRenderer)}
+ * {@link WebViewCompat#getWebViewRenderProcessClient()},
+ * {@link WebViewCompat#setWebViewRenderProcessClient(WebViewRenderProcessClient)},
+ * {@link WebViewRenderProcessClient#onRenderProcessUnresponsive(WebView,WebViewRenderProcess)},
+ * {@link WebViewRenderProcessClient#onRenderProcessResponsive(WebView,WebViewRenderProcess)}
*/
- WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE),
+ WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE(WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
+ Features.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE),
/**
* This feature covers
@@ -302,12 +318,20 @@
* {@link ProxyController#clearProxyOverride(Executor, Runnable)}, and
* {@link ProxyController#clearProxyOverride(Runnable)}.
*/
- PROXY_OVERRIDE(WebViewFeature.PROXY_OVERRIDE),
+ PROXY_OVERRIDE(WebViewFeature.PROXY_OVERRIDE, Features.PROXY_OVERRIDE),
+
+ /**
+ * This feature covers
+ * {@link androidx.webkit.WebSettingsCompat#willSuppressErrorPage(WebSettings)} and
+ * {@link androidx.webkit.WebSettingsCompat#setWillSuppressErrorPage(WebSettings, boolean)}.
+ */
+ SUPPRESS_ERROR_PAGE(WebViewFeature.SUPPRESS_ERROR_PAGE, Features.SUPPRESS_ERROR_PAGE),
; // This semicolon ends the enum. Add new features with a trailing comma above this line.
private static final int NOT_SUPPORTED_BY_FRAMEWORK = -1;
- private final String mFeatureValue;
+ private final String mPublicFeatureValue;
+ private final String mInternalFeatureValue;
private final int mOsVersion;
/**
@@ -316,33 +340,42 @@
* <p>Features constructed with this constructor can be later converted to use the
* other constructor if framework support is added.
*
- * @param featureValue The feature string denoting this feature
+ * @param publicFeatureValue The public facing feature string denoting this feature
+ * @param internalFeatureValue The internal feature string denoting this feature
*/
- WebViewFeatureInternal(String featureValue) {
- this(featureValue, NOT_SUPPORTED_BY_FRAMEWORK);
+ WebViewFeatureInternal(@NonNull @WebViewFeature.WebViewSupportFeature String publicFeatureValue,
+ @NonNull String internalFeatureValue) {
+ this(publicFeatureValue, internalFeatureValue, NOT_SUPPORTED_BY_FRAMEWORK);
}
/**
* Creates a WebViewFeatureInternal that is implemented in the framework.
*
- * @param featureValue The feature string denoting this feature
- * @param osVersion The Android SDK level after which this feature is implemented in the
- * framework.
+ * @param publicFeatureValue The public facing feature string denoting this feature
+ * @param internalFeatureValue The internal feature string denoting this feature
+ * @param osVersion The Android SDK level after which this feature is implemented
+ * in the framework.
*/
- WebViewFeatureInternal(String featureValue, int osVersion) {
- assert !featureValue.endsWith(Features.DEV_SUFFIX);
- mFeatureValue = featureValue;
+ WebViewFeatureInternal(@NonNull @WebViewFeature.WebViewSupportFeature String publicFeatureValue,
+ @NonNull String internalFeatureValue, int osVersion) {
+ assert !publicFeatureValue.endsWith(Features.DEV_SUFFIX);
+ assert !internalFeatureValue.endsWith(Features.DEV_SUFFIX);
+ mPublicFeatureValue = publicFeatureValue;
+ mInternalFeatureValue = internalFeatureValue;
mOsVersion = osVersion;
}
/**
* Return the {@link WebViewFeatureInternal} corresponding to {@param feature}.
*/
- public static WebViewFeatureInternal getFeature(String feature) {
+ public static WebViewFeatureInternal getFeature(@NonNull @WebViewFeature.WebViewSupportFeature
+ String publicFeatureValue) {
for (WebViewFeatureInternal internalFeature : WebViewFeatureInternal.values()) {
- if (internalFeature.mFeatureValue.equals(feature)) return internalFeature;
+ if (internalFeature.mPublicFeatureValue.equals(publicFeatureValue)) {
+ return internalFeature;
+ }
}
- throw new RuntimeException("Unknown feature " + feature);
+ throw new RuntimeException("Unknown feature " + publicFeatureValue);
}
/**
@@ -361,7 +394,7 @@
*/
public boolean isSupportedByWebView() {
return BoundaryInterfaceReflectionUtil.containsFeature(
- LAZY_HOLDER.WEBVIEW_APK_FEATURES, mFeatureValue);
+ LAZY_HOLDER.WEBVIEW_APK_FEATURES, mInternalFeatureValue);
}
private static class LAZY_HOLDER {
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
index 53eaaa0..bd355dc 100644
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
@@ -24,8 +24,8 @@
import androidx.webkit.WebMessageCompat;
import androidx.webkit.WebMessagePortCompat;
import androidx.webkit.WebViewCompat;
-import androidx.webkit.WebViewRenderer;
-import androidx.webkit.WebViewRendererClient;
+import androidx.webkit.WebViewRenderProcess;
+import androidx.webkit.WebViewRenderProcessClient;
import org.chromium.support_lib_boundary.WebViewProviderBoundaryInterface;
import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
@@ -94,28 +94,28 @@
/**
* Adapter method for {@link WebViewCompat#getWebViewRenderer()}.
*/
- public WebViewRenderer getWebViewRenderer() {
- return WebViewRendererImpl.forInvocationHandler(mImpl.getWebViewRenderer());
+ public WebViewRenderProcess getWebViewRenderProcess() {
+ return WebViewRenderProcessImpl.forInvocationHandler(mImpl.getWebViewRenderer());
}
/**
* Adapter method for {@link WebViewCompat#getWebViewRendererClient()}.
*/
- public WebViewRendererClient getWebViewRendererClient() {
+ public WebViewRenderProcessClient getWebViewRenderProcessClient() {
InvocationHandler handler = mImpl.getWebViewRendererClient();
if (handler == null) return null;
- return ((WebViewRendererClientAdapter)
+ return ((WebViewRenderProcessClientAdapter)
BoundaryInterfaceReflectionUtil.getDelegateFromInvocationHandler(
- handler)).getWebViewRendererClient();
+ handler)).getWebViewRenderProcessClient();
}
/**
* Adapter method for {@link WebViewCompat#setWebViewRendererClient(WebViewRendererClient)}.
*/
- public void setWebViewRendererClient(
- Executor executor, WebViewRendererClient webViewRendererClient) {
+ public void setWebViewRenderProcessClient(
+ Executor executor, WebViewRenderProcessClient webViewRenderProcessClient) {
mImpl.setWebViewRendererClient(
BoundaryInterfaceReflectionUtil.createInvocationHandlerFor(
- new WebViewRendererClientAdapter(executor, webViewRendererClient)));
+ new WebViewRenderProcessClientAdapter(executor, webViewRenderProcessClient)));
}
}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessClientAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessClientAdapter.java
new file mode 100644
index 0000000..5e2125a
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessClientAdapter.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import android.webkit.WebView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.webkit.WebViewRenderProcess;
+import androidx.webkit.WebViewRenderProcessClient;
+
+import org.chromium.support_lib_boundary.WebViewRendererClientBoundaryInterface;
+import org.chromium.support_lib_boundary.util.Features;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.concurrent.Executor;
+
+class WebViewRenderProcessClientAdapter implements WebViewRendererClientBoundaryInterface {
+ private static final String[] sSupportedFeatures = new String[] {
+ Features.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
+ };
+
+ private final Executor mExecutor;
+ private final WebViewRenderProcessClient mWebViewRenderProcessClient;
+
+ WebViewRenderProcessClientAdapter(
+ Executor executor, WebViewRenderProcessClient webViewRenderProcessClient) {
+ mExecutor = executor;
+ mWebViewRenderProcessClient = webViewRenderProcessClient;
+ }
+
+ WebViewRenderProcessClient getWebViewRenderProcessClient() {
+ return mWebViewRenderProcessClient;
+ }
+
+ /**
+ * Returns the list of features this client supports. This feature list should always be a
+ * subset of the Features declared in WebViewFeature.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Override
+ public final String[] getSupportedFeatures() {
+ return sSupportedFeatures;
+ }
+
+ /**
+ * Invoked by chromium with arguments that need to be wrapped by support library adapter
+ * objects.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Override
+ public final void onRendererUnresponsive(
+ @NonNull final WebView view,
+ @NonNull /* WebViewRenderer */ final InvocationHandler renderer) {
+ final WebViewRenderProcess rendererObject =
+ WebViewRenderProcessImpl.forInvocationHandler(renderer);
+ final WebViewRenderProcessClient client = mWebViewRenderProcessClient;
+ if (mExecutor == null) {
+ client.onRenderProcessUnresponsive(view, rendererObject);
+ } else {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ client.onRenderProcessUnresponsive(view, rendererObject);
+ }
+ });
+ }
+ }
+
+ /**
+ * Invoked by chromium with arguments that need to be wrapped by support library adapter
+ * objects.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ @Override
+ public final void onRendererResponsive(
+ @NonNull final WebView view,
+ @NonNull /* WebViewRenderer */ InvocationHandler renderer) {
+ final WebViewRenderProcess rendererObject =
+ WebViewRenderProcessImpl.forInvocationHandler(renderer);
+ final WebViewRenderProcessClient client = mWebViewRenderProcessClient;
+ if (mExecutor == null) {
+ client.onRenderProcessResponsive(view, rendererObject);
+ } else {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ client.onRenderProcessResponsive(view, rendererObject);
+ }
+ });
+ }
+ }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessImpl.java b/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessImpl.java
new file mode 100644
index 0000000..82b5e51
--- /dev/null
+++ b/webkit/src/main/java/androidx/webkit/internal/WebViewRenderProcessImpl.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.webkit.internal;
+
+import androidx.annotation.Nullable;
+import androidx.webkit.WebViewFeature;
+import androidx.webkit.WebViewRenderProcess;
+
+import org.chromium.support_lib_boundary.WebViewRendererBoundaryInterface;
+import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.concurrent.Callable;
+
+/**
+ * Implementation of {@link WebViewRenderProcess}.
+ * This class uses the WebView APK to implement
+ * {@link WebViewRenderProcess} functionality.
+ */
+public class WebViewRenderProcessImpl extends WebViewRenderProcess {
+ private WebViewRendererBoundaryInterface mBoundaryInterface;
+
+ public WebViewRenderProcessImpl(WebViewRendererBoundaryInterface boundaryInterface) {
+ mBoundaryInterface = boundaryInterface;
+ }
+
+ /**
+ * Get a support library WebViewRenderProcess object that is 1:1 with the webview object.
+ */
+ public static @Nullable WebViewRenderProcessImpl forInvocationHandler(
+ InvocationHandler invocationHandler) {
+ // Make a possibly temporary proxy object in order to call into WebView.
+ final WebViewRendererBoundaryInterface boundaryInterface =
+ BoundaryInterfaceReflectionUtil.castToSuppLibClass(
+ WebViewRendererBoundaryInterface.class,
+ invocationHandler);
+
+ // Ask WebView to either call us back to create the wrapper object, or
+ // to return a previously created wrapper object.
+ return (WebViewRenderProcessImpl) boundaryInterface.getOrCreatePeer(
+ new Callable<Object>() {
+ @Override
+ public Object call() {
+ return new WebViewRenderProcessImpl(boundaryInterface);
+ }
+ });
+ }
+
+ @Override
+ public boolean terminate() {
+ final WebViewFeatureInternal feature =
+ WebViewFeatureInternal.getFeature(WebViewFeature.WEB_VIEW_RENDERER_TERMINATE);
+ if (feature.isSupportedByWebView()) {
+ return mBoundaryInterface.terminate();
+ } else {
+ throw WebViewFeatureInternal.getUnsupportedOperationException();
+ }
+ }
+}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewRendererClientAdapter.java b/webkit/src/main/java/androidx/webkit/internal/WebViewRendererClientAdapter.java
deleted file mode 100644
index d100de5..0000000
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewRendererClientAdapter.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit.internal;
-
-import android.webkit.WebView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.webkit.WebViewRenderer;
-import androidx.webkit.WebViewRendererClient;
-
-import org.chromium.support_lib_boundary.WebViewRendererClientBoundaryInterface;
-import org.chromium.support_lib_boundary.util.Features;
-
-import java.lang.reflect.InvocationHandler;
-import java.util.concurrent.Executor;
-
-class WebViewRendererClientAdapter implements WebViewRendererClientBoundaryInterface {
- private static final String[] sSupportedFeatures = new String[] {
- Features.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE,
- };
-
- private final Executor mExecutor;
- private final WebViewRendererClient mWebViewRendererClient;
-
- WebViewRendererClientAdapter(Executor executor, WebViewRendererClient webViewRendererClient) {
- mExecutor = executor;
- mWebViewRendererClient = webViewRendererClient;
- }
-
- WebViewRendererClient getWebViewRendererClient() {
- return mWebViewRendererClient;
- }
-
- /**
- * Returns the list of features this client supports. This feature list should always be a
- * subset of the Features declared in WebViewFeature.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- @Override
- public final String[] getSupportedFeatures() {
- return sSupportedFeatures;
- }
-
- /**
- * Invoked by chromium with arguments that need to be wrapped by support library adapter
- * objects.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- @Override
- public final void onRendererUnresponsive(
- @NonNull final WebView view,
- @NonNull /* WebViewRenderer */ final InvocationHandler renderer) {
- final WebViewRenderer rendererObject = WebViewRendererImpl.forInvocationHandler(renderer);
- final WebViewRendererClient client = mWebViewRendererClient;
- if (mExecutor == null) {
- client.onRendererUnresponsive(view, rendererObject);
- } else {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- client.onRendererUnresponsive(view, rendererObject);
- }
- });
- }
- }
-
- /**
- * Invoked by chromium with arguments that need to be wrapped by support library adapter
- * objects.
- *
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- @Override
- public final void onRendererResponsive(
- @NonNull final WebView view,
- @NonNull /* WebViewRenderer */ InvocationHandler renderer) {
- final WebViewRenderer rendererObject = WebViewRendererImpl.forInvocationHandler(renderer);
- final WebViewRendererClient client = mWebViewRendererClient;
- if (mExecutor == null) {
- client.onRendererResponsive(view, rendererObject);
- } else {
- mExecutor.execute(new Runnable() {
- @Override
- public void run() {
- client.onRendererResponsive(view, rendererObject);
- }
- });
- }
- }
-}
diff --git a/webkit/src/main/java/androidx/webkit/internal/WebViewRendererImpl.java b/webkit/src/main/java/androidx/webkit/internal/WebViewRendererImpl.java
deleted file mode 100644
index c1a440f..0000000
--- a/webkit/src/main/java/androidx/webkit/internal/WebViewRendererImpl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.webkit.internal;
-
-import androidx.annotation.Nullable;
-import androidx.webkit.WebViewFeature;
-import androidx.webkit.WebViewRenderer;
-
-import org.chromium.support_lib_boundary.WebViewRendererBoundaryInterface;
-import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
-
-import java.lang.reflect.InvocationHandler;
-import java.util.concurrent.Callable;
-
-/**
- * Implementation of {@link WebViewRenderer}.
- * This class uses the WebView APK to implement
- * {@link WebViewRenderer} functionality.
- */
-public class WebViewRendererImpl extends WebViewRenderer {
- private WebViewRendererBoundaryInterface mBoundaryInterface;
-
- public WebViewRendererImpl(WebViewRendererBoundaryInterface boundaryInterface) {
- mBoundaryInterface = boundaryInterface;
- }
-
- /**
- * Get a support library WebViewRenderer object that is 1:1 with the webview object.
- */
- public static @Nullable WebViewRendererImpl forInvocationHandler(
- InvocationHandler invocationHandler) {
- // Make a possibly temporary proxy object in order to call into WebView.
- final WebViewRendererBoundaryInterface boundaryInterface =
- BoundaryInterfaceReflectionUtil.castToSuppLibClass(
- WebViewRendererBoundaryInterface.class,
- invocationHandler);
-
- // Ask WebView to either call us back to create the wrapper object, or
- // to return a previously created wrapper object.
- return (WebViewRendererImpl) boundaryInterface.getOrCreatePeer(
- new Callable<Object>() {
- @Override
- public Object call() {
- return new WebViewRendererImpl(boundaryInterface);
- }
- });
- }
-
- @Override
- public boolean terminate() {
- final WebViewFeatureInternal feature =
- WebViewFeatureInternal.getFeature(WebViewFeature.WEB_VIEW_RENDERER_TERMINATE);
- if (feature.isSupportedByWebView()) {
- return mBoundaryInterface.terminate();
- } else {
- throw WebViewFeatureInternal.getUnsupportedOperationException();
- }
- }
-}
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 02db71a..c85c010 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -35,14 +35,22 @@
}
dependencies {
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+ // Using -PuseMaxDepVersions does not use the right version of the annotation processor
+ // Remove this workaround after b/127495641 is fixed
+ if (project.hasProperty('useMaxDepVersions')) {
+ annotationProcessor(project(":room:room-compiler"))
+ implementation(project(":room:room-runtime"))
+ } else {
+ annotationProcessor(ARCH_ROOM_COMPILER)
+ implementation(ARCH_ROOM_RUNTIME)
+ }
+
+ implementation(CONSTRAINT_LAYOUT, { transitive = true })
implementation project(':work:work-runtime-ktx')
- implementation "android.arch.lifecycle:extensions:1.1.0"
- implementation "android.arch.persistence.room:runtime:1.1.1"
- annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
- implementation "com.android.support:recyclerview-v7:27.1.1"
- implementation "com.android.support:appcompat-v7:27.1.1"
- implementation "com.android.support:design:27.1.1"
+ implementation(WORK_ARCH_CORE_RUNTIME)
+ implementation(ARCH_LIFECYCLE_EXTENSIONS)
+ implementation(ANDROIDX_RECYCLERVIEW)
+ implementation(MATERIAL)
}
tasks['check'].dependsOn(tasks['connectedCheck'])
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
index bee296c..9ecb6ab 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
@@ -16,9 +16,9 @@
package androidx.work.integration.testapp;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
index fa79f17..d95d7e1 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/MainActivity.java
@@ -19,18 +19,18 @@
import static androidx.work.ExistingWorkPolicy.KEEP;
import static androidx.work.ExistingWorkPolicy.REPLACE;
-import android.arch.lifecycle.Observer;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.Observer;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
@@ -306,5 +306,17 @@
}
});
+ findViewById(R.id.run_recursive_worker).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ OneTimeWorkRequest request =
+ new OneTimeWorkRequest.Builder(RecursiveWorker.class)
+ .addTag(RecursiveWorker.TAG)
+ .build();
+
+ WorkManager.getInstance().enqueue(request);
+ }
+ });
+
}
}
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RecursiveWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RecursiveWorker.java
new file mode 100644
index 0000000..de274a3
--- /dev/null
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RecursiveWorker.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.integration.testapp;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkManager;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link androidx.work.Worker} which requests itself to be scheduled.
+ */
+public class RecursiveWorker extends Worker {
+
+ public static String TAG = "RecursiveWorker";
+
+ public RecursiveWorker(@NonNull Context context, @NonNull WorkerParameters parameters) {
+ super(context, parameters);
+ }
+
+ @NonNull
+ @Override
+ public Result doWork() {
+ OneTimeWorkRequest newRequest = new OneTimeWorkRequest.Builder(RecursiveWorker.class)
+ .addTag(TAG)
+ .setInitialDelay(100, TimeUnit.MILLISECONDS)
+ .build();
+ WorkManager.getInstance().enqueue(newRequest);
+ return Result.success();
+ }
+}
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
index dd9dc74..4fc9c0d 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryActivity.java
@@ -16,17 +16,17 @@
package androidx.work.integration.testapp;
-import android.arch.lifecycle.Observer;
import android.content.Context;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.Observer;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryWorker.java
index 7d39c4b..3cf48e4 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/RetryWorker.java
@@ -33,9 +33,9 @@
package androidx.work.integration.testapp;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
index e012d28..d6122f5 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
@@ -17,9 +17,9 @@
package androidx.work.integration.testapp;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
index 573d1dd..b9798b6 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
@@ -16,8 +16,8 @@
package androidx.work.integration.testapp;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
index 2fada74..bf2d789 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
@@ -18,10 +18,10 @@
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Worker;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/Image.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/Image.java
index 07f456a..5bdbf93 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/Image.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/Image.java
@@ -15,11 +15,12 @@
*/
package androidx.work.integration.testapp.db;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Ignore;
-import android.arch.persistence.room.PrimaryKey;
import android.graphics.Bitmap;
-import android.support.annotation.NonNull;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.PrimaryKey;
/**
* A POJO for a processed image
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/ImageDao.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/ImageDao.java
index 7dd6284..bb1cd37 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/ImageDao.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/ImageDao.java
@@ -15,10 +15,10 @@
*/
package androidx.work.integration.testapp.db;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
import java.util.List;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/TestDatabase.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/TestDatabase.java
index 8eceed0..edff2e2 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/TestDatabase.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/TestDatabase.java
@@ -15,11 +15,12 @@
*/
package androidx.work.integration.testapp.db;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
import android.content.Context;
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+
/**
* A test database.
*/
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCount.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCount.java
index ebbc12c..78f4d86 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCount.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCount.java
@@ -15,9 +15,9 @@
*/
package androidx.work.integration.testapp.db;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.PrimaryKey;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
/**
* A POJO for a word and its count.
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCountDao.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCountDao.java
index c5c26c8..94898c2 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCountDao.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/db/WordCountDao.java
@@ -15,10 +15,10 @@
*/
package androidx.work.integration.testapp.db;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
import java.util.List;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
index 89ef1b6..1d68a91 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
@@ -17,10 +17,10 @@
package androidx.work.integration.testapp.imageprocessing;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import androidx.work.integration.testapp.db.Image;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingActivity.java
index 54df558..5b159d2 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingActivity.java
@@ -16,17 +16,17 @@
package androidx.work.integration.testapp.imageprocessing;
-import android.arch.lifecycle.Observer;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.integration.testapp.R;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
index 11b89d5..a9eaabd 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
@@ -20,10 +20,10 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
-import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Worker;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageRecyclerViewAdapter.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageRecyclerViewAdapter.java
index 2eeace2..8cbb23e 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageRecyclerViewAdapter.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageRecyclerViewAdapter.java
@@ -19,7 +19,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
-import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -29,6 +28,7 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
import androidx.work.integration.testapp.R;
import androidx.work.integration.testapp.db.Image;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
index 537410ac..be0cc69 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
@@ -17,10 +17,10 @@
package androidx.work.integration.testapp.imageprocessing;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Worker;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
index 45819b70..cba2df6 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/AnalyzeSherlockHolmesActivity.java
@@ -20,12 +20,12 @@
import static androidx.work.WorkInfo.State.SUCCEEDED;
import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.work.ArrayCreatingInputMerger;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
index c92c09e..08bde6e 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
@@ -17,8 +17,8 @@
import android.content.Context;
import android.content.res.AssetManager;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.Worker;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
index 3150172..a0c9622 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
@@ -16,8 +16,8 @@
package androidx.work.integration.testapp.sherlockholmes;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
index 2d29d5e..f2add48 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
@@ -16,9 +16,9 @@
package androidx.work.integration.testapp.sherlockholmes;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import androidx.work.integration.testapp.db.TestDatabase;
diff --git a/work/integration-tests/testapp/src/main/res/layout/activity_image_processing.xml b/work/integration-tests/testapp/src/main/res/layout/activity_image_processing.xml
index 51afe20..fc48977 100644
--- a/work/integration-tests/testapp/src/main/res/layout/activity_image_processing.xml
+++ b/work/integration-tests/testapp/src/main/res/layout/activity_image_processing.xml
@@ -15,23 +15,23 @@
~ limitations under the License.
-->
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="androidx.work.integration.testapp.imageprocessing.ImageProcessingActivity">
- <android.support.v7.widget.RecyclerView
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/image_recycler_view"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/image_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
- <android.support.design.widget.FloatingActionButton
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_image_white_24dp" />
- <android.support.design.widget.FloatingActionButton
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/clear_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -53,4 +53,4 @@
app:layout_constraintEnd_toStartOf="@+id/add_image"
app:srcCompat="@drawable/ic_clear_white_24dp" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/work/integration-tests/testapp/src/main/res/layout/activity_main.xml b/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
index 1da89a0..04bc823 100644
--- a/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
+++ b/work/integration-tests/testapp/src/main/res/layout/activity_main.xml
@@ -191,5 +191,13 @@
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"/>
+ <Button android:text="@string/run_recursive_worker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/run_recursive_worker"
+ android:layout_marginTop="12dp"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"/>
+
</LinearLayout>
</ScrollView>
diff --git a/work/integration-tests/testapp/src/main/res/values/strings.xml b/work/integration-tests/testapp/src/main/res/values/strings.xml
index e836218..8f26b5e 100644
--- a/work/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/work/integration-tests/testapp/src/main/res/values/strings.xml
@@ -23,5 +23,6 @@
<string name="exploding_work">Test Exploding Work</string>
<string name="replace_completed_work">Test Replacing Completed Work</string>
<string name="run_retry_worker">Run Retry Worker</string>
+ <string name="run_recursive_worker">Run Recursive Worker</string>
<string name="keep">Use KEEP</string>
</resources>
\ No newline at end of file
diff --git a/work/workmanager-ktx/api/2.0.0-rc01.txt b/work/workmanager-ktx/api/2.0.0-rc01.txt
new file mode 100644
index 0000000..d76133a
--- /dev/null
+++ b/work/workmanager-ktx/api/2.0.0-rc01.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public abstract class CoroutineWorker extends androidx.work.ListenableWorker {
+ ctor public CoroutineWorker(android.content.Context appContext, androidx.work.WorkerParameters params);
+ method public abstract suspend Object? doWork(kotlin.coroutines.experimental.Continuation<? super androidx.work.ListenableWorker.Result> p);
+ method public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
+ method public final void onStopped();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ property public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
+ }
+
+ public final class DataKt {
+ ctor public DataKt();
+ method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class ListenableFutureKt {
+ ctor public ListenableFutureKt();
+ }
+
+ public final class OneTimeWorkRequestKt {
+ ctor public OneTimeWorkRequestKt();
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
+ method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
+ }
+
+ public final class OperationKt {
+ ctor public OperationKt();
+ method public static suspend inline Object! await(androidx.work.Operation, kotlin.coroutines.experimental.Continuation<? super androidx.work.Operation.State.SUCCESS>! p);
+ }
+
+ public final class PeriodicWorkRequestKt {
+ ctor public PeriodicWorkRequestKt();
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit! repeatIntervalTimeUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration! repeatInterval);
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit! repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit! flexTimeIntervalUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration! repeatInterval, java.time.Duration! flexTimeInterval);
+ }
+
+}
+
diff --git a/work/workmanager-ktx/api/2.0.0.txt b/work/workmanager-ktx/api/2.0.0.txt
new file mode 100644
index 0000000..d76133a
--- /dev/null
+++ b/work/workmanager-ktx/api/2.0.0.txt
@@ -0,0 +1,42 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public abstract class CoroutineWorker extends androidx.work.ListenableWorker {
+ ctor public CoroutineWorker(android.content.Context appContext, androidx.work.WorkerParameters params);
+ method public abstract suspend Object? doWork(kotlin.coroutines.experimental.Continuation<? super androidx.work.ListenableWorker.Result> p);
+ method public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
+ method public final void onStopped();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ property public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
+ }
+
+ public final class DataKt {
+ ctor public DataKt();
+ method public static inline androidx.work.Data workDataOf(kotlin.Pair<java.lang.String,?>... pairs);
+ }
+
+ public final class ListenableFutureKt {
+ ctor public ListenableFutureKt();
+ }
+
+ public final class OneTimeWorkRequestKt {
+ ctor public OneTimeWorkRequestKt();
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.OneTimeWorkRequest.Builder! OneTimeWorkRequestBuilder();
+ method public static inline androidx.work.OneTimeWorkRequest.Builder setInputMerger(androidx.work.OneTimeWorkRequest.Builder, kotlin.reflect.KClass<? extends androidx.work.InputMerger> inputMerger);
+ }
+
+ public final class OperationKt {
+ ctor public OperationKt();
+ method public static suspend inline Object! await(androidx.work.Operation, kotlin.coroutines.experimental.Continuation<? super androidx.work.Operation.State.SUCCESS>! p);
+ }
+
+ public final class PeriodicWorkRequestKt {
+ ctor public PeriodicWorkRequestKt();
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit! repeatIntervalTimeUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration! repeatInterval);
+ method public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(long repeatInterval, java.util.concurrent.TimeUnit! repeatIntervalTimeUnit, long flexTimeInterval, java.util.concurrent.TimeUnit! flexTimeIntervalUnit);
+ method @RequiresApi(26) public static inline <reified W extends androidx.work.ListenableWorker> androidx.work.PeriodicWorkRequest.Builder! PeriodicWorkRequestBuilder(java.time.Duration! repeatInterval, java.time.Duration! flexTimeInterval);
+ }
+
+}
+
diff --git a/work/workmanager-ktx/api/res-2.0.0-rc01.txt b/work/workmanager-ktx/api/res-2.0.0-rc01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-ktx/api/res-2.0.0-rc01.txt
diff --git a/work/workmanager-ktx/api/res-2.0.0.txt b/work/workmanager-ktx/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-ktx/api/res-2.0.0.txt
diff --git a/work/workmanager-ktx/api/restricted_2.0.0-rc01.txt b/work/workmanager-ktx/api/restricted_2.0.0-rc01.txt
new file mode 100644
index 0000000..bdec7f9
--- /dev/null
+++ b/work/workmanager-ktx/api/restricted_2.0.0-rc01.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.work {
+
+ @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) public enum DirectExecutor implements java.util.concurrent.Executor {
+ method public void execute(Runnable command);
+ enum_constant public static final androidx.work.DirectExecutor INSTANCE;
+ }
+
+ public final class ListenableFutureKt {
+ method @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) public static suspend inline <R> Object! await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.experimental.Continuation<? super R>! p);
+ }
+
+}
+
diff --git a/work/workmanager-ktx/api/restricted_2.0.0.txt b/work/workmanager-ktx/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..bdec7f9
--- /dev/null
+++ b/work/workmanager-ktx/api/restricted_2.0.0.txt
@@ -0,0 +1,14 @@
+// Signature format: 3.0
+package androidx.work {
+
+ @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) public enum DirectExecutor implements java.util.concurrent.Executor {
+ method public void execute(Runnable command);
+ enum_constant public static final androidx.work.DirectExecutor INSTANCE;
+ }
+
+ public final class ListenableFutureKt {
+ method @RestrictTo({RestrictTo.Scope.LIBRARY_GROUP}) public static suspend inline <R> Object! await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.experimental.Continuation<? super R>! p);
+ }
+
+}
+
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
index 902a5ff..79a8825 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -16,9 +16,10 @@
package androidx.work
-import android.arch.core.executor.ArchTaskExecutor
import android.content.Context
import android.util.Log
+import androidx.arch.core.executor.ArchTaskExecutor
+
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -48,7 +49,7 @@
@Before
fun setUp() {
ArchTaskExecutor.getInstance()
- .setDelegate(object : android.arch.core.executor.TaskExecutor() {
+ .setDelegate(object : androidx.arch.core.executor.TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) {
runnable.run()
}
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
index dc98636..7fbc5c5 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
@@ -25,10 +25,10 @@
import kotlinx.coroutines.launch
/**
- * A {@link ListenableWorker} implementation that provides interop with Kotlin Coroutines. Override
+ * A [ListenableWorker] implementation that provides interop with Kotlin Coroutines. Override
* the [doWork] function to do your suspending work.
* <p>
- * By default, CoroutineWorker runs on {@link Dispatchers#Default}; this can be modified by
+ * By default, CoroutineWorker runs on [Dispatchers.Default]; this can be modified by
* overriding [coroutineContext].
* <p>
* A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
@@ -82,7 +82,7 @@
* stop.
*
* @return The [ListenableWorker.Result] of the result of the background work; note that
- * dependent work will not execute if you return [Result.failure]
+ * dependent work will not execute if you return [ListenableWorker.Result.failure]
*/
abstract suspend fun doWork(): Result
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/DirectExecutor.kt b/work/workmanager-ktx/src/main/java/androidx/work/DirectExecutor.kt
index 59d3748..969acf9 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/DirectExecutor.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/DirectExecutor.kt
@@ -16,7 +16,7 @@
package androidx.work
-import android.support.annotation.RestrictTo
+import androidx.annotation.RestrictTo
import java.util.concurrent.Executor
/**
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt b/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt
index a663a02..51504ab 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/ListenableFuture.kt
@@ -18,7 +18,7 @@
package androidx.work
-import android.support.annotation.RestrictTo
+import androidx.annotation.RestrictTo
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.CancellationException
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt b/work/workmanager-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt
index 61df5c8..e161c11 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/OneTimeWorkRequest.kt
@@ -19,7 +19,7 @@
package androidx.work
-import android.support.annotation.NonNull
+import androidx.annotation.NonNull
import kotlin.reflect.KClass
/**
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt b/work/workmanager-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt
index 6c38896..752ce197 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/PeriodicWorkRequest.kt
@@ -16,7 +16,7 @@
package androidx.work
-import android.support.annotation.RequiresApi
+import androidx.annotation.RequiresApi
import java.time.Duration
import java.util.concurrent.TimeUnit
@@ -27,8 +27,9 @@
* @param repeatIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
*/
inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Long,
- repeatIntervalTimeUnit: TimeUnit): PeriodicWorkRequest.Builder {
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit
+): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, repeatIntervalTimeUnit)
}
@@ -39,7 +40,8 @@
*/
@RequiresApi(26)
inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Duration): PeriodicWorkRequest.Builder {
+ repeatInterval: Duration
+): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(W::class.java, repeatInterval)
}
@@ -52,10 +54,11 @@
* @param flexIntervalTimeUnit @see [androidx.work.PeriodicWorkRequest.Builder]
*/
inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Long,
- repeatIntervalTimeUnit: TimeUnit,
- flexTimeInterval: Long,
- flexTimeIntervalUnit: TimeUnit): PeriodicWorkRequest.Builder {
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit,
+ flexTimeInterval: Long,
+ flexTimeIntervalUnit: TimeUnit
+): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(
W::class.java,
@@ -73,7 +76,8 @@
*/
@RequiresApi(26)
inline fun <reified W : ListenableWorker> PeriodicWorkRequestBuilder(
- repeatInterval: Duration,
- flexTimeInterval: Duration): PeriodicWorkRequest.Builder {
+ repeatInterval: Duration,
+ flexTimeInterval: Duration
+): PeriodicWorkRequest.Builder {
return PeriodicWorkRequest.Builder(W::class.java, repeatInterval, flexTimeInterval)
}
diff --git a/work/workmanager-rxjava2/api/2.0.0-rc01.txt b/work/workmanager-rxjava2/api/2.0.0-rc01.txt
new file mode 100644
index 0000000..fc1c405
--- /dev/null
+++ b/work/workmanager-rxjava2/api/2.0.0-rc01.txt
@@ -0,0 +1,12 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public abstract class RxWorker extends androidx.work.ListenableWorker {
+ ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
+ method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result>! createWork();
+ method protected io.reactivex.Scheduler! getBackgroundScheduler();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+}
+
diff --git a/work/workmanager-rxjava2/api/2.0.0.txt b/work/workmanager-rxjava2/api/2.0.0.txt
new file mode 100644
index 0000000..fc1c405
--- /dev/null
+++ b/work/workmanager-rxjava2/api/2.0.0.txt
@@ -0,0 +1,12 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public abstract class RxWorker extends androidx.work.ListenableWorker {
+ ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
+ method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result>! createWork();
+ method protected io.reactivex.Scheduler! getBackgroundScheduler();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+}
+
diff --git a/work/workmanager-rxjava2/api/res-2.0.0-rc01.txt b/work/workmanager-rxjava2/api/res-2.0.0-rc01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-rxjava2/api/res-2.0.0-rc01.txt
diff --git a/work/workmanager-rxjava2/api/res-2.0.0.txt b/work/workmanager-rxjava2/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-rxjava2/api/res-2.0.0.txt
diff --git a/work/workmanager-rxjava2/api/restricted_2.0.0-rc01.txt b/work/workmanager-rxjava2/api/restricted_2.0.0-rc01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/work/workmanager-rxjava2/api/restricted_2.0.0-rc01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/work/workmanager-rxjava2/api/restricted_2.0.0.txt b/work/workmanager-rxjava2/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/work/workmanager-rxjava2/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
index 4af549e..0d6e8d0 100644
--- a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
+++ b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
@@ -17,10 +17,10 @@
package androidx.work;
import android.content.Context;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.work.impl.utils.SynchronousExecutor;
import androidx.work.impl.utils.futures.SettableFuture;
diff --git a/work/workmanager-testing/api/2.0.0-rc01.txt b/work/workmanager-testing/api/2.0.0-rc01.txt
new file mode 100644
index 0000000..4754b0d
--- /dev/null
+++ b/work/workmanager-testing/api/2.0.0-rc01.txt
@@ -0,0 +1,22 @@
+// Signature format: 3.0
+package androidx.work.testing {
+
+ public class SynchronousExecutor implements java.util.concurrent.Executor {
+ ctor public SynchronousExecutor();
+ method public void execute(Runnable);
+ }
+
+ public interface TestDriver {
+ method public void setAllConstraintsMet(java.util.UUID);
+ method public void setInitialDelayMet(java.util.UUID);
+ method public void setPeriodDelayMet(java.util.UUID);
+ }
+
+ public final class WorkManagerTestInitHelper {
+ method public static androidx.work.testing.TestDriver! getTestDriver();
+ method public static void initializeTestWorkManager(android.content.Context);
+ method public static void initializeTestWorkManager(android.content.Context, androidx.work.Configuration);
+ }
+
+}
+
diff --git a/work/workmanager-testing/api/2.0.0.txt b/work/workmanager-testing/api/2.0.0.txt
new file mode 100644
index 0000000..4754b0d
--- /dev/null
+++ b/work/workmanager-testing/api/2.0.0.txt
@@ -0,0 +1,22 @@
+// Signature format: 3.0
+package androidx.work.testing {
+
+ public class SynchronousExecutor implements java.util.concurrent.Executor {
+ ctor public SynchronousExecutor();
+ method public void execute(Runnable);
+ }
+
+ public interface TestDriver {
+ method public void setAllConstraintsMet(java.util.UUID);
+ method public void setInitialDelayMet(java.util.UUID);
+ method public void setPeriodDelayMet(java.util.UUID);
+ }
+
+ public final class WorkManagerTestInitHelper {
+ method public static androidx.work.testing.TestDriver! getTestDriver();
+ method public static void initializeTestWorkManager(android.content.Context);
+ method public static void initializeTestWorkManager(android.content.Context, androidx.work.Configuration);
+ }
+
+}
+
diff --git a/work/workmanager-testing/api/res-2.0.0-rc01.txt b/work/workmanager-testing/api/res-2.0.0-rc01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-testing/api/res-2.0.0-rc01.txt
diff --git a/work/workmanager-testing/api/res-2.0.0.txt b/work/workmanager-testing/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager-testing/api/res-2.0.0.txt
diff --git a/work/workmanager-testing/api/restricted_2.0.0-rc01.txt b/work/workmanager-testing/api/restricted_2.0.0-rc01.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/work/workmanager-testing/api/restricted_2.0.0-rc01.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/work/workmanager-testing/api/restricted_2.0.0.txt b/work/workmanager-testing/api/restricted_2.0.0.txt
new file mode 100644
index 0000000..da4f6cc
--- /dev/null
+++ b/work/workmanager-testing/api/restricted_2.0.0.txt
@@ -0,0 +1 @@
+// Signature format: 3.0
diff --git a/work/workmanager-testing/build.gradle b/work/workmanager-testing/build.gradle
index dca9b89..2bbc54f 100644
--- a/work/workmanager-testing/build.gradle
+++ b/work/workmanager-testing/build.gradle
@@ -26,14 +26,12 @@
}
dependencies {
- // @aar and { transitive = true } are needed as a workaround for
- // https://github.com/gradle/gradle/issues/3170
implementation(project(':work:work-runtime'))
- implementation("android.arch.lifecycle:livedata-core:1.1.0@aar") { transitive = true }
- implementation("android.arch.persistence.room:runtime:1.1.1@aar") { transitive = true }
- annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
+ implementation(ARCH_LIFECYCLE_LIVEDATA_CORE)
+ implementation(ARCH_ROOM_RUNTIME)
+ annotationProcessor(ARCH_ROOM_COMPILER)
- androidTestImplementation "android.arch.core:core-testing:1.1.0"
+ androidTestImplementation(WORK_ARCH_CORE_TESTING)
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
androidTestImplementation(TEST_RUNNER)
@@ -42,6 +40,10 @@
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
testImplementation(JUNIT)
+ testImplementation(ROBOLECTRIC)
+ testImplementation(TEST_EXT_JUNIT)
+ testImplementation(TEST_CORE)
+ testImplementation(TEST_RUNNER)
}
supportLibrary {
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/CountingTestWorker.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/CountingTestWorker.java
index f9be524..b07246d 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/CountingTestWorker.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/CountingTestWorker.java
@@ -17,8 +17,8 @@
package androidx.work.testing.workers;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/TestWorker.java b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/TestWorker.java
index a9e96b6..2423096 100644
--- a/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/TestWorker.java
+++ b/work/workmanager-testing/src/androidTest/java/androidx/work/testing/workers/TestWorker.java
@@ -17,9 +17,9 @@
package androidx.work.testing.workers;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Logger;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/SynchronousExecutor.java b/work/workmanager-testing/src/main/java/androidx/work/testing/SynchronousExecutor.java
index d652f17..4d6612c 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/SynchronousExecutor.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/SynchronousExecutor.java
@@ -16,7 +16,7 @@
package androidx.work.testing;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import java.util.concurrent.Executor;
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestDriver.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestDriver.java
index bf15a96..7295f11c 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestDriver.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestDriver.java
@@ -16,7 +16,7 @@
package androidx.work.testing;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import java.util.UUID;
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
index d6c7902..48640f4 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestScheduler.java
@@ -17,9 +17,8 @@
package androidx.work.testing;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Worker;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Scheduler;
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
index cdfcf71..0b39778 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
@@ -17,24 +17,31 @@
package androidx.work.testing;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Configuration;
import androidx.work.WorkManager;
+import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
import java.util.concurrent.Executor;
/**
- * A concrete implementation of {@link WorkManager} which can be used for testing.
- * This implementation makes it easy to swap Schedulers.
+ * A concrete implementation of {@link WorkManager} which can be used for testing. This
+ * implementation makes it easy to swap Schedulers.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class TestWorkManagerImpl extends WorkManagerImpl implements TestDriver {
+class TestWorkManagerImpl extends WorkManagerImpl implements TestDriver {
+
+ private TestScheduler mScheduler;
+
TestWorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration) {
@@ -75,5 +82,29 @@
}
},
true);
+
+ // mScheduler is initialized in createSchedulers() called by super()
+ getProcessor().addExecutionListener(mScheduler);
+ }
+
+ @Override
+ public @NonNull List<Scheduler> createSchedulers(Context context) {
+ mScheduler = new TestScheduler();
+ return Collections.singletonList((Scheduler) mScheduler);
+ }
+
+ @Override
+ public void setAllConstraintsMet(@NonNull UUID workSpecId) {
+ mScheduler.setAllConstraintsMet(workSpecId);
+ }
+
+ @Override
+ public void setInitialDelayMet(@NonNull UUID workSpecId) {
+ mScheduler.setInitialDelayMet(workSpecId);
+ }
+
+ @Override
+ public void setPeriodDelayMet(@NonNull UUID workSpecId) {
+ mScheduler.setPeriodDelayMet(workSpecId);
}
}
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
index 775ff6e0..270e0d2 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
@@ -17,15 +17,11 @@
package androidx.work.testing;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Configuration;
-import androidx.work.impl.Scheduler;
import androidx.work.impl.WorkManagerImpl;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
/**
* Helps initialize {@link androidx.work.WorkManager} for testing.
@@ -54,32 +50,7 @@
public static void initializeTestWorkManager(
@NonNull Context context,
@NonNull Configuration configuration) {
-
- final TestScheduler scheduler = new TestScheduler();
- WorkManagerImpl workManager = new TestWorkManagerImpl(context, configuration) {
-
- @Override
- public @NonNull List<Scheduler> createSchedulers(Context context) {
- return Collections.singletonList((Scheduler) scheduler);
- }
-
- @Override
- public void setAllConstraintsMet(@NonNull UUID workSpecId) {
- scheduler.setAllConstraintsMet(workSpecId);
- }
-
- @Override
- public void setInitialDelayMet(@NonNull UUID workSpecId) {
- scheduler.setInitialDelayMet(workSpecId);
- }
-
- @Override
- public void setPeriodDelayMet(@NonNull UUID workSpecId) {
- scheduler.setPeriodDelayMet(workSpecId);
- }
- };
- workManager.getProcessor().addExecutionListener(scheduler);
- WorkManagerImpl.setDelegate(workManager);
+ WorkManagerImpl.setDelegate(new TestWorkManagerImpl(context, configuration));
}
/**
diff --git a/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
new file mode 100644
index 0000000..4671314
--- /dev/null
+++ b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.testing;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.WorkInfo;
+import androidx.work.WorkRequest;
+import androidx.work.impl.WorkManagerImpl;
+import androidx.work.testing.workers.TestWorker;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+
+@Config(manifest = Config.NONE)
+@RunWith(RobolectricTestRunner.class)
+@LargeTest
+@DoNotInstrument
+public class RobolectricSmokeTest {
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+ WorkManagerTestInitHelper.initializeTestWorkManager(context);
+ }
+
+ @Test
+ public void testWorker_shouldSucceedSynchronously()
+ throws InterruptedException, ExecutionException {
+ WorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+ // TestWorkManagerImpl is a subtype of WorkManagerImpl.
+ WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance();
+ workManagerImpl.enqueue(Collections.singletonList(request)).getResult().get();
+ WorkInfo status = workManagerImpl.getWorkInfoById(request.getId()).get();
+ assertThat(status.getState().isFinished(), is(true));
+ }
+}
diff --git a/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java b/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java
new file mode 100644
index 0000000..78b7ab0
--- /dev/null
+++ b/work/workmanager-testing/src/test/java/androidx/work/testing/workers/TestWorker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.testing.workers;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.work.Logger;
+import androidx.work.Worker;
+import androidx.work.WorkerParameters;
+
+/** A test {@link Worker} that prints a log and returns a successful result. */
+public class TestWorker extends Worker {
+ private static final String TAG = Logger.tagWithPrefix("TestWorker");
+
+ public TestWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
+ super(context, workerParams);
+ }
+
+ @Override
+ public @NonNull Result doWork() {
+ Log.i(TAG, "Doing work.");
+ return Result.success();
+ }
+}
diff --git a/work/workmanager/api/2.0.0-rc01.txt b/work/workmanager/api/2.0.0-rc01.txt
new file mode 100644
index 0000000..a616da8
--- /dev/null
+++ b/work/workmanager/api/2.0.0-rc01.txt
@@ -0,0 +1,284 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public final class ArrayCreatingInputMerger extends androidx.work.InputMerger {
+ ctor public ArrayCreatingInputMerger();
+ method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public enum BackoffPolicy {
+ enum_constant public static final androidx.work.BackoffPolicy EXPONENTIAL;
+ enum_constant public static final androidx.work.BackoffPolicy LINEAR;
+ }
+
+ public final class Configuration {
+ method public java.util.concurrent.Executor getExecutor();
+ method public int getMaxJobSchedulerId();
+ method public int getMinJobSchedulerId();
+ method public androidx.work.WorkerFactory getWorkerFactory();
+ field public static final int MIN_SCHEDULER_LIMIT = 20; // 0x14
+ }
+
+ public static final class Configuration.Builder {
+ ctor public Configuration.Builder();
+ method public androidx.work.Configuration build();
+ method public androidx.work.Configuration.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
+ method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
+ method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
+ method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory);
+ }
+
+ public final class Constraints {
+ ctor public Constraints(androidx.work.Constraints);
+ method public androidx.work.NetworkType getRequiredNetworkType();
+ method public boolean requiresBatteryNotLow();
+ method public boolean requiresCharging();
+ method @RequiresApi(23) public boolean requiresDeviceIdle();
+ method public boolean requiresStorageNotLow();
+ field public static final androidx.work.Constraints! NONE;
+ }
+
+ public static final class Constraints.Builder {
+ ctor public Constraints.Builder();
+ method @RequiresApi(24) public androidx.work.Constraints.Builder addContentUriTrigger(android.net.Uri, boolean);
+ method public androidx.work.Constraints build();
+ method public androidx.work.Constraints.Builder setRequiredNetworkType(androidx.work.NetworkType);
+ method public androidx.work.Constraints.Builder setRequiresBatteryNotLow(boolean);
+ method public androidx.work.Constraints.Builder setRequiresCharging(boolean);
+ method @RequiresApi(23) public androidx.work.Constraints.Builder setRequiresDeviceIdle(boolean);
+ method public androidx.work.Constraints.Builder setRequiresStorageNotLow(boolean);
+ method @RequiresApi(24) public androidx.work.Constraints.Builder setTriggerContentMaxDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.Constraints.Builder setTriggerContentMaxDelay(java.time.Duration!);
+ method @RequiresApi(24) public androidx.work.Constraints.Builder setTriggerContentUpdateDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.Constraints.Builder setTriggerContentUpdateDelay(java.time.Duration!);
+ }
+
+ public final class Data {
+ ctor public Data(androidx.work.Data);
+ method public boolean getBoolean(String, boolean);
+ method public boolean[]? getBooleanArray(String);
+ method public double getDouble(String, double);
+ method public double[]? getDoubleArray(String);
+ method public float getFloat(String, float);
+ method public float[]? getFloatArray(String);
+ method public int getInt(String, int);
+ method public int[]? getIntArray(String);
+ method public java.util.Map<java.lang.String,java.lang.Object> getKeyValueMap();
+ method public long getLong(String, long);
+ method public long[]? getLongArray(String);
+ method public String? getString(String);
+ method public String[]? getStringArray(String);
+ field public static final androidx.work.Data! EMPTY;
+ field public static final int MAX_DATA_BYTES = 10240; // 0x2800
+ }
+
+ public static final class Data.Builder {
+ ctor public Data.Builder();
+ method public androidx.work.Data build();
+ method public androidx.work.Data.Builder putAll(androidx.work.Data);
+ method public androidx.work.Data.Builder putAll(java.util.Map<java.lang.String,java.lang.Object>);
+ method public androidx.work.Data.Builder putBoolean(String, boolean);
+ method public androidx.work.Data.Builder putBooleanArray(String, boolean[]);
+ method public androidx.work.Data.Builder putDouble(String, double);
+ method public androidx.work.Data.Builder putDoubleArray(String, double[]);
+ method public androidx.work.Data.Builder putFloat(String, float);
+ method public androidx.work.Data.Builder putFloatArray(String, float[]);
+ method public androidx.work.Data.Builder putInt(String, int);
+ method public androidx.work.Data.Builder putIntArray(String, int[]);
+ method public androidx.work.Data.Builder putLong(String, long);
+ method public androidx.work.Data.Builder putLongArray(String, long[]);
+ method public androidx.work.Data.Builder putString(String, String?);
+ method public androidx.work.Data.Builder putStringArray(String, String[]);
+ }
+
+ public enum ExistingPeriodicWorkPolicy {
+ enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy KEEP;
+ enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy REPLACE;
+ }
+
+ public enum ExistingWorkPolicy {
+ enum_constant public static final androidx.work.ExistingWorkPolicy APPEND;
+ enum_constant public static final androidx.work.ExistingWorkPolicy KEEP;
+ enum_constant public static final androidx.work.ExistingWorkPolicy REPLACE;
+ }
+
+ public abstract class InputMerger {
+ ctor public InputMerger();
+ method public abstract androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public abstract class ListenableWorker {
+ ctor @Keep public ListenableWorker(android.content.Context, androidx.work.WorkerParameters);
+ method public final android.content.Context getApplicationContext();
+ method public final java.util.UUID getId();
+ method public final androidx.work.Data getInputData();
+ method @RequiresApi(28) public final android.net.Network? getNetwork();
+ method public final int getRunAttemptCount();
+ method public final java.util.Set<java.lang.String> getTags();
+ method @RequiresApi(24) public final java.util.List<java.lang.String> getTriggeredContentAuthorities();
+ method @RequiresApi(24) public final java.util.List<android.net.Uri> getTriggeredContentUris();
+ method public final boolean isStopped();
+ method public void onStopped();
+ method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+ public abstract static class ListenableWorker.Result {
+ method public static androidx.work.ListenableWorker.Result failure();
+ method public static androidx.work.ListenableWorker.Result failure(androidx.work.Data);
+ method public static androidx.work.ListenableWorker.Result retry();
+ method public static androidx.work.ListenableWorker.Result success();
+ method public static androidx.work.ListenableWorker.Result success(androidx.work.Data);
+ }
+
+ public enum NetworkType {
+ enum_constant public static final androidx.work.NetworkType CONNECTED;
+ enum_constant public static final androidx.work.NetworkType METERED;
+ enum_constant public static final androidx.work.NetworkType NOT_REQUIRED;
+ enum_constant public static final androidx.work.NetworkType NOT_ROAMING;
+ enum_constant public static final androidx.work.NetworkType UNMETERED;
+ }
+
+ public final class OneTimeWorkRequest extends androidx.work.WorkRequest {
+ method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker>);
+ method public static java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<java.lang.Class<? extends androidx.work.ListenableWorker>>);
+ }
+
+ public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
+ ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>);
+ method public androidx.work.OneTimeWorkRequest.Builder setInitialDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.OneTimeWorkRequest.Builder setInitialDelay(java.time.Duration);
+ method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
+ }
+
+ public interface Operation {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.Operation.State.SUCCESS> getResult();
+ method public androidx.lifecycle.LiveData<androidx.work.Operation.State> getState();
+ }
+
+ public abstract static class Operation.State {
+ }
+
+ public static final class Operation.State.FAILURE extends androidx.work.Operation.State {
+ ctor public Operation.State.FAILURE(Throwable);
+ method public Throwable getThrowable();
+ }
+
+ public static final class Operation.State.IN_PROGRESS extends androidx.work.Operation.State {
+ }
+
+ public static final class Operation.State.SUCCESS extends androidx.work.Operation.State {
+ }
+
+ public final class OverwritingInputMerger extends androidx.work.InputMerger {
+ ctor public OverwritingInputMerger();
+ method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public final class PeriodicWorkRequest extends androidx.work.WorkRequest {
+ field public static final long MIN_PERIODIC_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIODIC_INTERVAL_MILLIS = 900000L; // 0xdbba0L
+ }
+
+ public static final class PeriodicWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.PeriodicWorkRequest.Builder,androidx.work.PeriodicWorkRequest> {
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
+ }
+
+ public abstract class WorkContinuation {
+ ctor public WorkContinuation();
+ method public static androidx.work.WorkContinuation combine(java.util.List<androidx.work.WorkContinuation>);
+ method public abstract androidx.work.Operation enqueue();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos();
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
+ method public final androidx.work.WorkContinuation then(androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation then(java.util.List<androidx.work.OneTimeWorkRequest>);
+ }
+
+ public final class WorkInfo {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getOutputData();
+ method public androidx.work.WorkInfo.State getState();
+ method public java.util.Set<java.lang.String> getTags();
+ }
+
+ public enum WorkInfo.State {
+ method public boolean isFinished();
+ enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
+ enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
+ enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
+ enum_constant public static final androidx.work.WorkInfo.State FAILED;
+ enum_constant public static final androidx.work.WorkInfo.State RUNNING;
+ enum_constant public static final androidx.work.WorkInfo.State SUCCEEDED;
+ }
+
+ public abstract class WorkManager {
+ method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public abstract androidx.work.Operation cancelAllWork();
+ method public abstract androidx.work.Operation cancelAllWorkByTag(String);
+ method public abstract androidx.work.Operation cancelUniqueWork(String);
+ method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
+ method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
+ method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest>);
+ method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
+ method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public static androidx.work.WorkManager getInstance();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
+ method public abstract androidx.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo> getWorkInfoById(java.util.UUID);
+ method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
+ method public static void initialize(android.content.Context, androidx.work.Configuration);
+ method public abstract androidx.work.Operation pruneWork();
+ }
+
+ public abstract class WorkRequest {
+ method public java.util.UUID getId();
+ field public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L; // 0x7530L
+ field public static final long MAX_BACKOFF_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_BACKOFF_MILLIS = 10000L; // 0x2710L
+ }
+
+ public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder, W extends androidx.work.WorkRequest> {
+ method public final B addTag(String);
+ method public final W build();
+ method public final B keepResultsForAtLeast(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration);
+ method public final B setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration);
+ method public final B setConstraints(androidx.work.Constraints);
+ method public final B setInputData(androidx.work.Data);
+ }
+
+ public abstract class Worker extends androidx.work.ListenableWorker {
+ ctor @Keep public Worker(android.content.Context, androidx.work.WorkerParameters);
+ method @WorkerThread public abstract androidx.work.ListenableWorker.Result doWork();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+ public abstract class WorkerFactory {
+ ctor public WorkerFactory();
+ method public abstract androidx.work.ListenableWorker? createWorker(android.content.Context, String, androidx.work.WorkerParameters);
+ }
+
+ public final class WorkerParameters {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getInputData();
+ method @RequiresApi(28) public android.net.Network? getNetwork();
+ method public int getRunAttemptCount();
+ method public java.util.Set<java.lang.String> getTags();
+ method @RequiresApi(24) public java.util.List<java.lang.String> getTriggeredContentAuthorities();
+ method @RequiresApi(24) public java.util.List<android.net.Uri> getTriggeredContentUris();
+ }
+
+}
+
diff --git a/work/workmanager/api/2.0.0.txt b/work/workmanager/api/2.0.0.txt
new file mode 100644
index 0000000..a616da8
--- /dev/null
+++ b/work/workmanager/api/2.0.0.txt
@@ -0,0 +1,284 @@
+// Signature format: 3.0
+package androidx.work {
+
+ public final class ArrayCreatingInputMerger extends androidx.work.InputMerger {
+ ctor public ArrayCreatingInputMerger();
+ method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public enum BackoffPolicy {
+ enum_constant public static final androidx.work.BackoffPolicy EXPONENTIAL;
+ enum_constant public static final androidx.work.BackoffPolicy LINEAR;
+ }
+
+ public final class Configuration {
+ method public java.util.concurrent.Executor getExecutor();
+ method public int getMaxJobSchedulerId();
+ method public int getMinJobSchedulerId();
+ method public androidx.work.WorkerFactory getWorkerFactory();
+ field public static final int MIN_SCHEDULER_LIMIT = 20; // 0x14
+ }
+
+ public static final class Configuration.Builder {
+ ctor public Configuration.Builder();
+ method public androidx.work.Configuration build();
+ method public androidx.work.Configuration.Builder setExecutor(java.util.concurrent.Executor);
+ method public androidx.work.Configuration.Builder setJobSchedulerJobIdRange(int, int);
+ method public androidx.work.Configuration.Builder setMaxSchedulerLimit(int);
+ method public androidx.work.Configuration.Builder setMinimumLoggingLevel(int);
+ method public androidx.work.Configuration.Builder setWorkerFactory(androidx.work.WorkerFactory);
+ }
+
+ public final class Constraints {
+ ctor public Constraints(androidx.work.Constraints);
+ method public androidx.work.NetworkType getRequiredNetworkType();
+ method public boolean requiresBatteryNotLow();
+ method public boolean requiresCharging();
+ method @RequiresApi(23) public boolean requiresDeviceIdle();
+ method public boolean requiresStorageNotLow();
+ field public static final androidx.work.Constraints! NONE;
+ }
+
+ public static final class Constraints.Builder {
+ ctor public Constraints.Builder();
+ method @RequiresApi(24) public androidx.work.Constraints.Builder addContentUriTrigger(android.net.Uri, boolean);
+ method public androidx.work.Constraints build();
+ method public androidx.work.Constraints.Builder setRequiredNetworkType(androidx.work.NetworkType);
+ method public androidx.work.Constraints.Builder setRequiresBatteryNotLow(boolean);
+ method public androidx.work.Constraints.Builder setRequiresCharging(boolean);
+ method @RequiresApi(23) public androidx.work.Constraints.Builder setRequiresDeviceIdle(boolean);
+ method public androidx.work.Constraints.Builder setRequiresStorageNotLow(boolean);
+ method @RequiresApi(24) public androidx.work.Constraints.Builder setTriggerContentMaxDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.Constraints.Builder setTriggerContentMaxDelay(java.time.Duration!);
+ method @RequiresApi(24) public androidx.work.Constraints.Builder setTriggerContentUpdateDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.Constraints.Builder setTriggerContentUpdateDelay(java.time.Duration!);
+ }
+
+ public final class Data {
+ ctor public Data(androidx.work.Data);
+ method public boolean getBoolean(String, boolean);
+ method public boolean[]? getBooleanArray(String);
+ method public double getDouble(String, double);
+ method public double[]? getDoubleArray(String);
+ method public float getFloat(String, float);
+ method public float[]? getFloatArray(String);
+ method public int getInt(String, int);
+ method public int[]? getIntArray(String);
+ method public java.util.Map<java.lang.String,java.lang.Object> getKeyValueMap();
+ method public long getLong(String, long);
+ method public long[]? getLongArray(String);
+ method public String? getString(String);
+ method public String[]? getStringArray(String);
+ field public static final androidx.work.Data! EMPTY;
+ field public static final int MAX_DATA_BYTES = 10240; // 0x2800
+ }
+
+ public static final class Data.Builder {
+ ctor public Data.Builder();
+ method public androidx.work.Data build();
+ method public androidx.work.Data.Builder putAll(androidx.work.Data);
+ method public androidx.work.Data.Builder putAll(java.util.Map<java.lang.String,java.lang.Object>);
+ method public androidx.work.Data.Builder putBoolean(String, boolean);
+ method public androidx.work.Data.Builder putBooleanArray(String, boolean[]);
+ method public androidx.work.Data.Builder putDouble(String, double);
+ method public androidx.work.Data.Builder putDoubleArray(String, double[]);
+ method public androidx.work.Data.Builder putFloat(String, float);
+ method public androidx.work.Data.Builder putFloatArray(String, float[]);
+ method public androidx.work.Data.Builder putInt(String, int);
+ method public androidx.work.Data.Builder putIntArray(String, int[]);
+ method public androidx.work.Data.Builder putLong(String, long);
+ method public androidx.work.Data.Builder putLongArray(String, long[]);
+ method public androidx.work.Data.Builder putString(String, String?);
+ method public androidx.work.Data.Builder putStringArray(String, String[]);
+ }
+
+ public enum ExistingPeriodicWorkPolicy {
+ enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy KEEP;
+ enum_constant public static final androidx.work.ExistingPeriodicWorkPolicy REPLACE;
+ }
+
+ public enum ExistingWorkPolicy {
+ enum_constant public static final androidx.work.ExistingWorkPolicy APPEND;
+ enum_constant public static final androidx.work.ExistingWorkPolicy KEEP;
+ enum_constant public static final androidx.work.ExistingWorkPolicy REPLACE;
+ }
+
+ public abstract class InputMerger {
+ ctor public InputMerger();
+ method public abstract androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public abstract class ListenableWorker {
+ ctor @Keep public ListenableWorker(android.content.Context, androidx.work.WorkerParameters);
+ method public final android.content.Context getApplicationContext();
+ method public final java.util.UUID getId();
+ method public final androidx.work.Data getInputData();
+ method @RequiresApi(28) public final android.net.Network? getNetwork();
+ method public final int getRunAttemptCount();
+ method public final java.util.Set<java.lang.String> getTags();
+ method @RequiresApi(24) public final java.util.List<java.lang.String> getTriggeredContentAuthorities();
+ method @RequiresApi(24) public final java.util.List<android.net.Uri> getTriggeredContentUris();
+ method public final boolean isStopped();
+ method public void onStopped();
+ method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+ public abstract static class ListenableWorker.Result {
+ method public static androidx.work.ListenableWorker.Result failure();
+ method public static androidx.work.ListenableWorker.Result failure(androidx.work.Data);
+ method public static androidx.work.ListenableWorker.Result retry();
+ method public static androidx.work.ListenableWorker.Result success();
+ method public static androidx.work.ListenableWorker.Result success(androidx.work.Data);
+ }
+
+ public enum NetworkType {
+ enum_constant public static final androidx.work.NetworkType CONNECTED;
+ enum_constant public static final androidx.work.NetworkType METERED;
+ enum_constant public static final androidx.work.NetworkType NOT_REQUIRED;
+ enum_constant public static final androidx.work.NetworkType NOT_ROAMING;
+ enum_constant public static final androidx.work.NetworkType UNMETERED;
+ }
+
+ public final class OneTimeWorkRequest extends androidx.work.WorkRequest {
+ method public static androidx.work.OneTimeWorkRequest from(Class<? extends androidx.work.ListenableWorker>);
+ method public static java.util.List<androidx.work.OneTimeWorkRequest> from(java.util.List<java.lang.Class<? extends androidx.work.ListenableWorker>>);
+ }
+
+ public static final class OneTimeWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.OneTimeWorkRequest.Builder,androidx.work.OneTimeWorkRequest> {
+ ctor public OneTimeWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>);
+ method public androidx.work.OneTimeWorkRequest.Builder setInitialDelay(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public androidx.work.OneTimeWorkRequest.Builder setInitialDelay(java.time.Duration);
+ method public androidx.work.OneTimeWorkRequest.Builder setInputMerger(Class<? extends androidx.work.InputMerger>);
+ }
+
+ public interface Operation {
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.work.Operation.State.SUCCESS> getResult();
+ method public androidx.lifecycle.LiveData<androidx.work.Operation.State> getState();
+ }
+
+ public abstract static class Operation.State {
+ }
+
+ public static final class Operation.State.FAILURE extends androidx.work.Operation.State {
+ ctor public Operation.State.FAILURE(Throwable);
+ method public Throwable getThrowable();
+ }
+
+ public static final class Operation.State.IN_PROGRESS extends androidx.work.Operation.State {
+ }
+
+ public static final class Operation.State.SUCCESS extends androidx.work.Operation.State {
+ }
+
+ public final class OverwritingInputMerger extends androidx.work.InputMerger {
+ ctor public OverwritingInputMerger();
+ method public androidx.work.Data merge(java.util.List<androidx.work.Data>);
+ }
+
+ public final class PeriodicWorkRequest extends androidx.work.WorkRequest {
+ field public static final long MIN_PERIODIC_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIODIC_INTERVAL_MILLIS = 900000L; // 0xdbba0L
+ }
+
+ public static final class PeriodicWorkRequest.Builder extends androidx.work.WorkRequest.Builder<androidx.work.PeriodicWorkRequest.Builder,androidx.work.PeriodicWorkRequest> {
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration);
+ ctor public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit);
+ ctor @RequiresApi(26) public PeriodicWorkRequest.Builder(Class<? extends androidx.work.ListenableWorker>, java.time.Duration, java.time.Duration);
+ }
+
+ public abstract class WorkContinuation {
+ ctor public WorkContinuation();
+ method public static androidx.work.WorkContinuation combine(java.util.List<androidx.work.WorkContinuation>);
+ method public abstract androidx.work.Operation enqueue();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos();
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
+ method public final androidx.work.WorkContinuation then(androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation then(java.util.List<androidx.work.OneTimeWorkRequest>);
+ }
+
+ public final class WorkInfo {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getOutputData();
+ method public androidx.work.WorkInfo.State getState();
+ method public java.util.Set<java.lang.String> getTags();
+ }
+
+ public enum WorkInfo.State {
+ method public boolean isFinished();
+ enum_constant public static final androidx.work.WorkInfo.State BLOCKED;
+ enum_constant public static final androidx.work.WorkInfo.State CANCELLED;
+ enum_constant public static final androidx.work.WorkInfo.State ENQUEUED;
+ enum_constant public static final androidx.work.WorkInfo.State FAILED;
+ enum_constant public static final androidx.work.WorkInfo.State RUNNING;
+ enum_constant public static final androidx.work.WorkInfo.State SUCCEEDED;
+ }
+
+ public abstract class WorkManager {
+ method public final androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation beginUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public final androidx.work.WorkContinuation beginWith(androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.WorkContinuation beginWith(java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public abstract androidx.work.Operation cancelAllWork();
+ method public abstract androidx.work.Operation cancelAllWorkByTag(String);
+ method public abstract androidx.work.Operation cancelUniqueWork(String);
+ method public abstract androidx.work.Operation cancelWorkById(java.util.UUID);
+ method public final androidx.work.Operation enqueue(androidx.work.WorkRequest);
+ method public abstract androidx.work.Operation enqueue(java.util.List<? extends androidx.work.WorkRequest>);
+ method public abstract androidx.work.Operation enqueueUniquePeriodicWork(String, androidx.work.ExistingPeriodicWorkPolicy, androidx.work.PeriodicWorkRequest);
+ method public androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, androidx.work.OneTimeWorkRequest);
+ method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
+ method public static androidx.work.WorkManager getInstance();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
+ method public abstract androidx.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo> getWorkInfoById(java.util.UUID);
+ method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
+ method public static void initialize(android.content.Context, androidx.work.Configuration);
+ method public abstract androidx.work.Operation pruneWork();
+ }
+
+ public abstract class WorkRequest {
+ method public java.util.UUID getId();
+ field public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L; // 0x7530L
+ field public static final long MAX_BACKOFF_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_BACKOFF_MILLIS = 10000L; // 0x2710L
+ }
+
+ public abstract static class WorkRequest.Builder<B extends androidx.work.WorkRequest.Builder, W extends androidx.work.WorkRequest> {
+ method public final B addTag(String);
+ method public final W build();
+ method public final B keepResultsForAtLeast(long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public final B keepResultsForAtLeast(java.time.Duration);
+ method public final B setBackoffCriteria(androidx.work.BackoffPolicy, long, java.util.concurrent.TimeUnit);
+ method @RequiresApi(26) public final B setBackoffCriteria(androidx.work.BackoffPolicy, java.time.Duration);
+ method public final B setConstraints(androidx.work.Constraints);
+ method public final B setInputData(androidx.work.Data);
+ }
+
+ public abstract class Worker extends androidx.work.ListenableWorker {
+ ctor @Keep public Worker(android.content.Context, androidx.work.WorkerParameters);
+ method @WorkerThread public abstract androidx.work.ListenableWorker.Result doWork();
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
+ }
+
+ public abstract class WorkerFactory {
+ ctor public WorkerFactory();
+ method public abstract androidx.work.ListenableWorker? createWorker(android.content.Context, String, androidx.work.WorkerParameters);
+ }
+
+ public final class WorkerParameters {
+ method public java.util.UUID getId();
+ method public androidx.work.Data getInputData();
+ method @RequiresApi(28) public android.net.Network? getNetwork();
+ method public int getRunAttemptCount();
+ method public java.util.Set<java.lang.String> getTags();
+ method @RequiresApi(24) public java.util.List<java.lang.String> getTriggeredContentAuthorities();
+ method @RequiresApi(24) public java.util.List<android.net.Uri> getTriggeredContentUris();
+ }
+
+}
+
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index bc7e240..a616da8 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -153,7 +153,7 @@
public interface Operation {
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.Operation.State.SUCCESS> getResult();
- method public android.arch.lifecycle.LiveData<androidx.work.Operation.State> getState();
+ method public androidx.lifecycle.LiveData<androidx.work.Operation.State> getState();
}
public abstract static class Operation.State {
@@ -192,7 +192,7 @@
method public static androidx.work.WorkContinuation combine(java.util.List<androidx.work.WorkContinuation>);
method public abstract androidx.work.Operation enqueue();
method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfos();
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosLiveData();
method public final androidx.work.WorkContinuation then(androidx.work.OneTimeWorkRequest);
method public abstract androidx.work.WorkContinuation then(java.util.List<androidx.work.OneTimeWorkRequest>);
}
@@ -230,13 +230,13 @@
method public abstract androidx.work.Operation enqueueUniqueWork(String, androidx.work.ExistingWorkPolicy, java.util.List<androidx.work.OneTimeWorkRequest>);
method public static androidx.work.WorkManager getInstance();
method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long> getLastCancelAllTimeMillis();
- method public abstract android.arch.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
+ method public abstract androidx.lifecycle.LiveData<java.lang.Long> getLastCancelAllTimeMillisLiveData();
method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo> getWorkInfoById(java.util.UUID);
- method public abstract android.arch.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
+ method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo> getWorkInfoByIdLiveData(java.util.UUID);
method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTag(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosByTagLiveData(String);
method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWork(String);
- method public abstract android.arch.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
+ method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo>> getWorkInfosForUniqueWorkLiveData(String);
method public static void initialize(android.content.Context, androidx.work.Configuration);
method public abstract androidx.work.Operation pruneWork();
}
diff --git a/work/workmanager/api/res-2.0.0-rc01.txt b/work/workmanager/api/res-2.0.0-rc01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager/api/res-2.0.0-rc01.txt
diff --git a/work/workmanager/api/res-2.0.0.txt b/work/workmanager/api/res-2.0.0.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/work/workmanager/api/res-2.0.0.txt
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index f140803..d424b64 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -42,21 +42,27 @@
}
dependencies {
- // @aar and { transitive = true } are needed as a workaround for
- // https://github.com/gradle/gradle/issues/3170
- api("android.arch.lifecycle:extensions:1.1.0@aar") { transitive = true }
+ // Using -PuseMaxDepVersions does not use the right version of the annotation processor
+ // Remove this workaround after b/127495641 is fixed
+ if (project.hasProperty('useMaxDepVersions')) {
+ annotationProcessor(project(":room:room-compiler"))
+ implementation(project(":room:room-runtime"))
+ androidTestImplementation(project(":room:room-testing"))
+ } else {
+ annotationProcessor(ARCH_ROOM_COMPILER)
+ implementation(ARCH_ROOM_RUNTIME)
+ androidTestImplementation(ARCH_ROOM_TESTING)
+ }
+
+ api(ARCH_LIFECYCLE_EXTENSIONS)
api(GUAVA_LISTENABLE_FUTURE)
- implementation("android.arch.persistence.room:runtime:1.1.1@aar") { transitive = true }
- annotationProcessor("android.arch.persistence.room:compiler:1.1.1")
- androidTestImplementation("android.arch.core:core-testing:1.1.0")
- androidTestImplementation("android.arch.persistence.room:testing:1.1.1")
androidTestImplementation(TEST_EXT_JUNIT)
androidTestImplementation(TEST_CORE)
+ androidTestImplementation(WORK_ARCH_CORE_TESTING)
androidTestImplementation(TEST_RUNNER)
androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
-
testImplementation(JUNIT)
}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/TestLifecycleOwner.java b/work/workmanager/src/androidTest/java/androidx/work/TestLifecycleOwner.java
index 0c70d6a..336a52d 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/TestLifecycleOwner.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/TestLifecycleOwner.java
@@ -16,9 +16,9 @@
package androidx.work;
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
/**
* Test Lifecycle Owner that begins in ON_START state.
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index 65556e8..3d853b7 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -29,16 +29,16 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
-import android.arch.persistence.room.testing.MigrationTestHelper;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.os.Build;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/ObserveForeverTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/ObserveForeverTest.java
index 65e629e..5c7541e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/ObserveForeverTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/ObserveForeverTest.java
@@ -14,180 +14,190 @@
* limitations under the License.
*/
-package androidx.work.impl;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
+//package androidx.work.impl;
+//
+//import static org.hamcrest.CoreMatchers.is;
+//import static org.hamcrest.CoreMatchers.notNullValue;
+//import static org.hamcrest.MatcherAssert.assertThat;
+//
+//import androidx.annotation.Nullable;
+//import androidx.lifecycle.Observer;
+//import androidx.test.ext.junit.runners.AndroidJUnit4;
+//import androidx.test.filters.MediumTest;
+//import androidx.test.platform.app.InstrumentationRegistry;
+//import androidx.work.Configuration;
+//import androidx.work.ExistingWorkPolicy;
+//import androidx.work.OneTimeWorkRequest;
+//import androidx.work.WorkContinuation;
+//import androidx.work.WorkInfo;
+//import androidx.work.impl.utils.SynchronousExecutor;
+//import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
+//import androidx.work.worker.TestWorker;
+//
+//import org.junit.After;
+//import org.junit.Before;
+//import org.junit.Ignore;
+//import org.junit.Test;
+//import org.junit.runner.RunWith;
+//
+//import java.util.Collections;
+//import java.util.List;
+//import java.util.concurrent.CountDownLatch;
+//import java.util.concurrent.ExecutionException;
+//import java.util.concurrent.TimeUnit;
+//
+///**
+// * TODO remove after moving to Room 2.1.x.
+// * see: b/74477406 for details.
+// *
+// * This test suite is being @Ignored because observeForever() can no longer be called on a
+// * background thread after the move to 2.x.
+// */
+//@RunWith(AndroidJUnit4.class)
+//@MediumTest
+//@Ignore
+//public class ObserveForeverTest {
+// private WorkManagerImpl mWorkManagerImpl;
+// private final OneTimeWorkRequest mWork = new OneTimeWorkRequest.Builder(TestWorker.class)
+// .addTag("foo")
+// .setInitialDelay(1, TimeUnit.HOURS)
+// .build();
+//
+// @Before
+// public void init() {
+// Configuration configuration = new Configuration.Builder()
+// .setExecutor(new SynchronousExecutor())
+// .build();
+// mWorkManagerImpl = new WorkManagerImpl(
+// InstrumentationRegistry.getInstrumentation().getTargetContext(),
+// configuration, new InstantWorkTaskExecutor());
+// WorkManagerImpl.setDelegate(mWorkManagerImpl);
+// }
+//
+// @After
+// public void tearDown() {
+// WorkManagerImpl.setDelegate(null);
+// }
+//
+// @Test
+// @Ignore
+// public void observeForever_byTags() throws InterruptedException {
+// LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
+// observer.expectValue();
+//
+// mWorkManagerImpl
+// .getWorkInfosByTagLiveData("foo")
+// .observeForever(observer);
+// assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
+//
+// forceGc();
+// observer.expectValue();
+// mWorkManagerImpl.enqueue(mWork);
+//
+// List<WorkInfo> received = observer.awaitNextValue();
+// assertThat(received.size(), is(1));
+// assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
+// }
+//
+// @Test
+// @Ignore
+// public void observeForever_byId() throws InterruptedException, ExecutionException {
+// LoggingObserver<WorkInfo> observer = new LoggingObserver<>();
+// observer.expectValue();
+//
+// mWorkManagerImpl
+// .getWorkInfoByIdLiveData(mWork.getId())
+// .observeForever(observer);
+//
+// mWorkManagerImpl.enqueue(mWork);
+//
+// WorkInfo received = observer.awaitNextValue();
+// assertThat(received, is(notNullValue()));
+// assertThat(received.getState(), is(WorkInfo.State.ENQUEUED));
+//
+// observer.expectValue();
+// forceGc();
+// mWorkManagerImpl.cancelAllWork().getResult().get();
+//
+// assertThat(observer.awaitNextValue().getState(), is(WorkInfo.State.CANCELLED));
+// }
+//
+// @Test
+// @Ignore
+// public void observeForever_uniqueWork() throws InterruptedException {
+// LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
+// observer.expectValue();
+//
+// mWorkManagerImpl
+// .getWorkInfosForUniqueWorkLiveData("custom-id")
+// .observeForever(observer);
+// assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
+//
+// forceGc();
+// observer.expectValue();
+// mWorkManagerImpl.beginUniqueWork("custom-id",
+// ExistingWorkPolicy.REPLACE,
+// mWork).enqueue();
+//
+// List<WorkInfo> received = observer.awaitNextValue();
+// assertThat(received.size(), is(1));
+// assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
+// }
+//
+// @Test
+// @Ignore
+// public void observeForever_workContinuation() throws InterruptedException {
+// LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
+// observer.expectValue();
+//
+// WorkContinuation workContinuation = mWorkManagerImpl.beginWith(mWork);
+// workContinuation.getWorkInfosLiveData().observeForever(observer);
+//
+// assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
+//
+// forceGc();
+//
+// observer.expectValue();
+// workContinuation.enqueue();
+//
+// List<WorkInfo> received = observer.awaitNextValue();
+// assertThat(received.size(), is(1));
+// assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
+// }
+//
+// private void forceGc() {
+// Runtime.getRuntime().gc();
+// Runtime.getRuntime().runFinalization();
+// Runtime.getRuntime().gc();
+// Runtime.getRuntime().runFinalization();
+// }
+//
+// static class LoggingObserver<T> implements Observer<T> {
+// CountDownLatch mLatch;
+// private T mValue;
+//
+// void expectValue() {
+// if (mLatch != null) {
+// throw new IllegalStateException("You've not consumed previous value yet");
+// }
+// mLatch = new CountDownLatch(1);
+// }
+//
+// T awaitNextValue() throws InterruptedException {
+// assertThat(mLatch.await(10, TimeUnit.SECONDS), is(true));
+// mLatch = null;
+// return mValue;
+// }
+//
+// @Override
+// public void onChanged(@Nullable T t) {
+// if (mLatch == null) {
+// throw new IllegalStateException("Not expecting a value yet");
+// }
+// mValue = t;
+// mLatch.countDown();
+// }
+// }
+//}
-import android.arch.lifecycle.Observer;
-import android.support.annotation.Nullable;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.work.Configuration;
-import androidx.work.ExistingWorkPolicy;
-import androidx.work.OneTimeWorkRequest;
-import androidx.work.WorkContinuation;
-import androidx.work.WorkInfo;
-import androidx.work.impl.utils.SynchronousExecutor;
-import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
-import androidx.work.worker.TestWorker;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-/**
- * TODO remove after moving to AndroidX.
- * see: b/74477406 for details.
- */
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class ObserveForeverTest {
- private WorkManagerImpl mWorkManagerImpl;
- private final OneTimeWorkRequest mWork = new OneTimeWorkRequest.Builder(TestWorker.class)
- .addTag("foo")
- .setInitialDelay(1, TimeUnit.HOURS)
- .build();
-
- @Before
- public void init() {
- Configuration configuration = new Configuration.Builder()
- .setExecutor(new SynchronousExecutor())
- .build();
- mWorkManagerImpl = new WorkManagerImpl(
- InstrumentationRegistry.getInstrumentation().getTargetContext(),
- configuration, new InstantWorkTaskExecutor());
- WorkManagerImpl.setDelegate(mWorkManagerImpl);
- }
-
- @After
- public void tearDown() {
- WorkManagerImpl.setDelegate(null);
- }
-
- @Test
- public void observeForever_byTags() throws InterruptedException {
- LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
- observer.expectValue();
-
- mWorkManagerImpl
- .getWorkInfosByTagLiveData("foo")
- .observeForever(observer);
- assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
-
- forceGc();
- observer.expectValue();
- mWorkManagerImpl.enqueue(mWork);
-
- List<WorkInfo> received = observer.awaitNextValue();
- assertThat(received.size(), is(1));
- assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
- }
-
- @Test
- public void observeForever_byId() throws InterruptedException, ExecutionException {
- LoggingObserver<WorkInfo> observer = new LoggingObserver<>();
- observer.expectValue();
-
- mWorkManagerImpl
- .getWorkInfoByIdLiveData(mWork.getId())
- .observeForever(observer);
-
- mWorkManagerImpl.enqueue(mWork);
-
- WorkInfo received = observer.awaitNextValue();
- assertThat(received, is(notNullValue()));
- assertThat(received.getState(), is(WorkInfo.State.ENQUEUED));
-
- observer.expectValue();
- forceGc();
- mWorkManagerImpl.cancelAllWork().getResult().get();
-
- assertThat(observer.awaitNextValue().getState(), is(WorkInfo.State.CANCELLED));
- }
-
- @Test
- public void observeForever_uniqueWork() throws InterruptedException {
- LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
- observer.expectValue();
-
- mWorkManagerImpl
- .getWorkInfosForUniqueWorkLiveData("custom-id")
- .observeForever(observer);
- assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
-
- forceGc();
- observer.expectValue();
- mWorkManagerImpl.beginUniqueWork("custom-id",
- ExistingWorkPolicy.REPLACE,
- mWork).enqueue();
-
- List<WorkInfo> received = observer.awaitNextValue();
- assertThat(received.size(), is(1));
- assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
- }
-
- @Test
- public void observeForever_workContinuation() throws InterruptedException {
- LoggingObserver<List<WorkInfo>> observer = new LoggingObserver<>();
- observer.expectValue();
-
- WorkContinuation workContinuation = mWorkManagerImpl.beginWith(mWork);
- workContinuation.getWorkInfosLiveData().observeForever(observer);
-
- assertThat(observer.awaitNextValue(), is(Collections.<WorkInfo>emptyList()));
-
- forceGc();
-
- observer.expectValue();
- workContinuation.enqueue();
-
- List<WorkInfo> received = observer.awaitNextValue();
- assertThat(received.size(), is(1));
- assertThat(received.get(0).getState(), is(WorkInfo.State.ENQUEUED));
- }
-
- private void forceGc() {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- }
-
- static class LoggingObserver<T> implements Observer<T> {
- CountDownLatch mLatch;
- private T mValue;
-
- void expectValue() {
- if (mLatch != null) {
- throw new IllegalStateException("You've not consumed previous value yet");
- }
- mLatch = new CountDownLatch(1);
- }
-
- T awaitNextValue() throws InterruptedException {
- assertThat(mLatch.await(10, TimeUnit.SECONDS), is(true));
- mLatch = null;
- return mValue;
- }
-
- @Override
- public void onChanged(@Nullable T t) {
- if (mLatch == null) {
- throw new IllegalStateException("Not expecting a value yet");
- }
- mValue = t;
- mLatch.countDown();
- }
- }
-}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index 8cda6bc..3cdf2d8 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -30,11 +30,12 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.lifecycle.Lifecycle;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -80,7 +81,7 @@
@Before
public void setUp() {
- ArchTaskExecutor.getInstance().setDelegate(new android.arch.core.executor.TaskExecutor() {
+ ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
@Override
public void executeOnDiskIO(@NonNull Runnable runnable) {
runnable.run();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
index b355ccd..3f56c07 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -25,13 +25,13 @@
import static java.util.concurrent.TimeUnit.SECONDS;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
-import android.arch.lifecycle.Observer;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+import androidx.lifecycle.Observer;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 2fa41ad..2cd36ff 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -49,12 +49,6 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.Observer;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.db.SupportSQLiteOpenHelper;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -62,14 +56,21 @@
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
import androidx.work.BackoffPolicy;
import androidx.work.Configuration;
import androidx.work.Constraints;
@@ -792,7 +793,6 @@
containsInAnyOrder(appendWork1.getStringId()));
}
-
@Test
@MediumTest
public void testEnqueueUniqueWork_appendsExistingWorkOnAppend()
@@ -1171,7 +1171,7 @@
}
@Test
- @MediumTest
+ @SmallTest
public void testCancelWorkById_cancelsDependentWork()
throws ExecutionException, InterruptedException {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 3a29aed..5a0839f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -35,7 +35,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -110,7 +109,7 @@
.setMinimumLoggingLevel(Log.VERBOSE)
.build();
mWorkTaskExecutor = new InstantWorkTaskExecutor();
- mWorkSpecDao = spy(mDatabase.workSpecDao());
+ mWorkSpecDao = mDatabase.workSpecDao();
mDependencyDao = mDatabase.dependencyDao();
mMockScheduler = mock(Scheduler.class);
}
@@ -273,6 +272,25 @@
}
@Test
+ @SmallTest
+ public void testFailedOnDeepHierarchy() {
+ OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
+ insertWork(work);
+ String previousId = work.getStringId();
+ String firstWorkId = previousId;
+ for (int i = 0; i < 500; ++i) {
+ work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
+ insertWork(work);
+ mDependencyDao.insertDependency(new Dependency(work.getStringId(), previousId));
+ previousId = work.getStringId();
+ }
+ WorkerWrapper workerWrapper = createBuilder(firstWorkId).build();
+ workerWrapper.setFailedAndResolve();
+ assertThat(mWorkSpecDao.getState(firstWorkId), is(FAILED));
+ assertThat(mWorkSpecDao.getState(previousId), is(FAILED));
+ }
+
+ @Test
@LargeTest
public void testRunning() throws InterruptedException {
OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 89b94e0..70f95f3 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -32,10 +32,10 @@
import android.content.Context;
import android.content.Intent;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -650,6 +650,39 @@
assertThat(numExecutionCompleted, is(2));
}
+ @Test
+ @LargeTest
+ @RepeatRule.Repeat(times = 1)
+ public void testDelayMet_withUnMetConstraintShouldNotCrashOnDestroy()
+ throws InterruptedException {
+ when(mBatteryChargingTracker.getInitialState()).thenReturn(false);
+ OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
+ .setPeriodStartTime(System.currentTimeMillis(), TimeUnit.MILLISECONDS)
+ .setConstraints(new Constraints.Builder()
+ .setRequiresCharging(true)
+ .build())
+ .build();
+
+ insertWork(work);
+
+ Intent delayMet = CommandHandler.createDelayMetIntent(mContext, work.getStringId());
+ mSpyDispatcher.postOnMainThread(
+ new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, delayMet, START_ID));
+
+ mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
+ assertThat(mLatch.getCount(), is(0L));
+
+ // Should not crash after we destroy the dispatcher
+ mDispatcher.onDestroy();
+ mBatteryChargingTracker.setState(true);
+ }
+
+ @Test
+ public void tearDownTest() {
+ mDispatcher.onDestroy();
+ assertThat(mDispatcher.getWorkTimer().getExecutorService().isShutdown(), is(true));
+ }
+
// Marking it public for mocking
public static class CommandInterceptingSystemDispatcher extends SystemAlarmDispatcher {
private final List<Intent> mCommands;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
index 0b211b4..f67ea77 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
@@ -22,8 +22,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index da750bf..1f27fac 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -28,15 +28,15 @@
import static org.mockito.Mockito.when;
import android.app.job.JobParameters;
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.executor.TaskExecutor;
import android.content.Context;
import android.net.Network;
import android.net.Uri;
import android.os.Build;
import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.ArchTaskExecutor;
+import androidx.arch.core.executor.TaskExecutor;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/WorkConstraintsTrackerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/WorkConstraintsTrackerTest.java
index 391c554..711f1e67 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/WorkConstraintsTrackerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/WorkConstraintsTrackerTest.java
@@ -26,8 +26,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.work.impl.constraints.controllers.ConstraintController;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/controllers/ConstraintControllerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/controllers/ConstraintControllerTest.java
index 68947aa..5e48bef 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/controllers/ConstraintControllerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/controllers/ConstraintControllerTest.java
@@ -24,8 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/BatteryChargingTrackerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/BatteryChargingTrackerTest.java
index 5861668..5203451 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/BatteryChargingTrackerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/constraints/trackers/BatteryChargingTrackerTest.java
@@ -34,8 +34,8 @@
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build;
-import android.support.annotation.RequiresApi;
+import androidx.annotation.RequiresApi;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/LiveDataUtilsTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/LiveDataUtilsTest.java
index 7d533daf..49deb61 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/LiveDataUtilsTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/LiveDataUtilsTest.java
@@ -20,13 +20,12 @@
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
-import android.arch.core.executor.testing.InstantTaskExecutorRule;
-import android.arch.core.util.Function;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MutableLiveData;
-import android.arch.lifecycle.Observer;
-import android.support.annotation.Nullable;
-
+import androidx.annotation.Nullable;
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
+import androidx.arch.core.util.Function;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.work.TestLifecycleOwner;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/RepeatRule.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/RepeatRule.java
index 300bedc..ea74196 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/RepeatRule.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/RepeatRule.java
@@ -16,7 +16,7 @@
package androidx.work.impl.utils;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
index 9f0b3e6..84b4abc 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/taskexecutor/InstantWorkTaskExecutor.java
@@ -16,8 +16,7 @@
package androidx.work.impl.utils.taskexecutor;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
import androidx.work.impl.utils.SynchronousExecutor;
import java.util.concurrent.Executor;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index e6e48dd..3224b6b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -26,8 +26,8 @@
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
index 225e018..3d3d84b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
index ba6c722..b175651 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
index af691c3..72327f3 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
index b89c3d5..393537b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
index 22b5609..d03b900 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
index 4c7afff..cca8f3d 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/LatchWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/LatchWorker.java
index d5ab0a4..f52cde1 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/LatchWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/LatchWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/RandomSleepTestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/RandomSleepTestWorker.java
index 67e7cfc..fee507f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/RandomSleepTestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/RandomSleepTestWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
index 674f59a..e2c95e2 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/ReturnNullResultWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/ReturnNullResultWorker.java
index 7502e6c..809bebf 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/ReturnNullResultWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/ReturnNullResultWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
index 2e8a9a8..3522e88 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/StopAwareWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/StopAwareWorker.java
index 4edd9ca..941fb0d 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/StopAwareWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/StopAwareWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
index 60a6e09..084fa74 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
@@ -17,9 +17,9 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/UsedWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/UsedWorker.java
index 8de2bd0..9f2bd0f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/UsedWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/UsedWorker.java
@@ -17,8 +17,8 @@
package androidx.work.worker;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/main/java/androidx/work/ArrayCreatingInputMerger.java b/work/workmanager/src/main/java/androidx/work/ArrayCreatingInputMerger.java
index 4d6b3b9..caf4f1d 100644
--- a/work/workmanager/src/main/java/androidx/work/ArrayCreatingInputMerger.java
+++ b/work/workmanager/src/main/java/androidx/work/ArrayCreatingInputMerger.java
@@ -16,7 +16,7 @@
package androidx.work;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import java.lang.reflect.Array;
import java.util.HashMap;
diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java
index 6d54d4b..a9cc897 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.IntRange;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
import android.util.Log;
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.impl.Scheduler;
import androidx.work.impl.utils.IdGenerator;
@@ -210,12 +210,18 @@
/**
* Specifies the maximum number of system requests made by {@link WorkManager}
* when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}.
- * When the application exceeds this limit, {@link WorkManager} maintains an internal queue
- * of {@link WorkRequest}s, and schedules them when slots become free.
* <p>
- * {@link WorkManager} requires a minimum of {@link Configuration#MIN_SCHEDULER_LIMIT}
- * slots; this is also the default value. The total number of slots also cannot exceed
- * {@code 50}.
+ * By default, WorkManager might schedule a large number of alarms or JobScheduler
+ * jobs. If your app uses JobScheduler or AlarmManager directly, this might exhaust the
+ * OS-enforced limit on the number of jobs or alarms an app is allowed to schedule. To help
+ * manage this situation, you can use this method to reduce the number of underlying jobs
+ * and alarms that WorkManager might schedule.
+ * <p>
+ * When the application exceeds this limit, WorkManager maintains an internal queue of
+ * {@link WorkRequest}s, and schedules them when slots become free.
+ * <p>
+ * WorkManager requires a minimum of {@link Configuration#MIN_SCHEDULER_LIMIT} slots; this
+ * is also the default value. The total number of slots also cannot exceed {@code 50}.
*
* @param maxSchedulerLimit The total number of jobs which can be enqueued by
* {@link WorkManager} when using
diff --git a/work/workmanager/src/main/java/androidx/work/Constraints.java b/work/workmanager/src/main/java/androidx/work/Constraints.java
index f471dfc..ad6d5d0 100644
--- a/work/workmanager/src/main/java/androidx/work/Constraints.java
+++ b/work/workmanager/src/main/java/androidx/work/Constraints.java
@@ -18,13 +18,14 @@
import static androidx.work.NetworkType.NOT_REQUIRED;
-import android.arch.persistence.room.ColumnInfo;
import android.net.Uri;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
diff --git a/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java b/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
index 8bfcf5d..a903dd0 100644
--- a/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
+++ b/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
@@ -17,8 +17,9 @@
package androidx.work;
import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.HashSet;
import java.util.Set;
diff --git a/work/workmanager/src/main/java/androidx/work/Data.java b/work/workmanager/src/main/java/androidx/work/Data.java
index c3dfd88..de75009 100644
--- a/work/workmanager/src/main/java/androidx/work/Data.java
+++ b/work/workmanager/src/main/java/androidx/work/Data.java
@@ -16,13 +16,14 @@
package androidx.work;
-import android.arch.persistence.room.TypeConverter;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.room.TypeConverter;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
diff --git a/work/workmanager/src/main/java/androidx/work/InputMerger.java b/work/workmanager/src/main/java/androidx/work/InputMerger.java
index 51e6e5f..993ea10 100644
--- a/work/workmanager/src/main/java/androidx/work/InputMerger.java
+++ b/work/workmanager/src/main/java/androidx/work/InputMerger.java
@@ -16,8 +16,8 @@
package androidx.work;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index a419cac..7726f1a 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -20,13 +20,13 @@
import android.content.Context;
import android.net.Network;
import android.net.Uri;
-import android.support.annotation.Keep;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.Keep;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/Logger.java b/work/workmanager/src/main/java/androidx/work/Logger.java
index b7dd644..a9fe640 100644
--- a/work/workmanager/src/main/java/androidx/work/Logger.java
+++ b/work/workmanager/src/main/java/androidx/work/Logger.java
@@ -16,10 +16,11 @@
package androidx.work;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
/**
* The class that handles logging requests for {@link WorkManager}. Currently, this class is not
* accessible and has only one default implementation, {@link LogcatLogger}, that writes to logcat
diff --git a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
index 500b7ea..90c5eed 100644
--- a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
@@ -17,8 +17,9 @@
package androidx.work;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import java.time.Duration;
import java.util.ArrayList;
diff --git a/work/workmanager/src/main/java/androidx/work/Operation.java b/work/workmanager/src/main/java/androidx/work/Operation.java
index e37455a..05c16ec 100644
--- a/work/workmanager/src/main/java/androidx/work/Operation.java
+++ b/work/workmanager/src/main/java/androidx/work/Operation.java
@@ -17,11 +17,12 @@
package androidx.work;
import android.annotation.SuppressLint;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.Observer;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/OverwritingInputMerger.java b/work/workmanager/src/main/java/androidx/work/OverwritingInputMerger.java
index ed945238..a87b51c 100644
--- a/work/workmanager/src/main/java/androidx/work/OverwritingInputMerger.java
+++ b/work/workmanager/src/main/java/androidx/work/OverwritingInputMerger.java
@@ -16,7 +16,7 @@
package androidx.work;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
index d599574..374c375 100644
--- a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
@@ -16,8 +16,9 @@
package androidx.work;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
index d36a43a..d625c93 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
@@ -15,11 +15,11 @@
*/
package androidx.work;
-import android.arch.lifecycle.LifecycleOwner;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.Observer;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
import com.google.common.util.concurrent.ListenableFuture;
@@ -90,7 +90,7 @@
* Returns a {@link LiveData} list of {@link WorkInfo}s that provide information about the
* status of each {@link OneTimeWorkRequest} in this {@link WorkContinuation}, as well as their
* prerequisites. If the state or outputs of any of the work changes, any attached
- * {@link android.arch.lifecycle.Observer}s will trigger.
+ * {@link Observer}s will trigger.
*
* @return A {@link LiveData} containing a list of {@link WorkInfo}s; you must use
* {@link LiveData#observe(LifecycleOwner, Observer)} to receive updates
diff --git a/work/workmanager/src/main/java/androidx/work/WorkInfo.java b/work/workmanager/src/main/java/androidx/work/WorkInfo.java
index 047c2951..c60fc570 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkInfo.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkInfo.java
@@ -16,8 +16,8 @@
package androidx.work;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.HashSet;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index a5a85bf7..e0cedd1 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -16,11 +16,11 @@
package androidx.work;
-import android.arch.lifecycle.LiveData;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LiveData;
import androidx.work.impl.WorkManagerImpl;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index 42cb68b..96f9f85 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -15,11 +15,10 @@
*/
package androidx.work;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.impl.model.WorkSpec;
import java.time.Duration;
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 7bba996..e414b35 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -18,10 +18,10 @@
import android.annotation.SuppressLint;
import android.content.Context;
-import android.support.annotation.Keep;
-import android.support.annotation.NonNull;
-import android.support.annotation.WorkerThread;
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
import androidx.work.impl.utils.futures.SettableFuture;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkerFactory.java b/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
index 9f067d5..dfc45c7 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkerFactory.java
@@ -17,9 +17,10 @@
package androidx.work;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import java.lang.reflect.Constructor;
diff --git a/work/workmanager/src/main/java/androidx/work/WorkerParameters.java b/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
index e40ad48..c5256d8 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
@@ -18,11 +18,11 @@
import android.net.Network;
import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
import java.util.Collection;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/ExecutionListener.java b/work/workmanager/src/main/java/androidx/work/impl/ExecutionListener.java
index 8474047..e37d803 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/ExecutionListener.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/ExecutionListener.java
@@ -16,9 +16,8 @@
package androidx.work.impl;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Worker;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java
index 0f150d6..aff0c38 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/OperationImpl.java
@@ -16,11 +16,10 @@
package androidx.work.impl;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MutableLiveData;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import androidx.work.Operation;
import androidx.work.impl.utils.futures.SettableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Processor.java b/work/workmanager/src/main/java/androidx/work/impl/Processor.java
index c241bd4..5558b22 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Processor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Processor.java
@@ -16,9 +16,9 @@
package androidx.work.impl;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Configuration;
import androidx.work.Logger;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Scheduler.java b/work/workmanager/src/main/java/androidx/work/impl/Scheduler.java
index 436d573..03274f0 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Scheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Scheduler.java
@@ -15,9 +15,8 @@
*/
package androidx.work.impl;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.impl.model.WorkSpec;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
index 22a4be5..2de246d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -21,9 +21,9 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Configuration;
import androidx.work.Logger;
import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
index 92ba9eb..103be77 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -16,12 +16,12 @@
package androidx.work.impl;
-import android.arch.lifecycle.LiveData;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LiveData;
import androidx.work.ArrayCreatingInputMerger;
import androidx.work.ExistingWorkPolicy;
import androidx.work.Logger;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
index 429e91e..86983c6 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -24,15 +24,15 @@
import static androidx.work.impl.model.WorkTypeConverters.StateIds.ENQUEUED;
import static androidx.work.impl.model.WorkTypeConverters.StateIds.RUNNING;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.Database;
-import android.arch.persistence.room.Room;
-import android.arch.persistence.room.RoomDatabase;
-import android.arch.persistence.room.TypeConverters;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.Database;
+import androidx.room.Room;
+import androidx.room.RoomDatabase;
+import androidx.room.TypeConverters;
+import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.work.Data;
import androidx.work.impl.model.Dependency;
import androidx.work.impl.model.DependencyDao;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
index 8b41ccc..b0a055c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -16,13 +16,13 @@
package androidx.work.impl;
-import android.arch.persistence.db.SupportSQLiteDatabase;
-import android.arch.persistence.room.migration.Migration;
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkTypeConverters;
import androidx.work.impl.utils.Preferences;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 7e12ab4..3b8768b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -16,15 +16,15 @@
package androidx.work.impl;
-import android.arch.core.util.Function;
-import android.arch.lifecycle.LiveData;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.arch.core.util.Function;
+import androidx.lifecycle.LiveData;
import androidx.work.Configuration;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.ExistingWorkPolicy;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
index 4d88a57..7caf8063 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
@@ -20,10 +20,10 @@
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import androidx.work.Configuration;
import androidx.work.WorkManager;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerLiveDataTracker.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerLiveDataTracker.java
index 9ea44c3..2357429 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerLiveDataTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerLiveDataTracker.java
@@ -16,11 +16,11 @@
package androidx.work.impl;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MediatorLiveData;
-import android.arch.lifecycle.Observer;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.Observer;
import java.util.Collections;
import java.util.IdentityHashMap;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index 11db087..30e1e80 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -27,12 +27,12 @@
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.InputMerger;
@@ -53,6 +53,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CancellationException;
@@ -482,10 +483,11 @@
return setToRunning;
}
- private void setFailedAndResolve() {
+ @VisibleForTesting
+ void setFailedAndResolve() {
mWorkDatabase.beginTransaction();
try {
- recursivelyFailWorkAndDependents(mWorkSpecId);
+ iterativelyFailWorkAndDependents(mWorkSpecId);
ListenableWorker.Result.Failure failure = (ListenableWorker.Result.Failure) mResult;
// Update Data as necessary.
Data output = failure.getOutputData();
@@ -497,15 +499,16 @@
}
}
- private void recursivelyFailWorkAndDependents(String workSpecId) {
- List<String> dependentIds = mDependencyDao.getDependentWorkIds(workSpecId);
- for (String id : dependentIds) {
- recursivelyFailWorkAndDependents(id);
- }
-
- // Don't fail already cancelled work.
- if (mWorkSpecDao.getState(workSpecId) != CANCELLED) {
- mWorkSpecDao.setState(FAILED, workSpecId);
+ private void iterativelyFailWorkAndDependents(String workSpecId) {
+ LinkedList<String> idsToProcess = new LinkedList<>();
+ idsToProcess.add(workSpecId);
+ while (!idsToProcess.isEmpty()) {
+ String id = idsToProcess.remove();
+ // Don't fail already cancelled work.
+ if (mWorkSpecDao.getState(id) != CANCELLED) {
+ mWorkSpecDao.setState(FAILED, id);
+ }
+ idsToProcess.addAll(mDependencyDao.getDependentWorkIds(id));
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index 88f3fbe..f68cc0b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -18,11 +18,11 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
import androidx.work.WorkInfo;
import androidx.work.impl.ExecutionListener;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/background/package-info.java
index 55774a1..70dbcaa 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.background;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
index c80fe5c..3e480cd 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
@@ -23,9 +23,9 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index 3f40ac5d..1ae162d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -19,11 +19,11 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
import androidx.work.Logger;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.WorkDatabase;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
index 869209e..ef12d58 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
@@ -18,10 +18,10 @@
import android.content.Context;
import android.content.Intent;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
import androidx.work.Logger;
import androidx.work.impl.constraints.WorkConstraintsTracker;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
index fc5e814..994ac65 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
@@ -19,11 +19,11 @@
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
import androidx.work.Logger;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.constraints.WorkConstraintsCallback;
@@ -225,6 +225,8 @@
// * It could also happen on the onExecutionCompleted() pass of the bgProcessor.
// To avoid calling mWakeLock.release() twice, we are synchronizing here.
synchronized (mLock) {
+ // clean up constraint trackers
+ mWorkConstraintsTracker.reset();
// stop timers
mDispatcher.getWorkTimer().stopTimer(mWorkSpecId);
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
index f4724e6..6772d3a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
@@ -21,13 +21,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
import androidx.work.impl.ExecutionListener;
import androidx.work.impl.Processor;
@@ -93,6 +93,7 @@
void onDestroy() {
mProcessor.removeExecutionListener(this);
+ mWorkTimer.onDestroy();
mCompletedListener = null;
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
index c3b995b..ef37ab0 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
@@ -18,9 +18,9 @@
import android.content.Context;
import android.content.Intent;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.impl.Scheduler;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmService.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmService.java
index 2d10e3f..cb5f4c3 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmService.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmService.java
@@ -17,11 +17,11 @@
package androidx.work.impl.background.systemalarm;
import android.app.Service;
-import android.arch.lifecycle.LifecycleService;
import android.content.Intent;
-import android.support.annotation.MainThread;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.MainThread;
+import androidx.annotation.RestrictTo;
+import androidx.lifecycle.LifecycleService;
import androidx.work.Logger;
import androidx.work.impl.utils.WakeLocks;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/WorkTimer.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/WorkTimer.java
index 6a1d8ae..3496ee5 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/WorkTimer.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/WorkTimer.java
@@ -16,10 +16,9 @@
package androidx.work.impl.background.systemalarm;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
import androidx.work.WorkRequest;
@@ -94,6 +93,12 @@
}
}
+ void onDestroy() {
+ // Calling shutdown() waits for pending scheduled WorkTimerRunnable's which is not
+ // something we care about. Hence call shutdownNow().
+ mExecutorService.shutdownNow();
+ }
+
@VisibleForTesting
synchronized Map<String, WorkTimerRunnable> getTimerMap() {
return mTimerMap;
@@ -104,6 +109,11 @@
return mListeners;
}
+ @VisibleForTesting
+ ScheduledExecutorService getExecutorService() {
+ return mExecutorService;
+ }
+
/**
* The actual runnable scheduled on the scheduled executor.
*/
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/package-info.java
index 138a6f3..c2c0369 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.background.systemalarm;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index 901300a..d662d9c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -16,18 +16,18 @@
package androidx.work.impl.background.systemjob;
-import static android.support.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.ContentUriTriggers;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index b5b3e40..665e3c1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -20,11 +20,11 @@
import android.content.Context;
import android.os.Build;
import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
import androidx.work.WorkInfo;
import androidx.work.impl.Scheduler;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index 93915a72..1330d1d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -21,11 +21,11 @@
import android.app.job.JobService;
import android.os.Build;
import android.os.PersistableBundle;
-import android.support.annotation.NonNull;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.WorkerParameters;
import androidx.work.impl.ExecutionListener;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/package-info.java
index 23972bc..d0fd0c1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.background.systemjob;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/ConstraintListener.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/ConstraintListener.java
index 8fbc40a..c4616a2 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/ConstraintListener.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/ConstraintListener.java
@@ -15,7 +15,7 @@
*/
package androidx.work.impl.constraints;
-import android.support.annotation.Nullable;
+import androidx.annotation.Nullable;
/**
* The listener for constraint changes.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsCallback.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsCallback.java
index 31a7c4b..89f10b6 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsCallback.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsCallback.java
@@ -15,8 +15,7 @@
*/
package androidx.work.impl.constraints;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
import androidx.work.impl.model.WorkSpec;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.java
index 6ec9f53..8e6dfa6 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/WorkConstraintsTracker.java
@@ -16,10 +16,10 @@
package androidx.work.impl.constraints;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Constraints;
import androidx.work.Logger;
import androidx.work.impl.constraints.controllers.BatteryChargingController;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryChargingController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryChargingController.java
index 9e73d20..ce8ebf5 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryChargingController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryChargingController.java
@@ -16,8 +16,8 @@
package androidx.work.impl.constraints.controllers;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryNotLowController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryNotLowController.java
index 40bdd20..07e5149 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryNotLowController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/BatteryNotLowController.java
@@ -16,8 +16,8 @@
package androidx.work.impl.constraints.controllers;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/ConstraintController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/ConstraintController.java
index 0d5782b..72b4ae5 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/ConstraintController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/ConstraintController.java
@@ -15,9 +15,8 @@
*/
package androidx.work.impl.constraints.controllers;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.work.impl.constraints.ConstraintListener;
import androidx.work.impl.constraints.trackers.ConstraintTracker;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkConnectedController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkConnectedController.java
index ff7a0a9..9514d44 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkConnectedController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkConnectedController.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.impl.constraints.NetworkState;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkMeteredController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkMeteredController.java
index 12ea923..2c323a0 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkMeteredController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkMeteredController.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Logger;
import androidx.work.impl.constraints.NetworkState;
import androidx.work.impl.constraints.trackers.Trackers;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkNotRoamingController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkNotRoamingController.java
index 5d2dba7..890980b 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkNotRoamingController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkNotRoamingController.java
@@ -20,8 +20,8 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Logger;
import androidx.work.impl.constraints.NetworkState;
import androidx.work.impl.constraints.trackers.Trackers;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkUnmeteredController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkUnmeteredController.java
index 9d053d7..085af4d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkUnmeteredController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/NetworkUnmeteredController.java
@@ -19,8 +19,8 @@
import static androidx.work.NetworkType.UNMETERED;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.impl.constraints.NetworkState;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/StorageNotLowController.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/StorageNotLowController.java
index 4be488e..6c1d40a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/StorageNotLowController.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/StorageNotLowController.java
@@ -16,8 +16,8 @@
package androidx.work.impl.constraints.controllers;
import android.content.Context;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.impl.constraints.trackers.Trackers;
import androidx.work.impl.model.WorkSpec;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/package-info.java
index 7fe5540..9915f4c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/controllers/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.constraints.controllers;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/package-info.java
index 8e989ed..2cda5a4 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.constraints;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryChargingTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryChargingTracker.java
index 622ef64..6241a14 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryChargingTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryChargingTracker.java
@@ -20,9 +20,9 @@
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryNotLowTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryNotLowTracker.java
index bc4f479..0a74b0f 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryNotLowTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BatteryNotLowTracker.java
@@ -19,9 +19,9 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BroadcastReceiverConstraintTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BroadcastReceiverConstraintTracker.java
index 501bc75..3ddc44a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BroadcastReceiverConstraintTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/BroadcastReceiverConstraintTracker.java
@@ -20,9 +20,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/ConstraintTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/ConstraintTracker.java
index 2049fb0..9dc2f07 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/ConstraintTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/ConstraintTracker.java
@@ -16,8 +16,8 @@
package androidx.work.impl.constraints.trackers;
import android.content.Context;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.impl.constraints.ConstraintListener;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/NetworkStateTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/NetworkStateTracker.java
index 91455f1..920a9ac 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/NetworkStateTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/NetworkStateTracker.java
@@ -25,10 +25,10 @@
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
-import android.support.annotation.RequiresApi;
-import android.support.annotation.RestrictTo;
-import android.support.v4.net.ConnectivityManagerCompat;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+import androidx.core.net.ConnectivityManagerCompat;
import androidx.work.Logger;
import androidx.work.impl.constraints.NetworkState;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/StorageNotLowTracker.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/StorageNotLowTracker.java
index 6c27ba6..e484bc6 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/StorageNotLowTracker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/StorageNotLowTracker.java
@@ -18,9 +18,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/Trackers.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/Trackers.java
index 2838f6b..e4e7af8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/Trackers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/Trackers.java
@@ -16,9 +16,10 @@
package androidx.work.impl.constraints.trackers;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
/**
* A singleton class to hold an instance of each {@link ConstraintTracker}.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/package-info.java
index 2c4bf95..ef0678d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/constraints/trackers/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.constraints.trackers;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/Dependency.java b/work/workmanager/src/main/java/androidx/work/impl/model/Dependency.java
index 42997a8..e927b11 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/Dependency.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/Dependency.java
@@ -16,12 +16,12 @@
package androidx.work.impl.model;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Index;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
/**
* Database entity that defines a dependency between two {@link WorkSpec}s.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/DependencyDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/DependencyDao.java
index 602aba9..8cc6b48 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/DependencyDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/DependencyDao.java
@@ -16,11 +16,11 @@
package androidx.work.impl.model;
-import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
+import static androidx.room.OnConflictStrategy.IGNORE;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
index cd97958..99136e3 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
@@ -16,12 +16,12 @@
package androidx.work.impl.model;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.PrimaryKey;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.PrimaryKey;
/**
* Stores system ids for a {@link WorkSpec} id.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
index 383e516..fced5ee 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
@@ -16,13 +16,13 @@
package androidx.work.impl.model;
-import static android.arch.persistence.room.OnConflictStrategy.REPLACE;
+import static androidx.room.OnConflictStrategy.REPLACE;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
/**
* A Data Access Object for {@link SystemIdInfo}.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkName.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkName.java
index 96510d5..e62f88d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkName.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkName.java
@@ -16,12 +16,12 @@
package androidx.work.impl.model;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Index;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
/**
* Database entity that defines a mapping from a name to a {@link WorkSpec} id.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
index f6ed812..659cd6e 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
@@ -16,11 +16,11 @@
package androidx.work.impl.model;
-import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
+import static androidx.room.OnConflictStrategy.IGNORE;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
index f01f1f1..24fd6e8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
@@ -22,17 +22,17 @@
import static androidx.work.WorkRequest.MAX_BACKOFF_MILLIS;
import static androidx.work.WorkRequest.MIN_BACKOFF_MILLIS;
-import android.arch.core.util.Function;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Embedded;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.Index;
-import android.arch.persistence.room.PrimaryKey;
-import android.arch.persistence.room.Relation;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.arch.core.util.Function;
+import androidx.room.ColumnInfo;
+import androidx.room.Embedded;
+import androidx.room.Entity;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+import androidx.room.Relation;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
index 9756b5e..9aff4db 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
@@ -16,17 +16,15 @@
package androidx.work.impl.model;
-import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
-
+import static androidx.room.OnConflictStrategy.IGNORE;
import static androidx.work.impl.model.WorkTypeConverters.StateIds.COMPLETED_STATES;
-import android.arch.lifecycle.LiveData;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
-import android.arch.persistence.room.Transaction;
-import android.support.annotation.NonNull;
-
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Transaction;
import androidx.work.Data;
import androidx.work.WorkInfo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTag.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTag.java
index 9e539f0..412c6e7 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTag.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTag.java
@@ -16,12 +16,12 @@
package androidx.work.impl.model;
-import android.arch.persistence.room.ColumnInfo;
-import android.arch.persistence.room.Entity;
-import android.arch.persistence.room.ForeignKey;
-import android.arch.persistence.room.Index;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
/**
* Database entity that defines a mapping from a tag to a {@link WorkSpec} id.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
index 275c9de..e99de4e 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
@@ -16,11 +16,11 @@
package androidx.work.impl.model;
-import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
+import static androidx.room.OnConflictStrategy.IGNORE;
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.Query;
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.Query;
import java.util.List;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
index 3f7eb90..be044f6 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTypeConverters.java
@@ -25,9 +25,9 @@
import static androidx.work.WorkInfo.State.RUNNING;
import static androidx.work.WorkInfo.State.SUCCEEDED;
-import android.arch.persistence.room.TypeConverter;
import android.net.Uri;
+import androidx.room.TypeConverter;
import androidx.work.BackoffPolicy;
import androidx.work.ContentUriTriggers;
import androidx.work.NetworkType;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/model/package-info.java
index 753a09b..c872afd 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.model;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/package-info.java
index e68ec18..3f9ab0d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index 5dcd0d0..4ab5935 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -20,10 +20,9 @@
import static androidx.work.WorkInfo.State.FAILED;
import static androidx.work.WorkInfo.State.SUCCEEDED;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
import androidx.work.Operation;
import androidx.work.WorkInfo;
import androidx.work.impl.OperationImpl;
@@ -35,6 +34,7 @@
import androidx.work.impl.model.DependencyDao;
import androidx.work.impl.model.WorkSpecDao;
+import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -68,7 +68,7 @@
abstract void runInternal();
void cancel(WorkManagerImpl workManagerImpl, String workSpecId) {
- recursivelyCancelWorkAndDependents(workManagerImpl.getWorkDatabase(), workSpecId);
+ iterativelyCancelWorkAndDependents(workManagerImpl.getWorkDatabase(), workSpecId);
Processor processor = workManagerImpl.getProcessor();
processor.stopAndCancelWork(workSpecId);
@@ -85,18 +85,20 @@
workManagerImpl.getSchedulers());
}
- private void recursivelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId) {
+ private void iterativelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId) {
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
DependencyDao dependencyDao = workDatabase.dependencyDao();
- List<String> dependentIds = dependencyDao.getDependentWorkIds(workSpecId);
- for (String id : dependentIds) {
- recursivelyCancelWorkAndDependents(workDatabase, id);
- }
-
- WorkInfo.State state = workSpecDao.getState(workSpecId);
- if (state != SUCCEEDED && state != FAILED) {
- workSpecDao.setState(CANCELLED, workSpecId);
+ LinkedList<String> idsToProcess = new LinkedList<>();
+ idsToProcess.add(workSpecId);
+ while (!idsToProcess.isEmpty()) {
+ String id = idsToProcess.remove();
+ // Don't fail already cancelled work.
+ WorkInfo.State state = workSpecDao.getState(id);
+ if (state != SUCCEEDED && state != FAILED) {
+ workSpecDao.setState(CANCELLED, id);
+ }
+ idsToProcess.addAll(dependencyDao.getDependentWorkIds(id));
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index a3f5cf6..2be0df1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -28,11 +28,11 @@
import android.content.Context;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingWorkPolicy;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index adc4d48..002b5f7 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -28,10 +28,10 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.Logger;
import androidx.work.impl.Schedulers;
import androidx.work.impl.WorkDatabase;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/IdGenerator.java b/work/workmanager/src/main/java/androidx/work/impl/utils/IdGenerator.java
index 96ac2be..01533f8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/IdGenerator.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/IdGenerator.java
@@ -20,7 +20,8 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.RestrictTo;
/**
* Generates unique IDs that are persisted in {@link SharedPreferences}.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/LiveDataUtils.java b/work/workmanager/src/main/java/androidx/work/impl/utils/LiveDataUtils.java
index 2e59bed..5c6f68c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/LiveDataUtils.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/LiveDataUtils.java
@@ -16,14 +16,14 @@
package androidx.work.impl.utils;
-import android.arch.core.util.Function;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MediatorLiveData;
-import android.arch.lifecycle.Observer;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.arch.core.util.Function;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.Observer;
import androidx.work.impl.utils.taskexecutor.TaskExecutor;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PackageManagerHelper.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PackageManagerHelper.java
index 3ddecd5..c5a5947 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/PackageManagerHelper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PackageManagerHelper.java
@@ -18,8 +18,8 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.support.annotation.NonNull;
+import androidx.annotation.NonNull;
import androidx.work.Logger;
/**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
index 7bf3538..bdb83fa 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
@@ -16,13 +16,14 @@
package androidx.work.impl.utils;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MutableLiveData;
import android.content.Context;
import android.content.SharedPreferences;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
/**
* Preferences for WorkManager.
@@ -103,7 +104,7 @@
}
/**
- * A {@link android.arch.lifecycle.LiveData} that responds to changes in
+ * A {@link LiveData} that responds to changes in
* {@link SharedPreferences} for the {@code lastCancelAllTime} value.
*/
private static class LastCancelAllLiveData extends MutableLiveData<Long>
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
index 3783fcf..be42162 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
@@ -16,8 +16,7 @@
package androidx.work.impl.utils;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.RestrictTo;
import androidx.work.Operation;
import androidx.work.impl.OperationImpl;
import androidx.work.impl.WorkDatabase;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
index 2316b79..6e245b8 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/StartWorkRunnable.java
@@ -16,8 +16,7 @@
package androidx.work.impl.utils;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.RestrictTo;
import androidx.work.WorkerParameters;
import androidx.work.impl.WorkManagerImpl;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
index ef275e5..cd0ce6c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/StatusRunnable.java
@@ -16,10 +16,9 @@
package androidx.work.impl.utils;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.WorkerThread;
-
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.WorkerThread;
import androidx.work.WorkInfo;
import androidx.work.impl.WorkDatabase;
import androidx.work.impl.WorkManagerImpl;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
index 3243f7e..2fa2adf 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
@@ -16,8 +16,7 @@
package androidx.work.impl.utils;
-import android.support.annotation.RestrictTo;
-
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import androidx.work.WorkInfo;
import androidx.work.impl.WorkDatabase;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/SynchronousExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/SynchronousExecutor.java
index 4bc3725..afdd32a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/SynchronousExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/SynchronousExecutor.java
@@ -16,8 +16,8 @@
package androidx.work.impl.utils;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.concurrent.Executor;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/WakeLocks.java b/work/workmanager/src/main/java/androidx/work/impl/utils/WakeLocks.java
index acae4b0..f82869d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/WakeLocks.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/WakeLocks.java
@@ -20,9 +20,9 @@
import android.content.Context;
import android.os.PowerManager;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Logger;
import java.util.HashMap;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/AbstractFuture.java b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/AbstractFuture.java
index 708ce46..f7b4881 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/AbstractFuture.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/AbstractFuture.java
@@ -18,9 +18,9 @@
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/DirectExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/DirectExecutor.java
index 22eb124..a0cf357 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/DirectExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/DirectExecutor.java
@@ -16,7 +16,7 @@
package androidx.work.impl.utils.futures;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
import java.util.concurrent.Executor;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/SettableFuture.java b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/SettableFuture.java
index 8507960..c888995 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/SettableFuture.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/SettableFuture.java
@@ -16,8 +16,8 @@
package androidx.work.impl.utils.futures;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/package-info.java
index 2ab0302..923635c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/futures/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/futures/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.utils.futures;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/utils/package-info.java
index 00318b8..f2b1e04 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.utils;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
index 0a90108..056442d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
@@ -16,8 +16,8 @@
package androidx.work.impl.utils.taskexecutor;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.concurrent.Executor;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
index 0b791b4..ef2eb6a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
@@ -18,8 +18,9 @@
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/package-info.java b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/package-info.java
index d9d1cdd..b20f6c7 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/package-info.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/taskexecutor/package-info.java
@@ -20,4 +20,4 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
package androidx.work.impl.utils.taskexecutor;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.RestrictTo;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java b/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
index 7559b54..ef29c98 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
@@ -17,9 +17,9 @@
package androidx.work.impl.workers;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
diff --git a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
index 98147a6..d7e0afc 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
@@ -17,12 +17,12 @@
package androidx.work.impl.workers;
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
import androidx.work.ListenableWorker;
import androidx.work.Logger;
import androidx.work.Worker;
diff --git a/work/workmanager/src/test/java/androidx/work/impl/WorkManagerLiveDataTrackerTest.java b/work/workmanager/src/test/java/androidx/work/impl/WorkManagerLiveDataTrackerTest.java
index 23179ee..dbc71a4 100644
--- a/work/workmanager/src/test/java/androidx/work/impl/WorkManagerLiveDataTrackerTest.java
+++ b/work/workmanager/src/test/java/androidx/work/impl/WorkManagerLiveDataTrackerTest.java
@@ -19,8 +19,8 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
-import android.arch.lifecycle.LiveData;
-import android.arch.lifecycle.MutableLiveData;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import org.junit.Test;
import org.junit.runner.RunWith;