Merge "Use dackka for localbroadcastmanager docs" into androidx-main
diff --git a/car/app/OWNERS b/car/app/OWNERS
index 7cdc4ee..cc30871 100644
--- a/car/app/OWNERS
+++ b/car/app/OWNERS
@@ -10,3 +10,4 @@
# Feature owners
per-file app/*[email protected]
+per-file app-automotive/*[email protected]
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index a696657a..5a2452e 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -972,6 +972,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class Row implements androidx.car.app.model.Item {
+ method public java.util.List getActions();
method public androidx.car.app.model.CarIcon? getImage();
method public androidx.car.app.model.Metadata? getMetadata();
method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
@@ -990,6 +991,7 @@
public static final class Row.Builder {
ctor public Row.Builder();
+ method public androidx.car.app.model.Row.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.Row.Builder addText(CharSequence);
method public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
method public androidx.car.app.model.Row build();
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index d69fc33..f3ccb17 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -1165,6 +1165,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class Row implements androidx.car.app.model.Item {
+ method public java.util.List getActions();
method public androidx.car.app.model.CarIcon? getImage();
method public androidx.car.app.model.Metadata? getMetadata();
method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
@@ -1183,6 +1184,7 @@
public static final class Row.Builder {
ctor public Row.Builder();
+ method public androidx.car.app.model.Row.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.Row.Builder addText(CharSequence);
method public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
method public androidx.car.app.model.Row build();
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index a696657a..5a2452e 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -972,6 +972,7 @@
}
@androidx.car.app.annotations.CarProtocol public final class Row implements androidx.car.app.model.Item {
+ method public java.util.List getActions();
method public androidx.car.app.model.CarIcon? getImage();
method public androidx.car.app.model.Metadata? getMetadata();
method public androidx.car.app.model.OnClickDelegate? getOnClickDelegate();
@@ -990,6 +991,7 @@
public static final class Row.Builder {
ctor public Row.Builder();
+ method public androidx.car.app.model.Row.Builder addAction(androidx.car.app.model.Action);
method public androidx.car.app.model.Row.Builder addText(CharSequence);
method public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
method public androidx.car.app.model.Row build();
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Row.java b/car/app/app/src/main/java/androidx/car/app/model/Row.java
index 64c0391..8987cfa 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Row.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Row.java
@@ -31,6 +31,7 @@
import androidx.annotation.RestrictTo;
import androidx.car.app.annotations.CarProtocol;
import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.constraints.ActionsConstraints;
import androidx.car.app.model.constraints.CarIconConstraints;
import androidx.car.app.model.constraints.CarTextConstraints;
import androidx.car.app.utils.CollectionUtils;
@@ -103,6 +104,8 @@
@Nullable
private final CarIcon mImage;
@Keep
+ private final List mActions;
+ @Keep
@Nullable
private final Toggle mToggle;
@Keep
@@ -148,6 +151,16 @@
return mImage;
}
+ /**
+ * Returns the list of additional actions at the end of the row.
+ *
+ * @see Builder#addAction(Action)
+ */
+ @NonNull
+ public List getActions() {
+ return mActions;
+ }
+
/** Returns the type of the image in the row. */
@RowImageType
public int getRowImageType() {
@@ -278,6 +291,7 @@
mTitle = builder.mTitle;
mTexts = CollectionUtils.unmodifiableCopy(builder.mTexts);
mImage = builder.mImage;
+ mActions = CollectionUtils.unmodifiableCopy(builder.mActions);
mToggle = builder.mToggle;
mOnClickDelegate = builder.mOnClickDelegate;
mMetadata = builder.mMetadata;
@@ -291,6 +305,7 @@
mTitle = null;
mTexts = Collections.emptyList();
mImage = null;
+ mActions = Collections.emptyList();
mToggle = null;
mOnClickDelegate = null;
mMetadata = EMPTY_METADATA;
@@ -307,6 +322,7 @@
final List mTexts = new ArrayList<>();
@Nullable
CarIcon mImage;
+ final List mActions = new ArrayList<>();
@Nullable
Toggle mToggle;
@Nullable
@@ -489,6 +505,23 @@
}
/**
+ * Adds an additional action to the end of the row.
+ *
+ * @throws NullPointerException if {@code action} is {@code null}
+ * @throws IllegalArgumentException if {@code action} contains unsupported Action types,
+ * exceeds the maximum number of allowed actions or does
+ * not contain a valid {@link CarIcon}.
+ */
+ @NonNull
+ public Builder addAction(@NonNull Action action) {
+ List mActionsCopy = new ArrayList<>(mActions);
+ mActionsCopy.add(requireNonNull(action));
+ ActionsConstraints.ACTIONS_CONSTRAINTS_ROW.validateOrThrow(mActionsCopy);
+ mActions.add(action);
+ return this;
+ }
+
+ /**
* Sets a {@link Toggle} to show in the row.
*
* @throws NullPointerException if {@code toggle} is {@code null}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
index d5df89c..5d05848 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
@@ -120,6 +120,17 @@
.setMaxActions(4)
.build();
+ /**
+ * Constraints for additional row actions. Only allows custom and back actions.
+ */
+ @NonNull
+ public static final ActionsConstraints ACTIONS_CONSTRAINTS_ROW =
+ new ActionsConstraints.Builder()
+ .setMaxActions(2)
+ .addDisallowedActionType(Action.TYPE_APP_ICON)
+ .setRequireActionIcons(true)
+ .build();
+
private final int mMaxActions;
private final int mMaxPrimaryActions;
private final int mMaxCustomTitles;
diff --git a/car/app/app/src/test/java/androidx/car/app/model/RowTest.java b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
index 06aee1d..d64eac7 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
@@ -165,6 +165,49 @@
}
@Test
+ public void addAction() {
+ Row row = new Row.Builder()
+ .setTitle("Title")
+ .addAction(Action.PAN)
+ .addAction(Action.BACK)
+ .build();
+ assertThat(row.getActions()).containsExactly(Action.PAN, Action.BACK);
+ }
+
+ @Test
+ public void addAction_invalidActionType_throws() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new Row.Builder().setTitle("Title").addAction(Action.APP_ICON).build());
+ }
+
+ @Test
+ public void addAction_manyActions_throws() {
+ CarIcon carIcon = TestUtils.getTestCarIcon(ApplicationProvider.getApplicationContext(),
+ "ic_test_1");
+ Action customAction = TestUtils.createAction("Title", carIcon);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new Row.Builder().setTitle("Title")
+ .addAction(Action.BACK)
+ .addAction(Action.PAN)
+ .addAction(customAction)
+ .build());
+ }
+
+ @Test
+ public void addAction_invalidActionNullIcon_throws() {
+ Action customAction = TestUtils.createAction("Title", null);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new Row.Builder().setTitle("Title")
+ .addAction(customAction)
+ .build());
+ }
+
+ @Test
public void setMetadata() {
Metadata metadata =
new Metadata.Builder().setPlace(
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SnapshotStateObserverBenchmark.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SnapshotStateObserverBenchmark.kt
index 7b760be..4fe328d 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SnapshotStateObserverBenchmark.kt
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/SnapshotStateObserverBenchmark.kt
@@ -22,6 +22,7 @@
import androidx.benchmark.junit4.measureRepeated
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.SnapshotStateObserver
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,7 +47,7 @@
private val doNothing: (Any) -> Unit = { _ -> }
private lateinit var stateObserver: SnapshotStateObserver
- private val models: List> = List(StateCount) { mutableStateOf(0) }
+ private val models: List> = List(StateCount) { mutableStateOf(0) }
private val nodes: List = List(ScopeCount) { it }
private lateinit var random: Random
@@ -116,6 +117,40 @@
}
@Test
+ fun derivedStateObservation() {
+ runOnUiThread {
+ val node = Any()
+ val states = models.take(3)
+ val derivedState = derivedStateOf {
+ states[0].value + states[1].value + states[2].value
+ }
+
+ stateObserver.observeReads(node, doNothing) {
+ // read derived state a few times
+ repeat(10) {
+ derivedState.value
+ }
+ }
+
+ benchmarkRule.measureRepeated {
+ stateObserver.observeReads(node, doNothing) {
+ // read derived state a few times
+ repeat(10) {
+ derivedState.value
+ }
+ }
+
+ runWithTimingDisabled {
+ states.forEach {
+ it.value += 1
+ }
+ Snapshot.sendApplyNotifications()
+ }
+ }
+ }
+ }
+
+ @Test
fun deeplyNestedModelObservations() {
assumeTrue(Build.VERSION.SDK_INT != 29)
runOnUiThread {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 1b652e8..65a96b1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -710,7 +710,9 @@
// Record derived state dependency mapping
if (value is DerivedState<*>) {
derivedStates.removeScope(value)
- value.dependencies.forEach { dependency ->
+ for (dependency in value.dependencies) {
+ // skip over empty objects from dependency array
+ if (dependency == null) break
derivedStates.add(dependency, value)
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
index ee172c5..76d772b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
@@ -18,13 +18,13 @@
@file:JvmMultifileClass
package androidx.compose.runtime
-import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
-import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
+import androidx.compose.runtime.collection.IdentityArrayMap
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.runtime.collection.mutableVectorOf
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.StateObject
import androidx.compose.runtime.snapshots.StateRecord
import androidx.compose.runtime.snapshots.current
-import androidx.compose.runtime.snapshots.fastForEach
import androidx.compose.runtime.snapshots.newWritableRecord
import androidx.compose.runtime.snapshots.sync
import androidx.compose.runtime.snapshots.withCurrent
@@ -50,7 +50,7 @@
* The [dependencies] list can be used to determine when a [StateObject] appears in the apply
* observer set, if the state could affect value of this derived state.
*/
- val dependencies: Set
+ val dependencies: Array
/**
* Mutation policy that controls how changes are handled after state dependencies update.
@@ -60,10 +60,6 @@
val policy: SnapshotMutationPolicy?
}
-private typealias DerivedStateObservers = Pair<(DerivedState<*>) -> Unit, (DerivedState<*>) -> Unit>
-
-private val derivedStateObservers = SnapshotThreadLocal>()
-
private val calculationBlockNestedLevel = SnapshotThreadLocal()
private class DerivedSnapshotState(
@@ -77,7 +73,7 @@
val Unset = Any()
}
- var dependencies: HashMap? = null
+ var dependencies: IdentityArrayMap? = null
var result: Any? = Unset
var resultHash: Int = 0
@@ -99,9 +95,9 @@
val dependencies = sync { dependencies }
if (dependencies != null) {
notifyObservers(derivedState) {
- for ((stateObject, readLevel) in dependencies.entries) {
+ dependencies.forEach { stateObject, readLevel ->
if (readLevel != 1) {
- continue
+ return@forEach
}
if (stateObject is DerivedSnapshotState<*>) {
@@ -140,9 +136,9 @@
// for correct invalidation later
if (forceDependencyReads) {
notifyObservers(this) {
- val dependencies = readable.dependencies ?: emptyMap()
+ val dependencies = readable.dependencies
val invalidationNestedLevel = calculationBlockNestedLevel.get() ?: 0
- for ((dependency, nestedLevel) in dependencies) {
+ dependencies?.forEach { dependency, nestedLevel ->
calculationBlockNestedLevel.set(nestedLevel + invalidationNestedLevel)
snapshot.readObserver?.invoke(dependency)
}
@@ -153,7 +149,7 @@
}
val nestedCalculationLevel = calculationBlockNestedLevel.get() ?: 0
- val newDependencies = HashMap()
+ val newDependencies = IdentityArrayMap()
val result = notifyObservers(this) {
calculationBlockNestedLevel.set(nestedCalculationLevel + 1)
@@ -218,18 +214,23 @@
// value is used instead which doesn't notify. This allow the read observer to read the
// value and only update the cache once.
Snapshot.current.readObserver?.invoke(this)
- return currentValue
+ return first.withCurrent {
+ @Suppress("UNCHECKED_CAST")
+ currentRecord(it, Snapshot.current, true, calculation).result as T
+ }
}
override val currentValue: T
get() = first.withCurrent {
@Suppress("UNCHECKED_CAST")
- currentRecord(it, Snapshot.current, true, calculation).result as T
+ currentRecord(it, Snapshot.current, false, calculation).result as T
}
- override val dependencies: Set
+ override val dependencies: Array
get() = first.withCurrent {
- currentRecord(it, Snapshot.current, false, calculation).dependencies?.keys ?: emptySet()
+ val record = currentRecord(it, Snapshot.current, false, calculation)
+ @Suppress("UNCHECKED_CAST")
+ record.dependencies?.keys ?: emptyArray()
}
override fun toString(): String = first.withCurrent {
@@ -260,16 +261,6 @@
}
}
-private inline fun notifyObservers(derivedState: DerivedState<*>, block: () -> R): R {
- val observers = derivedStateObservers.get() ?: persistentListOf()
- observers.fastForEach { (start, _) -> start(derivedState) }
- return try {
- block()
- } finally {
- observers.fastForEach { (_, done) -> done(derivedState) }
- }
-}
-
/**
* Creates a [State] object whose [State.value] is the result of [calculation]. The result of
* calculation will be cached in such a way that calling [State.value] repeatedly will not cause
@@ -307,6 +298,20 @@
calculation: () -> T,
): State = DerivedSnapshotState(calculation, policy)
+private typealias DerivedStateObservers = Pair<(DerivedState<*>) -> Unit, (DerivedState<*>) -> Unit>
+
+private val derivedStateObservers = SnapshotThreadLocal>()
+
+private inline fun notifyObservers(derivedState: DerivedState<*>, block: () -> R): R {
+ val observers = derivedStateObservers.get() ?: MutableVector(0)
+ observers.forEach { (start, _) -> start(derivedState) }
+ return try {
+ block()
+ } finally {
+ observers.forEach { (_, done) -> done(derivedState) }
+ }
+}
+
/**
* Observe the recalculations performed by any derived state that is recalculated during the
* execution of [block]. [start] is called before a calculation starts and [done] is called
@@ -321,15 +326,15 @@
done: (derivedState: State<*>) -> Unit,
block: () -> R
) {
- val previous = derivedStateObservers.get()
+ val observers = derivedStateObservers.get() ?: mutableVectorOf().also {
+ derivedStateObservers.set(it)
+ }
+
+ val observer = start to done
try {
- derivedStateObservers.set(
- (derivedStateObservers.get() ?: persistentListOf()).add(
- start to done
- )
- )
+ observers.add(observer)
block()
} finally {
- derivedStateObservers.set(previous)
+ observers.removeAt(observers.lastIndex)
}
}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index 55240ef..86dfb6b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -17,6 +17,7 @@
package androidx.compose.runtime.snapshots
import androidx.compose.runtime.DerivedState
+import androidx.compose.runtime.State
import androidx.compose.runtime.TestOnly
import androidx.compose.runtime.collection.IdentityArrayMap
import androidx.compose.runtime.collection.IdentityArraySet
@@ -125,8 +126,8 @@
scopeMap.currentScope = scope
observeDerivedStateRecalculations(
- start = { scopeMap.deriveStateScopeCount++ },
- done = { scopeMap.deriveStateScopeCount-- },
+ start = scopeMap.derivedStateEnterObserver,
+ done = scopeMap.derivedStateExitObserver
) {
Snapshot.observe(readObserver, null, block)
}
@@ -237,7 +238,22 @@
*/
var currentScope: Any? = null
- var deriveStateScopeCount = 0
+ /**
+ * Start observer for derived state recalculation
+ */
+ val derivedStateEnterObserver: (State<*>) -> Unit = { deriveStateScopeCount++ }
+
+ /**
+ * Exit observer for derived state recalculation
+ */
+ val derivedStateExitObserver: (State<*>) -> Unit = { deriveStateScopeCount-- }
+
+ /**
+ * Counter for skipping reads inside derived states. If count is > 0, read happens inside
+ * a derived state.
+ * Reads for derived states are captured separately through [DerivedState.dependencies].
+ */
+ private var deriveStateScopeCount = 0
/**
* Values that have been read during the scope's [SnapshotStateObserver.observeReads].
@@ -255,9 +271,15 @@
*/
private val invalidated = hashSetOf()
+ /**
+ * Invalidation index from state objects to derived states reading them.
+ */
private val dependencyToDerivedStates = IdentityScopeMap>()
- private val derivedStateToValue = HashMap, Any?>()
+ /**
+ * Last derived state value recorded during read.
+ */
+ private val recordedDerivedStateValues = HashMap, Any?>()
/**
* Record that [value] was read in [currentScope].
@@ -276,12 +298,13 @@
recordedValues.add(value)
if (value is DerivedState<*>) {
- dependencyToDerivedStates.removeScope(value)
val dependencies = value.dependencies
for (dependency in dependencies) {
+ // skip over dependency array
+ if (dependency == null) break
dependencyToDerivedStates.add(dependency, value)
}
- derivedStateToValue[value] = value.currentValue
+ recordedDerivedStateValues[value] = value.currentValue
}
}
@@ -293,7 +316,9 @@
recordedValues.fastForEach {
removeObservation(scope, it)
}
- scopeToValues.remove(scope)
+ // clearing the scope usually means that we are about to start observation again
+ // so it doesn't make sense to reallocate the set
+ recordedValues.clear()
}
/**
@@ -313,8 +338,9 @@
private fun removeObservation(scope: Any, value: Any) {
valueToScopes.remove(value, scope)
- if (value is DerivedState<*>) {
+ if (value is DerivedState<*> && value !in valueToScopes) {
dependencyToDerivedStates.removeScope(value)
+ recordedDerivedStateValues.remove(value)
}
}
@@ -325,6 +351,7 @@
valueToScopes.clear()
scopeToValues.clear()
dependencyToDerivedStates.clear()
+ recordedDerivedStateValues.clear()
}
/**
@@ -338,7 +365,7 @@
// Find derived state that is invalidated by this change
dependencyToDerivedStates.forEachScopeOf(value) { derivedState ->
derivedState as DerivedState
- val previousValue = derivedStateToValue[derivedState]
+ val previousValue = recordedDerivedStateValues[derivedState]
val policy = derivedState.policy ?: structuralEqualityPolicy()
// Invalidate only if currentValue is different than observed on read
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
index 6b9bd48..efc63b90 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTestsCommon.kt
@@ -542,6 +542,32 @@
assertEquals(1, changes)
}
+ @Test
+ fun readingDerivedStateConditionallyInvalidatesBothScopes() {
+ var changes = 0
+
+ runSimpleTest { stateObserver, state ->
+ val derivedState = derivedStateOf { state.value }
+
+ val onChange: (String) -> Unit = { changes++ }
+ stateObserver.observeReads("scope", onChange) {
+ // read derived state
+ derivedState.value
+ }
+
+ // read the same state in other scope
+ stateObserver.observeReads("other scope", onChange) {
+ derivedState.value
+ }
+
+ // stop observing state in other scope
+ stateObserver.observeReads("other scope", onChange) {
+ /* no-op */
+ }
+ }
+ assertEquals(1, changes)
+ }
+
private fun runSimpleTest(
block: (modelObserver: SnapshotStateObserver, data: MutableState) -> Unit
) {
diff --git a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatTest.java
index dd22f6e..0d145e0 100644
--- a/core/core/src/androidTest/java/androidx/core/app/ActivityCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/ActivityCompatTest.java
@@ -38,7 +38,9 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SdkSuppress;
+import androidx.test.rule.GrantPermissionRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -48,6 +50,13 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ActivityCompatTest extends BaseInstrumentationTestCase {
+ @Rule
+ public GrantPermissionRule mRuntimePermissionRule =
+ GrantPermissionRule.grant(Manifest.permission.ACCESS_FINE_LOCATION);
+
+ public static final String[] LOCATION_PERMISSIONS = {
+ Manifest.permission.ACCESS_FINE_LOCATION
+ };
public ActivityCompatTest() {
super(TestActivity.class);
@@ -61,27 +70,23 @@
public void testPermissionDelegate() {
try (ActivityScenario scenario =
ActivityScenario.launch(TestActivity.class)) {
- scenario.onActivity(new ActivityScenario.ActivityAction() {
- @Override
- public void perform(TestActivity activity) {
- ActivityCompat.PermissionCompatDelegate delegate =
- mock(PermissionCompatDelegate.class);
+ scenario.onActivity(activity -> {
+ PermissionCompatDelegate delegate =
+ mock(PermissionCompatDelegate.class);
- // First test setting the delegate
- ActivityCompat.setPermissionCompatDelegate(delegate);
+ // First test setting the delegate
+ ActivityCompat.setPermissionCompatDelegate(delegate);
- ActivityCompat.requestPermissions(activity, new String[]{
- Manifest.permission.ACCESS_FINE_LOCATION}, 42);
- verify(delegate).requestPermissions(same(activity), aryEq(
- new String[]{Manifest.permission.ACCESS_FINE_LOCATION}), eq(42));
+ ActivityCompat.requestPermissions(activity, LOCATION_PERMISSIONS, 42);
+ //noinspection ConstantConditions
+ verify(delegate).requestPermissions(
+ same(activity), aryEq(LOCATION_PERMISSIONS), eq(42));
- // Test clearing the delegate
- ActivityCompat.setPermissionCompatDelegate(null);
+ // Test clearing the delegate
+ ActivityCompat.setPermissionCompatDelegate(null);
- ActivityCompat.requestPermissions(activity, new String[]{
- Manifest.permission.ACCESS_FINE_LOCATION}, 42);
- verifyNoMoreInteractions(delegate);
- }
+ ActivityCompat.requestPermissions(activity, LOCATION_PERMISSIONS, 42);
+ verifyNoMoreInteractions(delegate);
});
}
}
@@ -90,18 +95,15 @@
public void testPermissionNull() {
try (ActivityScenario scenario =
ActivityScenario.launch(TestActivity.class)) {
- scenario.onActivity(new ActivityScenario.ActivityAction() {
- @Override
- public void perform(TestActivity activity) {
- String[] permissions = new String[]{null};
+ scenario.onActivity(activity -> {
+ String[] permissions = new String[]{null};
- try {
- ActivityCompat.requestPermissions(activity, permissions, 42);
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("Permission request for "
- + "permissions " + Arrays.toString(permissions) + " must not "
- + "contain null or empty values");
- }
+ try {
+ ActivityCompat.requestPermissions(activity, permissions, 42);
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageThat().contains("Permission request for "
+ + "permissions " + Arrays.toString(permissions) + " must not "
+ + "contain null or empty values");
}
});
}
@@ -111,20 +113,17 @@
public void testPermissionEmpty() {
try (ActivityScenario scenario =
ActivityScenario.launch(TestActivity.class)) {
- scenario.onActivity(new ActivityScenario.ActivityAction() {
- @Override
- public void perform(TestActivity activity) {
- String[] permissions = new String[]{
- Manifest.permission.ACCESS_FINE_LOCATION, ""
- };
+ scenario.onActivity(activity -> {
+ String[] permissions = new String[]{
+ Manifest.permission.ACCESS_FINE_LOCATION, ""
+ };
- try {
- ActivityCompat.requestPermissions(activity, permissions, 42);
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessageThat().contains("Permission request for "
- + "permissions " + Arrays.toString(permissions) + " must not "
- + "contain null or empty values");
- }
+ try {
+ ActivityCompat.requestPermissions(activity, permissions, 42);
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessageThat().contains("Permission request for "
+ + "permissions " + Arrays.toString(permissions) + " must not "
+ + "contain null or empty values");
}
});
}
@@ -152,7 +151,8 @@
@Test
public void testOnSharedElementsReady() {
AtomicInteger counter = new AtomicInteger();
- SharedElementCallback callback = new SharedElementCallback() {};
+ SharedElementCallback callback = new SharedElementCallback() {
+ };
android.app.SharedElementCallback.OnSharedElementsReadyListener listener =
counter::incrementAndGet;