Merge "Fix permissions and targetSdkVersion for sample apps" into androidx-main
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
index 692175f..5a08436 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
@@ -22,7 +22,7 @@
internal object AudioUnderrunQuery {
@Language("sql")
private fun getFullQuery() = """
- SELECT track.name, counter.value, counter.ts
+ SELECT counter.value, counter.ts
FROM track
JOIN counter ON track.id = counter.track_id
WHERE track.type = 'process_counter_track' AND track.name LIKE 'nRdy%'
@@ -38,7 +38,6 @@
): SubMetrics {
val queryResult = session.query(getFullQuery())
- var trackName: String? = null
var lastTs: Long? = null
var totalNs: Long = 0
var zeroNs: Long = 0
@@ -46,16 +45,9 @@
queryResult
.asSequence()
.forEach { lineVals ->
-
if (lineVals.size != EXPECTED_COLUMN_COUNT)
throw IllegalStateException("query failed")
- if (trackName == null) {
- trackName = lineVals[VAL_NAME] as String?
- } else if (trackName!! != lineVals[VAL_NAME]) {
- throw RuntimeException("There could be only one AudioTrack per measure")
- }
-
if (lastTs == null) {
lastTs = lineVals[VAL_TS] as Long
} else {
@@ -74,8 +66,7 @@
return SubMetrics((totalNs / 1_000_000).toInt(), (zeroNs / 1_000_000).toInt())
}
- private const val VAL_NAME = "name"
private const val VAL_VALUE = "value"
private const val VAL_TS = "ts"
- private const val EXPECTED_COLUMN_COUNT = 3
+ private const val EXPECTED_COLUMN_COUNT = 2
}
diff --git a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
index 8229714..9a9791e 100644
--- a/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/main/java/androidx/benchmark/integration/macrobenchmark/AudioUnderrunBenchmark.kt
@@ -27,7 +27,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,7 +34,7 @@
@LargeTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalMetricApi::class)
-class AudioUnderrunBenchmark() {
+class AudioUnderrunBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@@ -48,7 +47,6 @@
}
@Test
- @Ignore("b/297916125")
fun start() {
benchmarkRule.measureRepeated(
packageName = PACKAGE_NAME,
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
index f27a1e5..8c6a2e1 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricAdvertiseTest.kt
@@ -34,7 +34,7 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class RobolectricAdvertiseTest {
private val context: Context = RuntimeEnvironment.getApplication()
- private var bluetoothLe = BluetoothLe.getInstance(context)
+ private var bluetoothLe = BluetoothLe(context)
@Test
fun advertiseSuccess() = runTest {
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
index ab35eda..32729d4 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattClientTest.kt
@@ -28,6 +28,7 @@
import android.bluetooth.BluetoothGattService as FwkService
import android.bluetooth.BluetoothManager
import android.content.Context
+import android.os.Build
import androidx.bluetooth.BluetoothDevice
import androidx.bluetooth.BluetoothLe
import androidx.bluetooth.GattClient
@@ -37,9 +38,10 @@
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -99,16 +101,11 @@
@Before
fun setUp() {
- bluetoothLe = BluetoothLe.getInstance(context)
+ bluetoothLe = BluetoothLe(context)
clientAdapter = StubClientFrameworkAdapter(bluetoothLe.client.fwkAdapter)
bluetoothLe.client.fwkAdapter = clientAdapter
}
- @After
- fun tearDown() {
- bluetoothLe.client.fwkAdapter = clientAdapter.baseAdapter
- }
-
@Test
fun connectGatt() = runTest {
val device = createDevice("00:11:22:33:44:55")
@@ -117,9 +114,9 @@
acceptConnect()
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
+ Assert.assertEquals(sampleServices.size, services.size)
sampleServices.forEachIndexed { index, service ->
- Assert.assertEquals(service.uuid, getServices()[index].uuid)
+ Assert.assertEquals(service.uuid, services[index].uuid)
}
closed.complete(Unit)
}
@@ -156,10 +153,10 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
+ Assert.assertEquals(sampleServices.size, services.size)
Assert.assertEquals(testValue,
readCharacteristic(
- getServices()[0].getCharacteristic(readCharUuid)!!
+ services[0].getCharacteristic(readCharUuid)!!
).getOrNull()?.toInt())
closed.complete(Unit)
}
@@ -178,10 +175,10 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
+ Assert.assertEquals(sampleServices.size, services.size)
assertTrue(
readCharacteristic(
- getServices()[0].getCharacteristic(noPropertyCharUuid)!!
+ services[0].getCharacteristic(noPropertyCharUuid)!!
).exceptionOrNull()
is IllegalArgumentException)
}
@@ -223,8 +220,8 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
- val characteristic = getServices()[0].getCharacteristic(writeCharUuid)!!
+ Assert.assertEquals(sampleServices.size, services.size)
+ val characteristic = services[0].getCharacteristic(writeCharUuid)!!
Assert.assertEquals(initialValue,
readCharacteristic(characteristic).getOrNull()?.toInt())
@@ -249,10 +246,10 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
+ Assert.assertEquals(sampleServices.size, services.size)
assertTrue(
writeCharacteristic(
- getServices()[0].getCharacteristic(readCharUuid)!!,
+ services[0].getCharacteristic(readCharUuid)!!,
48.toByteArray()
).exceptionOrNull()
is IllegalArgumentException)
@@ -297,8 +294,8 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
- val characteristic = getServices()[0].getCharacteristic(notifyCharUuid)!!
+ Assert.assertEquals(sampleServices.size, services.size)
+ val characteristic = services[0].getCharacteristic(notifyCharUuid)!!
Assert.assertEquals(initialValue,
readCharacteristic(characteristic).getOrNull()?.toInt())
@@ -324,9 +321,9 @@
}
bluetoothLe.connectGatt(device) {
- Assert.assertEquals(sampleServices.size, getServices().size)
+ Assert.assertEquals(sampleServices.size, services.size)
subscribeToCharacteristic(
- getServices()[0].getCharacteristic(readCharUuid)!!,
+ services[0].getCharacteristic(readCharUuid)!!,
).collect {
// Should not be notified
fail()
@@ -334,6 +331,41 @@
}
}
+ @Test
+ fun servicesFlow_emittedWhenServicesChange() = runTest {
+ val device = createDevice("00:11:22:33:44:55")
+
+ val newServiceUuid = UUID.randomUUID()
+ val newService = FwkService(newServiceUuid, FwkService.SERVICE_TYPE_PRIMARY)
+ val newServices = sampleServices + newService
+
+ acceptConnect()
+
+ clientAdapter.onDiscoverServicesListener =
+ StubClientFrameworkAdapter.OnDiscoverServicesListener {
+ if (clientAdapter.gattServices.isEmpty()) {
+ clientAdapter.gattServices = sampleServices
+ }
+ clientAdapter.callback?.onServicesDiscovered(
+ clientAdapter.bluetoothGatt,
+ BluetoothGatt.GATT_SUCCESS
+ )
+ }
+
+ bluetoothLe.connectGatt(device) {
+ launch {
+ clientAdapter.gattServices = newServices
+ if (Build.VERSION.SDK_INT >= 31) {
+ clientAdapter.callback?.onServiceChanged(clientAdapter.bluetoothGatt!!)
+ }
+ }
+ val servicesEmitted = servicesFlow.take(2).toList()
+ Assert.assertEquals(sampleServices.size, servicesEmitted[0].size)
+ Assert.assertEquals(sampleServices.size + 1, servicesEmitted[1].size)
+ Assert.assertEquals(newServiceUuid, servicesEmitted[1][sampleServices.size].uuid)
+ }
+ }
+
private fun acceptConnect() {
clientAdapter.onConnectListener =
StubClientFrameworkAdapter.OnConnectListener { device, _ ->
@@ -372,7 +404,7 @@
}
class StubClientFrameworkAdapter(
- internal val baseAdapter: GattClient.FrameworkAdapter
+ private val baseAdapter: GattClient.FrameworkAdapter
) : GattClient.FrameworkAdapter {
var gattServices: List<FwkService> = listOf()
var callback: BluetoothGattCallback? = null
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
index 07e8583..741377c 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricGattServerTest.kt
@@ -41,7 +41,6 @@
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@@ -91,16 +90,11 @@
@Before
fun setUp() {
- bluetoothLe = BluetoothLe.getInstance(context)
+ bluetoothLe = BluetoothLe(context)
serverAdapter = StubServerFrameworkAdapter(bluetoothLe.server.fwkAdapter)
bluetoothLe.server.fwkAdapter = serverAdapter
}
- @After
- fun tearDown() {
- bluetoothLe.server.fwkAdapter = serverAdapter.baseAdapter
- }
-
@Test
fun openGattServer() = runTest {
val device = createDevice("00:11:22:33:44:55")
@@ -504,7 +498,7 @@
}
class StubServerFrameworkAdapter(
- val baseAdapter: GattServer.FrameworkAdapter
+ private val baseAdapter: GattServer.FrameworkAdapter
) : GattServer.FrameworkAdapter {
val shadowGattServer: ShadowBluetoothGattServer
get() = shadowOf(gattServer)
diff --git a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
index a983408..189873a 100644
--- a/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
+++ b/bluetooth/bluetooth-testing/src/test/kotlin/androidx/bluetooth/testing/RobolectricScanTest.kt
@@ -39,7 +39,7 @@
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class RobolectricScanTest {
private val context: Context = RuntimeEnvironment.getApplication()
- private var bluetoothLe = BluetoothLe.getInstance(context)
+ private var bluetoothLe = BluetoothLe(context)
private companion object {
private const val TIMEOUT_MS: Long = 2_000
}
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index 2618c72..65b4cb4 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -22,16 +22,12 @@
}
public final class AdvertiseResult {
- ctor public AdvertiseResult();
field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 104; // 0x68
field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 105; // 0x69
field public static final int ADVERTISE_STARTED = 101; // 0x65
- field public static final androidx.bluetooth.AdvertiseResult.Companion Companion;
- }
-
- public static final class AdvertiseResult.Companion {
+ field public static final androidx.bluetooth.AdvertiseResult INSTANCE;
}
public final class BluetoothAddress {
@@ -61,24 +57,22 @@
}
public final class BluetoothLe {
+ ctor public BluetoothLe(android.content.Context context);
method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
- method public static androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
- field public static final androidx.bluetooth.BluetoothLe.Companion Companion;
- }
-
- public static final class BluetoothLe.Companion {
- method public androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
}
public static interface BluetoothLe.GattClientScope {
method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
- method public java.util.List<androidx.bluetooth.GattService> getServices();
+ method public default java.util.List<androidx.bluetooth.GattService> getServices();
+ method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+ property public default java.util.List<androidx.bluetooth.GattService> services;
+ property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
}
public static final class BluetoothLe.GattServerConnectRequest {
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index 2618c72..65b4cb4 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -22,16 +22,12 @@
}
public final class AdvertiseResult {
- ctor public AdvertiseResult();
field public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 102; // 0x66
field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 103; // 0x67
field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 104; // 0x68
field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 105; // 0x69
field public static final int ADVERTISE_STARTED = 101; // 0x65
- field public static final androidx.bluetooth.AdvertiseResult.Companion Companion;
- }
-
- public static final class AdvertiseResult.Companion {
+ field public static final androidx.bluetooth.AdvertiseResult INSTANCE;
}
public final class BluetoothAddress {
@@ -61,24 +57,22 @@
}
public final class BluetoothLe {
+ ctor public BluetoothLe(android.content.Context context);
method @RequiresPermission("android.permission.BLUETOOTH_ADVERTISE") public suspend Object? advertise(androidx.bluetooth.AdvertiseParams advertiseParams, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>? block, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public suspend <R> Object? connectGatt(androidx.bluetooth.BluetoothDevice device, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattClientScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
- method public static androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
method public suspend <R> Object? openGattServer(java.util.List<androidx.bluetooth.GattService> services, kotlin.jvm.functions.Function2<? super androidx.bluetooth.BluetoothLe.GattServerConnectScope,? super kotlin.coroutines.Continuation<? super R>,?> block, kotlin.coroutines.Continuation<? super R>);
method @RequiresPermission("android.permission.BLUETOOTH_SCAN") public kotlinx.coroutines.flow.Flow<androidx.bluetooth.ScanResult> scan(optional java.util.List<androidx.bluetooth.ScanFilter> filters);
- field public static final androidx.bluetooth.BluetoothLe.Companion Companion;
- }
-
- public static final class BluetoothLe.Companion {
- method public androidx.bluetooth.BluetoothLe getInstance(android.content.Context context);
}
public static interface BluetoothLe.GattClientScope {
method public androidx.bluetooth.GattService? getService(java.util.UUID uuid);
- method public java.util.List<androidx.bluetooth.GattService> getServices();
+ method public default java.util.List<androidx.bluetooth.GattService> getServices();
+ method public kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> getServicesFlow();
method public suspend Object? readCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, kotlin.coroutines.Continuation<? super kotlin.Result<? extends byte[]>>);
method public kotlinx.coroutines.flow.Flow<byte[]> subscribeToCharacteristic(androidx.bluetooth.GattCharacteristic characteristic);
method public suspend Object? writeCharacteristic(androidx.bluetooth.GattCharacteristic characteristic, byte[] value, kotlin.coroutines.Continuation<? super kotlin.Result<? extends kotlin.Unit>>);
+ property public default java.util.List<androidx.bluetooth.GattService> services;
+ property public abstract kotlinx.coroutines.flow.StateFlow<java.util.List<androidx.bluetooth.GattService>> servicesFlow;
}
public static final class BluetoothLe.GattServerConnectRequest {
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
index beaa6f1..3c0e6e4 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothAddressTest.kt
@@ -18,6 +18,7 @@
import junit.framework.TestCase.assertEquals
import kotlin.test.assertFailsWith
+import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -66,10 +67,9 @@
fun constructorWithInvalidAddressType() {
val invalidAddressType = -1
- val bluetoothAddress = BluetoothAddress(TEST_ADDRESS_UNKNOWN, invalidAddressType)
+ val result = runCatching { BluetoothAddress(TEST_ADDRESS_UNKNOWN, invalidAddressType) }
- assertEquals(TEST_ADDRESS_UNKNOWN, bluetoothAddress.address)
- assertEquals(BluetoothAddress.ADDRESS_TYPE_UNKNOWN, bluetoothAddress.addressType)
+ assertTrue(result.exceptionOrNull() is IllegalArgumentException)
}
@Test
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
index 8e1f074..822fd61 100644
--- a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/BluetoothLeTest.kt
@@ -59,7 +59,7 @@
context = ApplicationProvider.getApplicationContext()
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
- bluetoothLe = BluetoothLe.getInstance(context)
+ bluetoothLe = BluetoothLe(context)
Assume.assumeNotNull(bluetoothAdapter)
Assume.assumeTrue(bluetoothAdapter.isEnabled)
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
index 69f8687..304bdd7 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/AdvertiseResult.kt
@@ -24,7 +24,7 @@
* An advertise result indicates the result of a request to start advertising, whether success
* or failure.
*/
-class AdvertiseResult {
+object AdvertiseResult {
@Target(AnnotationTarget.TYPE)
@RestrictTo(RestrictTo.Scope.LIBRARY)
@Retention(AnnotationRetention.SOURCE)
@@ -37,20 +37,18 @@
)
annotation class ResultType
- companion object {
- /** Advertise started successfully. */
- const val ADVERTISE_STARTED: Int = 101
+ /** Advertise started successfully. */
+ const val ADVERTISE_STARTED: Int = 101
- /** Advertise failed to start because the data is too large. */
- const val ADVERTISE_FAILED_DATA_TOO_LARGE: Int = 102
+ /** Advertise failed to start because the data is too large. */
+ const val ADVERTISE_FAILED_DATA_TOO_LARGE: Int = 102
- /** Advertise failed to start because the advertise feature is not supported. */
- const val ADVERTISE_FAILED_FEATURE_UNSUPPORTED: Int = 103
+ /** Advertise failed to start because the advertise feature is not supported. */
+ const val ADVERTISE_FAILED_FEATURE_UNSUPPORTED: Int = 103
- /** Advertise failed to start because of an internal error. */
- const val ADVERTISE_FAILED_INTERNAL_ERROR: Int = 104
+ /** Advertise failed to start because of an internal error. */
+ const val ADVERTISE_FAILED_INTERNAL_ERROR: Int = 104
- /** Advertise failed to start because of too many advertisers. */
- const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
- }
+ /** Advertise failed to start because of too many advertisers. */
+ const val ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: Int = 105
}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
index b3c7a70..ecbca45 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothAddress.kt
@@ -24,12 +24,10 @@
/**
* Represents a Bluetooth address for a remote device.
*
- * @property address valid Bluetooth MAC address
- * @property addressType valid address type
- *
+ * @property address a valid Bluetooth MAC address
+ * @property addressType a valid address type
*/
-class BluetoothAddress(val address: String, @AddressType addressType: Int) {
- @AddressType val addressType: Int
+class BluetoothAddress(val address: String, @AddressType val addressType: Int) {
companion object {
/** Address type is public and registered with the IEEE. */
const val ADDRESS_TYPE_PUBLIC: Int = 0
@@ -62,12 +60,13 @@
throw IllegalArgumentException("$address is not a valid Bluetooth address")
}
- this.addressType = when (addressType) {
+ when (addressType) {
ADDRESS_TYPE_PUBLIC,
ADDRESS_TYPE_RANDOM_STATIC,
ADDRESS_TYPE_RANDOM_RESOLVABLE,
- ADDRESS_TYPE_RANDOM_NON_RESOLVABLE -> addressType
- else -> ADDRESS_TYPE_UNKNOWN
+ ADDRESS_TYPE_RANDOM_NON_RESOLVABLE,
+ ADDRESS_TYPE_UNKNOWN -> Unit
+ else -> throw IllegalArgumentException("$addressType is not a valid address type")
}
}
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
index 5b34edc..c4dcf2c 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothLe.kt
@@ -42,6 +42,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.job
@@ -49,20 +50,10 @@
* Entry point for BLE related operations. This class provides a way to perform Bluetooth LE
* operations such as scanning, advertising, and connection with a respective [BluetoothDevice].
*/
-class BluetoothLe private constructor(private val context: Context) {
+class BluetoothLe constructor(private val context: Context) {
- companion object {
+ private companion object {
private const val TAG = "BluetoothLe"
- @Volatile
- @JvmStatic
- private var instance: BluetoothLe? = null
-
- @Suppress("VisiblySynchronized")
- @JvmStatic
- fun getInstance(context: Context) =
- instance ?: synchronized(this) {
- instance ?: BluetoothLe(context.applicationContext).also { instance = it }
- }
}
@RequiresApi(34)
@@ -226,9 +217,20 @@
interface GattClientScope {
/**
- * Gets the services discovered from the remote device.
+ * A flow of GATT services discovered from the remote device.
+ *
+ * If the services of the remote device has changed, the new services will be
+ * discovered and emitted automatically.
*/
- fun getServices(): List<GattService>
+ val servicesFlow: StateFlow<List<GattService>>
+
+ /**
+ * GATT services recently discovered from the remote device.
+ *
+ * Note that this can be changed, subscribe to [servicesFlow] to get notified
+ * of services changes.
+ */
+ val services: List<GattService> get() = servicesFlow.value
/**
* Gets the service of the remote device by UUID.
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
index d2a173b..01456ac 100644
--- a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/GattClient.kt
@@ -39,7 +39,10 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
@@ -141,6 +144,7 @@
val subscribeMap: MutableMap<FwkCharacteristic, SubscribeListener> = mutableMapOf()
val subscribeMutex = Mutex()
val attributeMap = AttributeMap()
+ val servicesFlow = MutableStateFlow<List<GattService>>(listOf())
val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
@@ -163,6 +167,16 @@
attributeMap.updateWithFrameworkServices(fwkAdapter.getServices())
if (status == BluetoothGatt.GATT_SUCCESS) connectResult.complete(Unit)
else cancel("service discover failed")
+ servicesFlow.tryEmit(attributeMap.getServices())
+ if (connectResult.isActive) {
+ if (status == BluetoothGatt.GATT_SUCCESS) connectResult.complete(Unit)
+ else connectResult.cancel("service discover failed")
+ }
+ }
+
+ override fun onServiceChanged(gatt: BluetoothGatt) {
+ // TODO: under API 31, we have to subscribe to the service changed characteristic.
+ fwkAdapter.discoverServices()
}
override fun onCharacteristicRead(
@@ -236,9 +250,7 @@
}
}
- override fun getServices(): List<GattService> {
- return attributeMap.getServices()
- }
+ override val servicesFlow: StateFlow<List<GattService>> = servicesFlow.asStateFlow()
override fun getService(uuid: UUID): GattService? {
return fwkAdapter.getService(uuid)?.let { attributeMap.fromFwkService(it) }
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
index a8b1eb1..3e1623b 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/advertiser/AdvertiserFragment.kt
@@ -136,7 +136,7 @@
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
- bluetoothLe = BluetoothLe.getInstance(requireContext())
+ bluetoothLe = BluetoothLe(requireContext())
_binding = FragmentAdvertiserBinding.inflate(inflater, container, false)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
index b65bbde..186431f 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/scanner/ScannerFragment.kt
@@ -140,7 +140,7 @@
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- bluetoothLe = BluetoothLe.getInstance(requireContext())
+ bluetoothLe = BluetoothLe(requireContext())
binding.tabLayout.addOnTabSelectedListener(onTabSelectedListener)
@@ -258,10 +258,10 @@
try {
bluetoothLe.connectGatt(deviceConnection.bluetoothDevice) {
- Log.d(TAG, "connectGatt result: getServices() = ${getServices()}")
+ Log.d(TAG, "connectGatt result: services() = $services")
deviceConnection.status = Status.CONNECTED
- deviceConnection.services = getServices()
+ deviceConnection.services = services
launch(Dispatchers.Main) {
updateDeviceUI(deviceConnection)
}
diff --git a/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt b/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt
new file mode 100644
index 0000000..3e82e00
--- /dev/null
+++ b/buildSrc-tests/src/test/java/androidx/build/SourceJarTaskHelperTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 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 com.google.common.truth.Truth.assertThat
+import org.gradle.testfixtures.ProjectBuilder
+import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
+import org.junit.Test
+
+class SourceJarTaskHelperTest {
+ @Test
+ fun generateMetadata() {
+ val project = ProjectBuilder.builder().build()
+ project.plugins.apply(KotlinMultiplatformPluginWrapper::class.java)
+ val extension = project.multiplatformExtension!!
+ extension.jvm()
+ val commonMain = extension.sourceSets.getByName("commonMain")
+ val jvmMain = extension.sourceSets.getByName("jvmMain")
+ val extraMain = extension.sourceSets.create("extraMain")
+ extraMain.dependsOn(commonMain)
+ jvmMain.dependsOn(commonMain)
+ jvmMain.dependsOn(extraMain)
+
+ val result = createSourceSetMetadata(extension)
+ assertThat(result).isEqualTo("""
+ {
+ "sourceSets": [
+ {
+ "name": "commonMain",
+ "dependencies": [],
+ "analysisPlatform": "common"
+ },
+ {
+ "name": "extraMain",
+ "dependencies": [
+ "commonMain"
+ ],
+ "analysisPlatform": "jvm"
+ },
+ {
+ "name": "jvmMain",
+ "dependencies": [
+ "commonMain",
+ "extraMain"
+ ],
+ "analysisPlatform": "jvm"
+ }
+ ]
+ }
+ """.trimIndent())
+ }
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
index 1e6ed89..04c4da3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/SourceJarTaskHelper.kt
@@ -258,7 +258,7 @@
"commonMain" to
mapOf(
"name" to commonMain.name,
- "dependencies" to commonMain.dependsOn.map { it.name },
+ "dependencies" to commonMain.dependsOn.map { it.name }.sorted(),
"analysisPlatform" to DokkaAnalysisPlatform.COMMON.jsonName
)
)
@@ -267,13 +267,15 @@
sourceSetsByName.getOrPut(it.name) {
mapOf(
"name" to it.name,
- "dependencies" to it.dependsOn.map { it.name },
+ "dependencies" to it.dependsOn.map { it.name }.sorted(),
"analysisPlatform" to target.docsPlatform().jsonName
)
}
}
}
- val sourceSetMetadata = mutableMapOf("sourceSets" to sourceSetsByName.values)
+ val sourceSetMetadata = mapOf(
+ "sourceSets" to sourceSetsByName.keys.sorted().map { sourceSetsByName[it] }
+ )
val gson = GsonBuilder().setPrettyPrinting().create()
return gson.toJson(sourceSetMetadata)
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
index ce74b04..33fac2e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/GuaranteedConfigurationsUtil.kt
@@ -718,6 +718,56 @@
return surfaceCombinations
}
+ /**
+ * Returns the minimally guaranteed stream combinations when one or more
+ * streams are configured as a 10-bit input.
+ */
+ @JvmStatic
+ fun get10BitSupportedCombinationList(): List<SurfaceCombination> {
+ return listOf(
+ // (PRIV, MAXIMUM)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM))
+ },
+ // (YUV, MAXIMUM)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+ },
+ // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM))
+ },
+ // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+ },
+ // (YUV, PREVIEW) + (YUV, MAXIMUM)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM))
+ },
+ // (PRIV, PREVIEW) + (PRIV, RECORD)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+ },
+ // (PRIV, PREVIEW) + (PRIV, RECORD) + (YUV, RECORD)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD))
+ },
+ // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+ SurfaceCombination().apply {
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD))
+ addSurfaceConfig(SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD))
+ },
+ )
+ }
+
@JvmStatic
fun generateConcurrentSupportedCombinationList(): List<SurfaceCombination> {
val surfaceCombinations: MutableList<SurfaceCombination> = arrayListOf()
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
index daa54a8..2122ec2 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
@@ -18,20 +18,19 @@
import android.annotation.SuppressLint
import android.content.Context
+import android.content.pm.PackageManager
import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
import android.graphics.ImageFormat
-import android.graphics.Point
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.params.StreamConfigurationMap
-import android.hardware.display.DisplayManager
import android.media.CamcorderProfile
import android.media.MediaRecorder
import android.os.Build
import android.util.Pair
+import android.util.Range
import android.util.Rational
import android.util.Size
-import android.view.Display
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.pipe.CameraMetadata
@@ -41,6 +40,8 @@
import androidx.camera.camera2.pipe.integration.compat.workaround.ResolutionCorrector
import androidx.camera.camera2.pipe.integration.compat.workaround.TargetAspectRatio
import androidx.camera.camera2.pipe.integration.impl.DisplayInfoManager
+import androidx.camera.camera2.pipe.integration.internal.DynamicRangeResolver
+import androidx.camera.core.DynamicRange
import androidx.camera.core.impl.AttachedSurfaceInfo
import androidx.camera.core.impl.CameraMode
import androidx.camera.core.impl.EncoderProfilesProxy
@@ -60,6 +61,8 @@
import androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_VGA
import java.util.Arrays
import java.util.Collections
+import kotlin.math.floor
+import kotlin.math.min
/**
* Camera device supported surface configuration combinations
@@ -85,22 +88,22 @@
private val concurrentSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
private val surfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
private val ultraHighSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
- private val cameraModeToSupportedCombinationsMap: MutableMap<Int, List<SurfaceCombination>> =
- mutableMapOf()
+ private val featureSettingsToSupportedCombinationsMap:
+ MutableMap<FeatureSettings, List<SurfaceCombination>> = mutableMapOf()
+ private val surfaceCombinations10Bit: MutableList<SurfaceCombination> = mutableListOf()
private var isRawSupported = false
private var isBurstCaptureSupported = false
private var isConcurrentCameraModeSupported = false
private var isUltraHighResolutionSensorSupported = false
internal lateinit var surfaceSizeDefinition: SurfaceSizeDefinition
private val surfaceSizeDefinitionFormats = mutableListOf<Int>()
- private val displayManager: DisplayManager =
- (context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager)
private val streamConfigurationMapCompat = getStreamConfigurationMapCompat()
private val extraSupportedSurfaceCombinationsContainer =
ExtraSupportedSurfaceCombinationsContainer()
private val displayInfoManager = DisplayInfoManager(context)
private val resolutionCorrector = ResolutionCorrector()
private val targetAspectRatio: TargetAspectRatio = TargetAspectRatio()
+ private val dynamicRangeResolver: DynamicRangeResolver = DynamicRangeResolver(cameraMetadata)
init {
checkCapabilities()
@@ -113,6 +116,10 @@
if (isConcurrentCameraModeSupported) {
generateConcurrentSupportedCombinationList()
}
+
+ if (dynamicRangeResolver.is10BitDynamicRangeSupported()) {
+ generate10BitSupportedCombinationList()
+ }
generateSurfaceSizeDefinition()
}
@@ -120,48 +127,50 @@
* Check whether the input surface configuration list is under the capability of any combination
* of this object.
*
- * @param cameraMode the working camera mode.
+ * @param featureSettings the settings for the camera's features/capabilities.
* @param surfaceConfigList the surface configuration list to be compared
+ *
* @return the check result that whether it could be supported
*/
fun checkSupported(
- cameraMode: Int,
+ featureSettings: FeatureSettings,
surfaceConfigList: List<SurfaceConfig>
): Boolean {
- // TODO(b/262772650): camera-pipe support for concurrent camera
- val targetSurfaceCombinations = getSurfaceCombinationsByCameraMode(cameraMode)
- for (surfaceCombination in targetSurfaceCombinations) {
- if (surfaceCombination
- .getOrderedSupportedSurfaceConfigList(surfaceConfigList) != null
- ) {
- return true
- }
+ return getSurfaceCombinationsByFeatureSettings(featureSettings).any {
+ it.getOrderedSupportedSurfaceConfigList(surfaceConfigList) != null
}
- return false
}
/**
- * Returns the supported surface combinations according to the specified camera mode.
+ * Returns the supported surface combinations according to the specified feature
+ * settings.
*/
- private fun getSurfaceCombinationsByCameraMode(
- @CameraMode.Mode cameraMode: Int
+ private fun getSurfaceCombinationsByFeatureSettings(
+ featureSettings: FeatureSettings
): List<SurfaceCombination> {
- if (cameraModeToSupportedCombinationsMap.containsKey(cameraMode)) {
- return cameraModeToSupportedCombinationsMap[cameraMode]!!
+ if (featureSettingsToSupportedCombinationsMap.containsKey(featureSettings)) {
+ return featureSettingsToSupportedCombinationsMap[featureSettings]!!
}
var supportedSurfaceCombinations: MutableList<SurfaceCombination> = mutableListOf()
- when (cameraMode) {
- CameraMode.CONCURRENT_CAMERA -> supportedSurfaceCombinations =
- concurrentSurfaceCombinations
+ if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_8_BIT) {
+ when (featureSettings.cameraMode) {
+ CameraMode.CONCURRENT_CAMERA -> supportedSurfaceCombinations =
+ concurrentSurfaceCombinations
- CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA -> {
- supportedSurfaceCombinations.addAll(ultraHighSurfaceCombinations)
- supportedSurfaceCombinations.addAll(surfaceCombinations)
+ CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA -> {
+ supportedSurfaceCombinations.addAll(ultraHighSurfaceCombinations)
+ supportedSurfaceCombinations.addAll(surfaceCombinations)
+ }
+
+ else -> supportedSurfaceCombinations.addAll(surfaceCombinations)
}
-
- else -> supportedSurfaceCombinations.addAll(surfaceCombinations)
+ } else if (featureSettings.requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
+ // For 10-bit outputs, only the default camera mode is currently supported.
+ if (featureSettings.cameraMode == CameraMode.DEFAULT) {
+ supportedSurfaceCombinations.addAll(surfaceCombinations10Bit)
+ }
}
- cameraModeToSupportedCombinationsMap[cameraMode] = supportedSurfaceCombinations
+ featureSettingsToSupportedCombinationsMap[featureSettings] = supportedSurfaceCombinations
return supportedSurfaceCombinations
}
@@ -188,7 +197,7 @@
* Finds the suggested stream specification of the newly added UseCaseConfig.
*
* @param cameraMode the working camera mode.
- * @param existingSurfaces the existing surfaces.
+ * @param attachedSurfaces the existing surfaces.
* @param newUseCaseConfigsSupportedSizeMap newly added UseCaseConfig to supported output sizes
* map.
* @return the suggested stream specs, which is a mapping from UseCaseConfig to the suggested
@@ -198,15 +207,34 @@
*/
fun getSuggestedStreamSpecifications(
cameraMode: Int,
- existingSurfaces: List<AttachedSurfaceInfo>,
+ attachedSurfaces: List<AttachedSurfaceInfo>,
newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>
): Pair<Map<UseCaseConfig<*>, StreamSpec>, Map<AttachedSurfaceInfo, StreamSpec>> {
+ // Refresh Preview Size based on current display configurations.
refreshPreviewSize()
- val surfaceConfigs: MutableList<SurfaceConfig> = ArrayList()
- for (scc in existingSurfaces) {
+ val surfaceConfigs: MutableList<SurfaceConfig> = mutableListOf()
+ for (scc in attachedSurfaces) {
surfaceConfigs.add(scc.surfaceConfig)
}
val newUseCaseConfigs = newUseCaseConfigsSupportedSizeMap.keys.toList()
+
+ // Get the index order list by the use case priority for finding stream configuration
+ val useCasesPriorityOrder = getUseCasesPriorityOrder(newUseCaseConfigs)
+ val resolvedDynamicRanges = dynamicRangeResolver.resolveAndValidateDynamicRanges(
+ attachedSurfaces,
+ newUseCaseConfigs, useCasesPriorityOrder
+ )
+ val requiredMaxBitDepth: Int = getRequiredMaxBitDepth(resolvedDynamicRanges)
+ val featureSettings = FeatureSettings(cameraMode, requiredMaxBitDepth)
+ require(
+ !(cameraMode != CameraMode.DEFAULT &&
+ requiredMaxBitDepth == DynamicRange.BIT_DEPTH_10_BIT)
+ ) {
+ "No supported surface combination is " +
+ "found for camera device - Id : $cameraId. 10 bit dynamic range is not " +
+ "currently supported in ${CameraMode.toLabelString(cameraMode)} camera mode."
+ }
+
// Use the small size (640x480) for new use cases to check whether there is any possible
// supported combination first
for (useCaseConfig in newUseCaseConfigs) {
@@ -220,83 +248,476 @@
)
}
- if (!checkSupported(cameraMode, surfaceConfigs)) {
+ if (!checkSupported(featureSettings, surfaceConfigs)) {
throw java.lang.IllegalArgumentException(
"No supported surface combination is found for camera device - Id : " + cameraId +
". May be attempting to bind too many use cases. " + "Existing surfaces: " +
- existingSurfaces + " New configs: " + newUseCaseConfigs
+ attachedSurfaces + " New configs: " + newUseCaseConfigs
)
}
- // Get the index order list by the use case priority for finding stream configuration
- val useCasesPriorityOrder: List<Int> = getUseCasesPriorityOrder(
- newUseCaseConfigs
+
+ val targetFpsRange =
+ getTargetFpsRange(attachedSurfaces, newUseCaseConfigs, useCasesPriorityOrder)
+ val maxSupportedFps = getMaxSupportedFps(attachedSurfaces)
+
+ val bestSizesAndFps = findBestSizesAndFps(
+ newUseCaseConfigsSupportedSizeMap,
+ attachedSurfaces,
+ newUseCaseConfigs,
+ maxSupportedFps,
+ useCasesPriorityOrder,
+ targetFpsRange,
+ featureSettings
)
- val supportedOutputSizesList: MutableList<List<Size>> = ArrayList()
+
+ val suggestedStreamSpecMap = generateSuggestedStreamSpecMap(
+ bestSizesAndFps.first,
+ targetFpsRange,
+ bestSizesAndFps.second,
+ newUseCaseConfigs,
+ useCasesPriorityOrder,
+ resolvedDynamicRanges,
+ )
+
+ return Pair.create(suggestedStreamSpecMap, mapOf<AttachedSurfaceInfo, StreamSpec>())
+ }
+
+ private fun getSupportedOutputSizesList(
+ newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasesPriorityOrder: List<Int>,
+ ): List<List<Size>> {
+ val supportedOutputSizesList: MutableList<List<Size>> = mutableListOf()
// Collect supported output sizes for all use cases
for (index in useCasesPriorityOrder) {
- var supportedOutputSizes: List<Size> =
- newUseCaseConfigsSupportedSizeMap[newUseCaseConfigs[index]]!!
+ var supportedOutputSizes = newUseCaseConfigsSupportedSizeMap[newUseCaseConfigs[index]]!!
supportedOutputSizes = applyResolutionSelectionOrderRelatedWorkarounds(
supportedOutputSizes,
newUseCaseConfigs[index].inputFormat
)
supportedOutputSizesList.add(supportedOutputSizes)
}
- // Get all possible size arrangements
- val allPossibleSizeArrangements: List<List<Size>> = getAllPossibleSizeArrangements(
- supportedOutputSizesList
- )
+ return supportedOutputSizesList
+ }
- var suggestedStreamSpecMap: Map<UseCaseConfig<*>, StreamSpec>? = null
+ private fun getTargetFpsRange(
+ attachedSurfaces: List<AttachedSurfaceInfo>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasesPriorityOrder: List<Int>
+ ): Range<Int>? {
+ var targetFrameRateForConfig: Range<Int>? = null
+ for (attachedSurfaceInfo in attachedSurfaces) {
+ // init target fps range for new configs from existing surfaces
+ targetFrameRateForConfig = getUpdatedTargetFrameRate(
+ attachedSurfaceInfo.targetFrameRate,
+ targetFrameRateForConfig
+ )
+ }
+ // update target fps for new configs using new use cases' priority order
+ for (index in useCasesPriorityOrder) {
+ targetFrameRateForConfig = getUpdatedTargetFrameRate(
+ newUseCaseConfigs[index].getTargetFrameRate(null),
+ targetFrameRateForConfig
+ )
+ }
+ return targetFrameRateForConfig
+ }
+
+ private fun getMaxSupportedFps(
+ attachedSurfaces: List<AttachedSurfaceInfo>,
+ ): Int {
+ var existingSurfaceFrameRateCeiling = Int.MAX_VALUE
+ for (attachedSurfaceInfo in attachedSurfaces) {
+ // get the fps ceiling for existing surfaces
+ existingSurfaceFrameRateCeiling = getUpdatedMaximumFps(
+ existingSurfaceFrameRateCeiling,
+ attachedSurfaceInfo.imageFormat, attachedSurfaceInfo.size
+ )
+ }
+ return existingSurfaceFrameRateCeiling
+ }
+
+ private fun findBestSizesAndFps(
+ newUseCaseConfigsSupportedSizeMap: Map<UseCaseConfig<*>, List<Size>>,
+ attachedSurfaces: List<AttachedSurfaceInfo>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ existingSurfaceFrameRateCeiling: Int,
+ useCasesPriorityOrder: List<Int>,
+ targetFrameRateForConfig: Range<Int>?,
+ featureSettings: FeatureSettings
+ ): Pair<List<Size>, Int> {
+ var bestSizes: List<Size>? = null
+ var bestConfigMaxFps = Int.MAX_VALUE
+ val allPossibleSizeArrangements = getAllPossibleSizeArrangements(
+ getSupportedOutputSizesList(
+ newUseCaseConfigsSupportedSizeMap,
+ newUseCaseConfigs,
+ useCasesPriorityOrder
+ )
+ )
// Transform use cases to SurfaceConfig list and find the first (best) workable combination
for (possibleSizeList in allPossibleSizeArrangements) {
// Attach SurfaceConfig of original use cases since it will impact the new use cases
- val surfaceConfigList: MutableList<SurfaceConfig> = ArrayList()
- for (sc in existingSurfaces) {
- surfaceConfigList.add(sc.surfaceConfig)
- }
-
- // Attach SurfaceConfig of new use cases
- for (i in possibleSizeList.indices) {
- val size = possibleSizeList[i]
- val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
- surfaceConfigList.add(
- SurfaceConfig.transformSurfaceConfig(
- cameraMode,
- newUseCase.inputFormat,
- size,
- getUpdatedSurfaceSizeDefinitionByFormat(newUseCase.inputFormat)
- )
- )
- }
-
- // Check whether the SurfaceConfig combination can be supported
- if (checkSupported(cameraMode, surfaceConfigList)) {
- suggestedStreamSpecMap = HashMap()
- for (useCaseConfig in newUseCaseConfigs) {
- suggestedStreamSpecMap.put(
- useCaseConfig,
- StreamSpec.builder(
- possibleSizeList[useCasesPriorityOrder.indexOf(
- newUseCaseConfigs.indexOf(useCaseConfig)
- )]
- ).build()
- )
+ val surfaceConfigList = getSurfaceConfigList(
+ featureSettings.cameraMode,
+ attachedSurfaces, possibleSizeList, newUseCaseConfigs,
+ useCasesPriorityOrder
+ )
+ val currentConfigFrameRateCeiling = getCurrentConfigFrameRateCeiling(
+ possibleSizeList, newUseCaseConfigs,
+ useCasesPriorityOrder, existingSurfaceFrameRateCeiling
+ )
+ var isConfigFrameRateAcceptable = true
+ if (targetFrameRateForConfig != null) {
+ if (existingSurfaceFrameRateCeiling > currentConfigFrameRateCeiling &&
+ currentConfigFrameRateCeiling < targetFrameRateForConfig.lower
+ ) {
+ // if the max fps before adding new use cases supports our target fps range
+ // BUT the max fps of the new configuration is below
+ // our target fps range, we'll want to check the next configuration until we
+ // get one that supports our target FPS
+ isConfigFrameRateAcceptable = false
}
- break
+ }
+
+ // only change the saved config if you get another that has a better max fps
+ if (checkSupported(featureSettings, surfaceConfigList)) {
+ // if we have a configuration where the max fps is acceptable for our target, break
+ if (isConfigFrameRateAcceptable) {
+ bestConfigMaxFps = currentConfigFrameRateCeiling
+ bestSizes = possibleSizeList
+ break
+ }
+ // if the config is supported by the device but doesn't meet the target frame rate,
+ // save the config
+ if (bestConfigMaxFps == Int.MAX_VALUE) {
+ bestConfigMaxFps = currentConfigFrameRateCeiling
+ bestSizes = possibleSizeList
+ } else if (bestConfigMaxFps < currentConfigFrameRateCeiling) {
+ // only change the saved config if the max fps is better
+ bestConfigMaxFps = currentConfigFrameRateCeiling
+ bestSizes = possibleSizeList
+ }
}
}
- if (suggestedStreamSpecMap == null) {
- throw java.lang.IllegalArgumentException(
- "No supported surface combination is found for camera device - Id : " +
- cameraId + " and Hardware level: " + hardwareLevel +
- ". May be the specified resolution is too large and not supported." +
- " Existing surfaces: " + existingSurfaces +
- " New configs: " + newUseCaseConfigs
+ require(bestSizes != null) {
+ "No supported surface combination is found for camera device - Id : $cameraId " +
+ "and Hardware level: $hardwareLevel. " +
+ "May be the specified resolution is too large and not supported. " +
+ "Existing surfaces: $attachedSurfaces. New configs: $newUseCaseConfigs."
+ }
+ return Pair(bestSizes, bestConfigMaxFps)
+ }
+
+ private fun generateSuggestedStreamSpecMap(
+ bestSizes: List<Size>,
+ targetFpsRange: Range<Int>?,
+ bestConfigMaxFps: Int,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasesPriorityOrder: List<Int>,
+ resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>,
+ ): Map<UseCaseConfig<*>, StreamSpec> {
+ val suggestedStreamSpecMap = mutableMapOf<UseCaseConfig<*>, StreamSpec>()
+ var targetFrameRateForDevice: Range<Int>? = null
+ if (targetFpsRange != null) {
+ targetFrameRateForDevice = getClosestSupportedDeviceFrameRate(
+ targetFpsRange,
+ bestConfigMaxFps
)
}
- return Pair.create(suggestedStreamSpecMap, mapOf<AttachedSurfaceInfo, StreamSpec>())
+ for ((index, useCaseConfig) in newUseCaseConfigs.withIndex()) {
+ val resolutionForUseCase =
+ bestSizes[
+ useCasesPriorityOrder.indexOf(index)]
+ val streamSpecBuilder = StreamSpec.builder(resolutionForUseCase)
+ .setDynamicRange(
+ checkNotNull(resolvedDynamicRanges[useCaseConfig])
+ )
+ if (targetFrameRateForDevice != null) {
+ streamSpecBuilder.setExpectedFrameRateRange(targetFrameRateForDevice)
+ }
+ suggestedStreamSpecMap[useCaseConfig] = streamSpecBuilder.build()
+ }
+ return suggestedStreamSpecMap
+ }
+
+ private fun getRequiredMaxBitDepth(
+ resolvedDynamicRanges: Map<UseCaseConfig<*>, DynamicRange>
+ ): Int {
+ for (dynamicRange in resolvedDynamicRanges.values) {
+ if (dynamicRange.bitDepth == DynamicRange.BIT_DEPTH_10_BIT) {
+ return DynamicRange.BIT_DEPTH_10_BIT
+ }
+ }
+ return DynamicRange.BIT_DEPTH_8_BIT
+ }
+
+ private fun getSurfaceConfigList(
+ @CameraMode.Mode cameraMode: Int,
+ attachedSurfaces: List<AttachedSurfaceInfo>,
+ possibleSizeList: List<Size>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasesPriorityOrder: List<Int>,
+ ): List<SurfaceConfig> {
+ val surfaceConfigList: MutableList<SurfaceConfig> = mutableListOf()
+ for (attachedSurfaceInfo in attachedSurfaces) {
+ surfaceConfigList.add(attachedSurfaceInfo.surfaceConfig)
+ }
+
+ // Attach SurfaceConfig of new use cases
+ for ((i, size) in possibleSizeList.withIndex()) {
+ val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
+ val imageFormat = newUseCase.inputFormat
+ // add new use case/size config to list of surfaces
+ val surfaceConfig = SurfaceConfig.transformSurfaceConfig(
+ cameraMode,
+ imageFormat,
+ size,
+ getUpdatedSurfaceSizeDefinitionByFormat(imageFormat)
+ )
+ surfaceConfigList.add(surfaceConfig)
+ }
+ return surfaceConfigList
+ }
+
+ private fun getCurrentConfigFrameRateCeiling(
+ possibleSizeList: List<Size>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasesPriorityOrder: List<Int>,
+ currentConfigFrameRateCeiling: Int,
+ ): Int {
+ var newConfigFrameRateCeiling: Int = currentConfigFrameRateCeiling
+ // Attach SurfaceConfig of new use cases
+ for ((i, size) in possibleSizeList.withIndex()) {
+ val newUseCase = newUseCaseConfigs[useCasesPriorityOrder[i]]
+ // get the maximum fps of the new surface and update the maximum fps of the
+ // proposed configuration
+ newConfigFrameRateCeiling = getUpdatedMaximumFps(
+ newConfigFrameRateCeiling,
+ newUseCase.inputFormat,
+ size
+ )
+ }
+ return newConfigFrameRateCeiling
+ }
+
+ private fun getMaxFrameRate(
+ imageFormat: Int,
+ size: Size?
+ ): Int {
+ var maxFrameRate = 0
+ try {
+ val minFrameDuration = getStreamConfigurationMapCompat().getOutputMinFrameDuration(
+ imageFormat,
+ size
+ ) ?: return 0
+ maxFrameRate = floor(1_000_000_000.0 / minFrameDuration + 0.05).toInt()
+ } catch (e1: IllegalArgumentException) {
+ // TODO: this try catch is in place for the rare that a surface config has a size
+ // incompatible for getOutputMinFrameDuration... put into a Quirk
+ }
+ return maxFrameRate
+ }
+
+ /**
+ *
+ * @param range
+ * @return the length of the range
+ */
+ private fun getRangeLength(range: Range<Int>): Int {
+ return range.upper - range.lower + 1
+ }
+
+ /**
+ * @return the distance between the nearest limits of two non-intersecting ranges
+ */
+ private fun getRangeDistance(firstRange: Range<Int>, secondRange: Range<Int>): Int {
+ require(
+ !firstRange.contains(secondRange.upper) &&
+ !firstRange.contains(secondRange.lower)
+ ) { "Ranges must not intersect" }
+ return if (firstRange.lower > secondRange.upper) {
+ firstRange.lower - secondRange.upper
+ } else {
+ secondRange.lower - firstRange.upper
+ }
+ }
+
+ /**
+ * @param targetFps the target frame rate range used while comparing to device-supported ranges
+ * @param storedRange the device-supported range that is currently saved and intersects with
+ * targetFps
+ * @param newRange a new potential device-supported range that intersects with targetFps
+ * @return the device-supported range that better matches the target fps
+ */
+ private fun compareIntersectingRanges(
+ targetFps: Range<Int>,
+ storedRange: Range<Int>,
+ newRange: Range<Int>
+ ): Range<Int> {
+ // TODO(b/272075984): some ranges may may have a larger intersection but may also have an
+ // excessively large portion that is non-intersecting. Will want to do further
+ // investigation to find a more optimized way to decide when a potential range has too
+ // much non-intersecting value and discard it
+ val storedIntersectionsize =
+ getRangeLength(storedRange.intersect(targetFps)).toDouble()
+ val newIntersectionSize = getRangeLength(newRange.intersect(targetFps)).toDouble()
+ val newRangeRatio = newIntersectionSize / getRangeLength(newRange)
+ val storedRangeRatio = storedIntersectionsize / getRangeLength(storedRange)
+ if (newIntersectionSize > storedIntersectionsize) {
+ // if new, the new range must have at least 50% of its range intersecting, OR has a
+ // larger percentage of intersection than the previous stored range
+ if (newRangeRatio >= .5 || newRangeRatio >= storedRangeRatio) {
+ return newRange
+ }
+ } else if (newIntersectionSize == storedIntersectionsize) {
+ // if intersecting ranges have same length... pick the one that has the higher
+ // intersection ratio
+ if (newRangeRatio > storedRangeRatio) {
+ return newRange
+ } else if (newRangeRatio == storedRangeRatio && newRange.lower > storedRange.lower
+ ) {
+ // if equal intersection size AND ratios pick the higher range
+ return newRange
+ }
+ } else if (storedRangeRatio < .5 && newRangeRatio > storedRangeRatio
+ ) {
+ // if the new one has a smaller range... only change if existing has an intersection
+ // ratio < 50% and the new one has an intersection ratio > than the existing one
+ return newRange
+ }
+ return storedRange
+ }
+
+ /**
+ * Finds a frame rate range supported by the device that is closest to the target frame rate
+ *
+ * @param targetFrameRate the Target Frame Rate resolved from all current existing surfaces
+ * and incoming new use cases
+ * @return a frame rate range supported by the device that is closest to targetFrameRate
+ */
+ private fun getClosestSupportedDeviceFrameRate(
+ targetFrameRate: Range<Int>,
+ maxFps: Int
+ ): Range<Int> {
+ var newTargetFrameRate = targetFrameRate
+ // get all fps ranges supported by device
+ val availableFpsRanges =
+ cameraMetadata[CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES]
+ ?: return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+ // if whole target frame rate range > maxFps of configuration, the target for this
+ // calculation will be [max,max].
+
+ // if the range is partially larger than maxFps, the target for this calculation will be
+ // [target.lower, max] for the sake of this calculation
+ newTargetFrameRate = Range(
+ min(newTargetFrameRate.lower, maxFps),
+ min(newTargetFrameRate.upper, maxFps)
+ )
+ var bestRange = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+ var currentIntersectSize = 0
+ for (potentialRange in availableFpsRanges) {
+ // ignore ranges completely larger than configuration's maximum fps
+ if (maxFps < potentialRange.lower) {
+ continue
+ }
+ if (bestRange == StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED) {
+ bestRange = potentialRange
+ }
+ // take if range is a perfect match
+ if (potentialRange == newTargetFrameRate) {
+ bestRange = potentialRange
+ break
+ }
+ try {
+ // bias towards a range that intersects on the upper end
+ val newIntersection = potentialRange.intersect(newTargetFrameRate)
+ val newIntersectSize: Int = getRangeLength(
+ newIntersection
+ )
+ // if this range intersects our target + no other range was already
+ if (currentIntersectSize == 0) {
+ bestRange = potentialRange
+ currentIntersectSize = newIntersectSize
+ } else if (newIntersectSize >= currentIntersectSize) {
+ // if the currently stored range + new range both intersect, check to see
+ // which one should be picked over the other
+ bestRange = compareIntersectingRanges(
+ newTargetFrameRate, bestRange,
+ potentialRange
+ )
+ currentIntersectSize = getRangeLength(
+ newTargetFrameRate.intersect(bestRange)
+ )
+ }
+ } catch (e: IllegalArgumentException) {
+ if (currentIntersectSize != 0) {
+ continue
+ }
+
+ // if no intersection is present, pick the range that is closer to our target
+ if (getRangeDistance(potentialRange, newTargetFrameRate)
+ < getRangeDistance(
+ bestRange, newTargetFrameRate
+ )
+ ) {
+ bestRange = potentialRange
+ } else if (getRangeDistance(potentialRange, newTargetFrameRate) ==
+ getRangeDistance(bestRange, newTargetFrameRate)
+ ) {
+ if (potentialRange.lower > bestRange.upper) {
+ // if they both have the same distance, pick the higher range
+ bestRange = potentialRange
+ } else if (getRangeLength(potentialRange)
+ < getRangeLength(bestRange)
+ ) {
+ // if one isn't higher than the other, pick the range with the
+ // shorter length
+ bestRange = potentialRange
+ }
+ }
+ }
+ }
+ return bestRange
+ }
+
+ /**
+ * @param newTargetFrameRate an incoming frame rate range
+ * @param storedTargetFrameRate a stored frame rate range to be modified
+ * @return adjusted target frame rate
+ *
+ * If the two ranges are both nonnull and disjoint of each other, then the range that was
+ * already stored will be used
+ */
+ private fun getUpdatedTargetFrameRate(
+ newTargetFrameRate: Range<Int>?,
+ storedTargetFrameRate: Range<Int>?
+ ): Range<Int>? {
+ var updatedTarget = storedTargetFrameRate
+ if (storedTargetFrameRate == null) {
+ // if stored value was null before, set it to the new value
+ updatedTarget = newTargetFrameRate
+ } else if (newTargetFrameRate != null) {
+ updatedTarget = try {
+ // get intersection of existing target fps
+ storedTargetFrameRate
+ .intersect(newTargetFrameRate)
+ } catch (e: java.lang.IllegalArgumentException) {
+ // no intersection, keep the previously stored value
+ storedTargetFrameRate
+ }
+ }
+ return updatedTarget
+ }
+
+ /**
+ * @param currentMaxFps the previously stored Max FPS
+ * @param imageFormat the image format of the incoming surface
+ * @param size the size of the incoming surface
+ */
+ private fun getUpdatedMaximumFps(currentMaxFps: Int, imageFormat: Int, size: Size): Int {
+ return min(currentMaxFps, getMaxFrameRate(imageFormat, size))
}
/**
@@ -317,7 +738,7 @@
imageFormat: Int
): List<Size> {
// Applies TargetAspectRatio workaround
- var ratio: Rational? =
+ val ratio: Rational? =
when (targetAspectRatio[cameraMetadata, streamConfigurationMapCompat]) {
TargetAspectRatio.RATIO_4_3 ->
AspectRatioUtil.ASPECT_RATIO_4_3
@@ -426,6 +847,12 @@
)
}
+ private fun generate10BitSupportedCombinationList() {
+ surfaceCombinations10Bit.addAll(
+ GuaranteedConfigurationsUtil.get10BitSupportedCombinationList()
+ )
+ }
+
/**
* Generation the size definition for VGA, s720p, PREVIEW, s1440p, RECORD, MAXIMUM and
* ULTRA_MAXIMUM.
@@ -621,42 +1048,14 @@
}
/**
- * Retrieves the display which has the max size among all displays.
- */
- private fun getMaxSizeDisplay(): Display {
- val displays: Array<Display> = displayManager.displays
- if (displays.size == 1) {
- return displays[0]
- }
- var maxDisplay: Display? = null
- var maxDisplaySize = -1
- for (display: Display in displays) {
- if (display.state != Display.STATE_OFF) {
- val displaySize = Point()
- display.getRealSize(displaySize)
- if (displaySize.x * displaySize.y > maxDisplaySize) {
- maxDisplaySize = displaySize.x * displaySize.y
- maxDisplay = display
- }
- }
- }
- if (maxDisplay == null) {
- throw IllegalArgumentException(
- "No display can be found from the input display manager!"
- )
- }
- return maxDisplay
- }
-
- /**
* Once the stream resource is occupied by one use case, it will impact the other use cases.
* Therefore, we need to define the priority for stream resource usage. For the use cases
* with the higher priority, we will try to find the best one for them in priority as
* possible.
*/
private fun getUseCasesPriorityOrder(newUseCaseConfigs: List<UseCaseConfig<*>>): List<Int> {
- val priorityOrder: MutableList<Int> = ArrayList()
- val priorityValueList: MutableList<Int> = ArrayList()
+ val priorityOrder: MutableList<Int> = mutableListOf()
+ val priorityValueList: MutableList<Int> = mutableListOf()
for (config in newUseCaseConfigs) {
val priority = config.getSurfaceOccupancyPriority(0)
if (!priorityValueList.contains(priority)) {
@@ -711,13 +1110,13 @@
if (Build.VERSION.SDK_INT >= 23 && highResolutionIncluded) {
val highResolutionOutputSizes = map?.getHighResolutionOutputSizes(imageFormat)
- if (highResolutionOutputSizes != null && highResolutionOutputSizes.isNotEmpty()) {
+ if (!highResolutionOutputSizes.isNullOrEmpty()) {
maxHighResolutionSize =
Collections.max(highResolutionOutputSizes.asList(), compareSizesByArea)
}
}
- return Collections.max(Arrays.asList(maxSize, maxHighResolutionSize), compareSizesByArea)
+ return Collections.max(listOf(maxSize, maxHighResolutionSize), compareSizesByArea)
}
/**
@@ -735,11 +1134,11 @@
// supportedOutputSizes
// for some use case
require(totalArrangementsCount != 0) { "Failed to find supported resolutions." }
- val allPossibleSizeArrangements: MutableList<MutableList<Size>> = ArrayList()
+ val allPossibleSizeArrangements: MutableList<MutableList<Size>> = mutableListOf()
// Initialize allPossibleSizeArrangements for the following operations
for (i in 0 until totalArrangementsCount) {
- val sizeList: MutableList<Size> = ArrayList()
+ val sizeList: MutableList<Size> = mutableListOf()
allPossibleSizeArrangements.add(sizeList)
}
@@ -769,7 +1168,23 @@
return allPossibleSizeArrangements
}
- companion object {
- private const val TAG = "SupportedSurfaceCombination"
- }
+ /**
+ * A collection of feature settings related to the Camera2 capabilities exposed by
+ * [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES] and device features exposed
+ * by [PackageManager.hasSystemFeature].
+ *
+ * @param cameraMode The camera mode. This involves the following mapping of mode to features:
+ * [CameraMode.CONCURRENT_CAMERA] -> [PackageManager.FEATURE_CAMERA_CONCURRENT]
+ * [CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA] ->
+ * [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR]
+ * @param requiredMaxBitDepth The required maximum bit depth for any non-RAW stream attached to
+ * the camera. A value of [DynamicRange.BIT_DEPTH_10_BIT] corresponds to the camera
+ * capability
+ * [CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT].
+ *
+ */
+ data class FeatureSettings(
+ @CameraMode.Mode val cameraMode: Int,
+ val requiredMaxBitDepth: Int
+ )
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
new file mode 100644
index 0000000..4d1fddd
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompat.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2023 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.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.core.checkApi
+import androidx.camera.core.DynamicRange
+
+/**
+ * Helper for accessing features in DynamicRangeProfiles in a backwards compatible fashion.
+ */
+@RequiresApi(21)
+class DynamicRangeProfilesCompat internal constructor(
+ private val mImpl: DynamicRangeProfilesCompatImpl
+) {
+ /**
+ * Returns a set of supported [DynamicRange] that can be referenced in a single
+ * capture request.
+ *
+ * For example if a particular 10-bit output capable device returns (STANDARD,
+ * HLG10, HDR10) as result from calling [getSupportedDynamicRanges] and
+ * [DynamicRangeProfiles.getProfileCaptureRequestConstraints]
+ * returns (STANDARD, HLG10) when given an argument
+ * of STANDARD. This means that the corresponding camera device will only accept and process
+ * capture requests that reference outputs configured using HDR10 dynamic range or
+ * alternatively some combination of STANDARD and HLG10. However trying to queue capture
+ * requests to outputs that reference both HDR10 and STANDARD/HLG10 will result in
+ * IllegalArgumentException.
+ *
+ * The list will be empty in case there are no constraints for the given dynamic range.
+ *
+ * @param dynamicRange The dynamic range that will be checked for constraints
+ * @return non-modifiable set of dynamic ranges
+ * @throws IllegalArgumentException If the dynamic range argument is not within the set
+ * returned by [getSupportedDynamicRanges].
+ */
+ fun getDynamicRangeCaptureRequestConstraints(
+ dynamicRange: DynamicRange
+ ): Set<DynamicRange> {
+ return mImpl.getDynamicRangeCaptureRequestConstraints(dynamicRange)
+ }
+
+ /**
+ * Returns a set of supported dynamic ranges.
+ *
+ * @return a non-modifiable set of dynamic ranges.
+ */
+ fun getSupportedDynamicRanges(): Set<DynamicRange> {
+ return mImpl.getSupportedDynamicRanges()
+ }
+
+ /**
+ * Checks whether a given dynamic range is suitable for latency sensitive use cases.
+ *
+ * Due to internal lookahead logic, camera outputs configured with some dynamic range
+ * profiles may experience additional latency greater than 3 buffers. Using camera outputs
+ * with such dynamic ranges for latency sensitive use cases such as camera preview is not
+ * recommended. Dynamic ranges that have such extra streaming delay are typically utilized for
+ * scenarios such as offscreen video recording.
+ *
+ * @param dynamicRange The dynamic range to check for extra latency
+ * @return `true` if the given profile is not suitable for latency sensitive use cases,
+ * `false` otherwise.
+ * @throws IllegalArgumentException If the dynamic range argument is not within the set
+ * returned by [getSupportedDynamicRanges].
+ */
+ fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+ return mImpl.isExtraLatencyPresent(dynamicRange)
+ }
+
+ /**
+ * Returns the underlying framework
+ * [DynamicRangeProfiles].
+ *
+ * @return the underlying [DynamicRangeProfiles] or
+ * `null` if the device doesn't support 10 bit dynamic range.
+ */
+ @RequiresApi(33)
+ fun toDynamicRangeProfiles(): DynamicRangeProfiles? {
+ checkApi(
+ 33, "DynamicRangesCompat can only be " +
+ "converted to DynamicRangeProfiles on API 33 or higher."
+ )
+ return mImpl.unwrap()
+ }
+
+ internal interface DynamicRangeProfilesCompatImpl {
+ fun getDynamicRangeCaptureRequestConstraints(
+ dynamicRange: DynamicRange
+ ): Set<DynamicRange>
+
+ fun getSupportedDynamicRanges(): Set<DynamicRange>
+
+ fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean
+ fun unwrap(): DynamicRangeProfiles?
+ }
+
+ companion object {
+ /**
+ * Returns a [DynamicRangeProfilesCompat] using the capabilities derived from the provided
+ * characteristics.
+ *
+ * @param cameraMetadata the metaData used to derive dynamic range information.
+ * @return a [DynamicRangeProfilesCompat] object.
+ */
+ fun fromCameraMetaData(
+ cameraMetadata: CameraMetadata
+ ): DynamicRangeProfilesCompat {
+ var rangesCompat: DynamicRangeProfilesCompat? = null
+ if (Build.VERSION.SDK_INT >= 33) {
+ rangesCompat = toDynamicRangesCompat(
+ cameraMetadata[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES]
+ )
+ }
+ return rangesCompat ?: DynamicRangeProfilesCompatBaseImpl.COMPAT_INSTANCE
+ }
+
+ /**
+ * Creates an instance from a framework [DynamicRangeProfiles]
+ * object.
+ *
+ * @param dynamicRangeProfiles a [DynamicRangeProfiles].
+ * @return an equivalent [DynamicRangeProfilesCompat] object.
+ */
+ @RequiresApi(33)
+ fun toDynamicRangesCompat(
+ dynamicRangeProfiles: DynamicRangeProfiles?
+ ): DynamicRangeProfilesCompat? {
+ if (dynamicRangeProfiles == null) {
+ return null
+ }
+ checkApi(
+ 33, "DynamicRangeProfiles can only " +
+ "be converted to DynamicRangesCompat on API 33 or higher."
+ )
+ return DynamicRangeProfilesCompat(
+ DynamicRangeProfilesCompatApi33Impl(
+ dynamicRangeProfiles
+ )
+ )
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
new file mode 100644
index 0000000..12a5687
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatApi33Impl.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 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.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.internal.DynamicRangeConversions
+import androidx.camera.core.DynamicRange
+import java.util.Collections
+
+@RequiresApi(33)
+internal class DynamicRangeProfilesCompatApi33Impl(
+ private val dynamicRangeProfiles: DynamicRangeProfiles
+) : DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+
+ override fun getDynamicRangeCaptureRequestConstraints(
+ dynamicRange: DynamicRange
+ ): Set<DynamicRange> {
+ val dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange)
+ require(dynamicRangeProfile != null) {
+ "DynamicRange is not supported: $dynamicRange"
+ }
+ return profileSetToDynamicRangeSet(
+ dynamicRangeProfiles.getProfileCaptureRequestConstraints(dynamicRangeProfile)
+ )
+ }
+
+ override fun getSupportedDynamicRanges() = profileSetToDynamicRangeSet(
+ dynamicRangeProfiles.supportedProfiles
+ )
+
+ override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+ val dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange)
+ require(
+ dynamicRangeProfile != null
+ ) {
+ "DynamicRange is not supported: $dynamicRange"
+ }
+ return dynamicRangeProfiles.isExtraLatencyPresent(dynamicRangeProfile)
+ }
+
+ override fun unwrap() = dynamicRangeProfiles
+
+ private fun dynamicRangeToFirstSupportedProfile(dynamicRange: DynamicRange) =
+ DynamicRangeConversions.dynamicRangeToFirstSupportedProfile(
+ dynamicRange,
+ dynamicRangeProfiles
+ )
+
+ private fun profileToDynamicRange(profile: Long): DynamicRange {
+ val result = DynamicRangeConversions.profileToDynamicRange(
+ profile
+ )
+ require(result != null) {
+ "Dynamic range profile cannot be converted to a DynamicRange object: $profile"
+ }
+ return result
+ }
+
+ private fun profileSetToDynamicRangeSet(profileSet: Set<Long>): Set<DynamicRange> {
+ if (profileSet.isEmpty()) {
+ return emptySet()
+ }
+ val dynamicRangeSet: MutableSet<DynamicRange> = mutableSetOf()
+ for (profile in profileSet) {
+ dynamicRangeSet.add(profileToDynamicRange(profile))
+ }
+ return Collections.unmodifiableSet(dynamicRangeSet)
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
new file mode 100644
index 0000000..b9a0cad
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatBaseImpl.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.RequiresApi
+import androidx.camera.core.DynamicRange
+import androidx.core.util.Preconditions
+
+@RequiresApi(21)
+internal class DynamicRangeProfilesCompatBaseImpl :
+ DynamicRangeProfilesCompat.DynamicRangeProfilesCompatImpl {
+ override fun getDynamicRangeCaptureRequestConstraints(
+ dynamicRange: DynamicRange
+ ): Set<DynamicRange> {
+ Preconditions.checkArgument(
+ DynamicRange.SDR == dynamicRange,
+ "DynamicRange is not supported: $dynamicRange"
+ )
+ return SDR_ONLY
+ }
+
+ override fun getSupportedDynamicRanges(): Set<DynamicRange> {
+ return SDR_ONLY
+ }
+
+ override fun isExtraLatencyPresent(dynamicRange: DynamicRange): Boolean {
+ Preconditions.checkArgument(
+ DynamicRange.SDR == dynamicRange,
+ "DynamicRange is not supported: $dynamicRange"
+ )
+ return false
+ }
+
+ override fun unwrap(): DynamicRangeProfiles? {
+ return null
+ }
+
+ companion object {
+ val COMPAT_INSTANCE: DynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat(DynamicRangeProfilesCompatBaseImpl())
+ private val SDR_ONLY = setOf(DynamicRange.SDR)
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
index 5b963d5..00ebe44 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
@@ -136,6 +136,10 @@
return outputSizes?.clone()
}
+ fun getOutputMinFrameDuration(format: Int, size: Size?): Long? {
+ return impl.getOutputMinFrameDuration(format, size)
+ }
+
/**
* Returns the [StreamConfigurationMap] represented by this object.
*/
@@ -147,6 +151,7 @@
fun getOutputSizes(format: Int): Array<Size>?
fun <T> getOutputSizes(klass: Class<T>): Array<Size>?
fun getHighResolutionOutputSizes(format: Int): Array<Size>?
+ fun getOutputMinFrameDuration(format: Int, size: Size?): Long?
/**
* Returns the underlying [StreamConfigurationMap] instance.
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
index b5bfa26..fef2df7 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
@@ -50,6 +50,10 @@
return null
}
+ override fun getOutputMinFrameDuration(format: Int, size: Size?): Long? {
+ return streamConfigurationMap?.getOutputMinFrameDuration(format, size)
+ }
+
override fun unwrap(): StreamConfigurationMap? {
return streamConfigurationMap
}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index 84bd53e..88ff62b 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -43,6 +43,7 @@
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
import androidx.camera.camera2.pipe.integration.interop.Camera2CameraControl
import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
+import androidx.camera.core.DynamicRange
import androidx.camera.core.UseCase
import androidx.camera.core.impl.CameraInternal
import androidx.camera.core.impl.CameraMode
@@ -457,7 +458,12 @@
)
}
- return supportedSurfaceCombination.checkSupported(CameraMode.DEFAULT, surfaceConfigs)
+ return supportedSurfaceCombination.checkSupported(
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), surfaceConfigs
+ )
}
private fun Collection<UseCase>.surfaceCount(): Int =
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt
new file mode 100644
index 0000000..090e587
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeConversions.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 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.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.camera.core.DynamicRange
+
+/**
+ * Utilities for converting between [DynamicRange] and profiles from
+ * [DynamicRangeProfiles].
+ */
+@RequiresApi(33)
+internal object DynamicRangeConversions {
+ private val PROFILE_TO_DR_MAP: MutableMap<Long, DynamicRange> = mutableMapOf()
+ private val DR_TO_PROFILE_MAP: MutableMap<DynamicRange?, List<Long>> = mutableMapOf()
+
+ init {
+ // SDR
+ PROFILE_TO_DR_MAP[DynamicRangeProfiles.STANDARD] = DynamicRange.SDR
+ DR_TO_PROFILE_MAP[DynamicRange.SDR] = listOf(DynamicRangeProfiles.STANDARD)
+
+ // HLG
+ PROFILE_TO_DR_MAP[DynamicRangeProfiles.HLG10] = DynamicRange.HLG_10_BIT
+ DR_TO_PROFILE_MAP[PROFILE_TO_DR_MAP[DynamicRangeProfiles.HLG10]] =
+ listOf(DynamicRangeProfiles.HLG10)
+
+ // HDR10
+ PROFILE_TO_DR_MAP[DynamicRangeProfiles.HDR10] = DynamicRange.HDR10_10_BIT
+ DR_TO_PROFILE_MAP[DynamicRange.HDR10_10_BIT] = listOf(DynamicRangeProfiles.HDR10)
+
+ // HDR10+
+ PROFILE_TO_DR_MAP[DynamicRangeProfiles.HDR10_PLUS] = DynamicRange.HDR10_PLUS_10_BIT
+ DR_TO_PROFILE_MAP[DynamicRange.HDR10_PLUS_10_BIT] = listOf(DynamicRangeProfiles.HDR10_PLUS)
+
+ // Dolby Vision 10-bit
+ // A list of the Camera2 10-bit dolby vision profiles ordered by priority. Any API that
+ // takes a DynamicRange with dolby vision encoding will attempt to convert to these
+ // profiles in order, using the first one that is supported. We will need to add a
+ // mechanism for choosing between these
+ val dolbyVision10BitProfilesOrdered = listOf(
+ DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM,
+ DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM_PO,
+ DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF,
+ DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF_PO
+ )
+ for (profile in dolbyVision10BitProfilesOrdered) {
+ PROFILE_TO_DR_MAP[profile] = DynamicRange.DOLBY_VISION_10_BIT
+ }
+ DR_TO_PROFILE_MAP[DynamicRange.DOLBY_VISION_10_BIT] =
+ dolbyVision10BitProfilesOrdered
+
+ // Dolby vision 8-bit
+ val dolbyVision8BitProfilesOrdered = listOf(
+ DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM,
+ DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM_PO,
+ DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF,
+ DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF_PO
+ )
+ for (profile in dolbyVision8BitProfilesOrdered) {
+ PROFILE_TO_DR_MAP[profile] = DynamicRange.DOLBY_VISION_8_BIT
+ }
+ DR_TO_PROFILE_MAP[DynamicRange.DOLBY_VISION_8_BIT] =
+ dolbyVision8BitProfilesOrdered
+ }
+
+ /**
+ * Converts Camera2 dynamic range profile constants to [DynamicRange].
+ */
+ @DoNotInline
+ fun profileToDynamicRange(profile: Long): DynamicRange? {
+ return PROFILE_TO_DR_MAP[profile]
+ }
+
+ /**
+ * Converts a [DynamicRange] to a Camera2 dynamic range profile.
+ *
+ *
+ * For dynamic ranges which can resolve to multiple profiles, the first supported profile
+ * from the passed [android.hardware.camera2.params.DynamicRangeProfiles] will be
+ * returned. The order in which profiles are checked for support is internally defined.
+ *
+ *
+ * This will only return profiles for fully defined dynamic ranges. For instance, if the
+ * format returned by [DynamicRange.getEncoding] is
+ * [DynamicRange.ENCODING_HDR_UNSPECIFIED], this will return `null`.
+ */
+ @DoNotInline
+ fun dynamicRangeToFirstSupportedProfile(
+ dynamicRange: DynamicRange,
+ dynamicRangeProfiles: DynamicRangeProfiles
+ ): Long? {
+ val orderedProfiles = DR_TO_PROFILE_MAP[dynamicRange]
+ if (orderedProfiles != null) {
+ val supportedList = dynamicRangeProfiles.supportedProfiles
+ for (profile in orderedProfiles) {
+ if (supportedList.contains(profile)) {
+ return profile
+ }
+ }
+ }
+
+ // No profile supported
+ return null
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
new file mode 100644
index 0000000..947d31c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeResolver.kt
@@ -0,0 +1,479 @@
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.DynamicRangeProfilesCompat
+import androidx.camera.core.DynamicRange
+import androidx.camera.core.impl.AttachedSurfaceInfo
+import androidx.camera.core.impl.UseCaseConfig
+import androidx.core.util.Preconditions
+
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class DynamicRangeResolver(val cameraMetadata: CameraMetadata) {
+ private val is10BitSupported: Boolean
+ private val dynamicRangesInfo: DynamicRangeProfilesCompat
+
+ init {
+ val availableCapabilities: IntArray? =
+ cameraMetadata[CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES]
+ is10BitSupported =
+ availableCapabilities?.contains(
+ CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
+ )
+ ?: false
+ dynamicRangesInfo = DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+ }
+
+ /**
+ * Returns whether 10-bit dynamic ranges are supported on this device.
+ */
+ fun is10BitDynamicRangeSupported(): Boolean = is10BitSupported
+
+ /**
+ * Returns a set of supported dynamic ranges for the dynamic ranges requested by the list of
+ * attached and new use cases.
+ *
+ *
+ * If a new use case requests a dynamic range that isn't supported, an
+ * IllegalArgumentException will be thrown.
+ */
+ fun resolveAndValidateDynamicRanges(
+ existingSurfaces: List<AttachedSurfaceInfo>,
+ newUseCaseConfigs: List<UseCaseConfig<*>>,
+ useCasePriorityOrder: List<Int>
+ ): Map<UseCaseConfig<*>, DynamicRange> {
+ // Create an ordered set of already-attached surface's dynamic ranges. These are assumed
+ // to be valid since they are already attached.
+ val orderedExistingDynamicRanges = mutableSetOf<DynamicRange>()
+ for (asi in existingSurfaces) {
+ orderedExistingDynamicRanges.add(asi.dynamicRange)
+ }
+
+ // Get the supported dynamic ranges from the device
+ val supportedDynamicRanges = dynamicRangesInfo.getSupportedDynamicRanges()
+
+ // Collect initial dynamic range constraints. This set will potentially shrink as we add
+ // more dynamic ranges. We start with the initial set of supported dynamic ranges to
+ // denote no constraints.
+ val combinedConstraints = supportedDynamicRanges.toMutableSet()
+ for (dynamicRange in orderedExistingDynamicRanges) {
+ updateConstraints(combinedConstraints, dynamicRange, dynamicRangesInfo)
+ }
+
+ // We want to resolve and validate dynamic ranges in the following order:
+ // 1. First validate fully defined dynamic ranges. No resolving is required here.
+ // 2. Resolve and validate partially defined dynamic ranges, such as HDR_UNSPECIFIED or
+ // dynamic ranges with concrete encodings but BIT_DEPTH_UNSPECIFIED. We can now potentially
+ // infer a dynamic range based on constraints of the fully defined dynamic ranges or
+ // the list of supported HDR dynamic ranges.
+ // 3. Finally, resolve and validate UNSPECIFIED dynamic ranges. These will resolve
+ // to dynamic ranges from the first 2 groups, or fall back to SDR if no other dynamic
+ // ranges are defined.
+ //
+ // To accomplish this, we need to partition the use cases into 3 categories.
+ val orderedFullyDefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+ val orderedPartiallyDefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+ val orderedUndefinedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+ for (priorityIdx in useCasePriorityOrder) {
+ val config = newUseCaseConfigs[priorityIdx]
+ val requestedDynamicRange = config.dynamicRange
+ if (isFullyUnspecified(requestedDynamicRange)
+ ) {
+ orderedUndefinedUseCaseConfigs.add(config)
+ } else if (isPartiallySpecified(requestedDynamicRange)
+ ) {
+ orderedPartiallyDefinedUseCaseConfigs.add(config)
+ } else {
+ orderedFullyDefinedUseCaseConfigs.add(config)
+ }
+ }
+ val resolvedDynamicRanges: MutableMap<UseCaseConfig<*>, DynamicRange> = mutableMapOf()
+ // Keep track of new dynamic ranges for more fine-grained error messages in exceptions.
+ // This allows us to differentiate between dynamic ranges from already-attached use cases
+ // and requested dynamic ranges from newly added use cases.
+ val orderedNewDynamicRanges: MutableSet<DynamicRange> = mutableSetOf()
+ // Now resolve and validate all of the dynamic ranges in order of the 3 partitions form
+ // above.
+ val orderedUseCaseConfigs: MutableList<UseCaseConfig<*>> = mutableListOf()
+ orderedUseCaseConfigs.addAll(orderedFullyDefinedUseCaseConfigs)
+ orderedUseCaseConfigs.addAll(orderedPartiallyDefinedUseCaseConfigs)
+ orderedUseCaseConfigs.addAll(orderedUndefinedUseCaseConfigs)
+ for (config in orderedUseCaseConfigs) {
+ val resolvedDynamicRange: DynamicRange = resolveDynamicRangeAndUpdateConstraints(
+ supportedDynamicRanges, orderedExistingDynamicRanges,
+ orderedNewDynamicRanges, config, combinedConstraints
+ )
+ resolvedDynamicRanges[config] = resolvedDynamicRange
+ if (!orderedExistingDynamicRanges.contains(resolvedDynamicRange)) {
+ orderedNewDynamicRanges.add(resolvedDynamicRange)
+ }
+ }
+ return resolvedDynamicRanges
+ }
+
+ private fun resolveDynamicRangeAndUpdateConstraints(
+ supportedDynamicRanges: Set<DynamicRange?>,
+ orderedExistingDynamicRanges: Set<DynamicRange>,
+ orderedNewDynamicRanges: Set<DynamicRange>,
+ config: UseCaseConfig<*>,
+ outCombinedConstraints: MutableSet<DynamicRange>
+ ): DynamicRange {
+ val requestedDynamicRange = config.dynamicRange
+ val resolvedDynamicRange: DynamicRange? = resolveDynamicRange(
+ requestedDynamicRange,
+ outCombinedConstraints, orderedExistingDynamicRanges, orderedNewDynamicRanges,
+ config.targetName
+ )
+ if (resolvedDynamicRange != null) {
+ updateConstraints(outCombinedConstraints, resolvedDynamicRange, dynamicRangesInfo)
+ } else {
+ throw IllegalArgumentException(
+ "Unable to resolve supported " +
+ "dynamic range. The dynamic range may not be supported on the device " +
+ "or may not be allowed concurrently with other attached use cases.\n" +
+ "Use case:\n" +
+ " ${config.targetName}\n" +
+ "Requested dynamic range:\n" +
+ " $requestedDynamicRange\n" +
+ "Supported dynamic ranges:\n" +
+ " $supportedDynamicRanges\n" +
+ "Constrained set of concurrent dynamic ranges:\n" +
+ " $outCombinedConstraints",
+ )
+ }
+ return resolvedDynamicRange
+ }
+
+ /**
+ * Resolves the requested dynamic range into a fully specified dynamic range.
+ *
+ *
+ * This uses existing fully-specified dynamic ranges, new fully-specified dynamic ranges,
+ * dynamic range constraints and the list of supported dynamic ranges to exhaustively search
+ * for a dynamic range if the requested dynamic range is not fully specified, i.e., it has an
+ * UNSPECIFIED encoding or UNSPECIFIED bitrate.
+ *
+ *
+ * Any dynamic range returned will be validated to work according to the constraints and
+ * supported dynamic ranges provided.
+ *
+ *
+ * If no suitable dynamic range can be found, returns `null`.
+ */
+ private fun resolveDynamicRange(
+ requestedDynamicRange: DynamicRange,
+ combinedConstraints: Set<DynamicRange>,
+ orderedExistingDynamicRanges: Set<DynamicRange>,
+ orderedNewDynamicRanges: Set<DynamicRange>,
+ rangeOwnerLabel: String
+ ): DynamicRange? {
+
+ // Dynamic range is already resolved if it is fully specified.
+ if (requestedDynamicRange.isFullySpecified) {
+ return if (combinedConstraints.contains(requestedDynamicRange)) {
+ requestedDynamicRange
+ } else null
+ // Requested dynamic range is full specified but unsupported. No need to continue
+ // trying to resolve.
+ }
+
+ // Explicitly handle the case of SDR with unspecified bit depth.
+ // SDR is only supported as 8-bit.
+ val requestedEncoding = requestedDynamicRange.encoding
+ val requestedBitDepth = requestedDynamicRange.bitDepth
+ if (requestedEncoding == DynamicRange.ENCODING_SDR &&
+ requestedBitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED
+ ) {
+ return if (combinedConstraints.contains(DynamicRange.SDR)) {
+ DynamicRange.SDR
+ } else null
+ // If SDR isn't supported, we can't resolve to any other dynamic range.
+ }
+
+ // First attempt to find another fully specified HDR dynamic range to resolve to from
+ // existing dynamic ranges
+ var resolvedDynamicRange = findSupportedHdrMatch(
+ requestedDynamicRange,
+ orderedExistingDynamicRanges, combinedConstraints
+ )
+ if (resolvedDynamicRange != null) {
+ Log.debug {
+ "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel " +
+ "from existing attached surface.\n" +
+ "$requestedDynamicRange\n->\n$resolvedDynamicRange"
+ }
+
+ return resolvedDynamicRange
+ }
+
+ // Attempt to find another fully specified HDR dynamic range to resolve to from
+ // new dynamic ranges
+ resolvedDynamicRange =
+ findSupportedHdrMatch(
+ requestedDynamicRange,
+ orderedNewDynamicRanges, combinedConstraints
+ )
+ if (resolvedDynamicRange != null) {
+ Log.debug {
+ "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel from " +
+ "concurrently bound use case." +
+ "\n$requestedDynamicRange\n->\n$resolvedDynamicRange"
+ }
+
+ return resolvedDynamicRange
+ }
+
+ // Now that we have checked existing HDR dynamic ranges, we must resolve fully unspecified
+ // and unspecified 8-bit dynamic ranges to SDR if it is supported. This ensures the
+ // default behavior for most use cases is to choose SDR when an HDR dynamic range isn't
+ // already present or explicitly requested.
+ if (canResolveWithinConstraints(
+ requestedDynamicRange, DynamicRange.SDR,
+ combinedConstraints
+ )
+ ) {
+ Log.debug {
+ "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel to " +
+ "no compatible HDR dynamic ranges.\n$requestedDynamicRange\n" +
+ "->\n${DynamicRange.SDR}"
+ }
+ return DynamicRange.SDR
+ }
+
+ // For unspecified HDR encodings (10-bit or unspecified bit depth), we have a
+ // couple options: the device recommended 10-bit encoding or the mandated HLG encoding.
+ if (requestedEncoding == DynamicRange.ENCODING_HDR_UNSPECIFIED &&
+ ((requestedBitDepth == DynamicRange.BIT_DEPTH_10_BIT ||
+ requestedBitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED))
+ ) {
+ val hdrDefaultRanges: MutableSet<DynamicRange> = mutableSetOf()
+
+ // Attempt to use the recommended 10-bit dynamic range
+ var recommendedRange: DynamicRange? = null
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ recommendedRange =
+ Api33Impl.getRecommended10BitDynamicRange(
+ cameraMetadata
+ )
+ if (recommendedRange != null) {
+ hdrDefaultRanges.add(recommendedRange)
+ }
+ }
+ // Attempt to fall back to HLG since it is a mandated required 10-bit
+ // dynamic range.
+ hdrDefaultRanges.add(DynamicRange.HLG_10_BIT)
+ resolvedDynamicRange =
+ findSupportedHdrMatch(
+ requestedDynamicRange, hdrDefaultRanges, combinedConstraints
+ )
+ if (resolvedDynamicRange != null) {
+ Log.debug {
+ "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel" +
+ "from ${
+ if ((resolvedDynamicRange == recommendedRange)) "recommended"
+ else "required"
+ } 10-bit supported dynamic range.\n" +
+ "${requestedDynamicRange}\n" +
+ "->\n" +
+ "$resolvedDynamicRange"
+ }
+ return resolvedDynamicRange
+ }
+ }
+
+ // Finally, attempt to find an HDR dynamic range for HDR or 10-bit dynamic ranges from
+ // the constraints of the other validated dynamic ranges. If there are no other dynamic
+ // ranges, this should be the full list of supported dynamic ranges.
+ // The constraints are unordered, so it may not produce an "optimal" dynamic range. This
+ // works for 8-bit, 10-bit or partially specified HDR dynamic ranges.
+ for (candidateRange: DynamicRange in combinedConstraints) {
+ check(candidateRange.isFullySpecified) {
+ "Candidate dynamic range must be fully specified."
+ }
+
+ // Only consider HDR constraints
+ if ((candidateRange == DynamicRange.SDR)) {
+ continue
+ }
+ if (canResolveDynamicRange(
+ requestedDynamicRange,
+ candidateRange
+ )
+ ) {
+ Log.debug {
+ "DynamicRangeResolver: Resolved dynamic range for use case $rangeOwnerLabel " +
+ "from validated dynamic range constraints or supported HDR dynamic " +
+ "ranges.\n$requestedDynamicRange\n->\n$candidateRange"
+ }
+ return candidateRange
+ }
+ }
+
+ // Unable to resolve dynamic range
+ return null
+ }
+
+ /**
+ * Updates the provided dynamic range constraints by combining them with the new constraints
+ * from the new dynamic range.
+ *
+ * @param combinedConstraints The constraints that will be updated. This set must not be empty.
+ * @param newDynamicRange The new dynamic range for which we'll apply new constraints
+ * @param dynamicRangesInfo Information about dynamic ranges to retrieve new constraints.
+ */
+ private fun updateConstraints(
+ combinedConstraints: MutableSet<DynamicRange>,
+ newDynamicRange: DynamicRange,
+ dynamicRangesInfo: DynamicRangeProfilesCompat
+ ) {
+ Preconditions.checkState(
+ combinedConstraints.isNotEmpty(), "Cannot update already-empty constraints."
+ )
+ val newConstraints =
+ dynamicRangesInfo.getDynamicRangeCaptureRequestConstraints(newDynamicRange)
+ if (newConstraints.isNotEmpty()) {
+ // Retain for potential exception message
+ val previousConstraints = combinedConstraints.toSet()
+ // Take the intersection of constraints
+ combinedConstraints.retainAll(newConstraints)
+ // This shouldn't happen if we're diligent about checking that dynamic range
+ // is within the existing constraints before attempting to call
+ // updateConstraints. If it happens, then the dynamic ranges are not mutually
+ // compatible.
+ require(combinedConstraints.isNotEmpty()) {
+ "Constraints of dynamic " +
+ "range cannot be combined with existing constraints.\n" +
+ "Dynamic range:\n" +
+ " $newDynamicRange\n" +
+ "Constraints:\n" +
+ " $newConstraints\n" +
+ "Existing constraints:\n" +
+ " $previousConstraints"
+ }
+ }
+ }
+
+ private fun findSupportedHdrMatch(
+ rangeToMatch: DynamicRange,
+ fullySpecifiedCandidateRanges: Collection<DynamicRange>,
+ constraints: Set<DynamicRange>
+ ): DynamicRange? {
+ // SDR can never match with HDR
+ if (rangeToMatch.encoding == DynamicRange.ENCODING_SDR) {
+ return null
+ }
+ for (candidateRange in fullySpecifiedCandidateRanges) {
+ val candidateEncoding = candidateRange.encoding
+ check(candidateRange.isFullySpecified) {
+ "Fully specified DynamicRange must have fully defined encoding."
+ }
+ if (candidateEncoding == DynamicRange.ENCODING_SDR) {
+ // Only consider HDR encodings
+ continue
+ }
+ if (canResolveWithinConstraints(
+ rangeToMatch,
+ candidateRange,
+ constraints
+ )
+ ) {
+ return candidateRange
+ }
+ }
+ return null
+ }
+
+ /**
+ * Returns `true` if the dynamic range is ENCODING_UNSPECIFIED and BIT_DEPTH_UNSPECIFIED.
+ */
+ private fun isFullyUnspecified(dynamicRange: DynamicRange): Boolean {
+ return (dynamicRange == DynamicRange.UNSPECIFIED)
+ }
+
+ /**
+ * Returns `true` if the dynamic range has an unspecified HDR encoding, a concrete
+ * encoding with unspecified bit depth, or a concrete bit depth.
+ */
+ private fun isPartiallySpecified(dynamicRange: DynamicRange): Boolean {
+ return dynamicRange.encoding == DynamicRange.ENCODING_HDR_UNSPECIFIED ||
+ (dynamicRange.encoding != DynamicRange.ENCODING_UNSPECIFIED &&
+ dynamicRange.bitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED) ||
+ (dynamicRange.encoding == DynamicRange.ENCODING_UNSPECIFIED &&
+ dynamicRange.bitDepth != DynamicRange.BIT_DEPTH_UNSPECIFIED)
+ }
+
+ /**
+ * Returns `true` if the test dynamic range can resolve to the candidate, fully specified
+ * dynamic range, taking into account constraints.
+ *
+ *
+ * A range can resolve if test fields are unspecified and appropriately match the fields
+ * of the fully specified dynamic range, or the test fields exactly match the fields of
+ * the fully specified dynamic range.
+ */
+ private fun canResolveWithinConstraints(
+ rangeToResolve: DynamicRange,
+ candidateRange: DynamicRange,
+ constraints: Set<DynamicRange>
+ ): Boolean {
+ if (!constraints.contains(candidateRange)) {
+ Log.debug {
+ "DynamicRangeResolver: Candidate Dynamic range is not within constraints.\n" +
+ "Dynamic range to resolve:\n" +
+ " $rangeToResolve\n" +
+ "Candidate dynamic range:\n" +
+ " $candidateRange"
+ }
+ return false
+ }
+ return canResolveDynamicRange(rangeToResolve, candidateRange)
+ }
+
+ /**
+ * Returns `true` if the test dynamic range can resolve to the fully specified dynamic
+ * range.
+ *
+ *
+ * A range can resolve if test fields are unspecified and appropriately match the fields
+ * of the fully specified dynamic range, or the test fields exactly match the fields of
+ * the fully specified dynamic range.
+ */
+ private fun canResolveDynamicRange(
+ testRange: DynamicRange,
+ fullySpecifiedRange: DynamicRange
+ ): Boolean {
+ check(fullySpecifiedRange.isFullySpecified) {
+ "Fully specified range $fullySpecifiedRange not actually fully specified."
+ }
+ if ((testRange.encoding == DynamicRange.ENCODING_HDR_UNSPECIFIED &&
+ fullySpecifiedRange.encoding == DynamicRange.ENCODING_SDR)
+ ) {
+ return false
+ }
+ return if ((testRange.encoding != DynamicRange.ENCODING_HDR_UNSPECIFIED
+ ) && (testRange.encoding != DynamicRange.ENCODING_UNSPECIFIED
+ ) && (testRange.encoding != fullySpecifiedRange.encoding)
+ ) {
+ false
+ } else (testRange.bitDepth == DynamicRange.BIT_DEPTH_UNSPECIFIED ||
+ testRange.bitDepth == fullySpecifiedRange.bitDepth)
+ }
+
+ @RequiresApi(33)
+ internal object Api33Impl {
+ @DoNotInline
+ fun getRecommended10BitDynamicRange(
+ cameraMetadata: CameraMetadata
+ ): DynamicRange? {
+ val recommendedProfile = cameraMetadata[
+ CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE]
+ return if (recommendedProfile != null) {
+ DynamicRangeConversions.profileToDynamicRange(recommendedProfile)
+ } else null
+ }
+ }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
index 4c76b49..6b4e41a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombinationTest.kt
@@ -20,8 +20,11 @@
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES
+import android.hardware.camera2.CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.params.DynamicRangeProfiles
import android.hardware.camera2.params.StreamConfigurationMap
import android.media.CamcorderProfile.QUALITY_1080P
import android.media.CamcorderProfile.QUALITY_2160P
@@ -42,6 +45,17 @@
import androidx.camera.camera2.pipe.integration.adapter.GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList
import androidx.camera.camera2.pipe.integration.adapter.GuaranteedConfigurationsUtil.getRAWSupportedCombinationList
import androidx.camera.camera2.pipe.integration.config.CameraAppComponent
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_SDR_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_HDR10_PLUS_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_SDR_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.LATENCY_NONE
import androidx.camera.camera2.pipe.testing.FakeCameraBackend
import androidx.camera.camera2.pipe.testing.FakeCameraDevices
import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
@@ -49,6 +63,7 @@
import androidx.camera.core.CameraSelector.LensFacing
import androidx.camera.core.CameraX
import androidx.camera.core.CameraXConfig
+import androidx.camera.core.DynamicRange
import androidx.camera.core.UseCase
import androidx.camera.core.concurrent.CameraCoordinator
import androidx.camera.core.impl.AttachedSurfaceInfo
@@ -57,6 +72,8 @@
import androidx.camera.core.impl.EncoderProfilesProxy
import androidx.camera.core.impl.EncoderProfilesProxy.VideoProfileProxy
import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.ImageInputConfig
+import androidx.camera.core.impl.StreamSpec
import androidx.camera.core.impl.SurfaceConfig
import androidx.camera.core.impl.UseCaseConfig
import androidx.camera.core.impl.UseCaseConfigFactory
@@ -85,6 +102,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
@@ -197,7 +215,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -214,7 +235,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -231,7 +255,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -248,7 +275,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -265,7 +295,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -282,7 +315,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -299,7 +335,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -316,7 +355,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -333,7 +375,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isFalse()
}
@@ -353,7 +398,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -373,7 +421,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -393,7 +444,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -413,7 +467,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -430,7 +487,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.DEFAULT, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -450,7 +510,10 @@
for (combination in combinationList) {
val isSupported =
supportedSurfaceCombination.checkSupported(
- CameraMode.CONCURRENT_CAMERA, combination.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.CONCURRENT_CAMERA,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), combination.surfaceConfigList
)
assertThat(isSupported).isTrue()
}
@@ -474,7 +537,10 @@
GuaranteedConfigurationsUtil.getUltraHighResolutionSupportedCombinationList().forEach {
assertThat(
supportedSurfaceCombination.checkSupported(
- CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA, it.surfaceConfigList
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA,
+ DynamicRange.BIT_DEPTH_8_BIT
+ ), it.surfaceConfigList
)
).isTrue()
}
@@ -1460,9 +1526,19 @@
attachedSurfaceInfoList: List<AttachedSurfaceInfo> = emptyList(),
hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
capabilities: IntArray? = null,
- compareWithAtMost: Boolean = false
+ compareWithAtMost: Boolean = false,
+ compareExpectedFps: Range<Int>? = null,
+ cameraMode: Int = CameraMode.DEFAULT,
+ useCasesExpectedDynamicRangeMap: Map<UseCase, DynamicRange> = emptyMap(),
+ dynamicRangeProfiles: DynamicRangeProfiles? = null,
+ default10BitProfile: Long? = null,
) {
- setupCamera(hardwareLevel = hardwareLevel, capabilities = capabilities)
+ setupCamera(
+ hardwareLevel = hardwareLevel,
+ capabilities = capabilities,
+ dynamicRangeProfiles = dynamicRangeProfiles,
+ default10BitProfile = default10BitProfile
+ )
val supportedSurfaceCombination = SupportedSurfaceCombination(
context, fakeCameraMetadata,
mockEncoderProfilesAdapter
@@ -1472,7 +1548,7 @@
val useCaseConfigToOutputSizesMap =
getUseCaseConfigToOutputSizesMap(useCaseConfigMap.values.toList())
val suggestedStreamSpecs = supportedSurfaceCombination.getSuggestedStreamSpecifications(
- CameraMode.DEFAULT,
+ cameraMode,
attachedSurfaceInfoList,
useCaseConfigToOutputSizesMap
).first
@@ -1485,6 +1561,19 @@
} else {
assertThat(sizeIsAtMost(resultSize, expectedSize)).isTrue()
}
+
+ compareExpectedFps?.let { _ ->
+ assertThat(
+ suggestedStreamSpecs[useCaseConfigMap[it]]!!.expectedFrameRateRange
+ ).isEqualTo(compareExpectedFps)
+ }
+ }
+
+ useCasesExpectedDynamicRangeMap.keys.forEach {
+ val resultDynamicRange = suggestedStreamSpecs[useCaseConfigMap[it]]!!.dynamicRange
+ val expectedDynamicRange = useCasesExpectedDynamicRangeMap[it]
+
+ assertThat(resultDynamicRange).isEqualTo(expectedDynamicRange)
}
}
@@ -1519,6 +1608,1165 @@
// //////////////////////////////////////////////////////////////////////////////////////////
//
+ // StreamSpec selection tests for DynamicRange
+ //
+ // //////////////////////////////////////////////////////////////////////////////////////////
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun check10BitDynamicRangeCombinationsSupported() {
+ setupCamera(
+ capabilities = intArrayOf(
+ CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT
+ )
+ )
+ val supportedSurfaceCombination = SupportedSurfaceCombination(
+ context, fakeCameraMetadata,
+ mockEncoderProfilesAdapter
+ )
+
+ GuaranteedConfigurationsUtil.get10BitSupportedCombinationList().forEach {
+ assertThat(
+ supportedSurfaceCombination.checkSupported(
+ SupportedSurfaceCombination.FeatureSettings(
+ CameraMode.DEFAULT,
+ DynamicRange.BIT_DEPTH_10_BIT
+ ),
+ it.surfaceConfigList
+ )
+ ).isTrue()
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun getSupportedStreamSpecThrows_whenUsingUnsupportedDynamicRange() {
+ val useCase =
+ createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ val useCaseExpectedResultMap = mapOf(
+ useCase to Size(0, 0) // Should throw before verifying size
+ )
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun getSupportedStreamSpecThrows_whenUsingConcurrentCameraAndSupported10BitRange() {
+ Shadows.shadowOf(context.packageManager).setSystemFeature(
+ PackageManager.FEATURE_CAMERA_CONCURRENT, true
+ )
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to Size(0, 0) // Should throw before verifying size
+ )
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ cameraMode = CameraMode.CONCURRENT_CAMERA,
+ dynamicRangeProfiles = HLG10_CONSTRAINED
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun getSupportedStreamSpecThrows_whenUsingUltraHighResolutionAndSupported10BitRange() {
+ Shadows.shadowOf(context.packageManager).setSystemFeature(
+ PackageManager.FEATURE_CAMERA_CONCURRENT, true
+ )
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to Size(0, 0) // Should throw before verifying size
+ )
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ cameraMode = CameraMode.ULTRA_HIGH_RESOLUTION_CAMERA,
+ dynamicRangeProfiles = HLG10_CONSTRAINED
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsHlg_dueToMandatory10Bit() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.HLG_10_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HLG10_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsHdr10_dueToRecommended10BitDynamicRange() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.HDR10_10_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HDR10_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+ default10BitProfile = DynamicRangeProfiles.HDR10
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision8_dueToSupportedDynamicRanges() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_HDR_UNSPECIFIED,
+ DynamicRange.BIT_DEPTH_8_BIT
+ )
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision8_fromUnspecifiedBitDepth() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_DOLBY_VISION,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision10_fromUnspecifiedBitDepth() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_DOLBY_VISION,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.DOLBY_VISION_10_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_10B_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision8_fromUnspecifiedHdrWithUnspecifiedBitDepth() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_HDR_UNSPECIFIED,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision10_fromUnspecifiedHdrWithUnspecifiedBitDepth() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_HDR_UNSPECIFIED,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.DOLBY_VISION_10_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+ default10BitProfile = DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision8_withUndefinedBitDepth_andFullyDefinedHlg10() {
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_DOLBY_VISION,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ videoUseCase to DynamicRange.HLG_10_BIT,
+ previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_returnsDolbyVision10_dueToDynamicRangeConstraints() {
+ // VideoCapture partially defined dynamic range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ // Preview fully defined dynamic range
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.DOLBY_VISION_8_BIT,
+
+ )
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ videoUseCase to DynamicRange.DOLBY_VISION_10_BIT,
+ previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_resolvesUnspecifiedDynamicRange_afterPartiallySpecifiedDynamicRange() {
+ // VideoCapture partially defined dynamic range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HDR_UNSPECIFIED_10_BIT
+ )
+ // Preview unspecified dynamic range
+ val previewUseCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ previewUseCase to DynamicRange.HLG_10_BIT,
+ videoUseCase to DynamicRange.HLG_10_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HLG10_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_resolvesUnspecifiedDynamicRangeToSdr() {
+ // Preview unspecified dynamic range
+ val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.SDR
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HLG10_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Test
+ fun dynamicRangeResolver_resolvesToSdr_when10BitNotSupported() {
+ // Preview unspecified dynamic range
+ val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.SDR
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Test
+ fun dynamicRangeResolver_resolvesToSdr8Bit_whenSdrWithUnspecifiedBitDepthProvided() {
+ // Preview unspecified dynamic range
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_SDR,
+ DynamicRange.BIT_DEPTH_UNSPECIFIED
+ )
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ useCase to maximumSize
+ )
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.SDR
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_resolvesUnspecified8Bit_usingConstraintsFrom10BitDynamicRange() {
+ // VideoCapture has 10-bit HDR range with constraint for 8-bit non-SDR range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.DOLBY_VISION_10_BIT
+ )
+ // Preview unspecified encoding but 8-bit bit depth
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_UNSPECIFIED,
+ DynamicRange.BIT_DEPTH_8_BIT
+ )
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ videoUseCase to DynamicRange.DOLBY_VISION_10_BIT,
+ previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ dynamicRangeProfiles = DOLBY_VISION_CONSTRAINED
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_resolvesToSdr_forUnspecified8Bit_whenNoOtherDynamicRangesPresent() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange(
+ DynamicRange.ENCODING_UNSPECIFIED,
+ DynamicRange.BIT_DEPTH_8_BIT
+ )
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ useCase to maximumSize
+ )
+
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ useCase to DynamicRange.SDR
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_SDR_UNCONSTRAINED
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeResolver_resolvesUnspecified8BitToDolbyVision8Bit_whenAlreadyPresent() {
+ // VideoCapture fully resolved Dolby Vision 8-bit
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.DOLBY_VISION_8_BIT
+ )
+ // Preview unspecified encoding / 8-bit
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.UNSPECIFIED
+ )
+
+ // Since there are no 10-bit dynamic ranges, the 10-bit resolution table isn't used.
+ // Instead, this will use the camera default LIMITED table which is limited to preview
+ // size for 2 PRIV use cases.
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to previewSize,
+ previewUseCase to previewSize
+ )
+
+ val useCaseExpectedDynamicRangeMap = mapOf(
+ videoUseCase to DynamicRange.DOLBY_VISION_8_BIT,
+ previewUseCase to DynamicRange.DOLBY_VISION_8_BIT
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ useCasesExpectedDynamicRangeMap = useCaseExpectedDynamicRangeMap,
+ dynamicRangeProfiles = DOLBY_VISION_8B_SDR_UNCONSTRAINED
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun tenBitTable_isUsed_whenAttaching10BitUseCaseToAlreadyAttachedSdrUseCases() {
+ // JPEG use case can't be attached with an existing PRIV + YUV in the 10-bit tables
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ // Size would be valid for LIMITED table
+ useCase to recordSize
+ )
+ // existing surfaces (Preview + ImageAnalysis)
+ val attachedPreview = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.PRIV,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.PRIVATE,
+ previewSize,
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+ val attachedAnalysis = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.YUV,
+ SurfaceConfig.ConfigSize.RECORD
+ ),
+ ImageFormat.YUV_420_888,
+ recordSize,
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ attachedSurfaceInfoList = listOf(attachedPreview, attachedAnalysis),
+ // LIMITED allows this combination, but 10-bit table does not
+ hardwareLevel = CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ dynamicRangeProfiles = HLG10_SDR_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun dynamicRangeConstraints_causeAutoResolutionToThrow() {
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ // Size would be valid for 10-bit table within constraints
+ useCase to recordSize
+ )
+ // existing surfaces (PRIV + PRIV)
+ val attachedPriv1 = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.PRIV,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.PRIVATE,
+ previewSize,
+ DynamicRange.HDR10_10_BIT,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+ val attachedPriv2 = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.PRIV,
+ SurfaceConfig.ConfigSize.RECORD
+ ),
+ ImageFormat.YUV_420_888,
+ recordSize,
+ DynamicRange.HDR10_PLUS_10_BIT,
+ listOf(UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+
+ // These constraints say HDR10 and HDR10_PLUS can be combined, but not HLG
+ val constraintsTable =
+ DynamicRangeProfiles(
+ longArrayOf(
+ DynamicRangeProfiles.HLG10,
+ DynamicRangeProfiles.HLG10,
+ LATENCY_NONE,
+ DynamicRangeProfiles.HDR10,
+ DynamicRangeProfiles.HDR10 or DynamicRangeProfiles.HDR10_PLUS,
+ LATENCY_NONE,
+ DynamicRangeProfiles.HDR10_PLUS,
+ DynamicRangeProfiles.HDR10_PLUS or DynamicRangeProfiles.HDR10,
+ LATENCY_NONE
+ )
+ )
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ attachedSurfaceInfoList = listOf(attachedPriv1, attachedPriv2),
+ dynamicRangeProfiles = constraintsTable,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canAttachHlgDynamicRange_toExistingSdrStreams() {
+ // JPEG use case can be attached with an existing PRIV + PRIV in the 10-bit tables
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ val useCaseExpectedSizeMap = mapOf(
+ // Size is valid for 10-bit table within constraints
+ useCase to recordSize
+ )
+ // existing surfaces (PRIV + PRIV)
+ val attachedPriv1 = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.PRIV,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.PRIVATE,
+ previewSize,
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+ val attachedPriv2 = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.PRIV,
+ SurfaceConfig.ConfigSize.RECORD
+ ),
+ ImageFormat.YUV_420_888,
+ recordSize,
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.IMAGE_ANALYSIS),
+ useCase.currentConfig,
+ /*targetFrameRate=*/null
+ )
+
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ attachedSurfaceInfoList = listOf(attachedPriv1, attachedPriv2),
+ dynamicRangeProfiles = HLG10_SDR_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun requiredSdrDynamicRangeThrows_whenCombinedWithConstrainedHlg() {
+ // VideoCapture HLG dynamic range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ // Preview SDR dynamic range
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.SDR
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+
+ // Fails because HLG10 is constrained to only HLG10
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HLG10_CONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ )
+ }
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun requiredSdrDynamicRange_canBeCombinedWithUnconstrainedHlg() {
+ // VideoCapture HLG dynamic range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HLG_10_BIT
+ )
+ // Preview SDR dynamic range
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.SDR
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+
+ // Should succeed due to HLG10 being unconstrained
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HLG10_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ )
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun multiple10BitUnconstrainedDynamicRanges_canBeCombined() {
+ // VideoCapture HDR10 dynamic range
+ val videoUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.VIDEO_CAPTURE,
+ dynamicRange = DynamicRange.HDR10_10_BIT
+ )
+ // Preview HDR10_PLUS dynamic range
+ val previewUseCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ dynamicRange = DynamicRange.HDR10_PLUS_10_BIT
+ )
+
+ val useCaseExpectedSizeMap = mutableMapOf(
+ videoUseCase to recordSize,
+ previewUseCase to previewSize
+ )
+
+ // Succeeds because both HDR10 and HDR10_PLUS are unconstrained
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedSizeMap,
+ dynamicRangeProfiles = HDR10_HDR10_PLUS_UNCONSTRAINED,
+ capabilities =
+ intArrayOf(CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT),
+ )
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // Resolution selection tests for FPS settings
+ //
+ // //////////////////////////////////////////////////////////////////////////////////////////
+
+ @Test
+ fun getSupportedOutputSizes_single_valid_targetFPS() {
+ // a valid target means the device is capable of that fps
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(25, 30)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase, Size(3840, 2160))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_single_invalid_targetFPS() {
+ // an invalid target means the device would neve be able to reach that fps
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(65, 70)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase, Size(800, 450))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_multiple_targetFPS_first_is_larger() {
+ // a valid target means the device is capable of that fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+ val useCase2 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(15, 25)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // both selected size should be no larger than 1920 x 1445
+ put(useCase1, Size(1920, 1445))
+ put(useCase2, Size(1920, 1445))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_multiple_targetFPS_first_is_smaller() {
+ // a valid target means the device is capable of that fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+ val useCase2 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(45, 50)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // both selected size should be no larger than 1920 x 1440
+ put(useCase1, Size(1920, 1440))
+ put(useCase2, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_multiple_targetFPS_intersect() {
+ // first and second new use cases have target fps that intersect each other
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 40)
+ )
+ val useCase2 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(35, 45)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // effective target fps becomes 35-40
+ // both selected size should be no larger than 1920 x 1080
+ put(useCase1, Size(1920, 1080))
+ put(useCase2, Size(1920, 1080))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_multiple_cases_first_has_targetFPS() {
+ // first new use case has a target fps, second new use case does not
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+ val useCase2 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // both selected size should be no larger than 1920 x 1440
+ put(useCase1, Size(1920, 1440))
+ put(useCase2, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_multiple_cases_second_has_targetFPS() {
+ // second new use case does not have a target fps, first new use case does not
+ val useCase1 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+ val useCase2 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // both selected size should be no larger than 1920 x 1440
+ put(useCase1, Size(1920, 1440))
+ put(useCase2, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_attached_with_targetFPS_no_new_targetFPS() {
+ // existing surface with target fps + new use case without a target fps
+ val useCase = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // size should be no larger than 1280 x 960
+ put(useCase, Size(1280, 960))
+ }
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.JPEG,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.JPEG,
+ Size(1280, 720),
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ Range(40, 50)
+ )
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_no_intersect() {
+ // existing surface with target fps + new use case with target fps that does not intersect
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 35)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // size of new surface should be no larger than 1280 x 960
+ put(useCase, Size(1280, 960))
+ }
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.JPEG,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.JPEG,
+ Size(1280, 720),
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ Range(40, 50)
+ )
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_with_intersect() {
+ // existing surface with target fps + new use case with target fps that intersect each other
+ val useCase = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(45, 50)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ // size of new surface should be no larger than 1280 x 720
+ put(useCase, Size(1280, 720))
+ }
+ // existing surface w/ target fps
+ val attachedSurfaceInfo = AttachedSurfaceInfo.create(
+ SurfaceConfig.create(
+ SurfaceConfig.ConfigType.JPEG,
+ SurfaceConfig.ConfigSize.PREVIEW
+ ),
+ ImageFormat.JPEG,
+ Size(1280, 720),
+ DynamicRange.SDR,
+ listOf(UseCaseConfigFactory.CaptureType.PREVIEW),
+ useCase.currentConfig,
+ Range(40, 50)
+ )
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ attachedSurfaceInfoList = listOf(attachedSurfaceInfo),
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
+ compareWithAtMost = true
+ )
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_device_supported_expectedFrameRateRange() {
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(15, 25)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(4032, 3024))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(10, 22)
+ )
+ // expected fps 10,22 because it has the largest intersection
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_exact_device_supported_expectedFrameRateRange() {
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(30, 40)
+ )
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(30, 30)
+ )
+ // expected fps 30,30 because the fps ceiling is 30
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_no_device_supported_expectedFrameRateRange() {
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(65, 65)
+ )
+
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(800, 450))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(60, 60)
+ )
+ // expected fps 60,60 because it is the closest range available
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_multiple_device_supported_expectedFrameRateRange() {
+
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(36, 45)
+ )
+
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(1280, 960))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(30, 40)
+ )
+ // expected size will give a maximum of 40 fps
+ // expected range 30,40. another range with the same intersection size was 30,50, but 30,40
+ // was selected instead because its range has a larger ratio of intersecting value vs
+ // non-intersecting
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_no_device_intersection_expectedFrameRateRange() {
+ // target fps is between ranges, but within device capability (for some reason lol)
+
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(26, 27)
+ )
+
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(30, 30)
+ )
+ // 30,30 was expected because it is the closest and shortest range to our target fps
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_no_device_intersection_equidistant_expectedFrameRateRange() {
+
+ // use case with target fps
+ val useCase1 = createUseCase(
+ UseCaseConfigFactory.CaptureType.PREVIEW,
+ targetFrameRate = Range<Int>(26, 26)
+ )
+
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(1920, 1440))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareWithAtMost = true,
+ compareExpectedFps = Range(30, 30)
+ )
+ // 30,30 selected because although there are other ranges that have the same distance to
+ // the target, 30,30 is the shortest range that also happens to be on the upper side of the
+ // target range
+ }
+
+ @Test
+ fun getSuggestedStreamSpec_has_no_expectedFrameRateRange() {
+ // a valid target means the device is capable of that fps
+
+ // use case with no target fps
+ val useCase1 = createUseCase(UseCaseConfigFactory.CaptureType.PREVIEW)
+
+ val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+ put(useCase1, Size(4032, 3024))
+ }
+ getSuggestedSpecsAndVerify(
+ useCaseExpectedResultMap,
+ hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+ compareExpectedFps = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+ )
+ // since no target fps present, no specific device fps will be selected, and is set to
+ // unspecified: (0,0)
+ }
+
+ // //////////////////////////////////////////////////////////////////////////////////////////
+ //
// Other tests
//
// //////////////////////////////////////////////////////////////////////////////////////////
@@ -1726,6 +2974,8 @@
highResolutionSupportedSizes: Array<Size>? = null,
maximumResolutionSupportedSizes: Array<Size>? = null,
maximumResolutionHighResolutionSupportedSizes: Array<Size>? = null,
+ dynamicRangeProfiles: DynamicRangeProfiles? = null,
+ default10BitProfile: Long? = null,
capabilities: IntArray? = null,
cameraId: CameraId = CameraId.fromCamera1Id(0)
) {
@@ -1759,6 +3009,17 @@
null
}
+ val deviceFPSRanges: Array<Range<Int>?> = arrayOf(
+ Range(10, 22),
+ Range(22, 22),
+ Range(30, 30),
+ Range(30, 50),
+ Range(30, 40),
+ Range(30, 60),
+ Range(50, 60),
+ Range(60, 60)
+ )
+
val characteristicsMap = mutableMapOf(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to hardwareLevel,
CameraCharacteristics.SENSOR_ORIENTATION to sensorOrientation,
@@ -1766,17 +3027,27 @@
CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_BACK,
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES to capabilities,
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP to mockMap,
+ CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES to deviceFPSRanges
).also { characteristicsMap ->
mockMaximumResolutionMap?.let {
if (Build.VERSION.SDK_INT >= 31) {
characteristicsMap[
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
] =
- mockMaximumResolutionMap
+ mockMaximumResolutionMap
}
}
}
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ dynamicRangeProfiles?.let {
+ characteristicsMap[REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] = it
+ }
+ default10BitProfile?.let {
+ characteristicsMap[REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE] = it
+ }
+ }
+
// set up FakeCafakeCameraMetadatameraMetadata
fakeCameraMetadata = FakeCameraMetadata(
cameraId = cameraId,
@@ -1809,6 +3080,81 @@
.thenReturn(it)
}
}
+
+ // setup to return different minimum frame durations depending on resolution
+ // minimum frame durations were designated only for the purpose of testing
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(4032, 3024))
+ )
+ )
+ .thenReturn(50000000L) // 20 fps, size maximum
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(3840, 2160))
+ )
+ )
+ .thenReturn(40000000L) // 25, size record
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(1920, 1440))
+ )
+ )
+ .thenReturn(33333333L) // 30
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(1920, 1080))
+ )
+ )
+ .thenReturn(28571428L) // 35
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(1280, 960))
+ )
+ )
+ .thenReturn(25000000L) // 40
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(1280, 720))
+ )
+ )
+ .thenReturn(22222222L) // 45, size preview/display
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(960, 544))
+ )
+ )
+ .thenReturn(20000000L) // 50
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(800, 450))
+ )
+ )
+ .thenReturn(16666666L) // 60fps
+
+ Mockito.`when`(
+ mockMap.getOutputMinFrameDuration(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.eq(Size(640, 480))
+ )
+ )
+ .thenReturn(16666666L) // 60fps
+
shadowCharacteristics.set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
mockMaximumResolutionMap?.let {
whenever(mockMaximumResolutionMap.getOutputSizes(ArgumentMatchers.anyInt()))
@@ -1891,7 +3237,8 @@
private fun createUseCase(
captureType: UseCaseConfigFactory.CaptureType,
- targetFrameRate: Range<Int>? = null
+ targetFrameRate: Range<Int>? = null,
+ dynamicRange: DynamicRange? = DynamicRange.UNSPECIFIED
): UseCase {
val builder = FakeUseCaseConfig.Builder(
captureType, when (captureType) {
@@ -1903,6 +3250,10 @@
targetFrameRate?.let {
builder.mutableConfig.insertOption(UseCaseConfig.OPTION_TARGET_FRAME_RATE, it)
}
+ builder.mutableConfig.insertOption(
+ ImageInputConfig.OPTION_INPUT_DYNAMIC_RANGE,
+ dynamicRange
+ )
return builder.build()
}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
new file mode 100644
index 0000000..b06ab92
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/DynamicRangeProfilesCompatTest.kt
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2023 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.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_10B_UNCONSTRAINED_SLOW
+import androidx.camera.camera2.pipe.integration.internal.DOLBY_VISION_8B_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_PLUS_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HDR10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_HDR10_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_SDR_CONSTRAINED
+import androidx.camera.camera2.pipe.integration.internal.HLG10_UNCONSTRAINED
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.DynamicRange
+import com.google.common.truth.Truth
+import org.junit.Assert
+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 org.robolectric.shadows.ShadowCameraCharacteristics
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class DynamicRangeProfilesCompatTest {
+
+ private val cameraId = CameraId.fromCamera1Id(0)
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canWrapAndUnwrapDynamicRangeProfiles() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+
+ Truth.assertThat(dynamicRangeProfilesCompat).isNotNull()
+ Truth.assertThat(dynamicRangeProfilesCompat?.toDynamicRangeProfiles())
+ .isEqualTo(HLG10_UNCONSTRAINED)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canSupportDynamicRangeFromHlg10Profile() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+ Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+ .contains(DynamicRange.HLG_10_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canSupportDynamicRangeFromHdr10Profile() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_UNCONSTRAINED)
+ Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+ .contains(DynamicRange.HDR10_10_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canSupportDynamicRangeFromHdr10PlusProfile() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HDR10_PLUS_UNCONSTRAINED)
+ Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+ .contains(DynamicRange.HDR10_PLUS_10_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canSupportDynamicRangeFromDolbyVision10bProfile() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED)
+ Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+ .contains(DynamicRange.DOLBY_VISION_10_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canSupportDynamicRangeFromDolbyVision8bProfile() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_8B_UNCONSTRAINED)
+ Truth.assertThat(dynamicRangeProfilesCompat?.getSupportedDynamicRanges())
+ .contains(DynamicRange.DOLBY_VISION_8_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canProduceConcurrentDynamicRangeConstraints() {
+ val hlg10ConstrainedWrapped =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_CONSTRAINED)
+ Truth.assertThat(
+ hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+ ).containsExactly(DynamicRange.SDR)
+ Truth.assertThat(
+ hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+ DynamicRange.HLG_10_BIT
+ )
+ ).containsExactly(DynamicRange.HLG_10_BIT)
+
+ val hlg10SdrConstrainedWrapped =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_SDR_CONSTRAINED)
+ Truth.assertThat(
+ hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+ ).containsExactly(DynamicRange.SDR, DynamicRange.HLG_10_BIT)
+ Truth.assertThat(
+ hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+ DynamicRange.HLG_10_BIT
+ )
+ ).containsExactly(DynamicRange.HLG_10_BIT, DynamicRange.SDR)
+
+ val hlg10Hdr10ConstrainedWrapped =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_HDR10_CONSTRAINED)
+ Truth.assertThat(
+ hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+ ).containsExactly(DynamicRange.SDR)
+ Truth.assertThat(
+ hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+ DynamicRange.HLG_10_BIT
+ )
+ ).containsExactly(DynamicRange.HLG_10_BIT, DynamicRange.HDR10_10_BIT)
+ Truth.assertThat(
+ hlg10Hdr10ConstrainedWrapped
+ ?.getDynamicRangeCaptureRequestConstraints(DynamicRange.HDR10_10_BIT)
+ ).containsExactly(DynamicRange.HDR10_10_BIT, DynamicRange.HLG_10_BIT)
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun producesDynamicRangeWithCorrectLatency() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED_SLOW)
+ Truth.assertThat(dynamicRangeProfilesCompat?.isExtraLatencyPresent(DynamicRange.SDR))
+ .isFalse()
+ Truth.assertThat(
+ dynamicRangeProfilesCompat?.isExtraLatencyPresent(DynamicRange.DOLBY_VISION_10_BIT)
+ ).isTrue()
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canProduceDynamicRangeWithoutConstraints() {
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+ Truth.assertThat(
+ dynamicRangeProfilesCompat?.getDynamicRangeCaptureRequestConstraints(
+ DynamicRange.HLG_10_BIT
+ )
+ ).isEmpty()
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun producesNullDynamicRangeProfilesFromNullCharacteristics() {
+ val cameraMetadata = FakeCameraMetadata(cameraId = cameraId)
+
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ Truth.assertThat(dynamicRangeProfilesCompat.toDynamicRangeProfiles()).isNull()
+ }
+
+ @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun canProduceDynamicRangesCompatFromCharacteristics() {
+ val cameraMetadata = FakeCameraMetadata(
+ cameraId = cameraId, characteristics = mutableMapOf(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES to HLG10_CONSTRAINED
+ )
+ )
+
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ Truth.assertThat(dynamicRangeProfilesCompat.toDynamicRangeProfiles())
+ .isEqualTo(HLG10_CONSTRAINED)
+ }
+
+ @Test
+ fun alwaysSupportsOnlySdrWithoutDynamicRangeProfilesInCharacteristics() {
+ val cameraMetadata = FakeCameraMetadata(cameraId = cameraId)
+
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+ .containsExactly(DynamicRange.SDR)
+ Truth.assertThat(
+ dynamicRangeProfilesCompat.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+ ).containsExactly(DynamicRange.SDR)
+ }
+
+ @Test
+ fun unsupportedDynamicRangeAlwaysThrowsException() {
+ val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+ DOLBY_VISION_8B_UNCONSTRAINED
+ }
+ val cameraMetadata = FakeCameraMetadata(
+ cameraId = cameraId, characteristics = characteristics
+ )
+
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+ .containsExactly(DynamicRange.SDR)
+ } else {
+ Truth.assertThat(dynamicRangeProfilesCompat.getSupportedDynamicRanges())
+ .containsExactly(
+ DynamicRange.SDR, DynamicRange.DOLBY_VISION_8_BIT
+ )
+ }
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ dynamicRangeProfilesCompat
+ .getDynamicRangeCaptureRequestConstraints(DynamicRange.DOLBY_VISION_10_BIT)
+ }
+
+ Assert.assertThrows(IllegalArgumentException::class.java) {
+ dynamicRangeProfilesCompat.isExtraLatencyPresent(DynamicRange.DOLBY_VISION_10_BIT)
+ }
+ }
+
+ @Test
+ fun sdrHasNoExtraLatency() {
+ val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+ HLG10_CONSTRAINED
+ }
+ val cameraMetadata = FakeCameraMetadata(
+ cameraId = cameraId, characteristics = characteristics
+ )
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ Truth.assertThat(dynamicRangeProfilesCompat.isExtraLatencyPresent(DynamicRange.SDR))
+ .isFalse()
+ }
+
+ @Test
+ fun sdrHasSdrConstraint_whenConcurrentDynamicRangesNotSupported() {
+ val characteristics = mutableMapOf<CameraCharacteristics.Key<*>, Any?>()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ characteristics[CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES] =
+ HLG10_CONSTRAINED
+ }
+ val cameraMetadata = FakeCameraMetadata(
+ cameraId = cameraId, characteristics = characteristics
+ )
+ val dynamicRangeProfilesCompat =
+ DynamicRangeProfilesCompat.fromCameraMetaData(cameraMetadata)
+
+ Truth.assertThat(
+ dynamicRangeProfilesCompat.getDynamicRangeCaptureRequestConstraints(DynamicRange.SDR)
+ )
+ .containsExactly(DynamicRange.SDR)
+ }
+}
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+fun ShadowCameraCharacteristics.addDynamicRangeProfiles(
+ dynamicRangeProfiles: DynamicRangeProfiles
+) {
+ set(
+ CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+ dynamicRangeProfiles
+ )
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt
new file mode 100644
index 0000000..bcaae5a
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/internal/DynamicRangeTestCases.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 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.
+ */
+@file:RequiresApi(33)
+
+package androidx.camera.camera2.pipe.integration.internal
+
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10_PLUS
+import android.hardware.camera2.params.DynamicRangeProfiles.HLG10
+import android.hardware.camera2.params.DynamicRangeProfiles.STANDARD
+import androidx.annotation.RequiresApi
+
+val HLG10_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(longArrayOf(HLG10, 0, 0))
+}
+
+val HLG10_CONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, HLG10, LATENCY_NONE
+ )
+ )
+}
+
+val HLG10_SDR_CONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, HLG10 or STANDARD, LATENCY_NONE
+ )
+ )
+}
+
+val HLG10_HDR10_CONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, HLG10 or HDR10, LATENCY_NONE,
+ HDR10, HDR10 or HLG10, LATENCY_NONE
+ )
+ )
+}
+
+val HDR10_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+ HDR10, CONSTRAINTS_NONE, LATENCY_NONE
+ )
+ )
+}
+
+val HDR10_PLUS_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+ HDR10_PLUS, CONSTRAINTS_NONE, LATENCY_NONE
+ )
+ )
+}
+
+val HDR10_HDR10_PLUS_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+ HDR10, CONSTRAINTS_NONE, LATENCY_NONE,
+ HDR10_PLUS, CONSTRAINTS_NONE, LATENCY_NONE
+ )
+ )
+}
+
+val DOLBY_VISION_10B_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+ DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+ )
+ )
+}
+
+val DOLBY_VISION_10B_UNCONSTRAINED_SLOW by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+ DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NON_ZERO
+ )
+ )
+}
+
+val DOLBY_VISION_8B_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ DOLBY_VISION_8B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+ )
+ )
+}
+
+val DOLBY_VISION_8B_SDR_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ DOLBY_VISION_8B_HDR_OEM, DOLBY_VISION_8B_HDR_OEM or STANDARD, LATENCY_NONE
+ )
+ )
+}
+
+val DOLBY_VISION_8B_UNCONSTRAINED_HLG10_UNCONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, CONSTRAINTS_NONE, LATENCY_NONE,
+ DOLBY_VISION_8B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE,
+ )
+ )
+}
+
+val DOLBY_VISION_CONSTRAINED by lazy {
+ DynamicRangeProfiles(
+ longArrayOf(
+ HLG10, HLG10, LATENCY_NONE, // HLG is mandated
+ DOLBY_VISION_10B_HDR_OEM, DOLBY_VISION_10B_HDR_OEM or DOLBY_VISION_8B_HDR_OEM,
+ LATENCY_NONE,
+ DOLBY_VISION_8B_HDR_OEM, DOLBY_VISION_8B_HDR_OEM or DOLBY_VISION_10B_HDR_OEM,
+ LATENCY_NONE
+ )
+ )
+}
+
+const val LATENCY_NONE = 0L
+private const val LATENCY_NON_ZERO = 3L
+private const val CONSTRAINTS_NONE = 0L
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
index e998934..1c0a7c4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java
@@ -61,6 +61,7 @@
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.CameraMode;
import androidx.camera.core.impl.Config;
+import androidx.camera.core.impl.PreviewConfig;
import androidx.camera.core.impl.RestrictedCameraControl;
import androidx.camera.core.impl.RestrictedCameraControl.CameraOperation;
import androidx.camera.core.impl.RestrictedCameraInfo;
@@ -70,6 +71,7 @@
import androidx.camera.core.impl.SurfaceConfig;
import androidx.camera.core.impl.UseCaseConfig;
import androidx.camera.core.impl.UseCaseConfigFactory;
+import androidx.camera.core.impl.stabilization.StabilizationMode;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.streamsharing.StreamSharing;
import androidx.core.util.Preconditions;
@@ -688,8 +690,11 @@
supportedOutputSizesSorter.getSortedSupportedOutputSizes(
combinedUseCaseConfig));
- // TODO(kailianc): extract the use case's video stabilization settings and set
- // to isPreviewStabilizationOn
+ if (useCase.getCurrentConfig() instanceof PreviewConfig) {
+ isPreviewStabilizationOn =
+ ((PreviewConfig) useCase.getCurrentConfig())
+ .getPreviewStabilizationMode() == StabilizationMode.ON;
+ }
}
// Get suggested stream specifications and update the use case session configuration
diff --git a/camera/integration-tests/coretestapp/lint-baseline.xml b/camera/integration-tests/coretestapp/lint-baseline.xml
index 78ea985..9e9f16f 100644
--- a/camera/integration-tests/coretestapp/lint-baseline.xml
+++ b/camera/integration-tests/coretestapp/lint-baseline.xml
@@ -238,8 +238,8 @@
<issue
id="RestrictedApiAndroidX"
message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (createParentFolder(pictureFolder)) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ errorLine1=" if (!createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
</issue>
@@ -247,8 +247,143 @@
<issue
id="RestrictedApiAndroidX"
message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
- errorLine1=" if (createParentFolder(pictureFolder)) {"
- errorLine2=" ~~~~~~~~~~~~~">
+ errorLine1=" if (!createParentFolder(pictureFolder)) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.canDeviceWriteToMediaStore can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (!canDeviceWriteToMediaStore()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoFileOutputOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" this, generateVideoFileOutputOptions(fileName, extension));"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.generateVideoMediaStoreOptions can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" generateVideoMediaStoreOptions(getContentResolver(), fileName));"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" String videoFilePath = getAbsolutePathFromUri(getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" String videoFilePath = getAbsolutePathFromUri(getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" MediaStore.Video.Media.EXTERNAL_CONTENT_URI);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.createParentFolder can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" if (videoFilePath == null || !createParentFolder(videoFilePath)) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" videoFilePath = getAbsolutePathFromUri("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" getApplicationContext().getContentResolver(),"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
+ </issue>
+
+ <issue
+ id="RestrictedApiAndroidX"
+ message="FileUtil.getAbsolutePathFromUri can only be called from within the same library group (referenced groupId=`androidx.camera` from groupId=`androidx.camera.integration-tests`)"
+ errorLine1=" uri"
+ errorLine2=" ~~~">
<location
file="src/main/java/androidx/camera/integration/core/CameraXService.java"/>
</issue>
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
index df79650..5ce4624 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXServiceTest.kt
@@ -33,6 +33,8 @@
import androidx.camera.core.ImageCapture
import androidx.camera.core.UseCase
import androidx.camera.integration.core.CameraXService.ACTION_BIND_USE_CASES
+import androidx.camera.integration.core.CameraXService.ACTION_START_RECORDING
+import androidx.camera.integration.core.CameraXService.ACTION_STOP_RECORDING
import androidx.camera.integration.core.CameraXService.ACTION_TAKE_PICTURE
import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_ANALYSIS_ENABLED
import androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_CAPTURE_ENABLED
@@ -52,6 +54,7 @@
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.After
@@ -78,6 +81,7 @@
val permissionRule: GrantPermissionRule =
GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO,
)
@get:Rule
@@ -162,7 +166,7 @@
}
@Test
- fun canReceiveAnalysisFrame() = runBlocking {
+ fun canReceiveAnalysisFrame() {
// Arrange.
context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
putExtra(EXTRA_IMAGE_ANALYSIS_ENABLED, true)
@@ -176,7 +180,7 @@
}
@Test
- fun canTakePicture() = runBlocking {
+ fun canTakePicture() {
// Arrange.
context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
putExtra(EXTRA_IMAGE_CAPTURE_ENABLED, true)
@@ -190,6 +194,25 @@
assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
}
+ @Test
+ fun canRecordVideo() = runBlocking {
+ // Arrange.
+ context.startService(createServiceIntent(ACTION_BIND_USE_CASES).apply {
+ putExtra(EXTRA_VIDEO_CAPTURE_ENABLED, true)
+ })
+
+ // Act.
+ val latch = service.acquireRecordVideoCountDownLatch()
+ context.startService(createServiceIntent(ACTION_START_RECORDING))
+
+ delay(3000L)
+
+ context.startService(createServiceIntent(ACTION_STOP_RECORDING))
+
+ // Assert.
+ assertThat(latch.await(15, TimeUnit.SECONDS)).isTrue()
+ }
+
private fun createServiceIntent(action: String? = null) =
Intent(context, CameraXService::class.java).apply {
action?.let { setAction(it) }
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 3fd027e..5ee37e2 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -79,11 +79,13 @@
<service
android:name=".CameraXService"
android:exported="true"
- android:foregroundServiceType="camera"
+ android:foregroundServiceType="camera|microphone"
android:label="CameraX Service">
<intent-filter>
<action android:name="androidx.camera.integration.core.intent.action.BIND_USE_CASES" />
<action android:name="androidx.camera.integration.core.intent.action.TAKE_PICTURE" />
+ <action android:name="androidx.camera.integration.core.intent.action.START_RECORDING" />
+ <action android:name="androidx.camera.integration.core.intent.action.STOP_RECORDING" />
</intent-filter>
</service>
</application>
@@ -97,4 +99,5 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
</manifest>
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index b304a9a..e20ec12 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -35,7 +35,6 @@
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE;
-
import static java.util.Objects.requireNonNull;
import android.Manifest;
@@ -322,6 +321,8 @@
private Button mZoomIn2XToggle;
private Button mZoomResetToggle;
private Toast mEvToast = null;
+ private Toast mPSToast = null;
+ private ToggleButton mPreviewStabilizationToggle;
private OpenGLRenderer mPreviewRenderer;
private DisplayManager.DisplayListener mDisplayListener;
@@ -330,6 +331,7 @@
private DynamicRange mDynamicRange = DynamicRange.SDR;
private final Set<DynamicRange> mSelectableDynamicRanges = new HashSet<>();
private int mVideoMirrorMode = MIRROR_MODE_ON_FRONT_ONLY;
+ private boolean mIsPreviewStabilizationOn = false;
SessionMediaUriSet mSessionImagesUriSet = new SessionMediaUriSet();
SessionMediaUriSet mSessionVideosUriSet = new SessionMediaUriSet();
@@ -1057,6 +1059,14 @@
mEvToast.show();
}
+ void showPreviewStabilizationToast(String message) {
+ if (mPSToast != null) {
+ mPSToast.cancel();
+ }
+ mPSToast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT);
+ mPSToast.show();
+ }
+
private void updateAppUIForE2ETest(@NonNull String testCase) {
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
@@ -1114,7 +1124,7 @@
}
}
- @SuppressLint("NullAnnotationGroup")
+ @SuppressLint({"NullAnnotationGroup", "RestrictedApiAndroidX"})
@OptIn(markerClass = androidx.camera.core.ExperimentalZeroShutterLag.class)
private void updateButtonsUi() {
mRecordUi.setEnabled(mVideoToggle.isChecked());
@@ -1124,6 +1134,8 @@
&& getCameraInfo().isZslSupported() ? View.VISIBLE : View.GONE);
mZslToggle.setEnabled(mPhotoToggle.isChecked());
mCameraDirectionButton.setEnabled(getCameraInfo() != null);
+ mPreviewStabilizationToggle.setEnabled(mCamera != null
+ && mCamera.getCameraInfo().getPreviewCapabilities().isStabilizationSupported());
mTorchButton.setEnabled(isFlashAvailable());
// Flash button
mFlashButton.setEnabled(mPhotoToggle.isChecked() && isFlashAvailable());
@@ -1166,6 +1178,7 @@
setUpTorchButton();
setUpEVButton();
setUpZoomButton();
+ setUpPreviewStabilizationButton();
mCaptureQualityToggle.setOnCheckedChangeListener(mOnCheckedChangeListener);
mZslToggle.setOnCheckedChangeListener(mOnCheckedChangeListener);
}
@@ -1261,6 +1274,7 @@
mPlusEV = findViewById(R.id.plus_ev_toggle);
mDecEV = findViewById(R.id.dec_ev_toggle);
mZslToggle = findViewById(R.id.zsl_toggle);
+ mPreviewStabilizationToggle = findViewById(R.id.preview_stabilization);
mZoomSeekBar = findViewById(R.id.seekBar);
mZoomRatioLabel = findViewById(R.id.zoomRatio);
mZoomIn2XToggle = findViewById(R.id.zoom_in_2x_toggle);
@@ -1567,12 +1581,14 @@
/**
* Builds all use cases based on current settings and return as an array.
*/
+ @SuppressLint("RestrictedApiAndroidX")
private List<UseCase> buildUseCases() {
List<UseCase> useCases = new ArrayList<>();
if (mPreviewToggle.isChecked()) {
Preview preview = new Preview.Builder()
.setTargetName("Preview")
.setTargetAspectRatio(mTargetAspectRatio)
+ .setPreviewStabilizationEnabled(mIsPreviewStabilizationOn)
.build();
resetViewIdlingResource();
// Use the listener of the future to make sure the Preview setup the new surface.
@@ -1863,6 +1879,16 @@
mZoomResetToggle.setOnClickListener(v -> setZoomRatio(1.0f));
}
+ private void setUpPreviewStabilizationButton() {
+ mPreviewStabilizationToggle.setOnClickListener(v -> {
+ mIsPreviewStabilizationOn = !mIsPreviewStabilizationOn;
+ if (mIsPreviewStabilizationOn) {
+ showPreviewStabilizationToast("Preview Stabilization On, FOV changes");
+ }
+ tryBindUseCases();
+ });
+ }
+
void setZoomRatio(float newZoom) {
if (mCamera == null) {
return;
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
index 4e5333c..ac8e9cf 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXService.java
@@ -17,7 +17,16 @@
package androidx.camera.integration.core;
import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+import static androidx.camera.testing.impl.FileUtil.canDeviceWriteToMediaStore;
import static androidx.camera.testing.impl.FileUtil.createParentFolder;
+import static androidx.camera.testing.impl.FileUtil.generateVideoFileOutputOptions;
+import static androidx.camera.testing.impl.FileUtil.generateVideoMediaStoreOptions;
+import static androidx.camera.testing.impl.FileUtil.getAbsolutePathFromUri;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INSUFFICIENT_STORAGE;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
+import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -25,6 +34,7 @@
import android.app.NotificationManager;
import android.content.ContentValues;
import android.content.Intent;
+import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -45,8 +55,14 @@
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.video.FileOutputOptions;
+import androidx.camera.video.MediaStoreOutputOptions;
+import androidx.camera.video.OutputOptions;
+import androidx.camera.video.PendingRecording;
import androidx.camera.video.Recorder;
+import androidx.camera.video.Recording;
import androidx.camera.video.VideoCapture;
+import androidx.camera.video.VideoRecordEvent;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
@@ -82,6 +98,10 @@
"androidx.camera.integration.core.intent.action.BIND_USE_CASES";
public static final String ACTION_TAKE_PICTURE =
"androidx.camera.integration.core.intent.action.TAKE_PICTURE";
+ public static final String ACTION_START_RECORDING =
+ "androidx.camera.integration.core.intent.action.START_RECORDING";
+ public static final String ACTION_STOP_RECORDING =
+ "androidx.camera.integration.core.intent.action.STOP_RECORDING";
// Extras
public static final String EXTRA_VIDEO_CAPTURE_ENABLED = "EXTRA_VIDEO_CAPTURE_ENABLED";
@@ -94,12 +114,14 @@
// Members only accessed on main thread //
////////////////////////////////////////////////////////////////////////////////////////////////
private final Map<Class<?>, UseCase> mBoundUseCases = new HashMap<>();
+ @Nullable
+ private Recording mActiveRecording;
//--------------------------------------------------------------------------------------------//
////////////////////////////////////////////////////////////////////////////////////////////////
// Members for testing //
////////////////////////////////////////////////////////////////////////////////////////////////
- private final Set<Uri> mSavedImageUri = new HashSet<>();
+ private final Set<Uri> mSavedMediaUri = new HashSet<>();
@Nullable
private Consumer<Collection<UseCase>> mOnUseCaseBoundCallback;
@@ -107,6 +129,8 @@
private CountDownLatch mAnalysisFrameLatch;
@Nullable
private CountDownLatch mTakePictureLatch;
+ @Nullable
+ private CountDownLatch mRecordVideoLatch;
//--------------------------------------------------------------------------------------------//
@Override
@@ -131,6 +155,10 @@
bindToLifecycle(intent);
} else if (ACTION_TAKE_PICTURE.equals(action)) {
takePicture();
+ } else if (ACTION_START_RECORDING.equals(action)) {
+ startRecording();
+ } else if (ACTION_STOP_RECORDING.equals(action)) {
+ stopRecording();
}
}
return super.onStartCommand(intent, flags, startId);
@@ -222,6 +250,12 @@
return (ImageCapture) mBoundUseCases.get(ImageCapture.class);
}
+ @SuppressWarnings("unchecked")
+ @Nullable
+ private VideoCapture<Recorder> getVideoCapture() {
+ return (VideoCapture<Recorder>) mBoundUseCases.get(VideoCapture.class);
+ }
+
private void takePicture() {
ImageCapture imageCapture = getImageCapture();
if (imageCapture == null) {
@@ -250,7 +284,7 @@
long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
Log.d(TAG, "Saved image " + outputFileResults.getSavedUri()
+ " (" + durationMs + " ms)");
- mSavedImageUri.add(outputFileResults.getSavedUri());
+ mSavedMediaUri.add(outputFileResults.getSavedUri());
if (mTakePictureLatch != null) {
mTakePictureLatch.countDown();
}
@@ -272,6 +306,52 @@
}
}
+ private void startRecording() {
+ VideoCapture<Recorder> videoCapture = getVideoCapture();
+ if (videoCapture == null) {
+ Log.w(TAG, "VideoCapture is not bound.");
+ return;
+ }
+
+ createDefaultVideoFolderIfNotExist();
+ if (mActiveRecording == null) {
+ PendingRecording pendingRecording;
+ String fileName = "video_" + System.currentTimeMillis();
+ String extension = "mp4";
+ if (canDeviceWriteToMediaStore()) {
+ // Use MediaStoreOutputOptions for public share media storage.
+ pendingRecording = getVideoCapture().getOutput().prepareRecording(
+ this,
+ generateVideoMediaStoreOptions(getContentResolver(), fileName));
+ } else {
+ // Use FileOutputOption for devices in MediaStoreVideoCannotWrite Quirk.
+ pendingRecording = getVideoCapture().getOutput().prepareRecording(
+ this, generateVideoFileOutputOptions(fileName, extension));
+ }
+ //noinspection MissingPermission
+ mActiveRecording = pendingRecording
+ .withAudioEnabled()
+ .start(ContextCompat.getMainExecutor(this), mRecordingListener);
+ } else {
+ Log.e(TAG, "It should stop the active recording before start a new one.");
+ }
+ }
+
+ private void stopRecording() {
+ if (mActiveRecording != null) {
+ mActiveRecording.stop();
+ mActiveRecording = null;
+ }
+ }
+
+ private void createDefaultVideoFolderIfNotExist() {
+ String videoFilePath = getAbsolutePathFromUri(getContentResolver(),
+ MediaStore.Video.Media.EXTERNAL_CONTENT_URI);
+ if (videoFilePath == null || !createParentFolder(videoFilePath)) {
+ Log.e(TAG, "Failed to create parent directory for: " + videoFilePath);
+ }
+ }
+
private final ImageAnalysis.Analyzer mAnalyzer = image -> {
if (mAnalysisFrameLatch != null) {
mAnalysisFrameLatch.countDown();
@@ -279,6 +359,58 @@
image.close();
};
+ private final Consumer<VideoRecordEvent> mRecordingListener = event -> {
+ if (event instanceof VideoRecordEvent.Finalize) {
+ VideoRecordEvent.Finalize finalize = (VideoRecordEvent.Finalize) event;
+
+ switch (finalize.getError()) {
+ case ERROR_NONE:
+ case ERROR_FILE_SIZE_LIMIT_REACHED:
+ case ERROR_DURATION_LIMIT_REACHED:
+ case ERROR_INSUFFICIENT_STORAGE:
+ case ERROR_SOURCE_INACTIVE:
+ Uri uri = finalize.getOutputResults().getOutputUri();
+ OutputOptions outputOptions = finalize.getOutputOptions();
+ String msg;
+ String videoFilePath;
+ if (outputOptions instanceof MediaStoreOutputOptions) {
+ msg = "Saved video " + uri;
+ videoFilePath = getAbsolutePathFromUri(
+ getApplicationContext().getContentResolver(),
+ uri
+ );
+ } else if (outputOptions instanceof FileOutputOptions) {
+ videoFilePath = ((FileOutputOptions) outputOptions).getFile().getPath();
+ MediaScannerConnection.scanFile(this,
+ new String[]{videoFilePath}, null,
+ (path, uri1) -> Log.i(TAG, "Scanned " + path + " -> uri= " + uri1));
+ msg = "Saved video " + videoFilePath;
+ } else {
+ throw new AssertionError("Unknown or unsupported OutputOptions type: "
+ + outputOptions.getClass().getSimpleName());
+ }
+ // The video file path is used in tracing e2e test log. Don't remove it.
+ Log.d(TAG, "Saved video file: " + videoFilePath);
+
+ if (finalize.getError() != ERROR_NONE) {
+ msg += " with code (" + finalize.getError() + ")";
+ }
+ Log.d(TAG, msg, finalize.getCause());
+
+ mSavedMediaUri.add(uri);
+ if (mRecordVideoLatch != null) {
+ mRecordVideoLatch.countDown();
+ }
+ break;
+ default:
+ String errMsg = "Video capture failed by (" + finalize.getError() + "): "
+ + finalize.getCause();
+ Log.e(TAG, errMsg, finalize.getCause());
+ }
+ mActiveRecording = null;
+ }
+ };
+
@RequiresApi(26)
static class Api26Impl {
@@ -320,8 +452,15 @@
}
@VisibleForTesting
+ @NonNull
+ CountDownLatch acquireRecordVideoCountDownLatch() {
+ mRecordVideoLatch = new CountDownLatch(1);
+ return mRecordVideoLatch;
+ }
+
+ @VisibleForTesting
void deleteSavedMediaFiles() {
- deleteUriSet(mSavedImageUri);
+ deleteUriSet(mSavedMediaUri);
}
private void deleteUriSet(@NonNull Set<Uri> uriSet) {
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
index a1e8f19..53613a2 100644
--- a/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_camera_xmain.xml
@@ -308,6 +308,20 @@
app:layout_constraintLeft_toRightOf="@id/plus_ev_toggle"
app:layout_constraintTop_toBottomOf="@id/VideoToggle" />
+ <ToggleButton
+ android:id="@+id/preview_stabilization"
+ android:layout_width="46dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="5dp"
+ android:layout_marginTop="1dp"
+ android:background="@android:drawable/btn_default"
+ android:textOff="@string/toggle_preview_stabilization_off"
+ android:textOn="@string/toggle_preview_stabilization_on"
+ android:textSize="11dp"
+ android:translationZ="1dp"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/direction_toggle" />
+
<Button
android:id="@+id/video_quality"
android:layout_width="46dp"
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
index de7293e..65335ef 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/donottranslate-strings.xml
@@ -30,6 +30,8 @@
<string name="toggle_capture_quality_off">MIN</string>
<string name="toggle_capture_zsl_on">ZSL ON</string>
<string name="toggle_capture_zsl_off">ZSL OFF</string>
+ <string name="toggle_preview_stabilization_on">PS ON</string>
+ <string name="toggle_preview_stabilization_off">PS OFF</string>
<string name="toggle_plus_ev">+EV</string>
<string name="toggle_dec_ev">-EV</string>
<string name="toggle_zoom_in_2x">2X</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
index bbdd5df..2ce2fd1 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"সূচীৰ টেব"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"টেবৰ দীঘলীয়া শিৰোনামৰ সৈতে গ্ৰিডৰ টেব"</string>
<string name="tab_title_search" msgid="1892925693146631173">"সন্ধানৰ টেব"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"টেব ল’ড কৰি থকা হৈছে"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"টেবৰ টেম্পলে’টৰ ল’ড হৈ থকা ডেম’"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"কোনো টেব নথকা টেবৰ টেম্পলে’টৰ ডেম’"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"অজ্ঞাত হ’ষ্টৰ বাবে প্ৰতিচ্ছবি দেখুৱাব নোৱাৰি"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
index 343df8c..2cd921b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Укладка \"Спіс\""</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Укладка з доўгай назвай і змесцівам у выглядзе сеткі"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Укладка \"Пошук\""</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Ідзе загрузка ўкладкі"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Дэмаверсія загрузкі шаблона ўкладкі"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Дэмаверсія шаблона без укладак"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Відарысы нельга адлюстроўваць для невядомага хоста"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
index a9343bd..5111661 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"তালিকা ট্যাব"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ট্যাবের দীর্ঘ শিরোনাম সহ গ্রিড ট্যাব"</string>
<string name="tab_title_search" msgid="1892925693146631173">"সার্চ ট্যাব"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"\'ট্যাব\' লোড করা হচ্ছে"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"টেম্পলেট লোড করার সম্পর্কিত ডেমো ট্যাব"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ট্যাব ছাড়াই ট্যাব টেমপ্লেটের ডেমো"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"অজানা হোস্টের জন্য ছবি দেখানো যায় না"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
index c6da65a..e7115bc 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Pestanya de llista"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Pestanya de quadrícula amb títol llarg"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Pestanya de cerca"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"S\'està carregant la pestanya"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demostració de càrrega d\'una plantilla de pestanya"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demostració d\'una plantilla de pestanya sense pestanyes"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"No es poden mostrar les imatges per a un amfitrió desconegut"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
index 4f89f7e..dc1614f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Listefane"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Gitterfane med lang fanetitel"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Søgefane"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Indlæser fanen"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstration af indlæsning af faneskabelon"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstration af faneskabelon uden faner"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Billeder kan ikke vises for en ukendt host"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
index 50c6aaf..8ebf3ba 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Tab „Liste“"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Tab „Raster“ mit langem Tab-Titel"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Tab „Suche“"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Tab wird geladen"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Tab-Vorlage – Demo wird geladen"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Tab-Vorlage – Demo „Keine Tabs“"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Für einen unbekannten Host können keine Bilder angezeigt werden"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
index f5543de..80cd6ee 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Pestaña de lista"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Pestaña de cuadrícula con título largo"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Pestaña de búsqueda"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Cargando pestaña"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demo de carga de plantilla de pestañas"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo de plantilla de pestañas sin pestañas"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Las imágenes no se pueden mostrar en un host desconocido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
index bdd159e..1c790f3 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Zerrendaren fitxa"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Saretaren fitxa izenburu luzearekin"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Bilaketen fitxa"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Fitxa kargatzen"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Fitxen txantiloi bat kargatzearen demo-bertsioa"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Fitxen txantiloien fitxarik gabeko demo-bertsioa"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Ostalari ezezagunei ezin zaizkie bistaratu irudiak"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
index 6ece42c..2b1282c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"برگه فهرست"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"برگه جدول با عنوان برگه طولانی"</string>
<string name="tab_title_search" msgid="1892925693146631173">"برگه جستجو"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"درحال بار کردن «برگه»"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"نمونه بارگیری الگوی برگه"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"نمونه عدم وجود برگه الگوی برگه"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"بهدلیل نامشخص بودن میزبان، تصویر نمایش داده نمیشود"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
index cd5b960..d623ed5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Luettelovälilehti"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Ruudukkovälilehti ja pitkä välilehden nimi"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Hakuvälilehti"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Ladataan välilehteä"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Välilehtimallin lataamisen esittely"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Välilehtimallin (ei välilehtiä) esittely"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Kuvia ei voida näyttää tuntemattomalle isännälle"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
index f488f44..5b45a10 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Onglet de liste"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Onglet de grille avec titre d\'onglet long"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Onglet de recherche"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Chargement de l\'onglet"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Démonstration de chargement du modèle d\'onglet"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Démonstrations du modèle d\'onglet sans onglets"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Les images d\'un hôte inconnu ne peuvent pas être affichées"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
index c53b92f..5d559ff 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Pestana de lista"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Pestana de grade con título de pestana longo"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Pestana de busca"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Cargando pestana"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demostración de carga do modelo de pestanas"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demostración do modelo de pestanas sen pestanas"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"As imaxes non se poden mostrar nun host descoñecido"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
index d156d06..cb90252 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"સૂચિ ટૅબ"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ટૅબનું લાંબું શીર્ષક ધરાવતું ગ્રીડ ટૅબ"</string>
<string name="tab_title_search" msgid="1892925693146631173">"શોધ ટૅબ"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ટૅબ લોડ થઈ રહ્યું છે"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ટૅબનો નમૂનો લોડ થવાનો ડેમો"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ટૅબના નમૂનાનો, ટૅબ વિનાનો ડેમો"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"અજાણ્યા હોસ્ટ માટે છબીઓ બતાવી શકાતી નથી"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
index 0a01056..109e8e6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"सूची टैब"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"लंबे टैब टाइटल वाले ग्रिड टैब"</string>
<string name="tab_title_search" msgid="1892925693146631173">"खोज टैब"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"टैब लोड हो रहा है"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"टैब टेंप्लेट लोड करने का डेमो"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"बिना किसी टैब वाले टैब टेंप्लेट का डेमो"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"अनजान होस्ट के लिए इमेज नहीं दिखाई जा सकतीं"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
index e4dcad5..e114756 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Ցանկերի ներդիր"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Ցանցերի ներդիր՝ երկար վերնագրով"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Որոնումների ներդիր"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Ներդիրի բեռնում"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ներդիրների ձևանմուշի բեռնման դեմո"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Առանց ներդիրների ձևանմուշի դեմո"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Պատկերները չեն կարող ցուցադրվել անհայտ խնամորդի համար"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
index 6de4b46..dba3f3a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Listaflipi"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Töfluflipi með löngum flipatitli"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Leitarflipi"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Hleður flipa"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Prufuútgáfa hleðslu flipasniðmáts"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Prufuútgáfa flipasniðmáts með engum flipum"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Ekki er hægt að birta myndir fyrir óþekktan hýsil"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
index 93da01d..ac062b5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"כרטיסייה של רשימה"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"כרטיסיית רשת עם שם כרטיסייה ארוך"</string>
<string name="tab_title_search" msgid="1892925693146631173">"כרטיסיית חיפוש"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"הכרטיסייה נטענת"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"הדגמה של העלאת תבנית הכרטיסייה"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"הדגמה של תבנית הכרטיסייה כשאין כרטיסיות"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"לא ניתן להציג תמונות למארח לא ידוע"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
index 7299eb7..e3b064d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Тізім қойындысы"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Қойынды атауы ұзын тор қойындысы"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Іздеу қойындысы"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Қойынды жүктеліп жатыр"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Қойынды үлгісінің демо нұсқасын жүктеу"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Қойынды үлгісі – қойындылардың демо нұсқасы жоқ"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Суреттер белгісіз хост үшін көрсетілмейді."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
index 4a4a70e6a..42a4a75 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
@@ -162,7 +162,7 @@
<string name="go_straight" msgid="2301747728609198718">"ನೇರವಾಗಿ ಹೋಗಿ"</string>
<string name="turn_right" msgid="4710562732720109969">"ಬಲಕ್ಕೆ ತಿರುಗಿ"</string>
<string name="take_520" msgid="3804796387195842741">"520 ಗೆ ಹೋಗಿ"</string>
- <string name="gas_station" msgid="1203313937444666161">"ಗ್ಯಾಸ್ ಸ್ಟೇಶನ್"</string>
+ <string name="gas_station" msgid="1203313937444666161">"ಪೆಟ್ರೋಲ್ ಬಂಕ್"</string>
<string name="short_route" msgid="4831864276538141265">"ಕಡಿಮೆ ದೂರದ ಮಾರ್ಗ"</string>
<string name="less_busy" msgid="310625272281710983">"ಕಡಿಮೆ ಟ್ರಾಫಿಕ್"</string>
<string name="hov_friendly" msgid="6956152104754594971">"HOV ಸ್ನೇಹಿ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
index a23af53..d3c96b0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"탭 목록"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"긴 탭 제목이 있는 그리드 탭"</string>
<string name="tab_title_search" msgid="1892925693146631173">"검색 탭"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"탭 로드 중"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"탭 템플릿 로딩 데모"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"탭 없음 탭 템플릿 데모"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"알려지지 않은 호스트의 이미지를 표시할 수 없음"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
index 8feac44..074c234 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Тизме өтмөгү"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Өтмөктүн аталышы узун болгон торчо түрүндөгү өтмөк"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Издөө өтмөгү"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Өтмөк жүктөлүүдө"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Өтмөктүн үлгүсүн жүктөөнүн демо версиясы"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Өтмөктүн үлгүсү, өтмөктөр жок демо версиясы"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Сүрөттөрдү белгисиз башкы түйүн үчүн көрсөтүүгө болбойт"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
index c0b11ad..6afe958 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"ແຖບລາຍຊື່"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ແຖບຕາໜ່າງພ້ອມຊື່ແຖບຍາວ"</string>
<string name="tab_title_search" msgid="1892925693146631173">"ແຖບຊອກຫາ"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ກຳລັງໂຫຼດແຖບ"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ເດໂມແມ່ແບບແຖບການໂຫຼດ"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ເດໂມແມ່ແບບແຖບທີ່ບໍ່ມີແຖບ"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"ບໍ່ສາມາດສະແດງຮູບສຳລັບໂຮສທີ່ບໍ່ຮູ້ຈັກໄດ້"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
index 3ad011a..4963476 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Saraksta skata cilne"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Režģa skata cilne ar garu cilnes nosaukumu"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Cilne meklēšanai"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Notiek cilnes ielāde…"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Cilnes veidnes ielādes demonstrācija"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrācija cilnes veidnei, kurā nav ciļņu"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Nevar rādīt attēlus nezināmam saimniekdatoram"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
index ca74eef..b1c63b2f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"सूची टॅब"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"लांब टॅब शीर्षक असलेला ग्रिड टॅब"</string>
<string name="tab_title_search" msgid="1892925693146631173">"शोध टॅब"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"टॅब लोड करत आहे"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"टॅब टेंप्लेट लोड होत आहे डेमो"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"टॅब टेंप्लेट टॅब नाही डेमो"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"अज्ञात होस्टला इमेज दाखवल्या जाऊ शकत नाहीत"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
index 584f12b..53ec16b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Listefane"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Rutenettfane med lang fanetittel"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Søkefane"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Laster inn fanen"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Laster inn demo av fanemal"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demo av fanemal uten faner"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Bilder kan ikke vises for en ukjent vert"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
index 67bfcef..79b778d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"लिस्ट ट्याब"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ट्याबको लामो शीर्षक भएको ग्रिड ट्याब"</string>
<string name="tab_title_search" msgid="1892925693146631173">"सर्च ट्याब"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ट्याब लोड गरिँदै छ"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ट्याब टेम्प्लेट लोड गर्ने डेमो"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"कुनै पनि ट्याब नभएको ट्याब टेम्प्लेटको डेमो"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"अज्ञात होस्टका हकमा फोटोहरू देखाउन मिल्दैन"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
index 429cae1..42cf151 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"ଲିଷ୍ଟ ଟାବ"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ଲମ୍ବା ଟାବ ଟାଇଟେଲ ସହ ଗ୍ରୀଡ ଟାବ"</string>
<string name="tab_title_search" msgid="1892925693146631173">"ସର୍ଚ୍ଚ ଟାବ"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ଟାବ ଲୋଡ ହେଉଛି"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ଟାବ ଟେମ୍ପଲେଟର ଲୋଡିଂ ଡେମୋ"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ଟାବ ଟେମ୍ପଲେଟ କୌଣସି ଟାବ ବିନା ଡେମୋ"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"ଏକ ଅଜଣା ହୋଷ୍ଟ ପାଇଁ ଇମେଜଗୁଡ଼ିକୁ ଡିସପ୍ଲେ କରାଯାଇପାରିବ ନାହିଁ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
index 821bb56..0d162a5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"ਸੂਚੀ ਟੈਬ"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"ਲੰਮੇ ਟੈਬ ਸਿਰਲੇਖ ਵਾਲੇ ਗਰਿੱਡ ਟੈਬ"</string>
<string name="tab_title_search" msgid="1892925693146631173">"ਖੋਜੋ ਟੈਬ"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ਟੈਬ ਨੂੰ ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ਟੈਬ ਟੈਮਪਲੇਟ ਲੋਡ ਕਰਨ ਦੀ ਪ੍ਰਕਿਰਿਆ ਦਾ ਡੈਮੋ"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ਬਿਨਾ ਕਿਸੇ ਟੈਬ ਵਾਲੇ ਟੈਬ ਟੈਮਪਲੇਟ ਦਾ ਡੈਮੋ"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"ਕਿਸੇ ਅਗਿਆਤ ਹੋਸਟ ਲਈ ਚਿੱਤਰ ਨਹੀਂ ਦਿਖਾਏ ਜਾ ਸਕਦੇ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
index fddf150..9649d73 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Karta listy"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Wersja demonstracyjna szablonu kart korzystająca z długich kart"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Karta wyszukiwania"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Ładowanie karty"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ładowanie wersji demonstracyjnej szablonu kart"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Wersja demonstracyjna szablonu kart bez kart"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Nie można wyświetlać grafiki z nieznanego serwera"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
index 4cd8d32..e5f8a3c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Filă cu listă"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Filă cu grilă și titlu lung"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Filă cu căutare"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Se încarcă fila"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstrație cu încărcarea șablonului de file"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrație fără file pentru șablon"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Nu se pot afișa imagini pentru o gazdă necunoscută"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
index ba76ba6..0c20c56 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Вкладка списка"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Вкладка сетки с длинным названием"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Вкладка поиска"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Загрузка вкладки…"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демонстрация загрузки шаблона вкладок"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демонстрация шаблона без вкладок"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Нельзя показывать изображения для неизвестного хоста."</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
index 918242d..146ca3e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"ලැයිස්තු පටිත්ත"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"දිගු පටිති මාතෘකාව සහිත ජාල පටිත්ත"</string>
<string name="tab_title_search" msgid="1892925693146631173">"සෙවීම් පටිත්ත"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"පටිත්ත පූරණය වේ"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"පටිති අච්චු පූරණය වීමේ ආදර්ශනය"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"පටිති අච්චු පටිති නැති ආදර්ශනය"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"නොදන්නා සංග්රාහකයෙක් සඳහා රූප සංදර්ශනය කළ නොහැකිය"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
index 601c7d9..d494703 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Karta zoznamu"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Karta mriežky s dlhým názvom karty"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Karta vyhľadávania"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Načítava sa karta"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Ukážka načítavania šablóny karty"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Ukážka šablón karty bez kariet"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Obrázky nie je možné zobraziť pre neznámeho hostiteľa"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
index 2621c98..23a7b4c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Skeda e listës"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Skeda e rrjetës me titull të gjatë skede"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Skeda e \"Kërko\""</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Skeda po ngarkohet"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Demonstrimi i ngarkimit të shabllonit të skedës"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Demonstrimi i shabllonit të skedës pa skeda"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Imazhet nuk mund të shfaqen për një organizator të panjohur"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
index 7703167..8537e2a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Listflik"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Elnätsflik med lång fliktitel"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Sökflik"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Läser in flik"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Laddningsflikmallsdemo"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Flikmall utan flikdemo"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Bilder kan inte visas för en okänd värd"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
index a0f833e..c56746b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Liste Sekmesi"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Uzun Sekme Başlıklı Izgara Sekmesi"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Arama Sekmesi"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Sekme Yükleniyor"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Sekme Şablonu Yükleme Demosu"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Sekme Şablonu Sekme İçermeyen Demo"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Resimler, bilinmeyen ana makine için gösterilemez"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
index 7be0627..3b429dd 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Вкладка зі списком"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Вкладка із сіткою та довгою назвою"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Вкладка пошуку"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Завантаження вкладки"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Демонстрація шаблона вкладки: завантаження"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Демонстрація шаблона вкладки: немає вкладок"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Зображення не показуються для невідомого хосту"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
index b244662..d82e510 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
@@ -268,8 +268,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"فہرست ٹیب"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"طویل ٹیب کے عنوان کے ساتھ گرڈ ٹیب"</string>
<string name="tab_title_search" msgid="1892925693146631173">"تلاش ٹیب"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"ٹیب لوڈ ہو رہا ہے"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"ٹیب کی تمثیل ڈیمو لوڈ ہو رہا ہے"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"ٹیب کی تمثیل کوئی ٹیبز ڈیمو نہیں"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"نامعلوم میزبان کیلئے تصاویر کو ڈسپلے نہیں کیا جا سکتا"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
index 76ad292..69dd820 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
@@ -264,8 +264,7 @@
<string name="tab_title_list" msgid="5104962518489668123">"Thẻ dạng danh sách"</string>
<string name="tab_title_grid" msgid="5268168907976325154">"Thẻ dạng lưới có tiêu đề thẻ dài"</string>
<string name="tab_title_search" msgid="1892925693146631173">"Thẻ tìm kiếm"</string>
- <!-- no translation found for tab_title_loading (5385807479734490989) -->
- <skip />
+ <string name="tab_title_loading" msgid="5385807479734490989">"Đang tải thẻ"</string>
<string name="tab_template_loading_demo_title" msgid="4638051615030345574">"Bản demo mẫu thẻ đang tải"</string>
<string name="tab_template_no_tabs_demo_title" msgid="6907466201298082309">"Bản demo mẫu thẻ không có thẻ"</string>
<string name="images_unknown_host_error" msgid="3180661817432720076">"Không thể hiển thị hình ảnh cho một máy chủ không xác định"</string>
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 166ecd1..361d875 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -2,13 +2,13 @@
package androidx.compose.foundation {
public final class BackgroundKt {
- method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
- method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
}
public final class BasicMarqueeKt {
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing MarqueeSpacing(float spacing);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeDelayMillis();
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeIterations();
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing getDefaultMarqueeSpacing();
@@ -28,7 +28,7 @@
public final class BasicTooltipKt {
method @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
+ method @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
@@ -47,9 +47,9 @@
}
public final class BorderKt {
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
}
@androidx.compose.runtime.Immutable public final class BorderStroke {
@@ -83,7 +83,7 @@
}
public final class ClipScrollableContainerKt {
- method public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
}
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface CombinedClickableNode extends androidx.compose.ui.node.PointerInputModifierNode {
@@ -103,7 +103,7 @@
}
public final class FocusableKt {
- method public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
@@ -434,8 +434,8 @@
}
public final class ScrollableKt {
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
- method public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
@kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
@@ -1243,7 +1243,7 @@
package androidx.compose.foundation.selection {
public final class SelectableGroupKt {
- method public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
}
public final class SelectableKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 5c2e81b..5a3ef8a 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -2,13 +2,13 @@
package androidx.compose.foundation {
public final class BackgroundKt {
- method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
- method public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, androidx.compose.ui.graphics.Brush brush, optional androidx.compose.ui.graphics.Shape shape, optional @FloatRange(from=0.0, to=1.0) float alpha);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier background(androidx.compose.ui.Modifier, long color, optional androidx.compose.ui.graphics.Shape shape);
}
public final class BasicMarqueeKt {
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing MarqueeSpacing(float spacing);
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier basicMarquee(androidx.compose.ui.Modifier, optional int iterations, optional int animationMode, optional int delayMillis, optional int initialDelayMillis, optional androidx.compose.foundation.MarqueeSpacing spacing, optional float velocity);
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeDelayMillis();
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static int getDefaultMarqueeIterations();
method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.foundation.MarqueeSpacing getDefaultMarqueeSpacing();
@@ -28,7 +28,7 @@
public final class BasicTooltipKt {
method @androidx.compose.runtime.Composable public static void BasicTooltipBox(androidx.compose.ui.window.PopupPositionProvider positionProvider, kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, androidx.compose.foundation.BasicTooltipState state, optional androidx.compose.ui.Modifier modifier, optional boolean focusable, optional boolean enableUserInput, kotlin.jvm.functions.Function0<kotlin.Unit> content);
- method public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
+ method @androidx.compose.runtime.Stable public static androidx.compose.foundation.BasicTooltipState BasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
method @androidx.compose.runtime.Composable public static androidx.compose.foundation.BasicTooltipState rememberBasicTooltipState(optional boolean initialIsVisible, optional boolean isPersistent, optional androidx.compose.foundation.MutatorMutex mutatorMutex);
}
@@ -47,9 +47,9 @@
}
public final class BorderKt {
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
- method public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, androidx.compose.foundation.BorderStroke border, optional androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, androidx.compose.ui.graphics.Brush brush, androidx.compose.ui.graphics.Shape shape);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier border(androidx.compose.ui.Modifier, float width, long color, optional androidx.compose.ui.graphics.Shape shape);
}
@androidx.compose.runtime.Immutable public final class BorderStroke {
@@ -83,7 +83,7 @@
}
public final class ClipScrollableContainerKt {
- method public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier clipScrollableContainer(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.Orientation orientation);
}
@SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface CombinedClickableNode extends androidx.compose.ui.node.PointerInputModifierNode {
@@ -103,7 +103,7 @@
}
public final class FocusableKt {
- method public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier focusGroup(androidx.compose.ui.Modifier);
method public static androidx.compose.ui.Modifier focusable(androidx.compose.ui.Modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
@@ -436,8 +436,8 @@
}
public final class ScrollableKt {
- method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
- method public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
+ method @SuppressCompatibility @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, androidx.compose.foundation.OverscrollEffect? overscrollEffect, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.foundation.gestures.BringIntoViewSpec bringIntoViewSpec);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier scrollable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.ScrollableState state, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional boolean reverseDirection, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource);
}
@kotlin.jvm.JvmDefaultWithCompatibility public interface ScrollableState {
@@ -1245,7 +1245,7 @@
package androidx.compose.foundation.selection {
public final class SelectableGroupKt {
- method public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
+ method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier selectableGroup(androidx.compose.ui.Modifier);
}
public final class SelectableKt {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
index 46d036b..6fc417c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/GraphicsSurfaceTest.kt
@@ -72,7 +72,7 @@
import org.junit.runner.RunWith
@LargeTest
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
@RunWith(AndroidJUnit4::class)
class GraphicsSurfaceTest {
@get:Rule
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
index 2a770bc..622afb5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Background.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation
import androidx.annotation.FloatRange
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
@@ -40,6 +41,7 @@
* @param color color to paint background with
* @param shape desired shape of the background
*/
+@Stable
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
@@ -70,6 +72,7 @@
* @param alpha Opacity to be applied to the [brush], with `0` being completely transparent and
* `1` being completely opaque. The value must be between `0` and `1`.
*/
+@Stable
fun Modifier.background(
brush: Brush,
shape: Shape = RectangleShape,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
index 4b77a1d..d173f4a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
@@ -135,6 +135,7 @@
* Note: this modifier and corresponding APIs are experimental pending some refinements in the API
* surface, mostly related to customisation params.
*/
+@Stable
@ExperimentalFoundationApi
fun Modifier.basicMarquee(
iterations: Int = DefaultMarqueeIterations,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
index 26deed4..dd0593b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicTooltip.kt
@@ -104,6 +104,7 @@
* @param mutatorMutex [MutatorMutex] used to ensure that for all of the tooltips associated
* with the mutator mutex, only one will be shown on the screen at any time.
*/
+@Stable
fun BasicTooltipState(
initialIsVisible: Boolean = false,
isPersistent: Boolean = true,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
index 0bae435..330d630 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Border.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.CacheDrawModifierNode
import androidx.compose.ui.draw.CacheDrawScope
@@ -63,6 +64,7 @@
* @param border [BorderStroke] class that specifies border appearance, such as size and color
* @param shape shape of the border
*/
+@Stable
fun Modifier.border(border: BorderStroke, shape: Shape = RectangleShape) =
border(width = border.width, brush = border.brush, shape = shape)
@@ -76,6 +78,7 @@
* @param color color to paint the border with
* @param shape shape of the border
*/
+@Stable
fun Modifier.border(width: Dp, color: Color, shape: Shape = RectangleShape) =
border(width, SolidColor(color), shape)
@@ -90,6 +93,7 @@
* @param brush brush to paint the border with
* @param shape shape of the border
*/
+@Stable
fun Modifier.border(width: Dp, brush: Brush, shape: Shape) =
this then BorderModifierNodeElement(width, brush, shape)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
index d817e37..5a4da47 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/ClipScrollableContainer.kt
@@ -17,6 +17,7 @@
package androidx.compose.foundation
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Rect
@@ -33,6 +34,7 @@
*
* @param orientation orientation of the scrolling
*/
+@Stable
fun Modifier.clipScrollableContainer(orientation: Orientation) =
then(
if (orientation == Orientation.Vertical) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
index 0548c70..88c8618 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Focusable.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.BringIntoViewRequesterNode
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusEventModifierNode
import androidx.compose.ui.focus.FocusProperties
@@ -101,6 +102,7 @@
*
* @sample androidx.compose.foundation.samples.FocusableFocusGroupSample
*/
+@Stable
fun Modifier.focusGroup(): Modifier {
return this
.then(focusGroupInspectorInfo)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index db49a38..47afcb9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -110,6 +110,7 @@
* @param interactionSource [MutableInteractionSource] that will be used to emit
* drag events when this scrollable is being dragged.
*/
+@Stable
@OptIn(ExperimentalFoundationApi::class)
fun Modifier.scrollable(
state: ScrollableState,
@@ -163,6 +164,7 @@
* Note: This API is experimental as it brings support for some experimental features:
* [overscrollEffect] and [bringIntoViewScroller].
*/
+@Stable
@ExperimentalFoundationApi
fun Modifier.scrollable(
state: ScrollableState,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
index 913d91f..b6ab655 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/selection/SelectableGroup.kt
@@ -16,6 +16,7 @@
package androidx.compose.foundation.selection
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.selectableGroup
import androidx.compose.ui.semantics.semantics
@@ -26,6 +27,7 @@
*
* @see selectableGroup
*/
+@Stable
fun Modifier.selectableGroup() = this.semantics {
selectableGroup()
}
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index d319950..989002c 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -660,7 +660,10 @@
public final class ScaffoldKt {
method @androidx.compose.runtime.Composable public static void Scaffold(androidx.compose.foundation.layout.WindowInsets contentWindowInsets, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static boolean getScaffoldSubcomposeInMeasureFix();
method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static void setScaffoldSubcomposeInMeasureFix(boolean);
+ property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final boolean ScaffoldSubcomposeInMeasureFix;
}
@androidx.compose.runtime.Stable public final class ScaffoldState {
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index d319950..989002c 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -660,7 +660,10 @@
public final class ScaffoldKt {
method @androidx.compose.runtime.Composable public static void Scaffold(androidx.compose.foundation.layout.WindowInsets contentWindowInsets, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material.ScaffoldState scaffoldState, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material.SnackbarHostState,kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional boolean isFloatingActionButtonDocked, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? drawerContent, optional boolean drawerGesturesEnabled, optional androidx.compose.ui.graphics.Shape drawerShape, optional float drawerElevation, optional long drawerBackgroundColor, optional long drawerContentColor, optional long drawerScrimColor, optional long backgroundColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static boolean getScaffoldSubcomposeInMeasureFix();
method @androidx.compose.runtime.Composable public static androidx.compose.material.ScaffoldState rememberScaffoldState(optional androidx.compose.material.DrawerState drawerState, optional androidx.compose.material.SnackbarHostState snackbarHostState);
+ method @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static void setScaffoldSubcomposeInMeasureFix(boolean);
+ property @SuppressCompatibility @androidx.compose.material.ExperimentalMaterialApi public static final boolean ScaffoldSubcomposeInMeasureFix;
}
@androidx.compose.runtime.Stable public final class ScaffoldState {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
index c77c4e8..85e9194 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ScaffoldTest.kt
@@ -39,6 +39,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.SubcomposeLayout
@@ -67,8 +68,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlin.math.roundToInt
import kotlinx.coroutines.runBlocking
import org.junit.Ignore
@@ -356,7 +357,8 @@
}
with(rule.density) {
assertThat(fabPosition.x).isWithin(1f).of(
- (rule.rootWidth().toPx() - fabSize.width) / 2f)
+ (rule.rootWidth().toPx() - fabSize.width) / 2f
+ )
}
val expectedFabY = bottomBarPosition.y - (fabSize.height / 2)
assertThat(fabPosition.y).isEqualTo(expectedFabY)
@@ -396,7 +398,8 @@
}
with(rule.density) {
assertThat(fabPosition.x).isWithin(1f).of(
- rule.rootWidth().toPx() - fabSize.width - fabSpacing.toPx())
+ rule.rootWidth().toPx() - fabSize.width - fabSpacing.toPx()
+ )
}
val expectedFabY = bottomBarPosition.y - (fabSize.height / 2)
assertThat(fabPosition.y).isEqualTo(expectedFabY)
@@ -415,7 +418,8 @@
Scaffold(
topBar = {
Box(
- Modifier.requiredSize(10.dp)
+ Modifier
+ .requiredSize(10.dp)
.shadow(4.dp)
.zIndex(4f)
.background(color = Color.White)
@@ -423,7 +427,8 @@
}
) {
Box(
- Modifier.requiredSize(10.dp)
+ Modifier
+ .requiredSize(10.dp)
.background(color = Color.White)
)
}
@@ -491,9 +496,11 @@
val animatedFab = @Composable {
AnimatedVisibility(visible = showFab.value) {
FloatingActionButton(
- modifier = Modifier.onGloballyPositioned { positioned ->
- actualFabSize = positioned.size
- }.testTag(fabTestTag),
+ modifier = Modifier
+ .onGloballyPositioned { positioned ->
+ actualFabSize = positioned.size
+ }
+ .testTag(fabTestTag),
onClick = {}
) {
Icon(Icons.Filled.Favorite, null)
@@ -768,7 +775,7 @@
layout(constraints.maxWidth, constraints.maxHeight) {
onPlaceCount++
- Truth.assertWithMessage("Expected onSizeChangedCount to be >= 1")
+ assertWithMessage("Expected onSizeChangedCount to be >= 1")
.that(onSizeChangedCount).isAtLeast(1)
assertThat(size).isNotNull()
placeables.forEach { it.place(0, 0) }
@@ -778,7 +785,73 @@
}
}
- Truth.assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
+ assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
+ }
+
+ @OptIn(ExperimentalMaterialApi::class)
+ @Test
+ fun scaffold_subcomposeInMeasureFix_enabled_measuresChildrenInMeasurement() {
+ ScaffoldSubcomposeInMeasureFix = true
+ var size: IntSize? = null
+ var measured = false
+ rule.setContent {
+ Layout(
+ content = {
+ Scaffold(
+ content = {
+ Box(Modifier.onSizeChanged { size = it })
+ }
+ )
+ }
+ ) { measurables, constraints ->
+ measurables.map { it.measure(constraints) }
+ measured = true
+ layout(0, 0) {
+ // Empty measurement since we only care about placement
+ }
+ }
+ }
+
+ assertWithMessage("Measure should have been executed")
+ .that(measured).isTrue()
+ assertWithMessage("Expected size to be initialized")
+ .that(size).isNotNull()
+ }
+
+ @OptIn(ExperimentalMaterialApi::class)
+ @Test
+ fun scaffold_subcomposeInMeasureFix_disabled_measuresChildrenInPlacement() {
+ ScaffoldSubcomposeInMeasureFix = false
+ var size: IntSize? = null
+ var measured = false
+ var placed = false
+ rule.setContent {
+ Layout(
+ content = {
+ Scaffold(
+ content = {
+ Box(Modifier.onSizeChanged { size = it })
+ }
+ )
+ }
+ ) { measurables, constraints ->
+ val placeables = measurables.map { it.measure(constraints) }
+ measured = true
+ assertWithMessage("Expected size to not be initialized in placement")
+ .that(size).isNull()
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeables.forEach { it.place(0, 0) }
+ placed = true
+ }
+ }
+ }
+
+ assertWithMessage("Measure should have been executed")
+ .that(measured).isTrue()
+ assertWithMessage("Placement should have been executed")
+ .that(placed).isTrue()
+ assertWithMessage("Expected size to be initialized")
+ .that(size).isNotNull()
}
private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
index 106f8ad..41eaced 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
@@ -29,7 +29,10 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.UiComposable
@@ -78,7 +81,7 @@
/**
* The possible positions for a [FloatingActionButton] attached to a [Scaffold].
*/
[email protected]
+@JvmInline
value class FabPosition internal constructor(@Suppress("unused") private val value: Int) {
companion object {
/**
@@ -364,6 +367,22 @@
}
/**
+ * Flag indicating if [Scaffold] should subcompose and measure its children during measurement or
+ * during placement.
+ * Set this flag to false to keep Scaffold's old measurement behavior (measuring in placement).
+ *
+ * <b>This flag will be removed in Compose 1.6.0-beta01.</b> If you encounter any issues with the
+ * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=742043
+ */
+// TODO(b/299621062): Remove flag before beta
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalMaterialApi
+@set:ExperimentalMaterialApi
+@ExperimentalMaterialApi
+var ScaffoldSubcomposeInMeasureFix by mutableStateOf(true)
+
+/**
* Layout for a [Scaffold]'s content.
*
* @param isFabDocked whether the FAB (if present) is docked to the bottom bar or not
@@ -376,6 +395,7 @@
* @param bottomBar the content to place at the bottom of the [Scaffold], on top of the
* [content], typically a [BottomAppBar].
*/
+@OptIn(ExperimentalMaterialApi::class)
@Composable
@UiComposable
private fun ScaffoldLayout(
@@ -388,6 +408,46 @@
contentWindowInsets: WindowInsets,
bottomBar: @Composable @UiComposable () -> Unit
) {
+ if (ScaffoldSubcomposeInMeasureFix) {
+ ScaffoldLayoutWithMeasureFix(
+ isFabDocked = isFabDocked,
+ fabPosition = fabPosition,
+ topBar = topBar,
+ content = content,
+ snackbar = snackbar,
+ fab = fab,
+ contentWindowInsets = contentWindowInsets,
+ bottomBar = bottomBar
+ )
+ } else {
+ LegacyScaffoldLayout(
+ isFabDocked = isFabDocked,
+ fabPosition = fabPosition,
+ topBar = topBar,
+ content = content,
+ snackbar = snackbar,
+ fab = fab,
+ contentWindowInsets = contentWindowInsets,
+ bottomBar = bottomBar
+ )
+ }
+}
+
+/**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+@UiComposable
+private fun ScaffoldLayoutWithMeasureFix(
+ isFabDocked: Boolean,
+ fabPosition: FabPosition,
+ topBar: @Composable @UiComposable () -> Unit,
+ content: @Composable @UiComposable (PaddingValues) -> Unit,
+ snackbar: @Composable @UiComposable () -> Unit,
+ fab: @Composable @UiComposable () -> Unit,
+ contentWindowInsets: WindowInsets,
+ bottomBar: @Composable @UiComposable () -> Unit
+) {
SubcomposeLayout { constraints ->
val layoutWidth = constraints.maxWidth
val layoutHeight = constraints.maxHeight
@@ -447,6 +507,7 @@
layoutWidth - FabSpacing.roundToPx() - fabWidth
}
}
+
FabPosition.End -> {
if (layoutDirection == LayoutDirection.Ltr) {
layoutWidth - FabSpacing.roundToPx() - fabWidth
@@ -454,6 +515,7 @@
FabSpacing.roundToPx()
}
}
+
else -> (layoutWidth - fabWidth) / 2
}
@@ -550,6 +612,183 @@
}
/**
+ * Legacy layout for a [Scaffold]'s content, subcomposing and measuring during placement.
+ */
+@Composable
+@UiComposable
+private fun LegacyScaffoldLayout(
+ isFabDocked: Boolean,
+ fabPosition: FabPosition,
+ topBar: @Composable @UiComposable () -> Unit,
+ content: @Composable @UiComposable (PaddingValues) -> Unit,
+ snackbar: @Composable @UiComposable () -> Unit,
+ fab: @Composable @UiComposable () -> Unit,
+ contentWindowInsets: WindowInsets,
+ bottomBar: @Composable @UiComposable () -> Unit
+) {
+ SubcomposeLayout { constraints ->
+ val layoutWidth = constraints.maxWidth
+ val layoutHeight = constraints.maxHeight
+
+ val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+
+ layout(layoutWidth, layoutHeight) {
+ val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
+ it.measure(looseConstraints)
+ }
+
+ val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+ val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
+ // respect only bottom and horizontal for snackbar and fab
+ val leftInset = contentWindowInsets
+ .getLeft(this@SubcomposeLayout, layoutDirection)
+ val rightInset = contentWindowInsets
+ .getRight(this@SubcomposeLayout, layoutDirection)
+ val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+ // offset the snackbar constraints by the insets values
+ it.measure(
+ looseConstraints.offset(
+ -leftInset - rightInset,
+ -bottomInset
+ )
+ )
+ }
+
+ val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+ val fabPlaceables =
+ subcompose(ScaffoldLayoutContent.Fab, fab).fastMap { measurable ->
+ // respect only bottom and horizontal for snackbar and fab
+ val leftInset =
+ contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+ val rightInset =
+ contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+ val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+ measurable.measure(
+ looseConstraints.offset(
+ -leftInset - rightInset,
+ -bottomInset
+ )
+ )
+ }
+
+ val fabPlacement = if (fabPlaceables.isNotEmpty()) {
+ val fabWidth = fabPlaceables.fastMaxBy { it.width }?.width ?: 0
+ val fabHeight = fabPlaceables.fastMaxBy { it.height }?.height ?: 0
+ // FAB distance from the left of the layout, taking into account LTR / RTL
+ if (fabWidth != 0 && fabHeight != 0) {
+ val fabLeftOffset = when (fabPosition) {
+ FabPosition.Start -> {
+ if (layoutDirection == LayoutDirection.Ltr) {
+ FabSpacing.roundToPx()
+ } else {
+ layoutWidth - FabSpacing.roundToPx() - fabWidth
+ }
+ }
+
+ FabPosition.End -> {
+ if (layoutDirection == LayoutDirection.Ltr) {
+ layoutWidth - FabSpacing.roundToPx() - fabWidth
+ } else {
+ FabSpacing.roundToPx()
+ }
+ }
+
+ else -> (layoutWidth - fabWidth) / 2
+ }
+
+ FabPlacement(
+ isDocked = isFabDocked,
+ left = fabLeftOffset,
+ width = fabWidth,
+ height = fabHeight
+ )
+ } else {
+ null
+ }
+ } else {
+ null
+ }
+
+ val bottomBarPlaceables = subcompose(ScaffoldLayoutContent.BottomBar) {
+ CompositionLocalProvider(
+ LocalFabPlacement provides fabPlacement,
+ content = bottomBar
+ )
+ }.fastMap { it.measure(looseConstraints) }
+
+ val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
+ val fabOffsetFromBottom = fabPlacement?.let {
+ if (bottomBarHeight == null) {
+ it.height + FabSpacing.roundToPx() +
+ contentWindowInsets.getBottom(this@SubcomposeLayout)
+ } else {
+ if (isFabDocked) {
+ // Total height is the bottom bar height + half the FAB height
+ bottomBarHeight + (it.height / 2)
+ } else {
+ // Total height is the bottom bar height + the FAB height + the padding
+ // between the FAB and bottom bar
+ bottomBarHeight + it.height + FabSpacing.roundToPx()
+ }
+ }
+ }
+
+ val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
+ snackbarHeight +
+ (fabOffsetFromBottom ?: bottomBarHeight
+ ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
+ } else {
+ 0
+ }
+
+ val bodyContentHeight = layoutHeight - topBarHeight
+
+ val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
+ val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
+ val innerPadding = PaddingValues(
+ top =
+ if (topBarPlaceables.isEmpty()) {
+ insets.calculateTopPadding()
+ } else {
+ 0.dp
+ },
+ bottom =
+ if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
+ insets.calculateBottomPadding()
+ } else {
+ bottomBarHeight.toDp()
+ },
+ start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
+ end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection)
+ )
+ content(innerPadding)
+ }.fastMap { it.measure(looseConstraints.copy(maxHeight = bodyContentHeight)) }
+
+ // Placing to control drawing order to match default elevation of each placeable
+ bodyContentPlaceables.fastForEach {
+ it.place(0, topBarHeight)
+ }
+ topBarPlaceables.fastForEach {
+ it.place(0, 0)
+ }
+ snackbarPlaceables.fastForEach {
+ it.place(0, layoutHeight - snackbarOffsetFromBottom)
+ }
+ // The bottom bar is always at the bottom of the layout
+ bottomBarPlaceables.fastForEach {
+ it.place(0, layoutHeight - (bottomBarHeight ?: 0))
+ }
+ // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
+ fabPlaceables.fastForEach {
+ it.place(fabPlacement?.left ?: 0, layoutHeight - (fabOffsetFromBottom ?: 0))
+ }
+ }
+ }
+}
+
+/**
* Placement information for a [FloatingActionButton] inside a [Scaffold].
*
* @property isDocked whether the FAB should be docked with the bottom bar
diff --git a/compose/material3/material3-adaptive/api/current.txt b/compose/material3/material3-adaptive/api/current.txt
index 955380f..c21a89f 100644
--- a/compose/material3/material3-adaptive/api/current.txt
+++ b/compose/material3/material3-adaptive/api/current.txt
@@ -96,15 +96,6 @@
property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue layoutValue;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum NavigationSuiteAlignment {
- method public static androidx.compose.material3.adaptive.NavigationSuiteAlignment valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.NavigationSuiteAlignment[] values();
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment BottomHorizontal;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment EndVertical;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment StartVertical;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment TopHorizontal;
- }
-
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteColors {
method public long getNavigationBarContainerColor();
method public long getNavigationBarContentColor();
@@ -121,14 +112,7 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteDefaults {
- method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.NavigationSuiteColors colors(optional long navigationBarContainerColor, optional long navigationBarContentColor, optional long navigationRailContainerColor, optional long navigationRailContentColor, optional long navigationDrawerContainerColor, optional long navigationDrawerContentColor);
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationBarAlignment();
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationDrawerAlignment();
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationRailAlignment();
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationBarAlignment;
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationDrawerAlignment;
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationRailAlignment;
field public static final androidx.compose.material3.adaptive.NavigationSuiteDefaults INSTANCE;
}
@@ -141,13 +125,15 @@
property public final androidx.compose.material3.NavigationRailItemColors navigationRailItemColors;
}
- public final class NavigationSuiteScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuite(androidx.compose.material3.adaptive.NavigationSuiteScaffoldScope, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScaffoldScope,kotlin.Unit> navigationSuite, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteScaffoldDefaults {
+ method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
+ field public static final androidx.compose.material3.adaptive.NavigationSuiteScaffoldDefaults INSTANCE;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface NavigationSuiteScaffoldScope {
- method public androidx.compose.ui.Modifier alignment(androidx.compose.ui.Modifier, androidx.compose.material3.adaptive.NavigationSuiteAlignment alignment);
+ public final class NavigationSuiteScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface NavigationSuiteScope {
diff --git a/compose/material3/material3-adaptive/api/restricted_current.txt b/compose/material3/material3-adaptive/api/restricted_current.txt
index 955380f..c21a89f 100644
--- a/compose/material3/material3-adaptive/api/restricted_current.txt
+++ b/compose/material3/material3-adaptive/api/restricted_current.txt
@@ -96,15 +96,6 @@
property public abstract androidx.compose.material3.adaptive.ThreePaneScaffoldValue layoutValue;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public enum NavigationSuiteAlignment {
- method public static androidx.compose.material3.adaptive.NavigationSuiteAlignment valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
- method public static androidx.compose.material3.adaptive.NavigationSuiteAlignment[] values();
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment BottomHorizontal;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment EndVertical;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment StartVertical;
- enum_constant public static final androidx.compose.material3.adaptive.NavigationSuiteAlignment TopHorizontal;
- }
-
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteColors {
method public long getNavigationBarContainerColor();
method public long getNavigationBarContentColor();
@@ -121,14 +112,7 @@
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteDefaults {
- method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
method @androidx.compose.runtime.Composable public androidx.compose.material3.adaptive.NavigationSuiteColors colors(optional long navigationBarContainerColor, optional long navigationBarContentColor, optional long navigationRailContainerColor, optional long navigationRailContentColor, optional long navigationDrawerContainerColor, optional long navigationDrawerContentColor);
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationBarAlignment();
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationDrawerAlignment();
- method public androidx.compose.material3.adaptive.NavigationSuiteAlignment getNavigationRailAlignment();
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationBarAlignment;
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationDrawerAlignment;
- property public final androidx.compose.material3.adaptive.NavigationSuiteAlignment NavigationRailAlignment;
field public static final androidx.compose.material3.adaptive.NavigationSuiteDefaults INSTANCE;
}
@@ -141,13 +125,15 @@
property public final androidx.compose.material3.NavigationRailItemColors navigationRailItemColors;
}
- public final class NavigationSuiteScaffoldKt {
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuite(androidx.compose.material3.adaptive.NavigationSuiteScaffoldScope, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> content);
- method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScaffoldScope,kotlin.Unit> navigationSuite, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class NavigationSuiteScaffoldDefaults {
+ method public String calculateFromAdaptiveInfo(androidx.compose.material3.adaptive.WindowAdaptiveInfo adaptiveInfo);
+ field public static final androidx.compose.material3.adaptive.NavigationSuiteScaffoldDefaults INSTANCE;
}
- @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface NavigationSuiteScaffoldScope {
- method public androidx.compose.ui.Modifier alignment(androidx.compose.ui.Modifier, androidx.compose.material3.adaptive.NavigationSuiteAlignment alignment);
+ public final class NavigationSuiteScaffoldKt {
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuite(optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional androidx.compose.material3.adaptive.NavigationSuiteColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.NavigationSuiteScope,kotlin.Unit> navigationSuiteItems, optional androidx.compose.ui.Modifier modifier, optional String layoutType, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigationSuiteScaffoldLayout(kotlin.jvm.functions.Function0<kotlin.Unit> navigationSuite, optional String layoutType, optional kotlin.jvm.functions.Function0<kotlin.Unit> content);
}
@SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public interface NavigationSuiteScope {
diff --git a/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3-adaptive/samples/NavigationSuiteScaffoldSamples.kt b/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3-adaptive/samples/NavigationSuiteScaffoldSamples.kt
index 51ee44b..01ccec2 100644
--- a/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3-adaptive/samples/NavigationSuiteScaffoldSamples.kt
+++ b/compose/material3/material3-adaptive/samples/src/main/java/androidx/compose/material3-adaptive/samples/NavigationSuiteScaffoldSamples.kt
@@ -23,10 +23,8 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.NavigationSuite
-import androidx.compose.material3.adaptive.NavigationSuiteAlignment
-import androidx.compose.material3.adaptive.NavigationSuiteDefaults
import androidx.compose.material3.adaptive.NavigationSuiteScaffold
+import androidx.compose.material3.adaptive.NavigationSuiteScaffoldDefaults
import androidx.compose.material3.adaptive.NavigationSuiteType
import androidx.compose.material3.adaptive.calculateWindowAdaptiveInfo
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -47,19 +45,17 @@
var selectedItem by remember { mutableIntStateOf(0) }
val navItems = listOf("Songs", "Artists", "Playlists")
val navSuiteType =
- NavigationSuiteDefaults.calculateFromAdaptiveInfo(calculateWindowAdaptiveInfo())
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(calculateWindowAdaptiveInfo())
NavigationSuiteScaffold(
- navigationSuite = {
- NavigationSuite {
- navItems.forEachIndexed { index, navItem ->
- item(
- icon = { Icon(Icons.Filled.Favorite, contentDescription = navItem) },
- label = { Text(navItem) },
- selected = selectedItem == index,
- onClick = { selectedItem = index }
- )
- }
+ navigationSuiteItems = {
+ navItems.forEachIndexed { index, navItem ->
+ item(
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = navItem) },
+ label = { Text(navItem) },
+ selected = selectedItem == index,
+ onClick = { selectedItem = index }
+ )
}
}
) {
@@ -79,36 +75,25 @@
var selectedItem by remember { mutableIntStateOf(0) }
val navItems = listOf("Songs", "Artists", "Playlists")
val adaptiveInfo = calculateWindowAdaptiveInfo()
+ // Custom configuration that shows a navigation drawer in large screens.
val customNavSuiteType = with(adaptiveInfo) {
if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded) {
NavigationSuiteType.NavigationDrawer
- } else if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact) {
- NavigationSuiteType.NavigationRail
} else {
- NavigationSuiteDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
}
}
- // Custom configuration that shows nav rail on end of screen in small screens, and navigation
- // drawer in large screens.
NavigationSuiteScaffold(
- navigationSuite = {
- NavigationSuite(
- layoutType = customNavSuiteType,
- modifier = if (customNavSuiteType == NavigationSuiteType.NavigationRail) {
- Modifier.alignment(NavigationSuiteAlignment.EndVertical)
- } else {
- Modifier
- }
- ) {
- navItems.forEachIndexed { index, navItem ->
- item(
- icon = { Icon(Icons.Filled.Favorite, contentDescription = navItem) },
- label = { Text(navItem) },
- selected = selectedItem == index,
- onClick = { selectedItem = index }
- )
- }
+ layoutType = customNavSuiteType,
+ navigationSuiteItems = {
+ navItems.forEachIndexed { index, navItem ->
+ item(
+ icon = { Icon(Icons.Filled.Favorite, contentDescription = navItem) },
+ label = { Text(navItem) },
+ selected = selectedItem == index,
+ onClick = { selectedItem = index }
+ )
}
}
) {
diff --git a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffold.kt b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffold.kt
index 0629248..0d60c95 100644
--- a/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffold.kt
+++ b/compose/material3/material3-adaptive/src/commonMain/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffold.kt
@@ -53,14 +53,7 @@
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.IntrinsicMeasurable
import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.node.ParentDataModifierNode
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.util.fastAll
-import androidx.compose.ui.util.fastFilterNotNull
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastMaxOfOrNull
@@ -74,8 +67,10 @@
* Example custom configuration usage:
* @sample androidx.compose.material3.adaptive.samples.NavigationSuiteScaffoldCustomConfigSample
*
- * @param navigationSuite the navigation component to be displayed, typically [NavigationSuite]
+ * @param navigationSuiteItems the navigation items to be displayed
* @param modifier the [Modifier] to be applied to the navigation suite scaffold
+ * @param layoutType the current [NavigationSuiteType]. Defaults to
+ * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
* @param containerColor the color used for the background of the navigation suite scaffold. Use
* [Color.Transparent] to have no color
* @param contentColor the preferred color for content inside the navigation suite scaffold.
@@ -86,112 +81,87 @@
@ExperimentalMaterial3AdaptiveApi
@Composable
fun NavigationSuiteScaffold(
- navigationSuite: @Composable NavigationSuiteScaffoldScope.() -> Unit,
+ navigationSuiteItems: NavigationSuiteScope.() -> Unit,
modifier: Modifier = Modifier,
+ layoutType: NavigationSuiteType =
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
containerColor: Color = MaterialTheme.colorScheme.background,
contentColor: Color = contentColorFor(containerColor),
content: @Composable () -> Unit = {},
) {
Surface(modifier = modifier, color = containerColor, contentColor = contentColor) {
NavigationSuiteScaffoldLayout(
- navigationSuite = navigationSuite,
+ navigationSuite = {
+ NavigationSuite(layoutType = layoutType, content = navigationSuiteItems)
+ },
+ layoutType = layoutType,
content = content
)
}
}
/**
- * Layout for a [NavigationSuiteScaffold]'s content.
+ * Layout for a [NavigationSuiteScaffold]'s content. This function wraps the [content] and places
+ * the [navigationSuite] component according to the given [layoutType].
*
- * @param navigationSuite the navigation suite of the [NavigationSuiteScaffold]
- * @param content the main body of the [NavigationSuiteScaffold]
- * @throws [IllegalArgumentException] if there is more than one [NavigationSuiteAlignment] for the
- * given navigation component
+ * @param navigationSuite the navigation component to be displayed, typically [NavigationSuite]
+ * @param layoutType the current [NavigationSuiteType]. Defaults to
+ * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
+ * @param content the content of your screen
*/
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@ExperimentalMaterial3AdaptiveApi
@Composable
-private fun NavigationSuiteScaffoldLayout(
- navigationSuite: @Composable NavigationSuiteScaffoldScope.() -> Unit,
+fun NavigationSuiteScaffoldLayout(
+ navigationSuite: @Composable () -> Unit,
+ layoutType: NavigationSuiteType =
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
content: @Composable () -> Unit = {}
) {
Layout(
- contents = listOf({ NavigationSuiteScaffoldScopeImpl.navigationSuite() }, content)
+ contents = listOf(navigationSuite, content)
) { (navigationMeasurables, contentMeasurables), constraints ->
val navigationPlaceables = navigationMeasurables.fastMap { it.measure(constraints) }
- val alignments = navigationPlaceables.fastMap {
- (it.parentData as NavigationSuiteParentData).alignment
- }.fastFilterNotNull()
- if (alignments.fastAll { alignments[0] != it }) {
- throw IllegalArgumentException("There should be only one NavigationSuiteAlignment.")
- }
- val alignment = alignments.firstOrNull() ?: NavigationSuiteAlignment.StartVertical
+ val isNavigationBar = layoutType == NavigationSuiteType.NavigationBar
val layoutHeight = constraints.maxHeight
val layoutWidth = constraints.maxWidth
val contentPlaceables = contentMeasurables.fastMap { it.measure(
- if (alignment == NavigationSuiteAlignment.TopHorizontal ||
- alignment == NavigationSuiteAlignment.BottomHorizontal
- ) {
+ if (isNavigationBar) {
constraints.copy(
- minHeight = layoutHeight - navigationPlaceables.fastMaxOfOrNull { it.height }!!,
- maxHeight = layoutHeight - navigationPlaceables.fastMaxOfOrNull { it.height }!!
+ minHeight = layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height }
+ ?: 0),
+ maxHeight = layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height }
+ ?: 0)
)
} else {
constraints.copy(
- minWidth = layoutWidth - navigationPlaceables.fastMaxOfOrNull { it.width }!!,
- maxWidth = layoutWidth - navigationPlaceables.fastMaxOfOrNull { it.width }!!
+ minWidth = layoutWidth - (navigationPlaceables.fastMaxOfOrNull { it.width }
+ ?: 0),
+ maxWidth = layoutWidth - (navigationPlaceables.fastMaxOfOrNull { it.width }
+ ?: 0)
)
}
) }
layout(layoutWidth, layoutHeight) {
- when (alignment) {
- NavigationSuiteAlignment.StartVertical -> {
- // Place the navigation component at the start of the screen.
- navigationPlaceables.fastForEach {
- it.placeRelative(0, 0)
- }
- // Place content to the side of the navigation component.
- contentPlaceables.fastForEach {
- it.placeRelative(navigationPlaceables.fastMaxOfOrNull { it.width }!!, 0)
- }
+ if (isNavigationBar) {
+ // Place content above the navigation component.
+ contentPlaceables.fastForEach {
+ it.placeRelative(0, 0)
}
-
- NavigationSuiteAlignment.EndVertical -> {
- // Place the navigation component at the end of the screen.
- navigationPlaceables.fastForEach {
- it.placeRelative(
- layoutWidth - navigationPlaceables.fastMaxOfOrNull { it.width }!!,
- 0
- )
- }
- // Place content at the start of the screen.
- contentPlaceables.fastForEach {
- it.placeRelative(0, 0)
- }
+ // Place the navigation component at the bottom of the screen.
+ navigationPlaceables.fastForEach {
+ it.placeRelative(
+ 0,
+ layoutHeight - (navigationPlaceables.fastMaxOfOrNull { it.height } ?: 0))
}
-
- NavigationSuiteAlignment.TopHorizontal -> {
- // Place the navigation component at the start of the screen.
- navigationPlaceables.fastForEach {
- it.placeRelative(0, 0)
- }
- // Place content below the navigation component.
- contentPlaceables.fastForEach {
- it.placeRelative(0, navigationPlaceables.fastMaxOfOrNull { it.height }!!)
- }
+ } else {
+ // Place the navigation component at the start of the screen.
+ navigationPlaceables.fastForEach {
+ it.placeRelative(0, 0)
}
-
- NavigationSuiteAlignment.BottomHorizontal -> {
- // Place content above the navigation component.
- contentPlaceables.fastForEach {
- it.placeRelative(0, 0)
- }
- // Place the navigation component at the bottom of the screen.
- navigationPlaceables.fastForEach {
- it.placeRelative(
- 0,
- layoutHeight - navigationPlaceables.fastMaxOfOrNull { it.height }!!)
- }
+ // Place content to the side of the navigation component.
+ contentPlaceables.fastForEach {
+ it.placeRelative((navigationPlaceables.fastMaxOfOrNull { it.width } ?: 0), 0)
}
}
}
@@ -207,7 +177,7 @@
*
* @param modifier the [Modifier] to be applied to the navigation component
* @param layoutType the current [NavigationSuiteType] of the [NavigationSuiteScaffold]. Defaults to
- * [NavigationSuiteDefaults.calculateFromAdaptiveInfo]
+ * [NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo]
* @param colors [NavigationSuiteColors] that will be used to determine the container (background)
* color of the navigation component and the preferred color for content inside the navigation
* component
@@ -216,10 +186,10 @@
*/
@ExperimentalMaterial3AdaptiveApi
@Composable
-fun NavigationSuiteScaffoldScope.NavigationSuite(
+fun NavigationSuite(
modifier: Modifier = Modifier,
layoutType: NavigationSuiteType =
- NavigationSuiteDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
+ NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(WindowAdaptiveInfoDefault),
colors: NavigationSuiteColors = NavigationSuiteDefaults.colors(),
content: NavigationSuiteScope.() -> Unit
) {
@@ -228,7 +198,7 @@
when (layoutType) {
NavigationSuiteType.NavigationBar -> {
NavigationBar(
- modifier = modifier.alignment(NavigationSuiteDefaults.NavigationBarAlignment),
+ modifier = modifier,
containerColor = colors.navigationBarContainerColor,
contentColor = colors.navigationBarContentColor
) {
@@ -251,7 +221,7 @@
NavigationSuiteType.NavigationRail -> {
NavigationRail(
- modifier = modifier.alignment(NavigationSuiteDefaults.NavigationRailAlignment),
+ modifier = modifier,
containerColor = colors.navigationRailContainerColor,
contentColor = colors.navigationRailContentColor
) {
@@ -274,7 +244,7 @@
NavigationSuiteType.NavigationDrawer -> {
PermanentDrawerSheet(
- modifier = modifier.alignment(NavigationSuiteDefaults.NavigationDrawerAlignment),
+ modifier = modifier,
drawerContainerColor = colors.navigationDrawerContainerColor,
drawerContentColor = colors.navigationDrawerContentColor
) {
@@ -296,37 +266,7 @@
}
}
-/** The scope associated with the [NavigationSuiteScaffold]. */
-@ExperimentalMaterial3AdaptiveApi
-interface NavigationSuiteScaffoldScope {
- /**
- * [Modifier] that should be applied to the [NavigationSuite] of the [NavigationSuiteScaffold]
- * in order to determine its alignment on the screen.
- *
- * @param alignment the desired [NavigationSuiteAlignment]
- */
- fun Modifier.alignment(alignment: NavigationSuiteAlignment): Modifier
-}
-
-/**
- * Represents the alignment of the navigation component of the [NavigationSuiteScaffold].
- *
- * The alignment informs the Navigation Suite Scaffold how to properly place the expected navigation
- * component on the screen in relation to the Navigation Suite Scaffold's content.
- */
-@ExperimentalMaterial3AdaptiveApi
-enum class NavigationSuiteAlignment {
- /** The navigation component is vertical and positioned at the start of the screen. */
- StartVertical,
- /** The navigation component is vertical and positioned at the end of the screen. */
- EndVertical,
- /** The navigation component is horizontal and positioned at the top of the screen. */
- TopHorizontal,
- /** The navigation component is horizontal and positioned at the bottom of the screen. */
- BottomHorizontal
-}
-
-/** The scope associated with the [NavigationSuite]. */
+/** The scope associated with the [NavigationSuiteScope]. */
@ExperimentalMaterial3AdaptiveApi
interface NavigationSuiteScope {
@@ -383,15 +323,16 @@
companion object {
/**
- * A navigation suite type that instructs the [NavigationSuite] to expect a [NavigationBar].
+ * A navigation suite type that instructs the [NavigationSuite] to expect a [NavigationBar]
+ * that will be displayed at the bottom of the screen.
*
* @see NavigationBar
*/
val NavigationBar = NavigationSuiteType(description = "NavigationBar")
/**
- * A navigation suite type that instructs the [NavigationSuite] to expect a
- * [NavigationRail].
+ * A navigation suite type that instructs the [NavigationSuite] to expect a [NavigationRail]
+ * that will be displayed at the start of the screen.
*
* @see NavigationRail
*/
@@ -399,7 +340,7 @@
/**
* A navigation suite type that instructs the [NavigationSuite] to expect a
- * [PermanentDrawerSheet].
+ * [PermanentDrawerSheet] that will be displayed at the start of the screen.
*
* @see PermanentDrawerSheet
*/
@@ -407,15 +348,15 @@
}
}
-/** Contains the default values used by the [NavigationSuite]. */
+/** Contains the default values used by the [NavigationSuiteScaffold]. */
@ExperimentalMaterial3AdaptiveApi
-object NavigationSuiteDefaults {
+object NavigationSuiteScaffoldDefaults {
/**
* Returns the expected [NavigationSuiteType] according to the provided [WindowAdaptiveInfo].
- * Usually used with the [NavigationSuite].
+ * Usually used with the [NavigationSuiteScaffold] and related APIs.
*
* @param adaptiveInfo the provided [WindowAdaptiveInfo]
- * @see NavigationSuite
+ * @see NavigationSuiteScaffold
*/
fun calculateFromAdaptiveInfo(adaptiveInfo: WindowAdaptiveInfo): NavigationSuiteType {
return with(adaptiveInfo) {
@@ -428,16 +369,11 @@
}
}
}
+}
- /** Default alignment for the [NavigationSuiteType.NavigationBar]. */
- val NavigationBarAlignment = NavigationSuiteAlignment.BottomHorizontal
-
- /** Default alignment for the [NavigationSuiteType.NavigationRail]. */
- val NavigationRailAlignment = NavigationSuiteAlignment.StartVertical
-
- /** Default alignment for the [NavigationSuiteType.NavigationDrawer]. */
- val NavigationDrawerAlignment = NavigationSuiteAlignment.StartVertical
-
+/** Contains the default values used by the [NavigationSuite]. */
+@ExperimentalMaterial3AdaptiveApi
+object NavigationSuiteDefaults {
/**
* Creates a [NavigationSuiteColors] with the provided colors for the container color, according
* to the Material specification.
@@ -526,64 +462,6 @@
)
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal object NavigationSuiteScaffoldScopeImpl : NavigationSuiteScaffoldScope {
- override fun Modifier.alignment(alignment: NavigationSuiteAlignment): Modifier {
- return this.then(
- AlignmentElement(alignment = alignment)
- )
- }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal class AlignmentElement(
- val alignment: NavigationSuiteAlignment
-) : ModifierNodeElement<AlignmentNode>() {
- override fun create(): AlignmentNode {
- return AlignmentNode(alignment)
- }
-
- override fun update(node: AlignmentNode) {
- node.alignment = alignment
- }
-
- override fun InspectorInfo.inspectableProperties() {
- name = "alignment"
- value = alignment
- }
-
- override fun hashCode(): Int = alignment.hashCode()
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- val otherModifier = other as? AlignmentElement ?: return false
- return alignment == otherModifier.alignment
- }
-}
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal class AlignmentNode(
- var alignment: NavigationSuiteAlignment
-) : ParentDataModifierNode, Modifier.Node() {
- override fun Density.modifyParentData(parentData: Any?) =
- ((parentData as? NavigationSuiteParentData) ?: NavigationSuiteParentData()).also {
- it.alignment = alignment
- }
-}
-
-/** Parent data associated with children. */
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal data class NavigationSuiteParentData(
- var alignment: NavigationSuiteAlignment? = null
-)
-
-internal val IntrinsicMeasurable.navigationSuiteParentData: NavigationSuiteParentData?
- get() = parentData as? NavigationSuiteParentData
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-internal val NavigationSuiteParentData?.alignment: NavigationSuiteAlignment
- get() = this?.alignment ?: NavigationSuiteAlignment.StartVertical
-
-@OptIn(ExperimentalMaterial3AdaptiveApi::class)
internal expect val WindowAdaptiveInfoDefault: WindowAdaptiveInfo
@Composable
get
@@ -594,7 +472,7 @@
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
-private class NavigationSuiteItem constructor(
+private class NavigationSuiteItem(
val selected: Boolean,
val onClick: () -> Unit,
val icon: @Composable () -> Unit,
diff --git a/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffoldTest.kt b/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffoldTest.kt
index 3ab8ee3..359f940 100644
--- a/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffoldTest.kt
+++ b/compose/material3/material3-adaptive/src/test/kotlin/androidx/compose/material3/adaptive/NavigationSuiteScaffoldTest.kt
@@ -36,7 +36,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(400.dp, 400.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -47,7 +47,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(400.dp, 800.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -58,7 +58,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(400.dp, 1000.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -69,7 +69,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(800.dp, 400.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -80,7 +80,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(800.dp, 800.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -91,7 +91,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(800.dp, 1000.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -102,7 +102,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(1000.dp, 400.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -113,7 +113,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(1000.dp, 800.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationRail)
}
@@ -124,7 +124,7 @@
windowSizeClass = WindowSizeClass.calculateFromSize(DpSize(1000.dp, 1000.dp))
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationRail)
}
@@ -136,7 +136,7 @@
isTableTop = true
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
@@ -148,7 +148,7 @@
isTableTop = true
)
- assertThat(NavigationSuiteDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
+ assertThat(NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(mockAdaptiveInfo))
.isEqualTo(NavigationSuiteType.NavigationBar)
}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index 2e69826..82b469d 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -1174,6 +1174,9 @@
public final class ScaffoldKt {
method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static boolean getScaffoldSubcomposeInMeasureFix();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static void setScaffoldSubcomposeInMeasureFix(boolean);
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final boolean ScaffoldSubcomposeInMeasureFix;
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index 2e69826..82b469d 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -1174,6 +1174,9 @@
public final class ScaffoldKt {
method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static boolean getScaffoldSubcomposeInMeasureFix();
+ method @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static void setScaffoldSubcomposeInMeasureFix(boolean);
+ property @SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api public static final boolean ScaffoldSubcomposeInMeasureFix;
}
@SuppressCompatibility @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class SearchBarColors {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
index 6d2d0b7..859ae81 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -48,7 +48,6 @@
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
@@ -186,9 +185,7 @@
rule.setContent {
val context = LocalContext.current
- val density = LocalDensity.current
- val resScreenWidth = context.resources.configuration.screenWidthDp
- with(density) { screenWidth = resScreenWidth.dp.roundToPx() }
+ screenWidth = context.resources.displayMetrics.widthPixels
val windowInsets = if (edgeToEdgeWrapper.edgeToEdgeEnabled)
WindowInsets(0) else BottomSheetDefaults.windowInsets
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 5ab689e..ab6f9b9 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.SubcomposeLayout
@@ -657,6 +658,72 @@
assertWithMessage("Expected placeCount to be >= 1").that(onPlaceCount).isAtLeast(1)
}
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
+ fun scaffold_subcomposeInMeasureFix_enabled_measuresChildrenInMeasurement() {
+ ScaffoldSubcomposeInMeasureFix = true
+ var size: IntSize? = null
+ var measured = false
+ rule.setContent {
+ Layout(
+ content = {
+ Scaffold(
+ content = {
+ Box(Modifier.onSizeChanged { size = it })
+ }
+ )
+ }
+ ) { measurables, constraints ->
+ measurables.map { it.measure(constraints) }
+ measured = true
+ layout(0, 0) {
+ // Empty measurement since we only care about placement
+ }
+ }
+ }
+
+ assertWithMessage("Measure should have been executed")
+ .that(measured).isTrue()
+ assertWithMessage("Expected size to be initialized")
+ .that(size).isNotNull()
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Test
+ fun scaffold_subcomposeInMeasureFix_disabled_measuresChildrenInPlacement() {
+ ScaffoldSubcomposeInMeasureFix = false
+ var size: IntSize? = null
+ var measured = false
+ var placed = false
+ rule.setContent {
+ Layout(
+ content = {
+ Scaffold(
+ content = {
+ Box(Modifier.onSizeChanged { size = it })
+ }
+ )
+ }
+ ) { measurables, constraints ->
+ val placeables = measurables.map { it.measure(constraints) }
+ measured = true
+ assertWithMessage("Expected size to not be initialized in placement")
+ .that(size).isNull()
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ placeables.forEach { it.place(0, 0) }
+ placed = true
+ }
+ }
+ }
+
+ assertWithMessage("Measure should have been executed")
+ .that(measured).isTrue()
+ assertWithMessage("Placement should have been executed")
+ .that(placed).isTrue()
+ assertWithMessage("Expected size to be initialized")
+ .that(size).isNotNull()
+ }
+
private fun assertDpIsWithinThreshold(actual: Dp, expected: Dp, threshold: Dp) {
assertThat(actual.value).isWithin(threshold.value).of(expected.value)
}
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
index 771d739..a053929a 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ModalBottomSheet.android.kt
@@ -88,7 +88,6 @@
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
import kotlin.math.max
-import kotlin.math.roundToInt
import kotlinx.coroutines.launch
/**
@@ -417,10 +416,7 @@
composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displayWidth: Int
- get() {
- val density = context.resources.displayMetrics.density
- return (context.resources.configuration.screenWidthDp * density).roundToInt()
- }
+ get() = context.resources.displayMetrics.widthPixels
private val params: WindowManager.LayoutParams =
WindowManager.LayoutParams().apply {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index fea69f4..4e2d001 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -27,7 +27,10 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -126,6 +129,7 @@
* @param bottomBar the content to place at the bottom of the [Scaffold], on top of the
* [content], typically a [NavigationBar].
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ScaffoldLayout(
fabPosition: FabPosition,
@@ -135,7 +139,42 @@
fab: @Composable () -> Unit,
contentWindowInsets: WindowInsets,
bottomBar: @Composable () -> Unit
+) {
+ if (ScaffoldSubcomposeInMeasureFix) {
+ ScaffoldLayoutWithMeasureFix(
+ fabPosition = fabPosition,
+ topBar = topBar,
+ content = content,
+ snackbar = snackbar,
+ fab = fab,
+ contentWindowInsets = contentWindowInsets,
+ bottomBar = bottomBar
+ )
+ } else {
+ LegacyScaffoldLayout(
+ fabPosition = fabPosition,
+ topBar = topBar,
+ content = content,
+ snackbar = snackbar,
+ fab = fab,
+ contentWindowInsets = contentWindowInsets,
+ bottomBar = bottomBar
+ )
+ }
+}
+/**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+private fun ScaffoldLayoutWithMeasureFix(
+ fabPosition: FabPosition,
+ topBar: @Composable () -> Unit,
+ content: @Composable (PaddingValues) -> Unit,
+ snackbar: @Composable () -> Unit,
+ fab: @Composable () -> Unit,
+ contentWindowInsets: WindowInsets,
+ bottomBar: @Composable () -> Unit
) {
SubcomposeLayout { constraints ->
val layoutWidth = constraints.maxWidth
@@ -295,6 +334,175 @@
}
/**
+ * Layout for a [Scaffold]'s content, subcomposing and measuring during measurement.
+ */
+@Composable
+private fun LegacyScaffoldLayout(
+ fabPosition: FabPosition,
+ topBar: @Composable () -> Unit,
+ content: @Composable (PaddingValues) -> Unit,
+ snackbar: @Composable () -> Unit,
+ fab: @Composable () -> Unit,
+ contentWindowInsets: WindowInsets,
+ bottomBar: @Composable () -> Unit
+) {
+ SubcomposeLayout { constraints ->
+ val layoutWidth = constraints.maxWidth
+ val layoutHeight = constraints.maxHeight
+
+ val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+
+ layout(layoutWidth, layoutHeight) {
+ val topBarPlaceables = subcompose(ScaffoldLayoutContent.TopBar, topBar).fastMap {
+ it.measure(looseConstraints)
+ }
+
+ val topBarHeight = topBarPlaceables.fastMaxBy { it.height }?.height ?: 0
+
+ val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).fastMap {
+ // respect only bottom and horizontal for snackbar and fab
+ val leftInset = contentWindowInsets
+ .getLeft(this@SubcomposeLayout, layoutDirection)
+ val rightInset = contentWindowInsets
+ .getRight(this@SubcomposeLayout, layoutDirection)
+ val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+ // offset the snackbar constraints by the insets values
+ it.measure(
+ looseConstraints.offset(
+ -leftInset - rightInset,
+ -bottomInset
+ )
+ )
+ }
+
+ val snackbarHeight = snackbarPlaceables.fastMaxBy { it.height }?.height ?: 0
+ val snackbarWidth = snackbarPlaceables.fastMaxBy { it.width }?.width ?: 0
+
+ val fabPlaceables =
+ subcompose(ScaffoldLayoutContent.Fab, fab).fastMapNotNull { measurable ->
+ // respect only bottom and horizontal for snackbar and fab
+ val leftInset =
+ contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+ val rightInset =
+ contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+ val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+ measurable.measure(
+ looseConstraints.offset(
+ -leftInset - rightInset,
+ -bottomInset
+ )
+ )
+ .takeIf { it.height != 0 && it.width != 0 }
+ }
+
+ val fabPlacement = if (fabPlaceables.isNotEmpty()) {
+ val fabWidth = fabPlaceables.fastMaxBy { it.width }!!.width
+ val fabHeight = fabPlaceables.fastMaxBy { it.height }!!.height
+ // FAB distance from the left of the layout, taking into account LTR / RTL
+ val fabLeftOffset = when (fabPosition) {
+ FabPosition.Start -> {
+ if (layoutDirection == LayoutDirection.Ltr) {
+ FabSpacing.roundToPx()
+ } else {
+ layoutWidth - FabSpacing.roundToPx() - fabWidth
+ }
+ }
+ FabPosition.End -> {
+ if (layoutDirection == LayoutDirection.Ltr) {
+ layoutWidth - FabSpacing.roundToPx() - fabWidth
+ } else {
+ FabSpacing.roundToPx()
+ }
+ }
+ else -> (layoutWidth - fabWidth) / 2
+ }
+
+ FabPlacement(
+ left = fabLeftOffset,
+ width = fabWidth,
+ height = fabHeight
+ )
+ } else {
+ null
+ }
+
+ val bottomBarPlaceables = subcompose(ScaffoldLayoutContent.BottomBar) {
+ CompositionLocalProvider(
+ LocalFabPlacement provides fabPlacement,
+ content = bottomBar
+ )
+ }.fastMap { it.measure(looseConstraints) }
+
+ val bottomBarHeight = bottomBarPlaceables.fastMaxBy { it.height }?.height
+ val fabOffsetFromBottom = fabPlacement?.let {
+ if (bottomBarHeight == null) {
+ it.height + FabSpacing.roundToPx() +
+ contentWindowInsets.getBottom(this@SubcomposeLayout)
+ } else {
+ // Total height is the bottom bar height + the FAB height + the padding
+ // between the FAB and bottom bar
+ bottomBarHeight + it.height + FabSpacing.roundToPx()
+ }
+ }
+
+ val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
+ snackbarHeight +
+ (fabOffsetFromBottom ?: bottomBarHeight
+ ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
+ } else {
+ 0
+ }
+
+ val bodyContentPlaceables = subcompose(ScaffoldLayoutContent.MainContent) {
+ val insets = contentWindowInsets.asPaddingValues(this@SubcomposeLayout)
+ val innerPadding = PaddingValues(
+ top =
+ if (topBarPlaceables.isEmpty()) {
+ insets.calculateTopPadding()
+ } else {
+ topBarHeight.toDp()
+ },
+ bottom =
+ if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
+ insets.calculateBottomPadding()
+ } else {
+ bottomBarHeight.toDp()
+ },
+ start = insets.calculateStartPadding((this@SubcomposeLayout).layoutDirection),
+ end = insets.calculateEndPadding((this@SubcomposeLayout).layoutDirection)
+ )
+ content(innerPadding)
+ }.fastMap { it.measure(looseConstraints) }
+
+ // Placing to control drawing order to match default elevation of each placeable
+ bodyContentPlaceables.fastForEach {
+ it.place(0, 0)
+ }
+ topBarPlaceables.fastForEach {
+ it.place(0, 0)
+ }
+ snackbarPlaceables.fastForEach {
+ it.place(
+ (layoutWidth - snackbarWidth) / 2 +
+ contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection),
+ layoutHeight - snackbarOffsetFromBottom
+ )
+ }
+ // The bottom bar is always at the bottom of the layout
+ bottomBarPlaceables.fastForEach {
+ it.place(0, layoutHeight - (bottomBarHeight ?: 0))
+ }
+ // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
+ fabPlacement?.let { placement ->
+ fabPlaceables.fastForEach {
+ it.place(placement.left, layoutHeight - fabOffsetFromBottom!!)
+ }
+ }
+ }
+ }
+}
+
+/**
* Object containing various default values for [Scaffold] component.
*/
object ScaffoldDefaults {
@@ -348,6 +556,22 @@
}
/**
+ * Flag indicating if [Scaffold] should subcompose and measure its children during measurement or
+ * during placement.
+ * Set this flag to false to keep Scaffold's old measurement behavior (measuring in placement).
+ *
+ * <b>This flag will be removed in Compose 1.6.0-beta01.</b> If you encounter any issues with the
+ * new behavior, please file an issue at: issuetracker.google.com/issues/new?component=742043
+ */
+// TODO(b/299621062): Remove flag before beta
+@Suppress("GetterSetterNames", "OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:Suppress("GetterSetterNames")
+@get:ExperimentalMaterial3Api
+@set:ExperimentalMaterial3Api
+@ExperimentalMaterial3Api
+var ScaffoldSubcomposeInMeasureFix by mutableStateOf(true)
+
+/**
* Placement information for a [FloatingActionButton] inside a [Scaffold].
*
* @property left the FAB's offset from the left edge of the bottom bar, already adjusted for RTL
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index a1f94db..4e0c3fe 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -1278,7 +1278,7 @@
private var reusingGroup = -1
private var childrenComposing: Int = 0
private var compositionToken: Int = 0
- private var sourceInformationEnabled = true
+ private var sourceInformationEnabled = false
private val derivedStateObserver = object : DerivedStateObserver {
override fun start(derivedState: DerivedState<*>) {
childrenComposing++
@@ -1458,6 +1458,12 @@
if (!forceRecomposeScopes) {
forceRecomposeScopes = parentContext.collectingParameterInformation
}
+
+ // Propagate collecting source information
+ if (!sourceInformationEnabled) {
+ sourceInformationEnabled = parentContext.collectingSourceInformation
+ }
+
parentProvider.read(LocalInspectionTables)?.let {
it.add(slotTable)
parentContext.recordInspectionTable(it)
@@ -1544,11 +1550,13 @@
private set
/**
- * Start collecting parameter information. This enables the tools API to always be able to
- * determine the parameter values of composable calls.
+ * Start collecting parameter information and line number information. This enables the tools
+ * API to always be able to determine the parameter values of composable calls as well as the
+ * source location of calls.
*/
override fun collectParameterInformation() {
forceRecomposeScopes = true
+ sourceInformationEnabled = true
}
@OptIn(InternalComposeApi::class)
@@ -2115,6 +2123,7 @@
CompositionContextImpl(
compoundKeyHash,
forceRecomposeScopes,
+ sourceInformationEnabled,
(composition as? CompositionImpl)?.observerHolder
)
)
@@ -3158,20 +3167,22 @@
@ComposeCompilerApi
override fun sourceInformation(sourceInformation: String) {
if (inserting && sourceInformationEnabled) {
- writer.insertAux(sourceInformation)
+ writer.recordGroupSourceInformation(sourceInformation)
}
}
@ComposeCompilerApi
override fun sourceInformationMarkerStart(key: Int, sourceInformation: String) {
- if (sourceInformationEnabled)
- start(key, objectKey = null, kind = GroupKind.Group, data = sourceInformation)
+ if (inserting && sourceInformationEnabled) {
+ writer.recordGrouplessCallSourceInformationStart(key, sourceInformation)
+ }
}
@ComposeCompilerApi
override fun sourceInformationMarkerEnd() {
- if (sourceInformationEnabled)
- end(isNode = false)
+ if (inserting && sourceInformationEnabled) {
+ writer.recordGrouplessCallSourceInformationEnd()
+ }
}
override fun disableSourceInformation() {
@@ -3503,6 +3514,7 @@
private inner class CompositionContextImpl(
override val compoundHashKey: Int,
override val collectingParameterInformation: Boolean,
+ override val collectingSourceInformation: Boolean,
override val observerHolder: CompositionObserverHolder?
) : CompositionContext() {
var inspectionTables: MutableSet<MutableSet<CompositionData>>? = null
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
index 4b64873..ae6583c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionContext.kt
@@ -38,6 +38,7 @@
abstract class CompositionContext internal constructor() {
internal abstract val compoundHashKey: Int
internal abstract val collectingParameterInformation: Boolean
+ internal abstract val collectingSourceInformation: Boolean
internal open val observerHolder: CompositionObserverHolder? get() = null
/**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index d75d4bf..6b38d81 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -1303,6 +1303,9 @@
internal override val collectingParameterInformation: Boolean
get() = false
+ internal override val collectingSourceInformation: Boolean
+ get() = false
+
internal override fun recordInspectionTable(table: MutableSet<CompositionData>) {
// TODO: The root recomposer might be a better place to set up inspection
// than the current configuration with an CompositionLocal
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 32a9f5c..decf811 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-@file:OptIn(InternalComposeApi::class)
-
package androidx.compose.runtime
+import androidx.compose.runtime.snapshots.fastAny
import androidx.compose.runtime.snapshots.fastFilterIndexed
import androidx.compose.runtime.snapshots.fastForEach
import androidx.compose.runtime.snapshots.fastMap
@@ -133,6 +132,11 @@
internal var anchors: ArrayList<Anchor> = arrayListOf()
/**
+ * A map of source information to anchor.
+ */
+ internal var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
+
+ /**
* Returns true if the slot table is empty
*/
override val isEmpty get() = groupsSize == 0
@@ -191,7 +195,7 @@
runtimeCheck(readers <= 0) { "Cannot start a writer when a reader is pending" }
writer = true
version++
- return SlotWriter(this)
+ return SlotWriter(table = this)
}
/**
@@ -204,7 +208,7 @@
* at [index] is still in this table.
*/
fun anchor(index: Int): Anchor {
- runtimeCheck(!writer) { "use active SlotWriter to create an anchor location instead " }
+ runtimeCheck(!writer) { "use active SlotWriter to create an anchor location instead" }
require(index in 0 until groupsSize) { "Parameter index is out of range" }
return anchors.getOrAdd(index, groupsSize) {
Anchor(index)
@@ -212,6 +216,14 @@
}
/**
+ * Return an anchor to the given index if there is one already, null otherwise.
+ */
+ fun tryAnchor(index: Int): Anchor? {
+ runtimeCheck(!writer) { "use active SlotWriter to crate an anchor for location instead" }
+ return if (index in 0 until groupsSize) anchors.find(index, groupsSize) else null
+ }
+
+ /**
* Return the group index for [anchor]. This [SlotTable] is assumed to own [anchor] but that
* is not validated. If [anchor] is not owned by this [SlotTable] the result is undefined.
* If a [SlotWriter] is open the [SlotWriter.anchorIndex] must be called instead as [anchor]
@@ -247,9 +259,22 @@
/**
* Close [reader].
*/
- internal fun close(reader: SlotReader) {
+ internal fun close(
+ reader: SlotReader,
+ sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
+ ) {
runtimeCheck(reader.table === this && readers > 0) { "Unexpected reader close()" }
readers--
+ if (sourceInformationMap != null) {
+ synchronized(this) {
+ val thisMap = this.sourceInformationMap
+ if (thisMap != null) {
+ thisMap.putAll(sourceInformationMap)
+ } else {
+ this.sourceInformationMap = sourceInformationMap
+ }
+ }
+ }
}
/**
@@ -263,11 +288,12 @@
groupsSize: Int,
slots: Array<Any?>,
slotsSize: Int,
- anchors: ArrayList<Anchor>
+ anchors: ArrayList<Anchor>,
+ sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
) {
require(writer.table === this && this.writer) { "Unexpected writer close()" }
this.writer = false
- setTo(groups, groupsSize, slots, slotsSize, anchors)
+ setTo(groups, groupsSize, slots, slotsSize, anchors, sourceInformationMap)
}
/**
@@ -279,7 +305,8 @@
groupsSize: Int,
slots: Array<Any?>,
slotsSize: Int,
- anchors: ArrayList<Anchor>
+ anchors: ArrayList<Anchor>,
+ sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?,
) {
// Adopt the slots from the writer
this.groups = groups
@@ -287,6 +314,7 @@
this.slots = slots
this.slotsSize = slotsSize
this.anchors = anchors
+ this.sourceInformationMap = sourceInformationMap
}
/**
@@ -357,6 +385,10 @@
return groupsSize > 0 && groups.containsMark(0)
}
+ fun sourceInformationOf(group: Int) = sourceInformationMap?.let { map ->
+ tryAnchor(group)?.let { anchor -> map[anchor] }
+ }
+
/**
* Find the nearest recompose scope for [group] that, when invalidated, will cause [group]
* group to be recomposed.
@@ -376,7 +408,7 @@
/**
* Finds the nearest recompose scope to the provided group and invalidates it. Return
- * true if the invalidation will cause the scope to reccompose, otherwise false which will
+ * true if the invalidation will cause the scope to recompose, otherwise false which will
* require forcing recomposition some other way.
*/
private fun invalidateGroup(group: Int): Boolean {
@@ -487,6 +519,35 @@
require(lastLocation < location) { "Anchor is out of order" }
lastLocation = location
}
+
+ // Verify source information is well-formed
+ fun verifySourceGroup(group: GroupSourceInformation) {
+ group.groups?.fastForEach { item ->
+ when (item) {
+ is Anchor -> {
+ require(item.valid) {
+ "Source map contains invalid anchor"
+ }
+ require(ownsAnchor(item)) {
+ "Source map anchor is not owned by the slot table"
+ }
+ }
+ is GroupSourceInformation -> verifySourceGroup(item)
+ }
+ }
+ }
+
+ sourceInformationMap?.let { sourceInformationMap ->
+ for ((anchor, sourceGroup) in sourceInformationMap) {
+ require(anchor.valid) {
+ "Source map contains invalid anchor"
+ }
+ require(ownsAnchor(anchor)) {
+ "Source map anchor is not owned by the slot table"
+ }
+ verifySourceGroup(sourceGroup)
+ }
+ }
}
/**
@@ -518,7 +579,21 @@
repeat(level) { append(' ') }
append("Group(")
append(index)
- append(") key=")
+ append(")")
+ tryAnchor(index)?.let { anchor ->
+ sourceInformationMap?.get(anchor)?.let { groupInformation ->
+ groupInformation.sourceInformation?.let {
+ if (it.startsWith("C(") || it.startsWith("CC(")) {
+ val start = it.indexOf("(") + 1
+ val endParen = it.indexOf(')')
+ append(" ")
+ append(it.substring(start, endParen))
+ append("()")
+ }
+ }
+ }
+ }
+ append(" key=")
append(groups.key(index))
fun dataIndex(index: Int) =
if (index >= groupsSize) slotsSize else groups.dataAnchor(index)
@@ -635,6 +710,105 @@
val valid get() = location != Int.MIN_VALUE
fun toIndexFor(slots: SlotTable) = slots.anchorIndex(this)
fun toIndexFor(writer: SlotWriter) = writer.anchorIndex(this)
+
+ override fun toString(): String {
+ return "${super.toString()}{ location = $location }"
+ }
+}
+
+internal class GroupSourceInformation(val key: Int, var sourceInformation: String?) {
+ var groups: ArrayList<Any /* Anchor | GroupSourceInformation */>? = null
+ var closed = false
+
+ fun startGrouplessCall(key: Int, sourceInformation: String) {
+ openInformation().add(GroupSourceInformation(key, sourceInformation))
+ }
+
+ fun endGrouplessCall() { openInformation().close() }
+
+ fun reportGroup(writer: SlotWriter, group: Int) {
+ openInformation().add(writer.anchor(group))
+ }
+
+ fun reportGroup(table: SlotTable, group: Int) {
+ openInformation().add(table.anchor(group))
+ }
+
+ fun addGroupAfter(writer: SlotWriter, predecessor: Int, group: Int) {
+ val groups = groups ?: ArrayList<Any>().also { groups = it }
+ val index = if (predecessor >= 0) {
+ val anchor = writer.tryAnchor(predecessor)
+ if (anchor != null) {
+ groups.fastIndexOf {
+ it == anchor ||
+ (it is GroupSourceInformation && it.hasAnchor(anchor))
+ }
+ } else 0
+ } else 0
+ groups.add(index, writer.anchor(group))
+ }
+
+ fun close() { closed = true }
+
+ // Return the current open nested source information or this.
+ private fun openInformation(): GroupSourceInformation =
+ (groups?.let {
+ groups -> groups.fastLastOrNull { it is GroupSourceInformation && !it.closed }
+ } as? GroupSourceInformation)?.openInformation() ?: this
+
+ private fun add(group: Any /* Anchor | GroupSourceInformation */) {
+ val groups = groups ?: ArrayList()
+ this.groups = groups
+ groups.add(group)
+ }
+
+ private fun hasAnchor(anchor: Anchor): Boolean =
+ groups?.fastAny {
+ it == anchor || (it is GroupSourceInformation && hasAnchor(anchor))
+ } == true
+
+ fun removeAnchor(anchor: Anchor): Boolean {
+ val groups = groups
+ if (groups != null) {
+ var index = groups.size - 1
+ while (index >= 0) {
+ when (val item = groups[index]) {
+ is Anchor -> if (item == anchor) groups.removeAt(index)
+ is GroupSourceInformation -> if (!item.removeAnchor(anchor)) {
+ groups.removeAt(index)
+ }
+ }
+ index--
+ }
+ if (groups.isEmpty()) {
+ this.groups = null
+ return false
+ }
+ return true
+ }
+ return true
+ }
+}
+
+private inline fun <T> ArrayList<T>.fastLastOrNull(predicate: (T) -> Boolean): T? {
+ var index = size - 1
+ while (index >= 0) {
+ val value = get(index)
+ if (predicate(value)) return value
+ index--
+ }
+ return null
+}
+
+private inline fun <T> ArrayList<T>.fastIndexOf(predicate: (T) -> Boolean): Int {
+ var index = 0
+ val size = size
+ while (index < size) {
+ val value = get(index)
+ if (predicate(value)) return index
+ index++
+ }
+ return -1
}
/**
@@ -668,6 +842,12 @@
private val slotsSize: Int = table.slotsSize
/**
+ * A local copy of the [sourceInformationMap] being created to be merged into [table]
+ * when the reader closes.
+ */
+ private var sourceInformationMap: HashMap<Anchor, GroupSourceInformation>? = null
+
+ /**
* True if the reader has been closed
*/
var closed: Boolean = false
@@ -927,7 +1107,7 @@
*/
fun close() {
closed = true
- table.close(this)
+ table.close(this, sourceInformationMap)
}
/**
@@ -935,14 +1115,17 @@
*/
fun startGroup() {
if (emptyCount <= 0) {
+ val parent = parent
+ val currentGroup = currentGroup
require(groups.parentAnchor(currentGroup) == parent) { "Invalid slot table detected" }
- parent = currentGroup
+ sourceInformationMap?.get(anchor(parent))?.reportGroup(table, currentGroup)
+ this.parent = currentGroup
currentEnd = currentGroup + groups.groupSize(currentGroup)
- val current = currentGroup++
- currentSlot = groups.slotAnchor(current)
- currentSlotEnd = if (current >= groupsSize - 1)
+ this.currentGroup = currentGroup + 1
+ currentSlot = groups.slotAnchor(currentGroup)
+ currentSlotEnd = if (currentGroup >= groupsSize - 1)
slotsSize else
- groups.dataAnchor(current + 1)
+ groups.dataAnchor(currentGroup + 1)
}
}
@@ -1023,6 +1206,20 @@
}
}
+ fun recordGroupSourceInformation(sourceInformation: String) {
+ val map = sourceInformationMap ?: HashMap()
+ this.sourceInformationMap = map
+ map[anchor(parent)] = GroupSourceInformation(0, sourceInformation)
+ }
+
+ fun recordGrouplessCallSourceInformationStart(key: Int, sourceInformation: String) {
+ sourceInformationMap?.get(tryAnchor(parent))?.startGrouplessCall(key, sourceInformation)
+ }
+
+ fun recordGrouplessCallSourceInformationEnd() {
+ sourceInformationMap?.get(tryAnchor(parent))?.endGrouplessCall()
+ }
+
/**
* Extract the keys from this point to the end of the group. The current is left unaffected.
* Must be called inside a group.
@@ -1057,6 +1254,11 @@
Anchor(index)
}
+ /**
+ * Return an anchor if one has already been created, null otherwise.
+ */
+ private fun tryAnchor(index: Int) = table.anchors.find(index, groupsSize)
+
private fun IntArray.node(index: Int) = if (isNode(index)) {
slots[nodeIndex(index)]
} else Composer.Empty
@@ -1126,11 +1328,16 @@
private var slots: Array<Any?> = table.slots
/**
- * A copy of the [SlotWriter.anchors] to avoid having to index through [table].
+ * A copy of the [SlotTable.anchors] to avoid having to index through [table].
*/
private var anchors: ArrayList<Anchor> = table.anchors
/**
+ * A copy of [SlotTable.sourceInformationMap] to avoid having to index through [table]
+ */
+ private var sourceInformationMap = table.sourceInformationMap
+
+ /**
* Group index of the start of the gap in the groups array.
*/
private var groupGapStart: Int = table.groupsSize
@@ -1340,7 +1547,8 @@
groupsSize = groupGapStart,
slots = slots,
slotsSize = slotsGapStart,
- anchors = anchors
+ anchors = anchors,
+ sourceInformationMap = sourceInformationMap,
)
}
@@ -1411,6 +1619,49 @@
currentSlot++
}
+ fun recordGroupSourceInformation(sourceInformation: String) {
+ if (insertCount > 0) {
+ groupSourceInformationFor(parent, sourceInformation)
+ }
+ }
+
+ fun recordGrouplessCallSourceInformationStart(key: Int, value: String) {
+ if (insertCount > 0) {
+ groupSourceInformationFor(parent, null)?.startGrouplessCall(key, value)
+ }
+ }
+
+ fun recordGrouplessCallSourceInformationEnd() {
+ if (insertCount > 0) {
+ groupSourceInformationFor(parent, null)?.endGrouplessCall()
+ }
+ }
+
+ private fun groupSourceInformationFor(
+ parent: Int,
+ sourceInformation: String?
+ ): GroupSourceInformation? {
+ val map = sourceInformationMap ?: HashMap()
+ this.sourceInformationMap = map
+ return map.getOrPut(anchor(parent)) {
+ val result = GroupSourceInformation(0, sourceInformation)
+
+ // If we called from a groupless call then the groups added before this call
+ // are not reflected in this group information so they need to be added now
+ // if they exist.
+ if (sourceInformation == null) {
+ var child = parent + 1
+ val end = currentGroup
+ while (child < end) {
+ result.reportGroup(this, child)
+ child += groups.groupSize(child)
+ }
+ }
+
+ result
+ }
+ }
+
/**
* Updates the node for the current node group to [value].
*/
@@ -1601,6 +1852,7 @@
fun startData(key: Int, aux: Any?) = startGroup(key, Composer.Empty, isNode = false, aux = aux)
private fun startGroup(key: Int, objectKey: Any?, isNode: Boolean, aux: Any?) {
+ val previousParent = parent
val inserting = insertCount > 0
nodeCountStack.push(nodeCount)
@@ -1637,9 +1889,11 @@
val newCurrent = current + 1
this.parent = current
this.currentGroup = newCurrent
+ if (previousParent >= 0) {
+ sourceInformationOf(previousParent)?.reportGroup(this, current)
+ }
newCurrent
} else {
- val previousParent = parent
startStack.push(previousParent)
saveCurrentGroupEnd()
val currentGroup = currentGroup
@@ -1740,7 +1994,7 @@
internal fun bashGroup() {
startGroup()
while (!isGroupEnd) {
- insertParentGroup(-3)
+ bashParentGroup()
skipGroup()
}
endGroup()
@@ -1799,6 +2053,13 @@
val oldSlot = currentSlot
val count = skipGroup()
+ // Remove the group from its parent information
+ sourceInformationOf(parent)?.let { sourceInformation ->
+ tryAnchor(oldGroup)?.let { anchor ->
+ sourceInformation.removeAnchor(anchor)
+ }
+ }
+
// Remove any recalculate markers ahead of this delete as they are in the group
// that is being deleted.
pendingRecalculateMarks?.let {
@@ -2080,6 +2341,42 @@
anchors
} else emptyList()
+ // Move any source information from the source table to the destination table
+ if (anchors.isNotEmpty()) {
+ val sourceSourceInformationMap = fromWriter.sourceInformationMap
+ if (sourceSourceInformationMap != null) {
+ var destinationSourceInformation = toWriter.sourceInformationMap
+ anchors.fastForEach { anchor ->
+ val information = sourceSourceInformationMap[anchor]
+ if (information != null) {
+ sourceSourceInformationMap.remove(anchor)
+ val map = destinationSourceInformation ?: run {
+ val map = HashMap<Anchor, GroupSourceInformation>()
+ destinationSourceInformation = map
+ toWriter.sourceInformationMap = destinationSourceInformation
+ map
+ }
+ map[anchor] = information
+ }
+ }
+ if (sourceSourceInformationMap.isEmpty()) {
+ fromWriter.sourceInformationMap = null
+ }
+ }
+ }
+
+ // Record the new group in the parent information
+ val toWriterParent = toWriter.parent
+ toWriter.sourceInformationOf(parent)?.let {
+ var predecessor = -1
+ var child = toWriterParent + 1
+ val endGroup = toWriter.currentGroup
+ while (child < endGroup) {
+ predecessor = child
+ child += toWriter.groups.groupSize(child)
+ }
+ it.addGroupAfter(toWriter, predecessor, endGroup)
+ }
val parentGroup = fromWriter.parent(fromIndex)
val anchorsRemoved = if (!removeSourceGroup) {
// e.g.: we can skip groups removal for insertTable of Composer because
@@ -2131,6 +2428,7 @@
if (hasMarks) {
toWriter.updateContainsMark(parent)
}
+
return anchors
}
}
@@ -2205,10 +2503,12 @@
val myGroups = groups
val mySlots = slots
val myAnchors = anchors
+ val mySourceInformation = sourceInformationMap
val groups = table.groups
val groupsSize = table.groupsSize
val slots = table.slots
val slotsSize = table.slotsSize
+ val sourceInformation = table.sourceInformationMap
this.groups = groups
this.slots = slots
this.anchors = table.anchors
@@ -2217,8 +2517,9 @@
this.slotsGapStart = slotsSize
this.slotsGapLen = slots.size - slotsSize
this.slotsGapOwner = groupsSize
+ this.sourceInformationMap = sourceInformation
- table.setTo(myGroups, 0, mySlots, 0, myAnchors)
+ table.setTo(myGroups, 0, mySlots, 0, myAnchors, mySourceInformation)
return this.anchors
}
@@ -2239,7 +2540,8 @@
* all remaining children of the current group will be parented by a new group and the
* [currentSlot] will be moved to after the group inserted.
*/
- fun insertParentGroup(key: Int) {
+ private fun bashParentGroup() {
+ val key = -3
runtimeCheck(insertCount == 0) { "Writer cannot be inserting" }
if (isGroupEnd) {
beginInsert()
@@ -2282,6 +2584,11 @@
addToGroupSizeAlongSpine(parentAddress, 1)
fixParentAnchorsFor(parent, currentGroupEnd, currentGroup)
this.currentGroup = currentGroupEnd
+
+ // Remove any source information for child groups in the bashed group as updating it
+ // will not work as the children are now separated by a group. Just clearing the list
+ // is sufficient as list will be rebuilt when the new content is generated.
+ sourceInformationOf(parent)?.let { group -> group.groups = null }
}
}
@@ -2693,7 +3000,9 @@
// Move the gap to start of the removal and grow the gap
moveGroupGapTo(start)
- if (anchors.isNotEmpty()) anchorsRemoved = removeAnchors(start, len)
+ if (anchors.isNotEmpty()) {
+ anchorsRemoved = removeAnchors(start, len, sourceInformationMap)
+ }
groupGapStart = start
val previousGapLen = groupGapLen
val newGapLen = previousGapLen + len
@@ -2707,14 +3016,25 @@
}
if (currentGroupEnd >= groupGapStart) currentGroupEnd -= len
+ val parent = parent
// Update markers if necessary
if (containsGroupMark(parent)) {
updateContainsMark(parent)
}
+
+ // Remove the group from its parent source information
anchorsRemoved
} else false
}
+ private fun sourceInformationOf(group: Int): GroupSourceInformation? =
+ sourceInformationMap?.let { map ->
+ tryAnchor(group)?.let { anchor -> map[anchor] }
+ }
+
+ internal fun tryAnchor(group: Int) =
+ if (group in 0 until size) anchors.find(group, size) else null
+
/**
* Remove [len] slots from [start].
*/
@@ -2782,7 +3102,11 @@
/**
* A helper function to remove the anchors for groups that are removed.
*/
- private fun removeAnchors(gapStart: Int, size: Int): Boolean {
+ private fun removeAnchors(
+ gapStart: Int,
+ size: Int,
+ sourceInformationMap: HashMap<Anchor, GroupSourceInformation>?
+ ): Boolean {
val gapLen = groupGapLen
val removeEnd = gapStart + size
val groupsSize = capacity - gapLen
@@ -2797,6 +3121,7 @@
if (location >= gapStart) {
if (location < removeEnd) {
anchor.location = Int.MIN_VALUE
+ sourceInformationMap?.remove(anchor)
removeAnchorStart = index
if (removeAnchorEnd == 0) removeAnchorEnd = index + 1
}
@@ -3037,7 +3362,9 @@
override val sourceInfo: String?
get() = if (table.groups.hasAux(group))
table.slots[table.groups.auxIndex(group)] as? String
- else null
+ else table.tryAnchor(group)?.let {
+ table.sourceInformationMap?.get(it)?.sourceInformation
+ }
override val node: Any?
get() = if (table.groups.isNode(group))
@@ -3056,11 +3383,12 @@
override fun iterator(): Iterator<CompositionGroup> {
validateRead()
- return GroupIterator(
- table,
- group + 1,
- group + table.groups.groupSize(group)
- )
+ return table.sourceInformationOf(group)?.let { SourceInformationGroupIterator(table, it) }
+ ?: GroupIterator(
+ table,
+ group + 1,
+ group + table.groups.groupSize(group)
+ )
}
override val groupSize: Int get() = table.groups.groupSize(group)
@@ -3090,6 +3418,21 @@
}
}
+private class SourceInformationSlotTableGroup(
+ val table: SlotTable,
+ val sourceInformation: GroupSourceInformation
+) : CompositionGroup, Iterable<CompositionGroup> {
+ override val key: Any = sourceInformation.key
+ override val sourceInfo: String? get() = sourceInformation.sourceInformation
+ override val node: Any? get() = null
+ override val data: Iterable<Any?> = emptyList()
+ override val compositionGroups: Iterable<CompositionGroup> = this
+ override val isEmpty: Boolean
+ get() = sourceInformation.groups?.isEmpty() != false
+ override fun iterator(): Iterator<CompositionGroup> =
+ SourceInformationGroupIterator(table, sourceInformation)
+}
+
private class GroupIterator(
val table: SlotTable,
start: Int,
@@ -3121,7 +3464,7 @@
private class DataIterator(
val table: SlotTable,
- val group: Int,
+ group: Int,
) : Iterable<Any?>, Iterator<Any?> {
val start = table.groups.dataAnchor(group)
val end = if (group + 1 < table.groupsSize)
@@ -3136,6 +3479,22 @@
).also { index++ }
}
+private class SourceInformationGroupIterator(
+ val table: SlotTable,
+ val group: GroupSourceInformation,
+) : Iterator<CompositionGroup> {
+ private val version = table.version
+ private var index = 0
+ override fun hasNext(): Boolean = group.groups?.let { index < it.size } ?: false
+ override fun next(): CompositionGroup {
+ return when (val group = group.groups?.get(index++)) {
+ is Anchor -> SlotTableGroup(table, group.location, version)
+ is GroupSourceInformation -> SourceInformationSlotTableGroup(table, group)
+ else -> composeRuntimeError("Unexpected group information structure")
+ }
+ }
+}
+
// Parent -1 is reserved to be the root parent index so the anchor must pivot on -2.
private const val parentAnchorPivot = -2
@@ -3364,6 +3723,11 @@
} else get(location)
}
+private fun ArrayList<Anchor>.find(index: Int, effectiveSize: Int): Anchor? {
+ val location = search(index, effectiveSize)
+ return if (location >= 0) get(location) else null
+}
+
/**
* This is inlined here instead to avoid allocating a lambda for the compare when this is used.
*/
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
index 0ed6a87..dd8461b 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/CompositionAndDerivedStateTests.kt
@@ -252,6 +252,8 @@
}
}
+ verifyConsistent()
+
expect(useD)
// Modify A
@@ -265,6 +267,7 @@
use = newUse
a++
expectChanges()
+ verifyConsistent()
revalidate()
expect(newUse, previous)
}
diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
index 333289b..4fc09ce 100644
--- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/SlotTableTests.kt
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-@file:OptIn(InternalComposeApi::class)
package androidx.compose.runtime
+import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.tooling.CompositionData
+import androidx.compose.runtime.tooling.CompositionGroup
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -24,7 +25,6 @@
import kotlin.test.assertSame
import kotlin.test.assertTrue
-@OptIn(InternalComposeApi::class)
class SlotTableTests {
@Test
fun testCanCreate() {
@@ -3910,6 +3910,445 @@
}
@Test
+ fun canReportNonGroupCallInformationDuringWrite() {
+ val slots = SlotTable()
+ slots.write { writer ->
+ writer.insert {
+ writer.group(100) {
+ writer.group(200, "C(200)") {
+ writer.grouplessCall(300, "C(300)") { }
+ writer.grouplessCall(301, "C(301)") { }
+ writer.group(302, "C(302)") { }
+ writer.grouplessCall(303, "C(303)") { }
+ writer.group(304, "C(304)") { }
+ writer.grouplessCall(305, "C(305)") {
+ writer.group(400, "C(400)") { }
+ writer.group(401, "C(401)") { }
+ }
+ writer.grouplessCall(306, "C(306)") {
+ writer.group(402, "C(402)") { }
+ writer.grouplessCall(403, "C(403)") {
+ writer.group(500, "C(500)") { }
+ writer.group(501, "C(501)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val expectedRoot = SourceGroup.group(100) {
+ group(200, "C(200)") {
+ group(300, "C(300)") { }
+ group(301, "C(301)") { }
+ group(302, "C(302)") { }
+ group(303, "C(303)") { }
+ group(304, "C(304)") { }
+ group(305, "C(305)") {
+ group(400, "C(400)") { }
+ group(401, "C(401)") { }
+ }
+ group(306, "C(306)") {
+ group(402, "C(402)") { }
+ group(403, "C(403)") {
+ group(500, "C(500)") { }
+ group(501, "C(501)") { }
+ }
+ }
+ }
+ }
+ val slotsRoot = SourceGroup.group(slots)
+ assertEquals(expectedRoot, slotsRoot)
+ }
+
+ @Test
+ fun canMoveSourceInformationFromAnotherTable() {
+ val sourceTable = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(200, "C(200)") {
+ grouplessCall(300, "C(300)") {
+ group(400, "C(400)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+ sourceTable.verifyWellFormed()
+
+ val mainTable = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(201, "C(201)") { }
+ }
+ }
+ }
+ }
+ }
+ mainTable.verifyWellFormed()
+
+ mainTable.write { writer ->
+ with(writer) {
+ group {
+ insert {
+ moveFrom(sourceTable, 0)
+ }
+ skipToGroupEnd()
+ }
+ }
+ }
+ mainTable.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(200, "C(200)") {
+ group(300, "C(300)") {
+ group(400, "C(400)") { }
+ }
+ }
+ group(201, "C(201)") { }
+ }
+ val received = SourceGroup.group(mainTable)
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canMoveSourceInformationIntoAGroupWithSourceInformation() {
+ val sourceTable = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(300, "C(300)") {
+ grouplessCall(400, "C(400)") {
+ group(500, "C(500)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+ sourceTable.verifyWellFormed()
+
+ val mainTable = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(201, "C(201)") { }
+ }
+ }
+ }
+ }
+ }
+ mainTable.verifyWellFormed()
+
+ mainTable.write { writer ->
+ with(writer) {
+ group {
+ group(201) {
+ insert {
+ moveFrom(sourceTable, 0)
+ }
+ skipToGroupEnd()
+ }
+ skipToGroupEnd()
+ }
+ }
+ }
+ mainTable.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(201, "C(201)") {
+ group(300, "C(300)") {
+ group(400, "C(400)") {
+ group(500, "C(500)") { }
+ }
+ }
+ }
+ }
+ val received = SourceGroup.group(mainTable)
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canRemoveAGroupBeforeAnEmptyGrouplessCall() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200, "C(2001)") { }
+ grouplessCall(201, "C(201)") { }
+ group(202, "C(202)") { }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ slots.write { writer ->
+ with(writer) {
+ group {
+ removeGroup()
+ skipToGroupEnd()
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(201, "C(201)") { }
+ group(202, "C(202)") { }
+ }
+ val received = SourceGroup.group(slots)
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canRemoveAGroupAfterAnEmptyGrouplessCall() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200, "C(200)") { }
+ grouplessCall(201, "C(201)") { }
+ group(202, "C(202)") { }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ slots.write { writer ->
+ with(writer) {
+ group {
+ skipGroup()
+ removeGroup()
+ skipToGroupEnd()
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(200, "C(200)") { }
+ group(201, "C(201)") { }
+ }
+ val received = SourceGroup.group(slots)
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canRemoveAGroupProducedInAGrouplessCall() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200, "C(200)") { }
+ grouplessCall(201, "C(201)") {
+ group(300, "C(300)") { }
+ }
+ group(202, "C(202)") { }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ slots.write { writer ->
+ with(writer) {
+ group {
+ skipGroup()
+ removeGroup()
+ skipToGroupEnd()
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(200, "C(200)") { }
+ group(202, "C(202)") { }
+ }
+ val received = SourceGroup.group(slots)
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canRemoveAGroupWithSourceInformation() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200, "C(200)") { }
+ group(201, "C(201)") { }
+ group(202, "C(202)") {
+ grouplessCall(300, "C(300)") {
+ group(400, "C(400)") {
+ group(500, "C(500)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ slots.write { writer ->
+ with(writer) {
+ group(100) {
+ skipGroup()
+ skipGroup()
+ group {
+ group {
+ removeGroup() // Remove group 500
+ skipToGroupEnd()
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val expected = SourceGroup.group(100) {
+ group(200, "C(200)") { }
+ group(201, "C(201)") { }
+ group(202, "C(202)") {
+ group(300, "C(300)") {
+ group(400, "C(400)") { }
+ }
+ }
+ }
+ val received = SourceGroup.group(slots)
+
+ assertEquals(expected, received)
+ }
+
+ @Test
+ fun canAddSourceInformationUsingAReader() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200) { }
+ group(201) { }
+ group(202) {
+ group(400) {
+ group(500) { }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ slots.verifyWellFormed()
+
+ val initialRootActual = SourceGroup.group(slots)
+ val initialRootExpected = SourceGroup.group(100) {
+ group(200) { }
+ group(201) { }
+ group(202) {
+ group(400) {
+ group(500) { }
+ }
+ }
+ }
+ assertEquals(initialRootExpected, initialRootActual)
+
+ slots.read { reader ->
+ with(reader) {
+ group(100) {
+ group(200, "C(200)") { }
+ group(201, "C(201)") { }
+ group(202, "C(202)") {
+ grouplessCall(300, "C(300)") {
+ group(400, "C(400)") {
+ group(500, "C(500)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ val updatedRootActual = SourceGroup.group(slots)
+ val updatedRootExpected = SourceGroup.group(100) {
+ group(200, "C(200)") { }
+ group(201, "C(201)") { }
+ group(202, "C(202)") {
+ group(300, "C(300)") {
+ group(400, "C(400)") {
+ group(500, "C(500)") { }
+ }
+ }
+ }
+ }
+ assertEquals(updatedRootExpected, updatedRootActual)
+ }
+
+ @Test
+ fun canAddAGrouplessCallToAGroupWithNoSourceInformation() {
+ val slots = SlotTable().apply {
+ write { writer ->
+ with(writer) {
+ insert {
+ group(100) {
+ group(200) {
+ group(300, "C(300)") { }
+ group(301, "C(301)") { }
+ grouplessCall(302, "C(302)") {
+ group(400, "C(400)") { }
+ }
+ }
+ group(201, "C(201)") {
+ group(303) {
+ group(401, "C(401)") { }
+ grouplessCall(402, "C(402)") { }
+ group(403, "C(403)") { }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ val expected = SourceGroup.group(100) {
+ group(200) {
+ group(300, "C(300)") { }
+ group(301, "C(301)") { }
+ group(302, "C(302)") {
+ group(400, "C(400)") { }
+ }
+ }
+ group(201, "C(201)") {
+ group(303) {
+ group(401, "C(401)") { }
+ group(402, "C(402)") { }
+ group(403, "C(403)") { }
+ }
+ }
+ }
+ val received = SourceGroup.group(slots)
+
+ assertEquals(expected, received)
+ }
+
+ @Test
fun canMoveAGroupFromATableIntoAnotherGroupAndModifyThatGroup() {
val slots = SlotTable()
var insertAnchor = Anchor(-1)
@@ -4013,34 +4452,47 @@
private fun SlotWriter.startNode(key: Any?, node: Any?) =
startNode(NodeKey, key, node)
-@OptIn(InternalComposeApi::class)
internal inline fun SlotWriter.group(block: () -> Unit) {
startGroup()
block()
endGroup()
}
-@OptIn(InternalComposeApi::class)
internal inline fun SlotWriter.group(key: Int, block: () -> Unit) {
startGroup(key)
block()
endGroup()
}
-@OptIn(InternalComposeApi::class)
+internal inline fun SlotWriter.group(key: Int, sourceInformation: String, block: () -> Unit) {
+ group(key) {
+ recordGroupSourceInformation(sourceInformation)
+ block()
+ }
+}
+
+internal inline fun SlotWriter.grouplessCall(
+ key: Int,
+ sourceInformation: String,
+ block: () -> Unit
+) {
+ recordGrouplessCallSourceInformationStart(key, sourceInformation)
+ block()
+ recordGrouplessCallSourceInformationEnd()
+}
+
internal inline fun SlotWriter.nodeGroup(key: Int, node: Any, block: () -> Unit = { }) {
startNode(NodeKey, key, node)
block()
endGroup()
}
-@OptIn(InternalComposeApi::class)
+
internal inline fun SlotWriter.insert(block: () -> Unit) {
beginInsert()
block()
endInsert()
}
-@OptIn(InternalComposeApi::class)
internal inline fun SlotReader.group(key: Int, block: () -> Unit) {
assertEquals(key, groupKey)
startGroup()
@@ -4048,14 +4500,30 @@
endGroup()
}
-@OptIn(InternalComposeApi::class)
+internal inline fun SlotReader.group(key: Int, sourceInformation: String, block: () -> Unit) {
+ assertEquals(key, groupKey)
+ startGroup()
+ recordGroupSourceInformation(sourceInformation)
+ block()
+ endGroup()
+}
+
+internal inline fun SlotReader.grouplessCall(
+ key: Int,
+ sourceInformation: String,
+ block: () -> Unit
+) {
+ recordGrouplessCallSourceInformationStart(key, sourceInformation)
+ block()
+ recordGrouplessCallSourceInformationEnd()
+}
+
internal inline fun SlotReader.group(block: () -> Unit) {
startGroup()
block()
endGroup()
}
-@OptIn(InternalComposeApi::class)
private inline fun SlotReader.expectNode(key: Int, node: Any, block: () -> Unit = { }) {
assertEquals(key, groupObjectKey)
assertEquals(node, groupNode)
@@ -4067,7 +4535,6 @@
private const val treeRoot = -1
private const val elementKey = 100
-@OptIn(InternalComposeApi::class)
private fun testSlotsNumbered(): SlotTable {
val slotTable = SlotTable()
slotTable.write { writer ->
@@ -4084,7 +4551,6 @@
}
// Creates 0 until 10 items each with 10 elements numbered 0...n with 0..n slots
-@OptIn(InternalComposeApi::class)
private fun testItems(): SlotTable {
val slots = SlotTable()
slots.write { writer ->
@@ -4121,7 +4587,6 @@
return slots
}
-@OptIn(InternalComposeApi::class)
private fun validateItems(slots: SlotTable) {
slots.read { reader ->
check(reader.groupKey == treeRoot) { "Invalid root key" }
@@ -4172,7 +4637,6 @@
}
}
-@OptIn(InternalComposeApi::class)
private fun narrowTrees(): Pair<SlotTable, List<Anchor>> {
val slots = SlotTable()
val anchors = mutableListOf<Anchor>()
@@ -4221,13 +4685,11 @@
return slots to anchors
}
-@OptIn(InternalComposeApi::class)
private fun SlotReader.expectGroup(key: Int): Int {
assertEquals(key, groupKey)
return skipGroup()
}
-@OptIn(InternalComposeApi::class)
private fun SlotReader.expectGroup(
key: Int,
block: () -> Unit
@@ -4238,12 +4700,10 @@
endGroup()
}
-@OptIn(InternalComposeApi::class)
private fun SlotReader.expectData(value: Any) {
assertEquals(value, next())
}
-@OptIn(InternalComposeApi::class)
private fun SlotReader.expectGroup(
key: Int,
objectKey: Any?,
@@ -4280,3 +4740,43 @@
"Expected test to throw an exception containing \"$message\""
)
}
+
+data class SourceGroup(val key: Any, val source: String?, val children: List<SourceGroup>) {
+
+ override fun toString(): String = buildString { toStringBuilder(this, 0) }
+
+ private fun toStringBuilder(builder: StringBuilder, indent: Int) {
+ repeat(indent) { builder.append(' ') }
+ builder.append("Group(")
+ builder.append(key)
+ builder.append(")")
+ if (source != null) {
+ builder.append(' ')
+ builder.append(source)
+ }
+ builder.appendLine()
+ children.fastForEach { it.toStringBuilder(builder, indent + 2) }
+ }
+
+ data class BuilderScope(private val children: ArrayList<SourceGroup> = ArrayList()) {
+ fun group(key: Int, source: String? = null, block: BuilderScope.() -> Unit) {
+ val scope = BuilderScope()
+ scope.block()
+ this.children.add(SourceGroup(key, source, scope.children))
+ }
+ }
+
+ companion object {
+ fun group(key: Int, block: BuilderScope.() -> Unit): SourceGroup {
+ val children = ArrayList<SourceGroup>()
+ val scope = BuilderScope(children)
+ scope.block()
+ return SourceGroup(key, null, children)
+ }
+
+ fun group(compositionData: CompositionData): SourceGroup =
+ groupOf(compositionData.compositionGroups.first())
+ private fun groupOf(group: CompositionGroup): SourceGroup =
+ SourceGroup(group.key, group.sourceInfo, group.compositionGroups.map(::groupOf))
+ }
+}
diff --git a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
index dcb954f..7fc505e 100644
--- a/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
+++ b/compose/runtime/runtime/src/nonEmulatorJvmTest/kotlin/androidx/compose/runtime/LiveEditTests.kt
@@ -102,7 +102,9 @@
}
@Test
- fun testNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest {
+ fun testNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest(
+ collectSourceInformation = SourceInfo.None
+ ) {
EnsureStatePreservedButRecomposed("a")
RestartGroup {
Text("Hello World")
@@ -112,7 +114,9 @@
}
@Test
- fun testMultipleNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest {
+ fun testMultipleNonRestartableFunctionPreservesParentAndSiblingState() = liveEditTest(
+ collectSourceInformation = SourceInfo.None
+ ) {
RestartGroup {
EnsureStatePreservedButRecomposed("a")
Target("b", restartable = false)
@@ -136,7 +140,9 @@
}
@Test
- fun testInlineComposableLambda() = liveEditTest {
+ fun testInlineComposableLambda() = liveEditTest(
+ collectSourceInformation = SourceInfo.None
+ ) {
RestartGroup {
InlineTarget("a")
EnsureStatePreservedButRecomposed("b")
@@ -165,7 +171,10 @@
@Test
fun testThrowing_recomposition() {
var recomposeCount = 0
- liveEditTest(reloadCount = 2) {
+ liveEditTest(
+ reloadCount = 2,
+ collectSourceInformation = SourceInfo.None,
+ ) {
RestartGroup {
MarkAsTarget()
@@ -216,7 +225,9 @@
@Test
fun testThrowing_recomposition_sideEffect() {
var recomposeCount = 0
- liveEditTest {
+ liveEditTest(
+ collectSourceInformation = SourceInfo.None
+ ) {
RestartGroup {
MarkAsTarget()
@@ -286,7 +297,9 @@
@Test
fun testThrowing_recomposition_remembered() {
var recomposeCount = 0
- liveEditTest {
+ liveEditTest(
+ collectSourceInformation = SourceInfo.None,
+ ) {
RestartGroup {
MarkAsTarget()
@@ -333,7 +346,10 @@
fun testThrowing_invalidationsCarriedAfterCrash() {
var recomposeCount = 0
val state = mutableStateOf(0)
- liveEditTest(reloadCount = 2) {
+ liveEditTest(
+ reloadCount = 2,
+ collectSourceInformation = SourceInfo.None,
+ ) {
RestartGroup {
RestartGroup {
MarkAsTarget()
@@ -391,7 +407,10 @@
@Test
fun testThrowing_movableContent_recomposition() {
var recomposeCount = 0
- liveEditTest(reloadCount = 2) {
+ liveEditTest(
+ reloadCount = 2,
+ collectSourceInformation = SourceInfo.None,
+ ) {
RestartGroup {
MarkAsTarget()
@@ -423,7 +442,10 @@
@Test
fun testThrowing_movableContent_throwAfterMove() {
var recomposeCount = 0
- liveEditTest(reloadCount = 2) {
+ liveEditTest(
+ reloadCount = 2,
+ collectSourceInformation = SourceInfo.None,
+ ) {
expectError("throwInMovableContent", 1)
val content = remember {
@@ -567,28 +589,71 @@
addTargetKey((currentComposer as ComposerImpl).parentKey())
}
+enum class SourceInfo {
+ None,
+ Collect,
+ Both,
+}
+
@OptIn(InternalComposeApi::class)
fun liveEditTest(
reloadCount: Int = 1,
+ collectSourceInformation: SourceInfo = SourceInfo.Both,
fn: @Composable LiveEditTestScope.() -> Unit,
-) = compositionTest {
- with(LiveEditTestScope()) {
- addCheck {
- (composition as? ControlledComposition)?.verifyConsistent()
- }
+) {
+ if (
+ collectSourceInformation == SourceInfo.Both ||
+ collectSourceInformation == SourceInfo.Collect
+ ) {
+ compositionTest {
+ with(LiveEditTestScope()) {
+ addCheck {
+ (composition as? ControlledComposition)?.verifyConsistent()
+ }
- recordErrors {
- compose { fn(this) }
- }
+ recordErrors {
+ compose {
+ currentComposer.collectParameterInformation()
+ fn(this)
+ }
+ }
- repeat(reloadCount) {
- invalidateTargets()
- recordErrors {
- advance()
+ repeat(reloadCount) {
+ invalidateTargets()
+ recordErrors {
+ advance()
+ }
+ }
+
+ runChecks()
}
}
+ }
- runChecks()
+ if (
+ collectSourceInformation == SourceInfo.Both ||
+ collectSourceInformation == SourceInfo.None
+ ) {
+ compositionTest {
+ with(LiveEditTestScope()) {
+ addCheck {
+ (composition as? ControlledComposition)?.verifyConsistent()
+ }
+
+ recordErrors {
+ compose { fn(this) }
+ }
+
+ repeat(reloadCount) {
+ invalidateTargets()
+ recordErrors {
+ advance()
+ }
+ }
+
+ runChecks()
+ }
+ }
}
}
diff --git a/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt b/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
index a0802ab..196eb55 100644
--- a/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
+++ b/compose/ui/ui-tooling-data/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/data/InspectableTests.kt
@@ -150,9 +150,6 @@
assertFalse(receiver.parameterCursor.hasNext())
}
- // Skip Inspectable
- callCursor.next()
-
// OneParameter(1)
validate {
parameter(name = "a", value = 1, fromDefault = false, static = true, compared = false)
@@ -339,11 +336,11 @@
val tree = slotTableRecord.store.first().asTree()
val list = tree.asList()
val parameters = list.filter { group ->
- group.parameters.isNotEmpty() && group.location.let {
+ group.parameters.isNotEmpty() && group.name == "Text" && group.location.let {
it != null && it.sourceFile == "InspectableTests.kt"
}
}
- val names = parameters.drop(1).first().parameters.map { it.name }
+ val names = parameters.first().parameters.map { it.name }
assertEquals(
"text, modifier, color, fontSize, fontStyle, fontWeight, fontFamily, " +
"letterSpacing, textDecoration, textAlign, lineHeight, overflow, softWrap, " +
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt
new file mode 100644
index 0000000..e96dac5
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/graphics/vector/CreateVectorPainterBenchmark.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 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.compose.ui.benchmark.graphics.vector
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.benchmark.R
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CreateVectorPainterBenchmark {
+
+ @get:Rule
+ val benchmarkRule = ComposeBenchmarkRule()
+
+ @Test
+ fun recreateContent() {
+ benchmarkRule.toggleStateBenchmarkDraw({
+ RecreateVectorPainterTestCase()
+ }, assertOneRecomposition = false)
+ }
+}
+
+private class RecreateVectorPainterTestCase : ComposeTestCase, ToggleableTestCase {
+
+ private var alpha by mutableStateOf(1f)
+
+ @Composable
+ override fun Content() {
+ Column {
+ Box(modifier = Modifier.wrapContentSize()) {
+ Image(
+ painter = painterResource(R.drawable.ic_hourglass),
+ contentDescription = null,
+ modifier = Modifier.size(200.dp),
+ alpha = alpha
+ )
+ }
+ }
+ }
+
+ override fun toggleState() {
+ if (alpha == 1.0f) {
+ alpha = 0.5f
+ } else {
+ alpha = 1.0f
+ }
+ }
+}
diff --git a/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml b/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml
new file mode 100644
index 0000000..1666c76
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/main/res/drawable/ic_hourglass.xml
@@ -0,0 +1,76 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24" >
+ <group
+ android:name="hourglass_frame"
+ android:translateX="12"
+ android:translateY="12"
+ android:scaleX="0.75"
+ android:scaleY="0.75" >
+ <group
+ android:name="hourglass_frame_pivot"
+ android:translateX="-12"
+ android:translateY="-12" >
+ <group
+ android:name="group_2_2"
+ android:translateX="12"
+ android:translateY="6.5" >
+ <path
+ android:name="path_2_2"
+ android:pathData="M 6.52099609375 -3.89300537109 c 0.0 0.0 -6.52099609375 6.87901306152 -6.52099609375 6.87901306152 c 0 0.0 -6.52099609375 -6.87901306152 -6.52099609375 -6.87901306152 c 0.0 0.0 13.0419921875 0.0 13.0419921875 0.0 Z M 9.99800109863 -6.5 c 0.0 0.0 -19.9960021973 0.0 -19.9960021973 0.0 c -0.890991210938 0.0 -1.33700561523 1.07699584961 -0.707000732422 1.70700073242 c 0.0 0.0 10.7050018311 11.2929992676 10.7050018311 11.2929992676 c 0 0.0 10.7050018311 -11.2929992676 10.7050018311 -11.2929992676 c 0.630004882812 -0.630004882812 0.183990478516 -1.70700073242 -0.707000732422 -1.70700073242 Z"
+ android:fillColor="#FF777777" />
+ </group>
+ <group
+ android:name="group_1_2"
+ android:translateX="12"
+ android:translateY="17.5" >
+ <path
+ android:name="path_2_1"
+ android:pathData="M 0 -2.98600769043 c 0 0.0 6.52099609375 6.87901306152 6.52099609375 6.87901306152 c 0.0 0.0 -13.0419921875 0.0 -13.0419921875 0.0 c 0.0 0.0 6.52099609375 -6.87901306152 6.52099609375 -6.87901306152 Z M 0 -6.5 c 0 0.0 -10.7050018311 11.2929992676 -10.7050018311 11.2929992676 c -0.630004882812 0.630004882812 -0.184005737305 1.70700073242 0.707000732422 1.70700073242 c 0.0 0.0 19.9960021973 0.0 19.9960021973 0.0 c 0.890991210938 0.0 1.33699035645 -1.07699584961 0.707000732422 -1.70700073242 c 0.0 0.0 -10.7050018311 -11.2929992676 -10.7050018311 -11.2929992676 Z"
+ android:fillColor="#FF777777" />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="fill_outlines"
+ android:translateX="12"
+ android:translateY="12"
+ android:scaleX="0.75"
+ android:scaleY="0.75" >
+ <group
+ android:name="fill_outlines_pivot"
+ android:translateX="-12"
+ android:translateY="-12" >
+ <clip-path
+ android:name="mask_1"
+ android:pathData="M 24 13.3999938965 c 0 0.0 -24 0.0 -24 0.0 c 0 0.0 0 10.6000061035 0 10.6000061035 c 0 0 24 0 24 0 c 0 0 0 -10.6000061035 0 -10.6000061035 Z" />
+ <group
+ android:name="group_1_3"
+ android:translateX="12"
+ android:translateY="12" >
+ <path
+ android:name="path_1_6"
+ android:pathData="M 10.7100067139 10.2900085449 c 0.629989624023 0.629989624023 0.179992675781 1.70999145508 -0.710006713867 1.70999145508 c 0 0 -20 0 -20 0 c -0.889999389648 0 -1.33999633789 -1.08000183105 -0.710006713867 -1.70999145508 c 0.0 0.0 9.76000976562 -10.2900085449 9.76000976563 -10.2900085449 c 0.0 0 -9.76000976562 -10.2899932861 -9.76000976563 -10.2899932861 c -0.629989624023 -0.630004882812 -0.179992675781 -1.71000671387 0.710006713867 -1.71000671387 c 0 0 20 0 20 0 c 0.889999389648 0 1.33999633789 1.08000183105 0.710006713867 1.71000671387 c 0.0 0.0 -9.76000976562 10.2899932861 -9.76000976563 10.2899932861 c 0.0 0 9.76000976562 10.2900085449 9.76000976563 10.2900085449 Z"
+ android:fillColor="#FF777777" />
+ </group>
+ </group>
+ </group>
+</vector>
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
index 68786aa..cacc19c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerIconTest.kt
@@ -531,7 +531,7 @@
* Parent Box (output icon = [PointerIcon.Crosshair])
* ⤷ Child Box (output icon = [PointerIcon.Crosshair])
*/
- @Ignore("b/267170292 - not yet implemented")
+ @Ignore("b/299482894 - not yet implemented")
@Test
fun parentChildPartialOverlap_parentModifierDynamicallyAddedWithMoveEvents() {
val isVisible = mutableStateOf(false)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
index 2ab3382..f55c989 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerIcon.kt
@@ -17,12 +17,11 @@
package androidx.compose.ui.input.pointer
import androidx.compose.runtime.Stable
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventPass.Main
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.TraversableNode
import androidx.compose.ui.node.TraversableNode.Companion.VisitSubtreeIfAction
import androidx.compose.ui.node.currentValueOf
@@ -31,6 +30,7 @@
import androidx.compose.ui.node.traverseSubtreeIf
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.LocalPointerIconService
+import androidx.compose.ui.unit.IntSize
/**
* Represents a pointer icon to use in [Modifier.pointerHoverIcon]
@@ -107,20 +107,20 @@
/*
* Changes the pointer hover icon if the node is in bounds and if the node is not overridden
- * by a parent pointer hover icon node. This node delegates to the pointer input node
- * (SuspendingPointerInputModifierNode) to determine if the pointer has entered or exited the
- * bounds of the modifier itself.
- *
- * Note: It will not delegate if a parent has overridden descendants.
+ * by a parent pointer hover icon node. This node implements [PointerInputModifierNode] so it can
+ * listen to pointer input events and determine if the pointer has entered or exited the bounds of
+ * the modifier itself.
*
* If the icon or overrideDescendants values are changed, this node will determine if it needs to
* walk down and/or up the modifier chain to update those pointer hover icon modifier nodes as well.
*/
-@OptIn(ExperimentalComposeUiApi::class)
internal class PointerHoverIconModifierNode(
icon: PointerIcon,
overrideDescendants: Boolean = false
-) : DelegatingNode(), TraversableNode, CompositionLocalConsumerModifierNode {
+) : Modifier.Node(),
+ TraversableNode,
+ PointerInputModifierNode,
+ CompositionLocalConsumerModifierNode {
/* Traversal key used with the [TraversableNode] interface to enable all the traversing
* functions (ancestor, child, subtree, and subtreeIf).
*/
@@ -158,27 +158,30 @@
private val pointerIconService: PointerIconService?
get() = currentValueOf(LocalPointerIconService)
- // Pointer Input handler for determining if a Pointer has Entered or Exited this node.
- private var delegatePointerInputModifierNode = delegate(
- SuspendingPointerInputModifierNodeImpl {
- awaitPointerEventScope {
- while (true) {
- val event = awaitPointerEvent(Main)
+ private var cursorInBoundsOfNode = false
- // Cursor within the surface area of this node's bounds
- if (event.type == PointerEventType.Enter) {
- cursorInBoundsOfNode = true
- displayIconIfDescendantsDoNotHavePriority()
- } else if (event.type == PointerEventType.Exit) {
- cursorInBoundsOfNode = false
- displayIconFromAncestorNodeWithCursorInBoundsOrDefaultIcon()
- }
- }
+ // Pointer Input callback for determining if a Pointer has Entered or Exited this node.
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize
+ ) {
+ if (pass == Main) {
+ // Cursor within the surface area of this node's bounds
+ if (pointerEvent.type == PointerEventType.Enter) {
+ cursorInBoundsOfNode = true
+ displayIconIfDescendantsDoNotHavePriority()
+ } else if (pointerEvent.type == PointerEventType.Exit) {
+ cursorInBoundsOfNode = false
+ displayIconFromAncestorNodeWithCursorInBoundsOrDefaultIcon()
}
}
- )
+ }
- private var cursorInBoundsOfNode = false
+ override fun onCancelPointerInput() {
+ // We aren't processing the event (only listening for enter/exit), so we don't need to
+ // do anything.
+ }
override fun onDetach() {
cursorInBoundsOfNode = false
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 01c1c3e..e9ff045 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -681,6 +681,8 @@
public abstract java\.util\.List<androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
public abstract androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
\^
+\$SUPPORT/slice/slice\-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics\.java:[0-9]+: warning: \[deprecation\] SliceView in androidx\.slice\.widget has been deprecated
+import androidx\.slice\.widget\.SliceView;
# b/296419682
\$SUPPORT/concurrent/concurrent\-futures/src/test/java/androidx/concurrent/futures/AbstractResolvableFutureTest\.java:[0-9]+: warning: \[removal\] resume\(\) in Thread has been deprecated and marked for removal
thread\.resume\(\);
@@ -729,4 +731,13 @@
# b/271306193 remove after aosp/2589888 :emoji:emoji:spdxSbomForRelease
spdx sboms require a version but project: noto\-emoji\-compat\-flatbuffers has no specified version
# > Configure project :internal-testutils-appcompat
-WARNING: The option setting 'android\.experimental\.lint\.reservedMemoryPerTask=[0-9]+g' is experimental\.
\ No newline at end of file
+WARNING: The option setting 'android\.experimental\.lint\.reservedMemoryPerTask=[0-9]+g' is experimental\.
+# > Task :slice:slice-builders-ktx:compileDebugKotlin
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/main/java/androidx/slice/builders/ListBuilder\.kt:[0-9]+:[0-9]+ 'ListBuilder' is deprecated\. Deprecated in Java
+# > Task :slice:slice-builders-ktx:compileDebugAndroidTestKotlin
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'SliceProvider' is deprecated\. Deprecated in Java
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'SliceSpecs' is deprecated\. Deprecated in Java
+w: file://\$SUPPORT/slice/slice\-builders\-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest\.kt:[0-9]+:[0-9]+ 'ListBuilder' is deprecated\. Deprecated in Java
+# > Task :slice:slice-benchmark:compileReleaseAndroidTestJavaWithJavac
+\$SUPPORT/slice/slice\-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics\.java:[0-9]+: warning: \[deprecation\] SliceHints in androidx\.slice\.core has been deprecated
+import androidx\.slice\.core\.SliceHints;
diff --git a/docs/onboarding_images/image10.png b/docs/onboarding_images/image10.png
new file mode 100644
index 0000000..ed1fd74
--- /dev/null
+++ b/docs/onboarding_images/image10.png
Binary files differ
diff --git a/docs/onboarding_images/image6.png b/docs/onboarding_images/image6.png
new file mode 100644
index 0000000..41795ce
--- /dev/null
+++ b/docs/onboarding_images/image6.png
Binary files differ
diff --git a/docs/onboarding_images/image7.png b/docs/onboarding_images/image7.png
new file mode 100644
index 0000000..27eef7d
--- /dev/null
+++ b/docs/onboarding_images/image7.png
Binary files differ
diff --git a/docs/onboarding_images/image8.png b/docs/onboarding_images/image8.png
new file mode 100644
index 0000000..da0dc66
--- /dev/null
+++ b/docs/onboarding_images/image8.png
Binary files differ
diff --git a/docs/onboarding_images/image9.png b/docs/onboarding_images/image9.png
new file mode 100644
index 0000000..d90dcc2
--- /dev/null
+++ b/docs/onboarding_images/image9.png
Binary files differ
diff --git a/docs/testing.md b/docs/testing.md
index 8050e0a..84e47d2 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -40,6 +40,164 @@
([example](https://r.android.com/2428721)). You can run these tests just like
any other JVM test using `test` Gradle task.
+### Adding screenshots tests using scuba library
+
+#### Prerequisites
+
+Golden project: Make sure that you have the golden directory in your root
+checkout (sibling of frameworks directory). If not re-init your repo to fetch
+the latest manifest file:
+
+```
+$ repo init -u sso://android/platform/manifest \
+ -b androidx-main && repo sync -c -j8
+```
+
+Set up your module: If your module is not using screenshot tests yet, you need
+to do the initial setup.
+
+1. Modify your gradle file: Add dependency on the diffing library into your
+ gradle file:
+
+ ```
+ androidTestImplementation project(“:test:screenshot:screenshot”)
+ ```
+
+ Important step: Add golden asset directory to be linked to your test apk:
+
+ ```
+ android {
+ sourceSets.androidTest.assets.srcDirs +=
+ // For androidx project (not in ui dir) use "/../../golden/project"
+ project.rootDir.absolutePath + "/../../golden/compose/material/material"
+ }
+ ```
+
+ This will bundle the goldens into your apk so they can be retrieved during
+ the test.
+
+2. Create directory and variable: In the golden directory, create a new
+ directory for your module (the directory that you added to your gradle file,
+ which in case of material was “compose/material/material”).
+
+ In your test module, create a variable pointing at your new directory:
+
+ ```
+ const val GOLDEN_MATERIAL = "compose/material/material"
+ ```
+
+#### Adding a screenshot test
+
+Here is an example of a minimal screenshot test for compose material.
+
+```
+@LargeTest
+@RunWith(JUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class CheckboxScreenshotTest {
+ @get:Rule val composeTestRule = createComposeRule()
+ @get:Rule val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL)
+
+ @Test
+ fun checkBoxTest_checked() {
+ composeTestRule.setMaterialContent {
+ Checkbox(Modifier.wrapContentSize(Alignment.TopStart),
+ checked = true,
+ onCheckedChange = {}
+ )
+ }
+ find(isToggleable())
+ .captureToBitmap()
+ .assertAgainstGolden(screenshotRule, "checkbox_checked")
+ }
+}
+```
+
+NOTE: The string “checkbox_checked” is the unique identifier of your golden in
+your module. We use that string to name the golden file so avoid special
+characters. Please avoid any substrings like: golden, image etc. as there is no
+need - instead just describe what the image contains.
+
+#### Guidance around diffing
+
+Try to take the smallest screenshot possible. This will reduce interference from
+other elements.
+
+By default we use a MSSIM comparer. This one is based on similarity. However we
+have quite a high bar currently which is 0.98 (1 is an exact match). You can
+provide your own threshold or even opt into a pixel perfect comparer for some
+reason.
+
+Note: The bigger screenshots you take the more you sacrifice in the precision as
+you can aggregate larger diffing errors, see the examples below.
+
+
+
+#### Generating your goldens in CI (Gerrit)
+
+Upload your CL to gerrit and run presubmit. You should see your test fail.
+
+Step 1: Click on the “Test” button below:
+
+
+
+Step 2: Click on the “Update scuba goldens” below:
+
+
+Step 3: You should see a dashboard similar to the example below. Check-out if
+the new screenshots look as expected and if yes click approve. This will create
+a new CL.
+
+
+Step 4: Link your original CL with the new goldens CL by setting the same Topic
+field in both CLs (any arbitrary string will do). This tells Gerrit to submit
+the CLs together, effectively providing a reference from the original CL to the
+new goldens. And re-run presubmit. Your tests should now pass!
+
+
+#### Running manually / debugging
+
+Screenshot tests can be run locally using pixel 2 api33 emulator. Start the
+emulator using [these](#emulator) steps.
+
+Wait until the emulator is running and run the tests as you would on a regular
+device.
+
+```
+$ ./gradlew <module>:cAT -Pandroid.testInstrumentationRunnerArguments.class=<class>
+```
+
+If the test passes, the results are limited to a .textproto file for each
+screenshot test. If the test fails, the results will also contain the actual
+screenshot and, if available, the golden reference image and the diff between
+the two. Note that this means that if you want to regenerate the golden image,
+you have to remove the golden image before running the test.
+
+To get the screenshot related results from the device onto your workstation, you
+can run
+
+```
+$ adb pull /sdcard/Android/data/<test-package>/cache/androidx_screenshots
+```
+
+where test-package is the identifier of you test apk, e.g.
+androidx.compose.material.test
+
+#### Locally updating the golden images
+
+After you run a screenshot test and pull the results to a desired location,
+verify that the actual images are the correct ones and copy them to the golden
+screenshots directory (the one you use to create the AndroidXScreenshotTestRule
+with) using this script.
+
+```
+androidx-main/frameworks/support/development/copy_screenshots_to_golden_repo.py \
+--input-dir=/tmp/androidx_screenshots/ --output-dir=androidx-main/golden/<test>/
+```
+
+Repeat for all screenshots, then create and upload a CL in the golden
+repository.
+
### What gets tested, and when {#affected-module-detector}
With over 45000 tests executed on every CI run, it is necessary for us to run
@@ -81,12 +239,6 @@
#### Disabling tests {#disabling-tests}
-To disable a device-side test in presubmit testing only -- but still have it run
-in postsubmit -- use the
-[`@FlakyTest`](https://developer.android.com/reference/androidx/test/filters/FlakyTest)
-annotation. There is currently no support for presubmit-only disabling of
-host-side tests.
-
If you need to stop a host- or device-side test from running entirely, use
JUnit's [`@Ignore`](http://junit.sourceforge.net/javadoc/org/junit/Ignore.html)
annotation. Do *not* use Android's `@Suppress` annotation, which only works with
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 2f50bc8..d1e02606 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -451,21 +451,28 @@
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 void animateToEnd(Object);
+ method public void animateToStart(Object);
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 public Object? controlDelayedTransition(android.view.ViewGroup, Object);
method protected void getBoundsOnScreen(android.view.View!, android.graphics.Rect!);
method protected static boolean isNullOrEmpty(java.util.List!);
+ method public boolean isSeekingSupported();
+ method public boolean isSeekingSupported(Object);
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 void setCurrentPlayTime(Object, float);
method public abstract void setEpicenter(Object, android.graphics.Rect);
method public abstract void setEpicenter(Object, android.view.View?);
method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable);
+ method public void setListenerForTransitionEnd(androidx.fragment.app.Fragment, Object, androidx.core.os.CancellationSignal, Runnable?, Runnable);
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/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
index c0ed273..a5f9e91 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.kt
@@ -188,13 +188,16 @@
firstOut: Operation?,
lastIn: Operation?
) {
- // First verify that we can run all transitions together
- val transitionImpl = transitionInfos.filterNot { transitionInfo ->
+ val filteredInfos = transitionInfos.filterNot { transitionInfo ->
// If there is no change in visibility, we can skip the TransitionInfo
transitionInfo.isVisibilityUnchanged
}.filter { transitionInfo ->
transitionInfo.handlingImpl != null
- }.fold(null as FragmentTransitionImpl?) { chosenImpl, transitionInfo ->
+ }
+ // First verify that we can run all transitions together
+ val transitionImpl = filteredInfos.fold(
+ null as FragmentTransitionImpl?
+ ) { chosenImpl, transitionInfo ->
val handlingImpl = transitionInfo.handlingImpl
require(chosenImpl == null || handlingImpl === chosenImpl) {
"Mixing framework transitions and AndroidX transitions is not allowed. Fragment " +
@@ -215,7 +218,7 @@
var exitingNames = ArrayList<String>()
val firstOutViews = ArrayMap<String, View>()
val lastInViews = ArrayMap<String, View>()
- for (transitionInfo: TransitionInfo in transitionInfos) {
+ for (transitionInfo: TransitionInfo in filteredInfos) {
val hasSharedElementTransition = transitionInfo.hasSharedElementTransition()
// Compute the shared element transition between the firstOut and lastIn Fragments
if (hasSharedElementTransition && (firstOut != null) && (lastIn != null)) {
@@ -344,12 +347,12 @@
}
val transitionEffect = TransitionEffect(
- transitionInfos, firstOut, lastIn, transitionImpl, sharedElementTransition,
+ filteredInfos, firstOut, lastIn, transitionImpl, sharedElementTransition,
sharedElementFirstOutViews, sharedElementLastInViews, sharedElementNameMapping,
enteringNames, exitingNames, firstOutViews, lastInViews, isPop
)
- transitionInfos.forEach { transitionInfo ->
+ filteredInfos.forEach { transitionInfo ->
transitionInfo.operation.addEffect(transitionEffect)
}
}
@@ -700,7 +703,134 @@
) : Effect() {
val transitionSignal = CancellationSignal()
+ var controller: Any? = null
+
+ override val isSeekingSupported: Boolean
+ get() = transitionImpl.isSeekingSupported &&
+ transitionInfos.all {
+ Build.VERSION.SDK_INT >= 34 &&
+ it.transition != null &&
+ transitionImpl.isSeekingSupported(it.transition)
+ }
+
+ val transitioning: Boolean
+ get() = transitionInfos.all {
+ it.operation.fragment.mTransitioning
+ }
+
+ override fun onStart(container: ViewGroup) {
+ // If the container has never been laid out, transitions will not start so
+ // so lets instantly complete them.
+ if (!ViewCompat.isLaidOut(container)) {
+ transitionInfos.forEach { transitionInfo: TransitionInfo ->
+ val operation: Operation = transitionInfo.operation
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "SpecialEffectsController: Container $container has not been " +
+ "laid out. Skipping onStart for operation $operation")
+ }
+ }
+ return
+ }
+ if (isSeekingSupported && transitioning) {
+ // We need to set the listener before we create the controller, but we need the
+ // controller to do the desired cancel behavior (animateToStart). So we use this
+ // lambda to set the proper cancel behavior to pass into the listener before the
+ // function is created.
+ var seekCancelLambda: (() -> Unit)? = null
+ // Now set up our completion signal on the completely merged transition set
+ val (enteringViews, mergedTransition) =
+ createMergedTransition(container, lastIn, firstOut)
+ transitionInfos.map { it.operation }.forEach { operation ->
+ val cancelRunnable = Runnable { seekCancelLambda?.invoke() }
+ transitionImpl.setListenerForTransitionEnd(
+ operation.fragment,
+ mergedTransition,
+ transitionSignal,
+ cancelRunnable,
+ Runnable {
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Transition for operation $operation has completed")
+ }
+ operation.completeEffect(this)
+ })
+ }
+
+ runTransition(enteringViews, container) {
+ controller =
+ transitionImpl.controlDelayedTransition(container, mergedTransition)
+ // If we fail to create a controller, it must be because of the container or
+ // the transition so we should throw an error.
+ check(controller != null) {
+ "Unable to start transition $mergedTransition for container $container."
+ }
+ seekCancelLambda = { transitionImpl.animateToStart(controller!!) }
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Started executing operations from $firstOut to $lastIn")
+ }
+ }
+ }
+ }
+
+ override fun onProgress(backEvent: BackEventCompat, container: ViewGroup) {
+ controller?.let { transitionImpl.setCurrentPlayTime(it, backEvent.progress) }
+ }
+
override fun onCommit(container: ViewGroup) {
+ // If the container has never been laid out, transitions will not start so
+ // so lets instantly complete them.
+ if (!ViewCompat.isLaidOut(container)) {
+ transitionInfos.forEach { transitionInfo: TransitionInfo ->
+ val operation: Operation = transitionInfo.operation
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "SpecialEffectsController: Container $container has not been " +
+ "laid out. Completing operation $operation")
+ }
+ transitionInfo.operation.completeEffect(this)
+ }
+ return
+ }
+ if (controller != null) {
+ transitionImpl.animateToEnd(controller!!)
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Ending execution of operations from $firstOut to $lastIn")
+ }
+ } else {
+ val (enteringViews, mergedTransition) =
+ createMergedTransition(container, lastIn, firstOut)
+ // Now set up our completion signal on the completely merged transition set
+ transitionInfos.map { it.operation }.forEach { operation ->
+ transitionImpl.setListenerForTransitionEnd(
+ operation.fragment,
+ mergedTransition,
+ transitionSignal,
+ Runnable {
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Transition for operation $operation has completed")
+ }
+ operation.completeEffect(this)
+ })
+ }
+ runTransition(enteringViews, container) {
+ transitionImpl.beginDelayedTransition(container, mergedTransition)
+ }
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Completed executing operations from $firstOut to $lastIn")
+ }
+ }
+ }
+
+ private fun createMergedTransition(
+ container: ViewGroup,
+ lastIn: Operation?,
+ firstOut: Operation?
+ ): Pair<ArrayList<View>, Any> {
// Every transition needs to target at least one View so that they
// don't interfere with one another. This is the view we use
// in cases where there are no real views to target
@@ -772,26 +902,13 @@
// Now iterate through the set of transitions and merge them together
for (transitionInfo: TransitionInfo in transitionInfos) {
val operation: Operation = transitionInfo.operation
- if (transitionInfo.isVisibilityUnchanged) {
- // No change in visibility, so we can immediately complete the transition
- transitionInfo.operation.completeEffect(this)
- continue
- }
val transition = transitionImpl.cloneTransition(transitionInfo.transition)
- val involvedInSharedElementTransition = (sharedElementTransition != null &&
- (operation === firstOut || operation === lastIn))
- if (transition == null) {
- // Nothing more to do if the transition is null
- if (!involvedInSharedElementTransition) {
- // Only complete the transition if this fragment isn't involved
- // in the shared element transition (as otherwise we need to wait
- // for that to finish)
- transitionInfo.operation.completeEffect(this)
- }
- } else {
+ if (transition != null) {
// Target the Transition to *only* the set of transitioning views
val transitioningViews = ArrayList<View>()
captureTransitioningViews(transitioningViews, operation.fragment.mView)
+ val involvedInSharedElementTransition = (sharedElementTransition != null &&
+ (operation === firstOut || operation === lastIn))
if (involvedInSharedElementTransition) {
// Remove all of the shared element views from the transition
if (operation === firstOut) {
@@ -804,8 +921,10 @@
transitionImpl.addTarget(transition, nonExistentView)
} else {
transitionImpl.addTargets(transition, transitioningViews)
- transitionImpl.scheduleRemoveTargets(transition, transition,
- transitioningViews, null, null, null, null)
+ transitionImpl.scheduleRemoveTargets(
+ transition, transition,
+ transitioningViews, null, null, null, null
+ )
if (operation.finalState === Operation.State.GONE) {
// We're hiding the Fragment. This requires a bit of extra work
// First, we need to avoid immediately applying the container change as
@@ -815,8 +934,10 @@
// essentially doing what applyState() would do for us
val transitioningViewsToHide = ArrayList(transitioningViews)
transitioningViewsToHide.remove(operation.fragment.mView)
- transitionImpl.scheduleHideFragmentView(transition,
- operation.fragment.mView, transitioningViewsToHide)
+ transitionImpl.scheduleHideFragmentView(
+ transition,
+ operation.fragment.mView, transitioningViewsToHide
+ )
// This OneShotPreDrawListener gets fired before the delayed start of
// the Transition and changes the visibility of any exiting child views
// that *ARE NOT* shared element transitions. The TransitionManager then
@@ -840,11 +961,13 @@
if (transitionInfo.isOverlapAllowed) {
// Overlap is allowed, so add them to the mergeTransition set
mergedTransition = transitionImpl.mergeTransitionsTogether(
- mergedTransition, transition, null)
+ mergedTransition, transition, null
+ )
} else {
// Overlap is not allowed, add them to the mergedNonOverlappingTransition
mergedNonOverlappingTransition = transitionImpl.mergeTransitionsTogether(
- mergedNonOverlappingTransition, transition, null)
+ mergedNonOverlappingTransition, transition, null
+ )
}
}
}
@@ -854,51 +977,14 @@
mergedTransition = transitionImpl.mergeTransitionsInSequence(mergedTransition,
mergedNonOverlappingTransition, sharedElementTransition)
- // If there's no transitions playing together, no non-overlapping transitions,
- // and no shared element transitions, mergedTransition will be null and
- // there's nothing else we need to do
- if (mergedTransition == null) {
- return
- }
- // Now set up our completion signal on the completely merged transition set
- transitionInfos.filterNot { transitionInfo ->
- // If there's change in visibility, we've already completed the transition
- transitionInfo.isVisibilityUnchanged
- }.forEach { transitionInfo: TransitionInfo ->
- val transition: Any? = transitionInfo.transition
- val operation: Operation = transitionInfo.operation
- val involvedInSharedElementTransition = sharedElementTransition != null &&
- (operation === firstOut || operation === lastIn)
- if (transition != null || involvedInSharedElementTransition) {
- // If the container has never been laid out, transitions will not start so
- // so lets instantly complete them.
- if (!ViewCompat.isLaidOut(container)) {
- if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
- Log.v(FragmentManager.TAG,
- "SpecialEffectsController: Container $container has not been " +
- "laid out. Completing operation $operation")
- }
- transitionInfo.operation.completeEffect(this)
- } else {
- transitionImpl.setListenerForTransitionEnd(
- transitionInfo.operation.fragment,
- mergedTransition,
- transitionSignal,
- Runnable {
- transitionInfo.operation.completeEffect(this)
- if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
- Log.v(FragmentManager.TAG,
- "Transition for operation $operation has completed")
- }
- })
- }
- }
- }
- // Transitions won't run if the container isn't laid out so
- // we can return early here to avoid doing unnecessary work.
- if (!ViewCompat.isLaidOut(container)) {
- return
- }
+ return Pair(enteringViews, mergedTransition)
+ }
+
+ private fun runTransition(
+ enteringViews: ArrayList<View>,
+ container: ViewGroup,
+ executeTransition: (() -> Unit)
+ ) {
// First, hide all of the entering views so they're in
// the correct initial state
setViewVisibility(enteringViews, View.INVISIBLE)
@@ -917,7 +1003,7 @@
}
}
// Now actually start the transition
- transitionImpl.beginDelayedTransition(container, mergedTransition)
+ executeTransition.invoke()
transitionImpl.setNameOverridesReordered(container, sharedElementFirstOutViews,
sharedElementLastInViews, inNames, sharedElementNameMapping)
// Then, show all of the entering views, putting them into
@@ -925,11 +1011,6 @@
setViewVisibility(enteringViews, View.VISIBLE)
transitionImpl.swapSharedElementTargets(sharedElementTransition,
sharedElementFirstOutViews, sharedElementLastInViews)
-
- if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
- Log.v(FragmentManager.TAG,
- "Completed executing operations from $firstOut to $lastIn")
- }
}
override fun onCancel(container: ViewGroup) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
index 0f0145d..8c7d538 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionCompat21.java
@@ -21,6 +21,7 @@
import android.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -221,6 +222,27 @@
}
@Override
+ public boolean isSeekingSupported() {
+ if (FragmentManager.isLoggingEnabled(Log.INFO)) {
+ Log.i(FragmentManager.TAG,
+ "Predictive back not available using Framework Transitions. Please switch"
+ + " to AndroidX Transition 1.5.0 or higher to enable seeking.");
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSeekingSupported(@NonNull Object transition) {
+ if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
+ Log.v(FragmentManager.TAG,
+ "Predictive back not available for framework transition "
+ + transition + ". Please switch to AndroidX Transition 1.5.0 or higher "
+ + "to enable seeking.");
+ }
+ return false;
+ }
+
+ @Override
public void scheduleRemoveTargets(@NonNull final Object overallTransitionObj,
@Nullable final Object enterTransition, @Nullable final ArrayList<View> enteringViews,
@Nullable final Object exitTransition, @Nullable final ArrayList<View> exitingViews,
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
index e9a7514..6a8d444 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransitionImpl.java
@@ -21,6 +21,7 @@
import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -150,6 +151,49 @@
@Nullable Object transition);
/**
+ * Returns {@code true} if the Transition is seekable.
+ */
+ public boolean isSeekingSupported() {
+ if (FragmentManager.isLoggingEnabled(Log.INFO)) {
+ Log.i(FragmentManager.TAG,
+ "Older versions of AndroidX Transition do not support seeking. Add dependency "
+ + "on AndroidX Transition 1.5.0 or higher to enable seeking.");
+ }
+ return false;
+ }
+
+ /**
+ * Returns {@code true} if the Transition is seekable.
+ */
+ public boolean isSeekingSupported(@NonNull Object transition) {
+ return false;
+ }
+
+ /**
+ * Allows for controlling a seekable transition
+ */
+ @Nullable
+ public Object controlDelayedTransition(@NonNull ViewGroup sceneRoot,
+ @NonNull Object transition) {
+ return null;
+ }
+
+ /**
+ * Uses given progress to set the current play time of the transition.
+ */
+ public void setCurrentPlayTime(@NonNull Object transitionController, float progress) { }
+
+ /**
+ * Animate the transition to end.
+ */
+ public void animateToEnd(@NonNull Object transitionController) { }
+
+ /**
+ * Animate the transition to start.
+ */
+ public void animateToStart(@NonNull Object transitionController) { }
+
+ /**
* Prepares for setting the shared element names by gathering the names of the incoming
* shared elements and clearing them. {@link #setNameOverridesReordered(View, ArrayList,
* ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element
@@ -230,6 +274,32 @@
public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
@NonNull Object transition, @NonNull CancellationSignal signal,
@NonNull Runnable transitionCompleteRunnable) {
+ setListenerForTransitionEnd(
+ outFragment, transition, signal, null, transitionCompleteRunnable
+ );
+ }
+
+ /**
+ * Set a listener for Transition end events. The default behavior immediately completes the
+ * transition.
+ *
+ * Use this when the given transition is seeking. The cancelRunnable should handle
+ * cleaning up the transition when seeking is cancelled.
+ *
+ * If the transition is not seeking, you should use
+ * {@link #setListenerForTransitionEnd(Fragment, Object, CancellationSignal, Runnable)}.
+ *
+ * @param outFragment The first fragment that is exiting
+ * @param transition all transitions to be executed on a single container
+ * @param signal used indicate the desired behavior on transition cancellation
+ * @param cancelRunnable runnable to handle the logic when the signal is cancelled
+ * @param transitionCompleteRunnable used to notify the FragmentManager when a transition is
+ * complete
+ */
+ public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
+ @NonNull Object transition, @NonNull CancellationSignal signal,
+ @Nullable Runnable cancelRunnable,
+ @NonNull Runnable transitionCompleteRunnable) {
transitionCompleteRunnable.run();
}
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index e22d9a9..3444a5e 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -208,6 +208,21 @@
}
+package androidx.glance.appwidget.component {
+
+ public final class ButtonsKt {
+ method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+ method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+ method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+ method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+ method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+ method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+ method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+ method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+ }
+
+}
+
package androidx.glance.appwidget.lazy {
public abstract sealed class GridCells {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index e22d9a9..3444a5e 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -208,6 +208,21 @@
}
+package androidx.glance.appwidget.component {
+
+ public final class ButtonsKt {
+ method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+ method @androidx.compose.runtime.Composable public static void CircleIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider? backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+ method @androidx.compose.runtime.Composable public static void FilledButton(String text, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines);
+ method @androidx.compose.runtime.Composable public static void FilledButton(String text, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional androidx.glance.ButtonColors colors, optional int maxLines, optional String? key);
+ method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines);
+ method @androidx.compose.runtime.Composable public static void OutlineButton(String text, androidx.glance.unit.ColorProvider contentColor, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.ImageProvider? icon, optional int maxLines, optional String? key);
+ method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, androidx.glance.action.Action onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor);
+ method @androidx.compose.runtime.Composable public static void SquareIconButton(androidx.glance.ImageProvider imageProvider, String? contentDescription, kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.glance.GlanceModifier modifier, optional boolean enabled, optional androidx.glance.unit.ColorProvider backgroundColor, optional androidx.glance.unit.ColorProvider contentColor, optional String? key);
+ }
+
+}
+
package androidx.glance.appwidget.lazy {
public abstract sealed class GridCells {
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
index 9107a59..8eb950b 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/AndroidManifest.xml
@@ -254,5 +254,32 @@
android:name="android.appwidget.provider"
android:resource="@xml/default_app_widget_info" />
</receiver>
+
+ <receiver
+ android:name="androidx.glance.appwidget.demos.ButtonsWidgetBroadcastReceiver"
+ android:enabled="@bool/glance_appwidget_available"
+ android:exported="false"
+ android:label="@string/buttons_widget_name">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <action android:name="android.intent.action.LOCALE_CHANGED" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/default_app_widget_info" />
+ </receiver>
+
+ <receiver
+ android:name="androidx.glance.appwidget.demos.BackgroundTintWidgetBroadcastReceiver"
+ android:label="@string/tint_widget"
+ android:enabled="@bool/glance_appwidget_available"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/default_app_widget_info" />
+ </receiver>
</application>
</manifest>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt
new file mode 100644
index 0000000..58f22cc
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/BackgroundTintWidget.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023 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.glance.appwidget.demos
+
+import android.content.Context
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.glance.ColorFilter
+import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.ImageProvider
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.provideContent
+import androidx.glance.background
+import androidx.glance.layout.Box
+import androidx.glance.layout.Column
+import androidx.glance.layout.size
+import androidx.glance.unit.ColorProvider
+
+class BackgroundTintWidgetBroadcastReceiver() : GlanceAppWidgetReceiver() {
+ override val glanceAppWidget: GlanceAppWidget
+ get() = BackgroundTintWidget()
+}
+
+/**
+ * Demonstrates tinting background drawables with [ColorFilter].
+ */
+class BackgroundTintWidget : GlanceAppWidget() {
+ override val sizeMode: SizeMode
+ get() = SizeMode.Exact
+
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+ provideContent {
+ GlanceTheme {
+ Column {
+ Box(
+ // Tint a <shape>
+ modifier = GlanceModifier
+ .size(width = 100.dp, height = 50.dp)
+ .background(
+ ImageProvider(R.drawable.shape_btn_demo),
+ tint = ColorFilter.tint(GlanceTheme.colors.primary)
+ ),
+ content = {})
+ Box(
+ // tint an AVD
+ modifier = GlanceModifier
+ .size(width = 100.dp, height = 50.dp)
+ .background(
+ ImageProvider(R.drawable.ic_android),
+ tint = ColorFilter.tint(ColorProvider(Color.Cyan))
+ ),
+ content = {}
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt
new file mode 100644
index 0000000..a9d63be
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/java/androidx/glance/appwidget/demos/ButtonsWidget.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2023 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.glance.appwidget.demos
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.glance.Button
+import androidx.glance.ButtonColors
+import androidx.glance.ButtonDefaults
+import androidx.glance.GlanceId
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.ImageProvider
+import androidx.glance.appwidget.GlanceAppWidget
+import androidx.glance.appwidget.GlanceAppWidgetReceiver
+import androidx.glance.appwidget.SizeMode
+import androidx.glance.appwidget.component.CircleIconButton
+import androidx.glance.appwidget.component.FilledButton
+import androidx.glance.appwidget.component.OutlineButton
+import androidx.glance.appwidget.component.SquareIconButton
+import androidx.glance.appwidget.lazy.LazyColumn
+import androidx.glance.appwidget.lazy.LazyItemScope
+import androidx.glance.appwidget.lazy.LazyListScope
+import androidx.glance.appwidget.provideContent
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Column
+import androidx.glance.layout.Row
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.fillMaxSize
+import androidx.glance.layout.height
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+
+class ButtonsWidgetBroadcastReceiver() : GlanceAppWidgetReceiver() {
+
+ override val glanceAppWidget: GlanceAppWidget
+ get() = ButtonsWidget()
+}
+
+/**
+ * Demonstrates different button styles. Outline buttons will render as standard buttons on
+ * apis <31.
+ */
+class ButtonsWidget() : GlanceAppWidget() {
+ override val sizeMode: SizeMode
+ get() = SizeMode.Exact // one callback each time widget resized
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ override suspend fun provideGlance(context: Context, id: GlanceId) {
+
+ provideContent {
+ val primary = GlanceTheme.colors.primary
+ val onPrimary = GlanceTheme.colors.onPrimary
+ val colors = ButtonDefaults.buttonColors(
+ backgroundColor = primary,
+ contentColor = onPrimary
+ )
+
+ LazyColumn(
+ modifier = GlanceModifier.fillMaxSize()
+ .background(Color.DarkGray)
+ .padding(16.dp)
+ ) {
+
+ paddedItem {
+ Button(
+ text = "Standard Button",
+ onClick = {},
+ modifier = GlanceModifier,
+ colors = colors,
+ maxLines = 1
+ )
+ }
+
+ paddedItem {
+ FilledButton(
+ text = "Filled Button",
+ colors = colors,
+ modifier = GlanceModifier,
+ onClick = {},
+ )
+ }
+
+ paddedItem {
+ FilledButton(
+ text = "Filled Button",
+ icon = ImageProvider(R.drawable.baseline_add_24),
+ colors = colors,
+ modifier = GlanceModifier,
+ onClick = {},
+ )
+ }
+
+ paddedItem {
+ OutlineButton(
+ text = "Outline Button",
+ contentColor = primary,
+ modifier = GlanceModifier,
+ onClick = {},
+ )
+ }
+
+ paddedItem {
+ OutlineButton(
+ text = "Outline Button",
+ icon = ImageProvider(R.drawable.baseline_add_24),
+ contentColor = primary,
+ modifier = GlanceModifier,
+ onClick = {},
+ )
+ }
+
+ paddedItem {
+ LongTextButtons(GlanceModifier, colors)
+ }
+
+ paddedItem {
+ IconButtons()
+ }
+ } // end lazy column
+ }
+ }
+}
+
+private fun LazyListScope.paddedItem(content: @Composable LazyItemScope.() -> Unit) {
+ this.item {
+ Column {
+ content()
+ Space()
+ }
+ }
+}
+
+@Composable
+private fun LongTextButtons(modifier: GlanceModifier, colors: ButtonColors) {
+ Row(modifier = modifier) {
+ FilledButton(
+ text = "Three\nLines\nof text",
+ icon = ImageProvider(R.drawable.baseline_add_24),
+ colors = colors,
+ modifier = GlanceModifier,
+ onClick = {},
+ )
+
+ Space()
+
+ FilledButton(
+ text = "Two\nLines\nof text",
+ icon = ImageProvider(R.drawable.baseline_add_24),
+ colors = colors,
+ modifier = GlanceModifier,
+ onClick = {},
+ maxLines = 2
+ )
+ }
+}
+
+@Composable
+private fun IconButtons() {
+ Row(
+ modifier = GlanceModifier.height(80.dp).padding(vertical = 8.dp),
+ verticalAlignment = Alignment.Vertical.CenterVertically
+ ) {
+ SquareIconButton(
+ imageProvider = ImageProvider(R.drawable.baseline_add_24),
+ contentDescription = "Add Button",
+ onClick = { }
+ )
+ Space()
+
+ CircleIconButton(
+ imageProvider = ImageProvider(R.drawable.baseline_local_phone_24),
+ contentDescription = "Call Button",
+ backgroundColor = GlanceTheme.colors.surfaceVariant,
+ contentColor = GlanceTheme.colors.onSurfaceVariant,
+ onClick = { }
+ )
+ Space()
+
+ CircleIconButton(
+ imageProvider = ImageProvider(R.drawable.baseline_local_phone_24),
+ contentDescription = "Call Button",
+ backgroundColor = null, // empty background
+ contentColor = GlanceTheme.colors.primary,
+ onClick = { }
+ )
+ }
+}
+
+@Composable
+private fun Space() = Spacer(GlanceModifier.size(8.dp))
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml
new file mode 100644
index 0000000..03d6cd3
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_add_24.xml
@@ -0,0 +1,21 @@
+<!--
+ Copyright 2023 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.
+ -->
+
+<vector android:height="24dp" android:tint="#000000"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml
new file mode 100644
index 0000000..95c5f8a
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/baseline_local_phone_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#000000"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"/>
+</vector>
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml
new file mode 100644
index 0000000..12bdd47
--- /dev/null
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/drawable/shape_btn_demo.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <stroke android:color="#FF00FF"/>
+ <corners android:radius="16dp"/>
+
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
index e74682e..04943bf 100644
--- a/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
+++ b/glance/glance-appwidget/integration-tests/demos/src/main/res/values/strings.xml
@@ -37,5 +37,6 @@
<string name="default_state_widget_name">Default State Widget</string>
<string name="progress_indicator_widget_name">ProgressBar Widget</string>
<string name="default_color_widget_name">Theme and Color Widget</string>
-
+ <string name="buttons_widget_name">Buttons Demo Widget</string>
+ <string name="tint_widget">Tint widget</string>
</resources>
diff --git a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
index ffb947e..fb842fb 100644
--- a/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
+++ b/glance/glance-appwidget/src/androidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverScreenshotTest.kt
@@ -26,14 +26,20 @@
import androidx.glance.Button
import androidx.glance.ButtonDefaults
import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.action.actionStartActivity
+import androidx.glance.appwidget.component.CircleIconButton
+import androidx.glance.appwidget.component.FilledButton
+import androidx.glance.appwidget.component.OutlineButton
+import androidx.glance.appwidget.component.SquareIconButton
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.test.R
import androidx.glance.background
import androidx.glance.color.ColorProvider
+import androidx.glance.color.colorProviders
import androidx.glance.layout.Alignment
import androidx.glance.layout.Box
import androidx.glance.layout.Column
@@ -589,6 +595,43 @@
mHostRule.waitForListViewChildCount(count)
mScreenshotRule.checkScreenshot(mHostRule.mHostView, "lazyColumn_alignment_start")
}
+
+ @Test
+ fun buttonTests_createFilledButton() {
+ TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.FilledButtonTest() }
+ mHostRule.startHost()
+ mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createFilledButton")
+ }
+
+ @Test
+ fun buttonTests_createOutlineButton() {
+ TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.OutlineButtonTest() }
+ mHostRule.startHost()
+ mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createOutlineButton")
+ }
+
+ @Test
+ fun buttonTests_createSquareButton() {
+ TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.SquareButtonTest() }
+ mHostRule.startHost()
+ mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createSquareButton")
+ }
+
+ @Test
+ fun buttonTests_createCircleButton() {
+ TestGlanceAppWidget.uiDefinition = { ButtonComponentsScreenshotTests.CircleButtonTest() }
+ mHostRule.startHost()
+ mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_createCircleButton")
+ }
+
+ @Test
+ fun buttonTests_buttonDefaultColors() {
+ TestGlanceAppWidget.uiDefinition = {
+ ButtonComponentsScreenshotTests.ButtonDefaultColorsTest()
+ }
+ mHostRule.startHost()
+ mScreenshotRule.checkScreenshot(mHostRule.mHostView, "buttonTests_buttonDefaultColors")
+ }
}
@Composable
@@ -872,3 +915,166 @@
)
}
}
+
+// Tests the opinionated button components
+private object ButtonComponentsScreenshotTests {
+
+ private val onClick: () -> Unit = {}
+
+ // a filled square
+ private val icon = ImageProvider(R.drawable.filled_oval)
+
+ private val buttonBg = ColorProvider(Color.Black)
+ private val buttonFg = ColorProvider(Color.White)
+
+ @Composable
+ private fun colors() = ButtonDefaults.buttonColors(
+ backgroundColor = buttonBg,
+ contentColor = buttonFg
+ )
+
+ @Composable
+ private fun Space() = Spacer(GlanceModifier.size(16.dp))
+
+ /**
+ * A rectangular magenta background
+ */
+ @Composable
+ private fun Background(content: @Composable () -> Unit) {
+ Box(
+ modifier = GlanceModifier.wrapContentSize().padding(16.dp).background(Color.Magenta),
+ content = content
+ )
+ }
+
+ @Composable
+ fun FilledButtonTest() {
+ Background {
+ Column {
+ FilledButton(
+ text = "Filled button\nbg 0x00, fg 0xff",
+ onClick = onClick,
+ icon = null,
+ colors = colors()
+ )
+ Space()
+ FilledButton(
+ text = "Filled btn + icon\nbg 0x00, fg 0xff",
+ onClick = onClick,
+ icon = icon,
+ colors = colors()
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun OutlineButtonTest() {
+ Background {
+ Column {
+ OutlineButton(
+ text = "Outline Button\nfg 0xff",
+ onClick = onClick,
+ icon = null,
+ contentColor = buttonFg
+ )
+ Space()
+ OutlineButton(
+ text = "Outline btn + icon\nfg 0xff",
+ onClick = onClick,
+ icon = icon,
+ contentColor = buttonFg
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun SquareButtonTest() {
+ Background {
+ // square button with rounded corners, icon (a square w/sharp corners) in center.
+ SquareIconButton(
+ imageProvider = icon,
+ contentDescription = null,
+ onClick = onClick,
+ backgroundColor = buttonBg,
+ contentColor = buttonFg
+ )
+ }
+ }
+
+ @Composable
+ fun CircleButtonTest() {
+ Background {
+ Column {
+ // Circle button with icon
+ CircleIconButton(
+ imageProvider = icon,
+ contentDescription = null,
+ onClick = onClick,
+ backgroundColor = buttonBg,
+ contentColor = buttonFg
+ )
+ Space()
+ // Icon only, no background
+ CircleIconButton(
+ imageProvider = icon,
+ contentDescription = null,
+ onClick = onClick,
+ backgroundColor = null,
+ contentColor = buttonFg
+ )
+ }
+ }
+ }
+
+ /**
+ * Tests that buttons inherit the expected colors from their theme.
+ */
+ @Composable
+ fun ButtonDefaultColorsTest() {
+ val unused = ColorProvider(Color.Cyan)
+
+ val colors = colorProviders(
+ primary = ColorProvider(Color.Green),
+ onPrimary = ColorProvider(Color.Black),
+ surface = ColorProvider(Color.Gray),
+ onSurface = ColorProvider(Color.Red),
+ background = ColorProvider(Color.DarkGray),
+ error = unused,
+ errorContainer = unused,
+ inverseOnSurface = unused,
+ inversePrimary = unused,
+ inverseSurface = unused,
+ onBackground = unused,
+ onError = unused,
+ onErrorContainer = unused,
+ onPrimaryContainer = unused,
+ onSecondary = unused,
+ onSecondaryContainer = unused,
+ onSurfaceVariant = unused,
+ onTertiary = unused,
+ onTertiaryContainer = unused,
+ outline = unused,
+ primaryContainer = unused,
+ secondary = unused,
+ secondaryContainer = unused,
+ surfaceVariant = unused,
+ tertiary = unused,
+ tertiaryContainer = unused,
+ )
+
+ GlanceTheme(
+ colors = colors
+ ) {
+ Column {
+ FilledButton("Filled button", icon = icon, onClick = onClick)
+ // [OutlineButton] does not have a default color, so not important to test here
+ Space()
+ SquareIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
+ Space()
+ CircleIconButton(imageProvider = icon, contentDescription = null, onClick = onClick)
+ }
+ }
+ }
+}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
index 51d1774..0c43fc1 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/ApplyModifiers.kt
@@ -79,28 +79,33 @@
}
actionModifier = modifier
}
+
is WidthModifier -> widthModifier = modifier
is HeightModifier -> heightModifier = modifier
is BackgroundModifier -> applyBackgroundModifier(context, rv, modifier, viewDef)
is PaddingModifier -> {
paddingModifiers = paddingModifiers?.let { it + modifier } ?: modifier
}
+
is VisibilityModifier -> visibility = modifier.visibility
is CornerRadiusModifier -> cornerRadius = modifier.radius
is AppWidgetBackgroundModifier -> {
// This modifier is handled somewhere else.
}
+
is SelectableGroupModifier -> {
if (!translationContext.canUseSelectableGroup) {
error(
"GlanceModifier.selectableGroup() can only be used on Row or Column " +
- "composables."
+ "composables."
)
}
}
+
is AlignmentModifier -> {
// This modifier is handled somewhere else.
}
+
is ClipToOutlineModifier -> clipToOutline = modifier
is EnabledModifier -> enabled = modifier
is SemanticsModifier -> semanticsModifier = modifier
@@ -226,7 +231,8 @@
}
// Wrap and Expand are done in XML on Android S & Sv2
if (Build.VERSION.SDK_INT < 33 &&
- width in listOf(Dimension.Wrap, Dimension.Expand)) return
+ width in listOf(Dimension.Wrap, Dimension.Expand)
+ ) return
ApplyModifiersApi31Impl.setViewWidth(rv, viewId, width)
}
@@ -256,7 +262,8 @@
}
// Wrap and Expand are done in XML on Android S & Sv2
if (Build.VERSION.SDK_INT < 33 &&
- height in listOf(Dimension.Wrap, Dimension.Expand)) return
+ height in listOf(Dimension.Wrap, Dimension.Expand)
+ ) return
ApplyModifiersApi31Impl.setViewHeight(rv, viewId, height)
}
@@ -267,8 +274,9 @@
viewDef: InsertedViewInfo
) {
val viewId = viewDef.mainViewId
- val imageProvider = modifier.imageProvider
- if (imageProvider != null) {
+
+ fun applyBackgroundImageModifier(modifier: BackgroundModifier.Image) {
+ val imageProvider = modifier.imageProvider
if (imageProvider is AndroidResourceImageProvider) {
rv.setViewBackgroundResource(viewId, imageProvider.resId)
}
@@ -276,24 +284,41 @@
// (removing modifiers is not really possible).
return
}
- when (val colorProvider = modifier.colorProvider) {
- is FixedColorProvider -> rv.setViewBackgroundColor(viewId, colorProvider.color.toArgb())
- is ResourceColorProvider -> rv.setViewBackgroundColorResource(
- viewId,
- colorProvider.resId
- )
- is DayNightColorProvider -> {
- if (Build.VERSION.SDK_INT >= 31) {
- rv.setViewBackgroundColor(
- viewId,
- colorProvider.day.toArgb(),
- colorProvider.night.toArgb()
- )
- } else {
- rv.setViewBackgroundColor(viewId, colorProvider.getColor(context).toArgb())
+
+ fun applyBackgroundColorModifier(modifier: BackgroundModifier.Color) {
+ when (val colorProvider = modifier.colorProvider) {
+ is FixedColorProvider -> rv.setViewBackgroundColor(
+ viewId,
+ colorProvider.color.toArgb()
+ )
+
+ is ResourceColorProvider -> rv.setViewBackgroundColorResource(
+ viewId,
+ colorProvider.resId
+ )
+
+ is DayNightColorProvider -> {
+ if (Build.VERSION.SDK_INT >= 31) {
+ rv.setViewBackgroundColor(
+ viewId,
+ colorProvider.day.toArgb(),
+ colorProvider.night.toArgb()
+ )
+ } else {
+ rv.setViewBackgroundColor(viewId, colorProvider.getColor(context).toArgb())
+ }
}
+
+ else -> Log.w(
+ GlanceAppWidgetTag,
+ "Unexpected background color modifier: $colorProvider"
+ )
}
- else -> Log.w(GlanceAppWidgetTag, "Unexpected background color modifier: $colorProvider")
+ }
+
+ when (modifier) {
+ is BackgroundModifier.Image -> applyBackgroundImageModifier(modifier)
+ is BackgroundModifier.Color -> applyBackgroundColorModifier(modifier)
}
}
@@ -319,6 +344,7 @@
is Dimension.Wrap -> {
rv.setViewLayoutWidth(viewId, WRAP_CONTENT.toFloat(), COMPLEX_UNIT_PX)
}
+
is Dimension.Expand -> rv.setViewLayoutWidth(viewId, 0f, COMPLEX_UNIT_PX)
is Dimension.Dp -> rv.setViewLayoutWidth(viewId, width.dp.value, COMPLEX_UNIT_DIP)
is Dimension.Resource -> rv.setViewLayoutWidthDimen(viewId, width.res)
@@ -334,6 +360,7 @@
is Dimension.Wrap -> {
rv.setViewLayoutHeight(viewId, WRAP_CONTENT.toFloat(), COMPLEX_UNIT_PX)
}
+
is Dimension.Expand -> rv.setViewLayoutHeight(viewId, 0f, COMPLEX_UNIT_PX)
is Dimension.Dp -> rv.setViewLayoutHeight(viewId, height.dp.value, COMPLEX_UNIT_DIP)
is Dimension.Resource -> rv.setViewLayoutHeightDimen(viewId, height.res)
@@ -350,9 +377,11 @@
is Dimension.Dp -> {
rv.setViewOutlinePreferredRadius(viewId, radius.dp.value, COMPLEX_UNIT_DIP)
}
+
is Dimension.Resource -> {
rv.setViewOutlinePreferredRadiusDimen(viewId, radius.res)
}
+
else -> error("Rounded corners should not be ${radius.javaClass.canonicalName}")
}
}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
index 2b86729..7a1f57e 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/NormalizeCompositionTree.kt
@@ -28,6 +28,9 @@
import androidx.glance.ImageProvider
import androidx.glance.action.ActionModifier
import androidx.glance.action.LambdaAction
+import androidx.glance.action.NoRippleOverride
+import androidx.glance.addChild
+import androidx.glance.addChildIfNotNull
import androidx.glance.appwidget.action.CompoundButtonAction
import androidx.glance.extractModifier
import androidx.glance.findModifier
@@ -141,7 +144,8 @@
child.modifier.extractLambdaAction()
if (action != null &&
child !is EmittableSizeBox &&
- child !is EmittableLazyItemWithChildren) {
+ child !is EmittableLazyItemWithChildren
+ ) {
val newKey = action.key + "+$index"
val newAction = LambdaAction(newKey, action.block)
actions.getOrPut(newKey) { mutableListOf() }.add(newAction)
@@ -162,6 +166,7 @@
action is LambdaAction -> action to modifiers
action is CompoundButtonAction && action.innerAction is LambdaAction ->
action.innerAction to modifiers
+
else -> null to modifiers
}
}
@@ -198,11 +203,11 @@
// before the target in the wrapper box. This allows us to support content scale as well as
// can help support additional processing on background images. Note: Button's don't support
// bg image modifier.
- (it is BackgroundModifier && it.imageProvider != null) ||
- // R- buttons are implemented using box, images and text.
- (isButton && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) ||
- // Ripples are implemented by placing a drawable after the target in the wrapper box.
- (it is ActionModifier && !hasBuiltinRipple())
+ (it is BackgroundModifier.Image) ||
+ // R- buttons are implemented using box, images and text.
+ (isButton && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) ||
+ // Ripples are implemented by placing a drawable after the target in the wrapper box.
+ (it is ActionModifier && !hasBuiltinRipple())
}
if (!shouldWrapTargetInABox) return target
@@ -227,7 +232,7 @@
// drawable's base was white/none, applying transparent tint will lead to black
// color. This shouldn't be issue for icon type drawables, but in this case we are
// emulating colored outline. So, we apply tint as well as alpha.
- bgModifier.colorProvider?.let {
+ (bgModifier as? BackgroundModifier.Color)?.colorProvider?.let {
colorFilterParams = TintAndAlphaColorFilterParams(it)
}
contentScale = ContentScale.FillBounds
@@ -237,14 +242,19 @@
// is applied back to the target. Note: We could have hoisted the bg color to box
// instead of adding it back to the target, but for buttons, we also add an outline
// background to the box.
- if (bgModifier.imageProvider != null) {
- backgroundImage = EmittableImage().apply {
- modifier = GlanceModifier.fillMaxSize()
- provider = bgModifier.imageProvider
- contentScale = bgModifier.contentScale
+ when (bgModifier) {
+ is BackgroundModifier.Image -> {
+ backgroundImage = EmittableImage().apply {
+ modifier = GlanceModifier.fillMaxSize()
+ provider = bgModifier.imageProvider
+ contentScale = bgModifier.contentScale
+ colorFilterParams = bgModifier.colorFilter?.colorFilterParams
+ }
}
- } else { // is a background color modifier
- targetModifiers += bgModifier
+
+ is BackgroundModifier.Color -> {
+ targetModifiers += bgModifier
+ }
}
}
}
@@ -256,9 +266,15 @@
targetModifiersMinusBg.extractModifier<ActionModifier>()
boxModifiers += actionModifier
if (actionModifier != null && !hasBuiltinRipple()) {
+ val maybeRippleOverride = actionModifier.rippleOverride
val rippleImageProvider =
- if (isButton) ImageProvider(R.drawable.glance_button_ripple)
- else ImageProvider(R.drawable.glance_ripple)
+ if (maybeRippleOverride != NoRippleOverride) {
+ ImageProvider(maybeRippleOverride)
+ } else if (isButton) {
+ ImageProvider(R.drawable.glance_button_ripple)
+ } else {
+ ImageProvider(R.drawable.glance_ripple)
+ }
rippleImage = EmittableImage().apply {
modifier = GlanceModifier.fillMaxSize()
provider = rippleImageProvider
@@ -282,22 +298,24 @@
return EmittableBox().apply {
modifier = boxModifiers.collect()
+ target.modifier = targetModifiers.collect()
+
if (isButton) contentAlignment = Alignment.Center
- backgroundImage?.let { children += it }
- children += target.apply { modifier = targetModifiers.collect() }
- rippleImage?.let { children += it }
+ addChildIfNotNull(backgroundImage)
+ addChild(target)
+ addChildIfNotNull(rippleImage)
}
}
private fun Emittable.hasBuiltinRipple() =
this is EmittableSwitch ||
- this is EmittableRadioButton ||
- this is EmittableCheckBox ||
- // S+ versions use a native button with fixed rounded corners and matching ripple set in
- // layout xml. In R- versions, buttons are implemented using a background drawable with
- // rounded corners and an EmittableText in R- versions.
- (this is EmittableButton && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ this is EmittableRadioButton ||
+ this is EmittableCheckBox ||
+ // S+ versions use a native button with fixed rounded corners and matching ripple set in
+ // layout xml. In R- versions, buttons are implemented using a background drawable with
+ // rounded corners and an EmittableText in R- versions.
+ (this is EmittableButton && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
private data class ExtractedSizeModifiers(
val sizeModifiers: GlanceModifier = GlanceModifier,
@@ -313,7 +331,8 @@
foldIn(ExtractedSizeModifiers()) { acc, modifier ->
if (modifier is WidthModifier ||
modifier is HeightModifier ||
- modifier is CornerRadiusModifier) {
+ modifier is CornerRadiusModifier
+ ) {
acc.copy(sizeModifiers = acc.sizeModifiers.then(modifier))
} else {
acc.copy(nonSizeModifiers = acc.nonSizeModifiers.then(modifier))
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt
new file mode 100644
index 0000000..e2c6559
--- /dev/null
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/component/Buttons.kt
@@ -0,0 +1,467 @@
+/*
+ * Copyright 2023 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.glance.appwidget.component
+
+import android.os.Build
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.glance.Button
+import androidx.glance.ButtonColors
+import androidx.glance.ButtonDefaults
+import androidx.glance.ColorFilter
+import androidx.glance.GlanceModifier
+import androidx.glance.GlanceTheme
+import androidx.glance.Image
+import androidx.glance.ImageProvider
+import androidx.glance.action.Action
+import androidx.glance.action.NoRippleOverride
+import androidx.glance.action.action
+import androidx.glance.action.clickable
+import androidx.glance.appwidget.R
+import androidx.glance.appwidget.cornerRadius
+import androidx.glance.appwidget.enabled
+import androidx.glance.background
+import androidx.glance.layout.Alignment
+import androidx.glance.layout.Box
+import androidx.glance.layout.Row
+import androidx.glance.layout.Spacer
+import androidx.glance.layout.padding
+import androidx.glance.layout.size
+import androidx.glance.layout.width
+import androidx.glance.text.FontWeight
+import androidx.glance.text.Text
+import androidx.glance.text.TextStyle
+import androidx.glance.unit.ColorProvider
+
+/**
+ * A button styled per Material3. It has a filled background. It is more opinionated than [Button]
+ * and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param colors The colors to use for the background and content of the button.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun FilledButton(
+ text: String,
+ onClick: () -> Unit,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ icon: ImageProvider? = null,
+ colors: ButtonColors = ButtonDefaults.buttonColors(),
+ maxLines: Int = Int.MAX_VALUE,
+ key: String? = null
+) = FilledButton(
+ text = text,
+ onClick = action(block = onClick, key = key),
+ modifier = modifier,
+ enabled = enabled,
+ icon = icon,
+ colors = colors,
+ maxLines = maxLines,
+)
+
+/**
+ * A button styled per Material3. It has a filled background. It is more opinionated than [Button]
+ * and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param colors The colors to use for the background and content of the button.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ */
+@Composable
+fun FilledButton(
+ text: String,
+ onClick: Action,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ icon: ImageProvider? = null,
+ colors: ButtonColors = ButtonDefaults.buttonColors(),
+ maxLines: Int = Int.MAX_VALUE,
+) = M3TextButton(
+ text = text,
+ modifier = modifier,
+ enabled = enabled,
+ icon = icon,
+ contentColor = colors.contentColor,
+ backgroundTint = colors.backgroundColor,
+ backgroundResource = R.drawable.glance_component_btn_filled,
+ onClick = onClick,
+ maxLines = maxLines,
+)
+
+/**
+ * An outline button styled per Material3. It has a transparent background. It is more opinionated
+ * than [Button] and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param contentColor The color used for the text, optional icon tint, and outline.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun OutlineButton(
+ text: String,
+ contentColor: ColorProvider,
+ onClick: () -> Unit,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ icon: ImageProvider? = null,
+ maxLines: Int = Int.MAX_VALUE,
+ key: String? = null
+) = OutlineButton(
+ text = text,
+ contentColor = contentColor,
+ onClick = action(block = onClick, key = key),
+ modifier = modifier,
+ enabled = enabled,
+ icon = icon,
+ maxLines = maxLines,
+)
+
+/**
+ * An outline button styled per Material3. It has a transparent background. It is more opinionated
+ * than [Button] and suitable for uses where M3 is preferred.
+ *
+ * @param text The text that this button will show.
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param icon An optional leading icon placed before the text.
+ * @param contentColor The color used for the text, optional icon tint, and outline.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated.
+ */
+@Composable
+fun OutlineButton(
+ text: String,
+ contentColor: ColorProvider,
+ onClick: Action,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ icon: ImageProvider? = null,
+ maxLines: Int = Int.MAX_VALUE,
+) {
+ val bg: ColorProvider = contentColor
+ val fg: ColorProvider = contentColor
+
+ M3TextButton(
+ text = text,
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ icon = icon,
+ contentColor = fg,
+ backgroundResource = R.drawable.glance_component_btn_outline,
+ backgroundTint = bg,
+ maxLines = maxLines,
+ )
+}
+
+/**
+ * Intended to fill the role of primary icon button or fab.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background.
+ * @param contentColor The color to tint the button's icon.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun SquareIconButton(
+ imageProvider: ImageProvider,
+ contentDescription: String?,
+ onClick: () -> Unit,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ backgroundColor: ColorProvider = GlanceTheme.colors.primary,
+ contentColor: ColorProvider = GlanceTheme.colors.onPrimary,
+ key: String? = null
+) = SquareIconButton(
+ imageProvider = imageProvider,
+ contentDescription = contentDescription,
+ onClick = action(block = onClick, key = key),
+ modifier = modifier,
+ enabled = enabled,
+ backgroundColor = backgroundColor,
+ contentColor = contentColor,
+)
+
+/**
+ * Intended to fill the role of primary icon button or fab.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background.
+ * @param contentColor The color to tint the button's icon.
+ */
+@Composable
+fun SquareIconButton(
+ imageProvider: ImageProvider,
+ contentDescription: String?,
+ onClick: Action,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ backgroundColor: ColorProvider = GlanceTheme.colors.primary,
+ contentColor: ColorProvider = GlanceTheme.colors.onPrimary,
+) = M3IconButton(
+ imageProvider = imageProvider,
+ contentDescription = contentDescription,
+ backgroundColor = backgroundColor,
+ contentColor = contentColor,
+ shape = IconButtonShape.Square,
+ modifier = modifier,
+ enabled = enabled,
+ onClick = onClick,
+)
+
+/**
+ * Intended to fill the role of secondary icon button.
+ * Background color may be null to have the button display as an icon with a 48x48dp hit area.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background. May be null to make background
+ * transparent.
+ * @param contentColor The color to tint the button's icon.
+ * @param key A stable and unique key that identifies the action for this button. This ensures
+ * that the correct action is triggered, especially in cases of items that change order. If not
+ * provided we use the key that is automatically generated by the Compose runtime, which is unique
+ * for every exact code location in the composition tree.
+ */
+@Composable
+fun CircleIconButton(
+ imageProvider: ImageProvider,
+ contentDescription: String?,
+ onClick: () -> Unit,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ backgroundColor: ColorProvider? = GlanceTheme.colors.background,
+ contentColor: ColorProvider = GlanceTheme.colors.onSurface,
+ key: String? = null
+) = CircleIconButton(
+ imageProvider = imageProvider,
+ contentDescription = contentDescription,
+ backgroundColor = backgroundColor,
+ contentColor = contentColor,
+ modifier = modifier,
+ enabled = enabled,
+ onClick = action(block = onClick, key = key)
+)
+
+/**
+ * Intended to fill the role of secondary icon button.
+ * Background color may be null to have the button display as an icon with a 48x48dp hit area.
+ *
+ * @param imageProvider the icon to be drawn in the button
+ * @param contentDescription Text used by accessibility services to describe what this image
+ * represents. This text should be localized, such as by using
+ * androidx.compose.ui.res.stringResource or similar
+ * @param onClick The action to be performed when this button is clicked.
+ * @param modifier The modifier to be applied to this button.
+ * @param enabled If false, the button will not be clickable.
+ * @param backgroundColor The color to tint the button's background. May be null to make background
+ * transparent.
+ * @param contentColor The color to tint the button's icon.
+ */
+@Composable
+fun CircleIconButton(
+ imageProvider: ImageProvider,
+ contentDescription: String?,
+ onClick: Action,
+ modifier: GlanceModifier = GlanceModifier,
+ enabled: Boolean = true,
+ backgroundColor: ColorProvider? = GlanceTheme.colors.background,
+ contentColor: ColorProvider = GlanceTheme.colors.onSurface,
+) = M3IconButton(
+ imageProvider = imageProvider,
+ contentDescription = contentDescription,
+ backgroundColor = backgroundColor,
+ contentColor = contentColor,
+ shape = IconButtonShape.Circle,
+ modifier = modifier,
+ enabled = enabled,
+ onClick = onClick,
+)
+
+private enum class IconButtonShape(
+ @DrawableRes val shape: Int,
+ @DimenRes val cornerRadius: Int,
+ @DrawableRes val ripple: Int,
+ val defaultSize: Dp
+) {
+ Square(
+ R.drawable.glance_component_btn_square,
+ R.dimen.glance_component_square_icon_button_corners,
+ ripple = if (isAtLeastApi31) NoRippleOverride
+ else R.drawable.glance_component_square_button_ripple,
+ defaultSize = 60.dp
+ ),
+ Circle(
+ R.drawable.glance_component_btn_circle,
+ R.dimen.glance_component_circle_icon_button_corners,
+ ripple = if (isAtLeastApi31) NoRippleOverride
+ else R.drawable.glance_component_circle_button_ripple,
+ defaultSize = 48.dp
+ )
+}
+
+@Composable
+private fun M3IconButton(
+ imageProvider: ImageProvider,
+ contentDescription: String?,
+ contentColor: ColorProvider,
+ backgroundColor: ColorProvider?,
+ shape: IconButtonShape,
+ onClick: Action,
+ modifier: GlanceModifier,
+ enabled: Boolean,
+) {
+
+ val backgroundModifier = if (backgroundColor == null)
+ GlanceModifier
+ else GlanceModifier.background(
+ ImageProvider(shape.shape),
+ tint = ColorFilter.tint(backgroundColor)
+ )
+
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = GlanceModifier
+ .size(shape.defaultSize) // acts as a default if not overridden by [modifier]
+ .then(modifier)
+ .then(backgroundModifier)
+ .clickable(onClick = onClick, rippleOverride = shape.ripple)
+ .enabled(enabled)
+ .then(maybeRoundCorners(shape.cornerRadius))
+ ) {
+ Image(
+ provider = imageProvider,
+ contentDescription = contentDescription,
+ colorFilter = ColorFilter.tint(contentColor),
+ modifier = GlanceModifier.size(24.dp)
+ )
+ }
+}
+
+@Composable
+private fun M3TextButton(
+ text: String,
+ onClick: Action,
+ modifier: GlanceModifier,
+ enabled: Boolean = true,
+ icon: ImageProvider?,
+ contentColor: ColorProvider,
+ @DrawableRes backgroundResource: Int,
+ backgroundTint: ColorProvider,
+ maxLines: Int,
+) {
+ val iconSize = 18.dp
+ val totalHorizontalPadding = if (icon != null) 24.dp else 16.dp
+
+ val Text = @Composable {
+ Text(
+ text = text,
+ style = TextStyle(color = contentColor, fontSize = 14.sp, FontWeight.Medium),
+ maxLines = maxLines
+ )
+ }
+
+ Box(
+ modifier = modifier
+ .padding(start = 16.dp, end = totalHorizontalPadding, top = 10.dp, bottom = 10.dp)
+ .background(ImageProvider(backgroundResource), tint = ColorFilter.tint(backgroundTint))
+ .enabled(enabled)
+ .clickable(
+ onClick = onClick,
+ rippleOverride = if (isAtLeastApi31) NoRippleOverride
+ else R.drawable.glance_component_m3_button_ripple
+ )
+ .then(maybeRoundCorners(R.dimen.glance_component_button_corners)),
+ contentAlignment = Alignment.Center
+ ) {
+
+ if (icon != null) {
+ Row(verticalAlignment = Alignment.Vertical.CenterVertically) {
+ Image(
+ provider = icon,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(contentColor),
+ modifier = GlanceModifier.size(iconSize)
+ ) // TODO: do we need a content description for a button icon?
+ Spacer(GlanceModifier.width(8.dp))
+ Text()
+ }
+ } else {
+ Box(GlanceModifier.size(iconSize)) {
+ // for accessibility only: force button to be the same min height as the icon
+ // version.
+ // remove once b/290677181 is addressed
+ }
+ Text()
+ }
+ }
+}
+
+private val isAtLeastApi31 get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+private fun maybeRoundCorners(@DimenRes radius: Int) =
+ if (isAtLeastApi31)
+ GlanceModifier.cornerRadius(radius)
+ else GlanceModifier
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml
new file mode 100644
index 0000000..efdd739
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_circle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <size
+ android:width="48dp"
+ android:height="48dp" />
+ <solid android:color="@android:color/black" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml
new file mode 100644
index 0000000..d267cc2
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_filled.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/black" />
+ <corners android:radius="@dimen/glance_component_button_corners"/>
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml
new file mode 100644
index 0000000..e8b905f
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_outline.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="@dimen/glance_component_button_corners"/>
+ <stroke android:width="1dp" android:color="@android:color/black" />
+ <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml
new file mode 100644
index 0000000..df423d0
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_btn_square.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="@dimen/glance_component_square_icon_button_corners" />
+ <solid android:color="@android:color/black" />
+</shape>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml
new file mode 100644
index 0000000..5c3c1e4
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_circle_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2022 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.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/glance_component_circle_icon_button_corners"/>
+ <solid android:color="@android:color/white"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml
new file mode 100644
index 0000000..8150c36
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_m3_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2022 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.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/glance_component_button_corners"/>
+ <solid android:color="@android:color/white"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml b/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml
new file mode 100644
index 0000000..db64c61
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/drawable/glance_component_square_button_ripple.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2022 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.
+-->
+<!-- Fixed radius ripple matching the button's outline -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/glance_component_square_icon_button_corners"/>
+ <solid android:color="@android:color/white"/>
+ </shape>
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml b/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml
new file mode 100644
index 0000000..5a4c16b
--- /dev/null
+++ b/glance/glance-appwidget/src/main/res/values/glance_component_dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright 2023 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.
+ -->
+
+<resources>
+ <dimen name="glance_component_button_corners">24dp</dimen>
+ <dimen name="glance_component_square_icon_button_corners">16dp</dimen>
+ <dimen name="glance_component_circle_icon_button_corners">9999.dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
index 3d90666..72071c9 100644
--- a/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
+++ b/glance/glance-wear-tiles/src/main/java/androidx/glance/wear/tiles/WearCompositionTranslator.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+@file:Suppress("deprecation")
package androidx.glance.wear.tiles
import android.content.Context
@@ -76,6 +76,7 @@
import androidx.glance.wear.tiles.curved.SweepAngleModifier
import androidx.glance.wear.tiles.curved.ThicknessModifier
import androidx.glance.wear.tiles.curved.findModifier
+import androidx.wear.tiles.ColorBuilders
import java.io.ByteArrayOutputStream
import java.util.Arrays
@@ -126,12 +127,13 @@
@Suppress("deprecation") // for backward compatibility
private fun BackgroundModifier.toProto(
context: Context
-): androidx.wear.tiles.ModifiersBuilders.Background? =
- this.colorProvider?.let { provider ->
+): androidx.wear.tiles.ModifiersBuilders.Background? = when (this) {
+ is BackgroundModifier.Color ->
androidx.wear.tiles.ModifiersBuilders.Background.Builder()
- .setColor(androidx.wear.tiles.ColorBuilders.argb(provider.getColorAsArgb(context)))
+ .setColor(ColorBuilders.argb(this.colorProvider.getColorAsArgb(context)))
.build()
- }
+ else -> error("Unexpected modifier $this")
+}
@Suppress("deprecation") // for backward compatibility
private fun BorderModifier.toProto(context: Context): androidx.wear.tiles.ModifiersBuilders.Border =
diff --git a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
index 8018dd2..6b9a108 100644
--- a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
+++ b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/BackgroundTest.kt
@@ -19,9 +19,13 @@
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.glance.BackgroundModifier
+import androidx.glance.ColorFilter
import androidx.glance.GlanceModifier
+import androidx.glance.ImageProvider
+import androidx.glance.TintColorFilterParams
import androidx.glance.background
import androidx.glance.findModifier
+import androidx.glance.unit.ColorProvider
import androidx.glance.unit.FixedColorProvider
import androidx.glance.unit.ResourceColorProvider
import androidx.glance.wear.tiles.test.R
@@ -32,9 +36,10 @@
class BackgroundTest {
@Test
fun canUseBackgroundModifier() {
- val modifier = GlanceModifier.background(color = Color(0xFF223344))
+ val modifier: GlanceModifier = GlanceModifier.background(color = Color(0xFF223344))
- val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
+ val addedModifier: BackgroundModifier.Color =
+ requireNotNull(modifier.findModifier<BackgroundModifier.Color>())
val modifierColors = addedModifier.colorProvider
assertIs<FixedColorProvider>(modifierColors)
@@ -48,10 +53,30 @@
fun canUseBackgroundModifier_resId() {
val modifier = GlanceModifier.background(color = R.color.color1)
- val addedModifier = requireNotNull(modifier.findModifier<BackgroundModifier>())
+ val addedModifier: BackgroundModifier.Color =
+ requireNotNull(modifier.findModifier<BackgroundModifier.Color>())
val modifierColors = addedModifier.colorProvider
assertIs<ResourceColorProvider>(modifierColors)
assertThat(modifierColors.resId).isEqualTo(R.color.color1)
}
+
+ @Test
+ fun canUseBackgroundModifier_colorFilteredImage() {
+ fun tintColor() = ColorProvider(Color.Magenta)
+
+ val modifier = GlanceModifier.background(
+ ImageProvider(R.drawable.oval), ColorFilter.tint(
+ tintColor()
+ )
+ )
+
+ val addedModifier: BackgroundModifier.Image =
+ requireNotNull(modifier.findModifier<BackgroundModifier.Image>())
+
+ assertThat(
+ (addedModifier.colorFilter?.colorFilterParams as TintColorFilterParams).colorProvider)
+ .isEqualTo(tintColor()
+ )
+ }
}
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index 818bc2f..e87350d 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -2,6 +2,7 @@
package androidx.glance {
public final class BackgroundKt {
+ method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, androidx.glance.ColorFilter? tint, optional int contentScale);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
@@ -128,8 +129,11 @@
public final class ActionKt {
method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+ method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
}
public abstract class ActionParameters {
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index 818bc2f..e87350d 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -2,6 +2,7 @@
package androidx.glance {
public final class BackgroundKt {
+ method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, androidx.glance.ColorFilter? tint, optional int contentScale);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.ImageProvider imageProvider, optional int contentScale);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, androidx.glance.unit.ColorProvider colorProvider);
method public static androidx.glance.GlanceModifier background(androidx.glance.GlanceModifier, @ColorRes int color);
@@ -128,8 +129,11 @@
public final class ActionKt {
method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick);
- method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, androidx.glance.action.Action onClick, optional @DrawableRes int rippleOverride);
+ method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ method @SuppressCompatibility @androidx.compose.runtime.Composable @androidx.glance.ExperimentalGlanceApi public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, optional String? key, optional @DrawableRes int rippleOverride, kotlin.jvm.functions.Function0<kotlin.Unit> block);
method @androidx.compose.runtime.Composable public static androidx.glance.GlanceModifier clickable(androidx.glance.GlanceModifier, kotlin.jvm.functions.Function0<kotlin.Unit> block);
+ field @DrawableRes public static final int NoRippleOverride = 0; // 0x0
}
public abstract class ActionParameters {
diff --git a/glance/glance/src/main/java/androidx/glance/Background.kt b/glance/glance/src/main/java/androidx/glance/Background.kt
index 67e1195..4b254af 100644
--- a/glance/glance/src/main/java/androidx/glance/Background.kt
+++ b/glance/glance/src/main/java/androidx/glance/Background.kt
@@ -23,26 +23,22 @@
import androidx.glance.unit.ColorProvider
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class BackgroundModifier private constructor(
- val colorProvider: ColorProvider?,
- val imageProvider: ImageProvider?,
- val contentScale: ContentScale = ContentScale.FillBounds,
-) : GlanceModifier.Element {
- init {
- require((colorProvider != null) xor (imageProvider != null)) {
- "Exactly one of colorProvider and imageProvider must be non-null"
- }
+sealed interface BackgroundModifier : GlanceModifier.Element {
+
+ class Color(val colorProvider: ColorProvider) : BackgroundModifier {
+ override fun toString() =
+ "BackgroundModifier(colorProvider=$colorProvider)"
}
- constructor(colorProvider: ColorProvider) :
- this(colorProvider = colorProvider, imageProvider = null)
-
- constructor(imageProvider: ImageProvider, contentScale: ContentScale) :
- this(colorProvider = null, imageProvider = imageProvider, contentScale = contentScale)
-
- override fun toString() =
- "BackgroundModifier(colorProvider=$colorProvider, imageProvider=$imageProvider, " +
- "contentScale=$contentScale)"
+ class Image(
+ val imageProvider: ImageProvider?,
+ val contentScale: ContentScale = ContentScale.FillBounds,
+ val colorFilter: ColorFilter? = null
+ ) : BackgroundModifier {
+ override fun toString() =
+ "BackgroundModifier(colorFilter=$colorFilter, imageProvider=$imageProvider, " +
+ "contentScale=$contentScale)"
+ }
}
/**
@@ -67,7 +63,7 @@
* the element.
*/
fun GlanceModifier.background(colorProvider: ColorProvider): GlanceModifier =
- this.then(BackgroundModifier(colorProvider))
+ this.then(BackgroundModifier.Color(colorProvider))
/**
* Apply a background image to the element this modifier is attached to.
@@ -76,4 +72,20 @@
imageProvider: ImageProvider,
contentScale: ContentScale = ContentScale.FillBounds
): GlanceModifier =
- this.then(BackgroundModifier(imageProvider, contentScale))
+ this.then(BackgroundModifier.Image(imageProvider, contentScale))
+
+/**
+ * Apply a background image to the element this modifier is attached to.
+ */
+fun GlanceModifier.background(
+ imageProvider: ImageProvider,
+ tint: ColorFilter?,
+ contentScale: ContentScale = ContentScale.FillBounds,
+ ): GlanceModifier =
+ this.then(
+ BackgroundModifier.Image(
+ imageProvider = imageProvider,
+ contentScale = contentScale,
+ colorFilter = tint
+ )
+ )
diff --git a/glance/glance/src/main/java/androidx/glance/Emittables.kt b/glance/glance/src/main/java/androidx/glance/Emittables.kt
index b9cf2f2..90a8168 100644
--- a/glance/glance/src/main/java/androidx/glance/Emittables.kt
+++ b/glance/glance/src/main/java/androidx/glance/Emittables.kt
@@ -38,6 +38,16 @@
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun EmittableWithChildren.addChild(e: Emittable) {
+ this.children += e
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun EmittableWithChildren.addChildIfNotNull(e: Emittable?) {
+ if (e != null) this.children += e
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract class EmittableLazyItemWithChildren : EmittableWithChildren() {
var alignment: Alignment = Alignment.CenterStart
}
diff --git a/glance/glance/src/main/java/androidx/glance/Image.kt b/glance/glance/src/main/java/androidx/glance/Image.kt
index ae5f43e..4011c8c 100644
--- a/glance/glance/src/main/java/androidx/glance/Image.kt
+++ b/glance/glance/src/main/java/androidx/glance/Image.kt
@@ -86,7 +86,9 @@
/**
* Effects used to modify the color of an image.
*/
-class ColorFilter internal constructor(internal val colorFilterParams: ColorFilterParams) {
+class ColorFilter internal constructor(
+ @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) val colorFilterParams: ColorFilterParams
+) {
companion object {
/**
* Set a tinting option for the image using the platform-specific default blending mode.
diff --git a/glance/glance/src/main/java/androidx/glance/action/Action.kt b/glance/glance/src/main/java/androidx/glance/action/Action.kt
index d214afe..91028c5e 100644
--- a/glance/glance/src/main/java/androidx/glance/action/Action.kt
+++ b/glance/glance/src/main/java/androidx/glance/action/Action.kt
@@ -17,6 +17,7 @@
package androidx.glance.action
import android.app.Activity
+import androidx.annotation.DrawableRes
import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.glance.ExperimentalGlanceApi
@@ -31,12 +32,16 @@
/**
* Apply an [Action], to be executed in response to a user click.
+ *
+ * @param onClick The action to run.
*/
fun GlanceModifier.clickable(onClick: Action): GlanceModifier =
this.then(ActionModifier(onClick))
/**
* Run [block] in response to a user click.
+ *
+ * @param block The action to run.
*/
@Composable
fun GlanceModifier.clickable(
@@ -47,7 +52,36 @@
/**
* Run [block] in response to a user click.
*
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
+ * @param block The action to run.v
+ */
+@Composable
+fun GlanceModifier.clickable(
+ @DrawableRes rippleOverride: Int = NoRippleOverride,
+ block: () -> Unit
+): GlanceModifier =
+ this.then(ActionModifier(action = action(block = block), rippleOverride = rippleOverride))
+
+/**
+ * Apply an [Action], to be executed in response to a user click.
+ *
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
+ * @param onClick The action to run.
+ */
+fun GlanceModifier.clickable(
+ onClick: Action,
+ @DrawableRes rippleOverride: Int = NoRippleOverride
+): GlanceModifier =
+ this.then(ActionModifier(action = onClick, rippleOverride = rippleOverride))
+
+/**
+ * Run [block] in response to a user click.
+ *
* @param block The action to run.
+ * @param rippleOverride A drawable resource to use as the onClick ripple. Use [NoRippleOverride]
+ * if no custom behavior is needed.
* @param key A stable and unique key that identifies the action for this element. This ensures
* that the correct action is triggered, especially in cases of items that change order. If not
* provided we use the key that is automatically generated by the Compose runtime, which is unique
@@ -57,13 +91,24 @@
@Composable
fun GlanceModifier.clickable(
key: String? = null,
+ @DrawableRes rippleOverride: Int = NoRippleOverride,
block: () -> Unit
): GlanceModifier =
- this.then(ActionModifier(action(key, block)))
+ this.then(ActionModifier(action = action(key, block), rippleOverride = rippleOverride))
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class ActionModifier(val action: Action) : GlanceModifier.Element {
+class ActionModifier(
+ val action: Action,
+ @DrawableRes val rippleOverride: Int = NoRippleOverride
+) : GlanceModifier.Element {
override fun toString(): String {
- return "ActionModifier(action=$action)"
+ return "ActionModifier(action=$action, rippleOverride=$rippleOverride)"
}
}
+
+/**
+ * Constant. Tells the system that there is no ripple override. When this is passed, the system
+ * will use default behavior for the ripple.
+ */
+@DrawableRes
+const val NoRippleOverride = 0
diff --git a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
index 490503ae..7cdafb4 100644
--- a/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
+++ b/mediarouter/mediarouter/src/main/res/values-ne/strings.xml
@@ -39,7 +39,7 @@
<string name="mr_controller_no_info_available" msgid="855271725131981086">"कुनै पनि जानकारी उपलब्ध छैन"</string>
<string name="mr_controller_casting_screen" msgid="9171231064758955152">"स्क्रिन Cast गरिँदै छ"</string>
<string name="mr_dialog_default_group_name" msgid="4115858704575247342">"समूह"</string>
- <string name="mr_dialog_groupable_header" msgid="4307018456678388936">"डिभाइस थप्नुहोस्"</string>
+ <string name="mr_dialog_groupable_header" msgid="4307018456678388936">"डिभाइस कनेक्ट गर्नुहोस्"</string>
<string name="mr_dialog_transferable_header" msgid="6068257520605505468">"कुनै समूहमा प्ले गर्नुहोस्"</string>
<string name="mr_cast_dialog_title_view_placeholder" msgid="2175930138959078155">"कुनै पनि जानकारी उपलब्ध छैन"</string>
<string name="mr_chooser_zero_routes_found_title" msgid="5213435473397442608">"कुनै पनि डिभाइस उपलब्ध छैन"</string>
diff --git a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
index de5b048..70ceae1 100644
--- a/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
+++ b/privacysandbox/ui/ui-client/src/main/java/androidx/privacysandbox/ui/client/SandboxedUiAdapterFactory.kt
@@ -133,6 +133,7 @@
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
surfaceView.setZOrderOnTop(isZOrderOnTop)
+ remoteSessionController.notifyZOrderChanged(isZOrderOnTop)
}
override fun close() {
diff --git a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
index 1e2ff92..c856d33 100644
--- a/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
+++ b/privacysandbox/ui/ui-core/src/main/aidl/androidx/privacysandbox/ui/core/IRemoteSessionController.aidl
@@ -21,4 +21,5 @@
void close();
void notifyConfigurationChanged(in Configuration configuration);
void notifyResized(int width, int height);
+ void notifyZOrderChanged(boolean isZOrderOnTop);
}
\ No newline at end of file
diff --git a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
index 6639148..66e1da8 100644
--- a/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
+++ b/privacysandbox/ui/ui-provider/src/main/java/androidx/privacysandbox/ui/provider/BinderAdapterDelegate.kt
@@ -185,6 +185,10 @@
}
}
+ override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
+ session.notifyZOrderChanged(isZOrderOnTop)
+ }
+
override fun close() {
val mHandler = Handler(Looper.getMainLooper())
mHandler.post {
diff --git a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
index e1ba4e8..3333303 100644
--- a/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
+++ b/privacysandbox/ui/ui-tests/src/androidTest/java/androidx/privacysandbox/ui/tests/endtoend/IntegrationTests.kt
@@ -203,6 +203,44 @@
assertTrue(configChangedLatch.count == 0.toLong())
}
+ /**
+ * Tests that the provider receives Z-order change updates.
+ */
+ @Test
+ fun testZOrderChanged() {
+ val openSessionLatch = CountDownLatch(1)
+ val adapter = TestSandboxedUiAdapter(
+ openSessionLatch,
+ null,
+ /* hasFailingTestSession=*/false
+ )
+ val coreLibInfo = adapter.toCoreLibInfo(context)
+ val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
+ view.setAdapter(adapterFromCoreLibInfo)
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ view.setZOrderOnTopAndEnableUserInteraction(!adapter.initialZOrderOnTop)
+ assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ /**
+ * Tests that the provider does not receive Z-order updates if the Z-order is unchanged.
+ */
+ @Test
+ fun testZOrderUnchanged() {
+ val openSessionLatch = CountDownLatch(1)
+ val adapter = TestSandboxedUiAdapter(
+ openSessionLatch,
+ null,
+ /* hasFailingTestSession=*/false
+ )
+ val coreLibInfo = adapter.toCoreLibInfo(context)
+ val adapterFromCoreLibInfo = SandboxedUiAdapterFactory.createFromCoreLibInfo(coreLibInfo)
+ view.setAdapter(adapterFromCoreLibInfo)
+ assertThat(openSessionLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+ view.setZOrderOnTopAndEnableUserInteraction(adapter.initialZOrderOnTop)
+ assertThat(adapter.zOrderLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)).isFalse()
+ }
+
@Test
fun testSessionError() {
val adapter = TestSandboxedUiAdapter(
@@ -240,6 +278,8 @@
) : SandboxedUiAdapter {
var isOpenSessionCalled = false
+ var initialZOrderOnTop = false
+ var zOrderLatch = CountDownLatch(1)
lateinit var session: SandboxedUiAdapter.Session
lateinit var internalClient: SandboxedUiAdapter.SessionClient
@@ -254,6 +294,7 @@
) {
internalClient = client
isOpenSessionCalled = true
+ initialZOrderOnTop = isZOrderOnTop
session = if (hasFailingTestSession) {
FailingTestSession(context)
} else {
@@ -301,6 +342,7 @@
}
override fun notifyZOrderChanged(isZOrderOnTop: Boolean) {
+ zOrderLatch.countDown()
}
override fun notifyConfigurationChanged(configuration: Configuration) {
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
index 6f0f335..f212956 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/SourceSet.kt
@@ -18,9 +18,11 @@
import androidx.room.compiler.processing.util.Source
import java.io.File
+import java.util.regex.Pattern
private val BY_ROUNDS_PATH_PATTERN =
- "(byRounds${File.separator}[0-9]+${File.separator})?(.*)".toPattern()
+ ("(byRounds${Pattern.quote(File.separator)}[0-9]+" +
+ "${Pattern.quote(File.separator)})?(.*)").toPattern()
/**
* Represents sources that are positioned in the [root] folder.
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
index 7158a10..cc3df0a 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceSerializeMetrics.java
@@ -59,6 +59,7 @@
@RunWith(AndroidJUnit4.class)
@LargeTest
@SdkSuppress(minSdkVersion = 19)
+@SuppressWarnings("deprecation")
public class SliceSerializeMetrics {
private static final boolean WRITE_SAMPLE_FILE = false;
diff --git a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
index 05482e8..b892033 100644
--- a/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
+++ b/slice/slice-benchmark/src/androidTest/java/androidx/slice/SliceViewMetrics.java
@@ -39,6 +39,7 @@
@RunWith(Parameterized.class)
@SmallTest
@SdkSuppress(minSdkVersion = 19)
+@SuppressWarnings("deprecation")
public class SliceViewMetrics {
private final int mMode;
diff --git a/slice/slice-builders-ktx/api/current.txt b/slice/slice-builders-ktx/api/current.txt
index 05623ec..6d9933f 100644
--- a/slice/slice-builders-ktx/api/current.txt
+++ b/slice/slice-builders-ktx/api/current.txt
@@ -1,49 +1,49 @@
// Signature format: 4.0
package androidx.slice.builders {
- public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
- ctor public CellBuilderDsl();
+ @Deprecated public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
+ ctor @Deprecated public CellBuilderDsl();
}
- public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
- ctor public GridRowBuilderDsl();
+ @Deprecated public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
+ ctor @Deprecated public GridRowBuilderDsl();
}
public final class GridRowBuilderKt {
- method public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
- method public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+ method @Deprecated public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+ method @Deprecated public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
}
- public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
- ctor public HeaderBuilderDsl();
+ @Deprecated public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
+ ctor @Deprecated public HeaderBuilderDsl();
}
- public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
- ctor public InputRangeBuilderDsl();
+ @Deprecated public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
+ ctor @Deprecated public InputRangeBuilderDsl();
}
- public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
- ctor public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
+ @Deprecated public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
+ ctor @Deprecated public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
}
public final class ListBuilderKt {
- method public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
- method public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
- method public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
- method public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
- method public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
- method public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
- method public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
- method public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
- method public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
+ method @Deprecated public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+ method @Deprecated public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
+ method @Deprecated public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
}
- public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
- ctor public RangeBuilderDsl();
+ @Deprecated public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
+ ctor @Deprecated public RangeBuilderDsl();
}
- public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
- ctor public RowBuilderDsl();
+ @Deprecated public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
+ ctor @Deprecated public RowBuilderDsl();
}
}
diff --git a/slice/slice-builders-ktx/api/restricted_current.txt b/slice/slice-builders-ktx/api/restricted_current.txt
index 05623ec..6d9933f 100644
--- a/slice/slice-builders-ktx/api/restricted_current.txt
+++ b/slice/slice-builders-ktx/api/restricted_current.txt
@@ -1,49 +1,49 @@
// Signature format: 4.0
package androidx.slice.builders {
- public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
- ctor public CellBuilderDsl();
+ @Deprecated public final class CellBuilderDsl extends androidx.slice.builders.GridRowBuilder.CellBuilder {
+ ctor @Deprecated public CellBuilderDsl();
}
- public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
- ctor public GridRowBuilderDsl();
+ @Deprecated public final class GridRowBuilderDsl extends androidx.slice.builders.GridRowBuilder {
+ ctor @Deprecated public GridRowBuilderDsl();
}
public final class GridRowBuilderKt {
- method public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
- method public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+ method @Deprecated public static inline androidx.slice.builders.GridRowBuilder cell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
+ method @Deprecated public static inline androidx.slice.builders.GridRowBuilder seeMoreCell(androidx.slice.builders.GridRowBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.CellBuilderDsl,kotlin.Unit> buildCell);
}
- public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
- ctor public HeaderBuilderDsl();
+ @Deprecated public final class HeaderBuilderDsl extends androidx.slice.builders.ListBuilder.HeaderBuilder {
+ ctor @Deprecated public HeaderBuilderDsl();
}
- public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
- ctor public InputRangeBuilderDsl();
+ @Deprecated public final class InputRangeBuilderDsl extends androidx.slice.builders.ListBuilder.InputRangeBuilder {
+ ctor @Deprecated public InputRangeBuilderDsl();
}
- public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
- ctor public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
+ @Deprecated public final class ListBuilderDsl extends androidx.slice.builders.ListBuilder {
+ ctor @Deprecated public ListBuilderDsl(android.content.Context context, android.net.Uri uri, long ttl);
}
public final class ListBuilderKt {
- method public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
- method public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
- method public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
- method public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
- method public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
- method public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
- method public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
- method public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
- method public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder gridRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.GridRowBuilderDsl,kotlin.Unit> buildGrid);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder header(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.HeaderBuilderDsl,kotlin.Unit> buildHeader);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder inputRange(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.InputRangeBuilderDsl,kotlin.Unit> buildInputRange);
+ method @Deprecated public static inline androidx.slice.Slice list(android.content.Context context, android.net.Uri uri, long ttl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.ListBuilderDsl,kotlin.Unit> addRows);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder range(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RangeBuilderDsl,kotlin.Unit> buildRange);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder row(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+ method @Deprecated public static inline androidx.slice.builders.ListBuilder seeMoreRow(androidx.slice.builders.ListBuilderDsl, kotlin.jvm.functions.Function1<? super androidx.slice.builders.RowBuilderDsl,kotlin.Unit> buildRow);
+ method @Deprecated public static androidx.slice.builders.SliceAction tapSliceAction(android.app.PendingIntent pendingIntent, androidx.core.graphics.drawable.IconCompat icon, optional int imageMode, CharSequence title);
+ method @Deprecated public static androidx.slice.builders.SliceAction toggleSliceAction(android.app.PendingIntent pendingIntent, optional androidx.core.graphics.drawable.IconCompat? icon, CharSequence title, boolean isChecked);
}
- public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
- ctor public RangeBuilderDsl();
+ @Deprecated public final class RangeBuilderDsl extends androidx.slice.builders.ListBuilder.RangeBuilder {
+ ctor @Deprecated public RangeBuilderDsl();
}
- public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
- ctor public RowBuilderDsl();
+ @Deprecated public final class RowBuilderDsl extends androidx.slice.builders.ListBuilder.RowBuilder {
+ ctor @Deprecated public RowBuilderDsl();
}
}
diff --git a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
index a05802a..6faebd4 100644
--- a/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
+++ b/slice/slice-builders-ktx/src/androidTest/java/androidx/slice/builders/SliceBuildersKtxTest.kt
@@ -33,6 +33,7 @@
@SdkSuppress(minSdkVersion = 19)
@MediumTest
+@Suppress("DEPRECATION")
class SliceBuildersKtxTest {
private val testUri = Uri.parse("content://com.example.android.sliceuri")
private val context = ApplicationProvider.getApplicationContext() as android.content.Context
diff --git a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
index 504b13b..9188617 100644
--- a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
+++ b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/GridRowBuilder.kt
@@ -20,6 +20,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class GridRowBuilderDsl : GridRowBuilder()
@@ -28,17 +36,41 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class CellBuilderDsl : GridRowBuilder.CellBuilder()
/**
* @see GridRowBuilder.addCell
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun GridRowBuilderDsl.cell(buildCell: CellBuilderDsl.() -> Unit) =
addCell(CellBuilderDsl().apply { buildCell() })
/**
* @see GridRowBuilder.setSeeMoreCell
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun GridRowBuilderDsl.seeMoreCell(buildCell: CellBuilderDsl.() -> Unit) =
setSeeMoreCell(CellBuilderDsl().apply { buildCell() })
diff --git a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
index 101ed21..a782bc4 100644
--- a/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
+++ b/slice/slice-builders-ktx/src/main/java/androidx/slice/builders/ListBuilder.kt
@@ -32,6 +32,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class ListBuilderDsl(context: Context, uri: Uri, ttl: Long) : ListBuilder(context, uri, ttl)
@@ -40,6 +48,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class RowBuilderDsl : ListBuilder.RowBuilder()
@@ -48,6 +64,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class InputRangeBuilderDsl : ListBuilder.InputRangeBuilder()
@@ -56,6 +80,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class RangeBuilderDsl : ListBuilder.RangeBuilder()
@@ -64,6 +96,14 @@
* Two implicit receivers that are annotated with @SliceMarker are not accessible in the same scope,
* ensuring a type-safe DSL.
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
@SliceMarker
class HeaderBuilderDsl : ListBuilder.HeaderBuilder()
@@ -94,6 +134,14 @@
* </pre>
* @see ListBuilder.build
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun list(
context: Context,
uri: Uri,
@@ -104,42 +152,98 @@
/**
* @see ListBuilder.setHeader
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.header(buildHeader: HeaderBuilderDsl.() -> Unit) =
setHeader(HeaderBuilderDsl().apply { buildHeader() })
/**
* @see ListBuilder.addGridRow
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.gridRow(buildGrid: GridRowBuilderDsl.() -> Unit) =
addGridRow(GridRowBuilderDsl().apply { buildGrid() })
/**
* @see ListBuilder.addRow
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.row(buildRow: RowBuilderDsl.() -> Unit) =
addRow(RowBuilderDsl().apply { buildRow() })
/**
* @see ListBuilder.setSeeMoreRow
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.seeMoreRow(buildRow: RowBuilderDsl.() -> Unit) =
setSeeMoreRow(RowBuilderDsl().apply { buildRow() })
/**
* @see ListBuilder.addInputRange
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.inputRange(buildInputRange: InputRangeBuilderDsl.() -> Unit) =
addInputRange(InputRangeBuilderDsl().apply { buildInputRange() })
/**
* @see ListBuilder.addRange
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
inline fun ListBuilderDsl.range(buildRange: RangeBuilderDsl.() -> Unit) =
addRange(RangeBuilderDsl().apply { buildRange() })
/**
* Factory method to build a tappable [SliceAction].
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
fun tapSliceAction(
pendingIntent: PendingIntent,
icon: IconCompat,
@@ -150,6 +254,14 @@
/**
* Factory method to build a toggleable [SliceAction].
*/
+@Deprecated(
+ """
+ Slice framework has been deprecated, it will not receive any updates moving forward.
+ If you are looking for a framework that handles communication across apps,
+ consider using AppSearchManager.
+ """,
+ ReplaceWith("AppSearchManager", "android.app.appsearch"))
+@Suppress("DEPRECATION")
fun toggleSliceAction(
pendingIntent: PendingIntent,
icon: IconCompat? = null,
diff --git a/slice/slice-builders/api/current.txt b/slice/slice-builders/api/current.txt
index 1351608..6801791 100644
--- a/slice/slice-builders/api/current.txt
+++ b/slice/slice-builders/api/current.txt
@@ -1,190 +1,190 @@
// Signature format: 4.0
package androidx.slice.builders {
- @RequiresApi(19) public class GridRowBuilder {
- ctor public GridRowBuilder();
- method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
- method public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
- method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+ @Deprecated @RequiresApi(19) public class GridRowBuilder {
+ ctor @Deprecated public GridRowBuilder();
+ method @Deprecated public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
}
- public static class GridRowBuilder.CellBuilder {
- ctor public GridRowBuilder.CellBuilder();
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
+ @Deprecated public static class GridRowBuilder.CellBuilder {
+ ctor @Deprecated public GridRowBuilder.CellBuilder();
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
}
- @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
- ctor @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
- ctor public ListBuilder(android.content.Context, android.net.Uri, long);
- method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
- method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
- method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
- method public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
- method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
- method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
- method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
- method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
- method @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
- method public androidx.slice.builders.ListBuilder setIsError(boolean);
- method public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
- method public androidx.slice.builders.ListBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
- field public static final int ACTION_WITH_LABEL = 6; // 0x6
- field public static final int ICON_IMAGE = 0; // 0x0
- field public static final long INFINITY = -1L; // 0xffffffffffffffffL
- field public static final int LARGE_IMAGE = 2; // 0x2
- field public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
- field public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
- field public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
- field public static final int RAW_IMAGE_LARGE = 4; // 0x4
- field public static final int RAW_IMAGE_SMALL = 3; // 0x3
- field public static final int SMALL_IMAGE = 1; // 0x1
- field public static final int UNKNOWN_IMAGE = 5; // 0x5
+ @Deprecated @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+ ctor @Deprecated @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
+ ctor @Deprecated public ListBuilder(android.content.Context, android.net.Uri, long);
+ method @Deprecated public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
+ method @Deprecated public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
+ method @Deprecated @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
+ method @Deprecated public androidx.slice.builders.ListBuilder setIsError(boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
+ method @Deprecated public androidx.slice.builders.ListBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+ field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+ field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+ field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+ field @Deprecated public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
+ field @Deprecated public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
+ field @Deprecated public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
+ field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+ field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+ field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+ field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
}
- public static class ListBuilder.HeaderBuilder {
- ctor public ListBuilder.HeaderBuilder();
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
+ @Deprecated public static class ListBuilder.HeaderBuilder {
+ ctor @Deprecated public ListBuilder.HeaderBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
}
- public static class ListBuilder.InputRangeBuilder {
- ctor public ListBuilder.InputRangeBuilder();
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
+ @Deprecated public static class ListBuilder.InputRangeBuilder {
+ ctor @Deprecated public ListBuilder.InputRangeBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
}
- public static class ListBuilder.RangeBuilder {
- ctor public ListBuilder.RangeBuilder();
- method public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
+ @Deprecated public static class ListBuilder.RangeBuilder {
+ ctor @Deprecated public ListBuilder.RangeBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
}
- public static final class ListBuilder.RatingBuilder {
- ctor public ListBuilder.RatingBuilder();
- method public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
+ @Deprecated public static final class ListBuilder.RatingBuilder {
+ ctor @Deprecated public ListBuilder.RatingBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
}
- public static class ListBuilder.RowBuilder {
- ctor public ListBuilder.RowBuilder();
- ctor public ListBuilder.RowBuilder(android.net.Uri);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
- method public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
+ @Deprecated public static class ListBuilder.RowBuilder {
+ ctor @Deprecated public ListBuilder.RowBuilder();
+ ctor @Deprecated public ListBuilder.RowBuilder(android.net.Uri);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
}
- @RequiresApi(19) public class SelectionBuilder {
- ctor public SelectionBuilder();
- method public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
- method public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
- method public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
- method public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
- method public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
- method public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
+ @Deprecated @RequiresApi(19) public class SelectionBuilder {
+ ctor @Deprecated public SelectionBuilder();
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
}
- @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
- method public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
- method public android.app.PendingIntent getAction();
- method public CharSequence? getContentDescription();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method public int getImageMode();
- method public String? getKey();
- method public int getPriority();
- method public CharSequence getTitle();
- method public boolean isActivity();
- method public boolean isChecked();
- method public boolean isDefaultToggle();
- method public boolean isToggle();
- method public androidx.slice.builders.SliceAction setChecked(boolean);
- method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
- method public androidx.slice.builders.SliceAction setKey(String);
- method public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
+ @Deprecated @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
+ method @Deprecated public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated public int getImageMode();
+ method @Deprecated public String? getKey();
+ method @Deprecated public int getPriority();
+ method @Deprecated public CharSequence getTitle();
+ method @Deprecated public boolean isActivity();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isDefaultToggle();
+ method @Deprecated public boolean isToggle();
+ method @Deprecated public androidx.slice.builders.SliceAction setChecked(boolean);
+ method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.SliceAction setKey(String);
+ method @Deprecated public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
}
- @RequiresApi(19) public abstract class TemplateSliceBuilder {
- method public androidx.slice.Slice build();
+ @Deprecated @RequiresApi(19) public abstract class TemplateSliceBuilder {
+ method @Deprecated public androidx.slice.Slice build();
}
}
diff --git a/slice/slice-builders/api/restricted_current.txt b/slice/slice-builders/api/restricted_current.txt
index 84c8575..fb90065 100644
--- a/slice/slice-builders/api/restricted_current.txt
+++ b/slice/slice-builders/api/restricted_current.txt
@@ -1,210 +1,210 @@
// Signature format: 4.0
package androidx.slice.builders {
- @RequiresApi(19) public class GridRowBuilder {
- ctor public GridRowBuilder();
- method public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
- method public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
- method public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+ @Deprecated @RequiresApi(19) public class GridRowBuilder {
+ ctor @Deprecated public GridRowBuilder();
+ method @Deprecated public androidx.slice.builders.GridRowBuilder addCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder setSeeMoreCell(androidx.slice.builders.GridRowBuilder.CellBuilder);
}
- public static class GridRowBuilder.CellBuilder {
- ctor public GridRowBuilder.CellBuilder();
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
+ @Deprecated public static class GridRowBuilder.CellBuilder {
+ ctor @Deprecated public GridRowBuilder.CellBuilder();
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addImage(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addOverlayText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder addTitleText(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setContentIntent(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.GridRowBuilder.CellBuilder setSliceAction(androidx.slice.builders.SliceAction);
}
- @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
- ctor @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
- ctor public ListBuilder(android.content.Context, android.net.Uri, long);
- method public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
- method public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
- method public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
- method public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
- method public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
- method public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
- method public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
- method public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
- method @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
- method public androidx.slice.builders.ListBuilder setIsError(boolean);
- method public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
- method public androidx.slice.builders.ListBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
- field public static final int ACTION_WITH_LABEL = 6; // 0x6
- field public static final int ICON_IMAGE = 0; // 0x0
- field public static final long INFINITY = -1L; // 0xffffffffffffffffL
- field public static final int LARGE_IMAGE = 2; // 0x2
- field public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
- field public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
- field public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
- field public static final int RAW_IMAGE_LARGE = 4; // 0x4
- field public static final int RAW_IMAGE_SMALL = 3; // 0x3
- field public static final int SMALL_IMAGE = 1; // 0x1
- field public static final int UNKNOWN_IMAGE = 5; // 0x5
+ @Deprecated @RequiresApi(19) public class ListBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+ ctor @Deprecated @RequiresApi(26) public ListBuilder(android.content.Context, android.net.Uri, java.time.Duration?);
+ ctor @Deprecated public ListBuilder(android.content.Context, android.net.Uri, long);
+ method @Deprecated public androidx.slice.builders.ListBuilder addAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder addGridRow(androidx.slice.builders.GridRowBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addInputRange(androidx.slice.builders.ListBuilder.InputRangeBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRange(androidx.slice.builders.ListBuilder.RangeBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRating(androidx.slice.builders.ListBuilder.RatingBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder addSelection(androidx.slice.builders.SelectionBuilder);
+ method @Deprecated public androidx.slice.builders.ListBuilder setAccentColor(@ColorInt int);
+ method @Deprecated public androidx.slice.builders.ListBuilder setHeader(androidx.slice.builders.ListBuilder.HeaderBuilder);
+ method @Deprecated @RequiresApi(21) public androidx.slice.builders.ListBuilder setHostExtras(android.os.PersistableBundle);
+ method @Deprecated public androidx.slice.builders.ListBuilder setIsError(boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder setKeywords(java.util.Set<java.lang.String!>);
+ method @Deprecated public androidx.slice.builders.ListBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder setSeeMoreRow(androidx.slice.builders.ListBuilder.RowBuilder);
+ field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+ field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+ field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+ field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+ field @Deprecated public static final int RANGE_MODE_DETERMINATE = 0; // 0x0
+ field @Deprecated public static final int RANGE_MODE_INDETERMINATE = 1; // 0x1
+ field @Deprecated public static final int RANGE_MODE_STAR_RATING = 2; // 0x2
+ field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+ field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+ field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+ field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
}
- public static class ListBuilder.HeaderBuilder {
- ctor public ListBuilder.HeaderBuilder();
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ListBuilder.HeaderBuilder(android.net.Uri);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
+ @Deprecated public static class ListBuilder.HeaderBuilder {
+ ctor @Deprecated public ListBuilder.HeaderBuilder();
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public ListBuilder.HeaderBuilder(android.net.Uri);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSubtitle(CharSequence, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setSummary(CharSequence, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.HeaderBuilder setTitle(CharSequence, boolean);
}
- public static class ListBuilder.InputRangeBuilder {
- ctor public ListBuilder.InputRangeBuilder();
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
+ @Deprecated public static class ListBuilder.InputRangeBuilder {
+ ctor @Deprecated public ListBuilder.InputRangeBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setMin(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setThumb(androidx.core.graphics.drawable.IconCompat);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.InputRangeBuilder setValue(int);
}
- public static class ListBuilder.RangeBuilder {
- ctor public ListBuilder.RangeBuilder();
- method public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
+ @Deprecated public static class ListBuilder.RangeBuilder {
+ ctor @Deprecated public ListBuilder.RangeBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setMode(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RangeBuilder setValue(int);
}
- public static final class ListBuilder.RatingBuilder {
- ctor public ListBuilder.RatingBuilder();
- method public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
- method public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
+ @Deprecated public static final class ListBuilder.RatingBuilder {
+ ctor @Deprecated public ListBuilder.RatingBuilder();
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMax(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setMin(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RatingBuilder setValue(float);
}
- public static class ListBuilder.RowBuilder {
- ctor public ListBuilder.RowBuilder();
- ctor public ListBuilder.RowBuilder(android.net.Uri);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
- method public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
- method public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
- method public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
+ @Deprecated public static class ListBuilder.RowBuilder {
+ ctor @Deprecated public ListBuilder.RowBuilder();
+ ctor @Deprecated public ListBuilder.RowBuilder(android.net.Uri);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder addEndItem(long);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setEndOfSection(boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setSubtitle(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitle(CharSequence?, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat, int);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.core.graphics.drawable.IconCompat?, int, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(androidx.slice.builders.SliceAction, boolean);
+ method @Deprecated public androidx.slice.builders.ListBuilder.RowBuilder setTitleItem(long);
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MessagingSliceBuilder extends androidx.slice.builders.TemplateSliceBuilder {
- ctor public MessagingSliceBuilder(android.content.Context, android.net.Uri);
- method public androidx.slice.builders.MessagingSliceBuilder! add(androidx.core.util.Consumer<androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!>!);
- method public androidx.slice.builders.MessagingSliceBuilder! add(androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!);
- field public static final int MAXIMUM_RETAINED_MESSAGES = 50; // 0x32
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class MessagingSliceBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+ ctor @Deprecated public MessagingSliceBuilder(android.content.Context, android.net.Uri);
+ method @Deprecated public androidx.slice.builders.MessagingSliceBuilder! add(androidx.core.util.Consumer<androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!>!);
+ method @Deprecated public androidx.slice.builders.MessagingSliceBuilder! add(androidx.slice.builders.MessagingSliceBuilder.MessageBuilder!);
+ field @Deprecated public static final int MAXIMUM_RETAINED_MESSAGES = 50; // 0x32
}
- public static final class MessagingSliceBuilder.MessageBuilder extends androidx.slice.builders.TemplateSliceBuilder {
- ctor public MessagingSliceBuilder.MessageBuilder(androidx.slice.builders.MessagingSliceBuilder!);
- method @RequiresApi(23) public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(android.graphics.drawable.Icon!);
- method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(androidx.core.graphics.drawable.IconCompat!);
- method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addText(CharSequence!);
- method public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addTimestamp(long);
+ @Deprecated public static final class MessagingSliceBuilder.MessageBuilder extends androidx.slice.builders.TemplateSliceBuilder {
+ ctor @Deprecated public MessagingSliceBuilder.MessageBuilder(androidx.slice.builders.MessagingSliceBuilder!);
+ method @Deprecated @RequiresApi(23) public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(android.graphics.drawable.Icon!);
+ method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addSource(androidx.core.graphics.drawable.IconCompat!);
+ method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addText(CharSequence!);
+ method @Deprecated public androidx.slice.builders.MessagingSliceBuilder.MessageBuilder! addTimestamp(long);
}
- @RequiresApi(19) public class SelectionBuilder {
- ctor public SelectionBuilder();
- method public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
- method public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
- method public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
- method public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
- method public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
- method public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
- method public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
- method public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
- method public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
+ @Deprecated @RequiresApi(19) public class SelectionBuilder {
+ ctor @Deprecated public SelectionBuilder();
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! addOption(String!, CharSequence!);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setContentDescription(CharSequence?);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(android.app.PendingIntent);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setInputAction(androidx.remotecallback.RemoteCallback);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setLayoutDirection(int);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setPrimaryAction(androidx.slice.builders.SliceAction);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setSelectedOption(String!);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setSubtitle(CharSequence?);
+ method @Deprecated public androidx.slice.builders.SelectionBuilder! setTitle(CharSequence?);
}
- @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, boolean);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, long, boolean);
- method public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
- method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- method public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
- method public android.app.PendingIntent getAction();
- method public CharSequence? getContentDescription();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method public int getImageMode();
- method public String? getKey();
- method public int getPriority();
- method public CharSequence getTitle();
- method public boolean isActivity();
- method public boolean isChecked();
- method public boolean isDefaultToggle();
- method public boolean isToggle();
- method public androidx.slice.builders.SliceAction setChecked(boolean);
- method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
- method public androidx.slice.builders.SliceAction setKey(String);
- method public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
+ @Deprecated @RequiresApi(19) public class SliceAction implements androidx.slice.core.SliceAction {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, boolean);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceAction(android.app.PendingIntent, CharSequence, long, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! create(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! create(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createDeeplink(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, int, CharSequence);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(android.app.PendingIntent, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ method @Deprecated public static androidx.slice.builders.SliceAction! createToggle(androidx.remotecallback.RemoteCallback, CharSequence, boolean);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated public int getImageMode();
+ method @Deprecated public String? getKey();
+ method @Deprecated public int getPriority();
+ method @Deprecated public CharSequence getTitle();
+ method @Deprecated public boolean isActivity();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isDefaultToggle();
+ method @Deprecated public boolean isToggle();
+ method @Deprecated public androidx.slice.builders.SliceAction setChecked(boolean);
+ method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.builders.SliceAction setKey(String);
+ method @Deprecated public androidx.slice.builders.SliceAction setPriority(@IntRange(from=0) int);
}
- @RequiresApi(19) public abstract class TemplateSliceBuilder {
- method public androidx.slice.Slice build();
+ @Deprecated @RequiresApi(19) public abstract class TemplateSliceBuilder {
+ method @Deprecated public androidx.slice.Slice build();
}
}
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
index 4523685..dddc854 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/GridRowBuilder.java
@@ -53,8 +53,13 @@
* rest of the content, this will take up space as a cell item in a row if added.
*
* @see ListBuilder#addGridRow(GridRowBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class GridRowBuilder {
private final List<CellBuilder> mCells = new ArrayList<>();
@@ -256,7 +261,12 @@
* @see ListBuilder#ICON_IMAGE
* @see ListBuilder#SMALL_IMAGE
* @see ListBuilder#ICON_IMAGE
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class CellBuilder {
/**
*/
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
index 77d5f5b..1b27322 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/ListBuilder.java
@@ -140,8 +140,13 @@
* @see androidx.slice.SliceProvider
* @see androidx.slice.SliceProvider#onBindSlice(Uri)
* @see androidx.slice.widget.SliceView
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class ListBuilder extends TemplateSliceBuilder {
private boolean mHasSeeMore;
@@ -565,7 +570,12 @@
* A range row supports displaying a horizontal progress indicator.
*
* @see ListBuilder#addRange(RangeBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class RangeBuilder {
private int mValue;
@@ -817,8 +827,13 @@
* An star rating row supports displaying a horizontal tappable stars allowing rating input.
*
* @see ListBuilder#addRating(RatingBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@SuppressLint("MissingBuildMethod")
+ @Deprecated
public static final class RatingBuilder {
/**
*/
@@ -1085,7 +1100,12 @@
* An input range row supports displaying a horizontal slider allowing slider input.
*
* @see ListBuilder#addInputRange(InputRangeBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class InputRangeBuilder {
private int mMin = 0;
@@ -1481,7 +1501,12 @@
* </ul>
*
* @see ListBuilder#addRow(RowBuilder)
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class RowBuilder {
private final Uri mUri;
@@ -2007,7 +2032,12 @@
* @see ListBuilder#setHeader(HeaderBuilder)
* @see ListBuilder#addAction(SliceAction)
* @see SliceAction
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public static class HeaderBuilder {
private final Uri mUri;
private CharSequence mTitle;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
index 4041d50..b995566 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/MessagingSliceBuilder.java
@@ -41,6 +41,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class MessagingSliceBuilder extends TemplateSliceBuilder {
/**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
index 4e5550a..1e72a08 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/SelectionBuilder.java
@@ -38,8 +38,13 @@
*
* A selection presents a list of options to the user and allows the user to select exactly one
* option.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SelectionBuilder {
private final List<Pair<String, CharSequence>> mOptions;
private final Set<String> mOptionKeys;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java b/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
index 64263dd..bc06970 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/SliceAction.java
@@ -36,8 +36,13 @@
/**
* Class representing an action, supports tappable icons, custom toggle icons, and default
* toggles, as well as date and time pickers.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceAction implements androidx.slice.core.SliceAction {
private final SliceActionImpl mSliceAction;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
index 07accb6..f52b6d1 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/TemplateSliceBuilder.java
@@ -39,8 +39,13 @@
/**
* Base class of builders of various template types.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public abstract class TemplateSliceBuilder {
private static final String TAG = "TemplateSliceBuilder";
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
index ec399bc..f7baa96 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/GridRowBuilderListV1Impl.java
@@ -45,6 +45,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class GridRowBuilderListV1Impl extends TemplateBuilderImpl {
private SliceAction mPrimaryAction;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
index 8718ac9..d9f47d6 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilder.java
@@ -42,6 +42,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public interface ListBuilder {
/**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
index faf5588..5848e3b 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderBasicImpl.java
@@ -60,6 +60,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class ListBuilderBasicImpl extends TemplateBuilderImpl implements ListBuilder {
boolean mIsError;
private Set<String> mKeywords;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
index 111bd0b..684f094 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/ListBuilderImpl.java
@@ -85,6 +85,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class ListBuilderImpl extends TemplateBuilderImpl implements ListBuilder {
private List<Slice> mSliceActions;
private Set<String> mKeywords;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
index 239e6fb..834b44a 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBasicImpl.java
@@ -37,6 +37,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class MessagingBasicImpl extends TemplateBuilderImpl implements
MessagingBuilder {
private MessageBuilder mLastMessage;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
index 03f48ce..3741273 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingBuilder.java
@@ -27,6 +27,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public interface MessagingBuilder {
/**
* Add a subslice to this builder.
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
index 8dacf8f..90a82f7 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingListV1Impl.java
@@ -33,6 +33,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class MessagingListV1Impl extends TemplateBuilderImpl implements MessagingBuilder{
private final ListBuilderImpl mListBuilder;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
index d913d79..0adf579 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/MessagingV1Impl.java
@@ -31,6 +31,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class MessagingV1Impl extends TemplateBuilderImpl implements MessagingBuilder {
/**
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
index 1c341f1..9dc3fca 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderBasicImpl.java
@@ -33,6 +33,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SelectionBuilderBasicImpl extends SelectionBuilderImpl {
public SelectionBuilderBasicImpl(Slice.Builder sliceBuilder,
SelectionBuilder selectionBuilder) {
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
index f25d0a6..0647caf 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderImpl.java
@@ -28,6 +28,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public abstract class SelectionBuilderImpl extends TemplateBuilderImpl {
private final SelectionBuilder mSelectionBuilder;
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
index 0c25ad0..1d2d673 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/SelectionBuilderListV2Impl.java
@@ -41,6 +41,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SelectionBuilderListV2Impl extends SelectionBuilderImpl {
public SelectionBuilderListV2Impl(Slice.Builder parentSliceBuilder,
SelectionBuilder selectionBuilder) {
diff --git a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
index 8cb26f66..a1be533 100644
--- a/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
+++ b/slice/slice-builders/src/main/java/androidx/slice/builders/impl/TemplateBuilderImpl.java
@@ -43,6 +43,7 @@
*/
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public abstract class TemplateBuilderImpl {
private Slice.Builder mSliceBuilder;
diff --git a/slice/slice-core/api/current.txt b/slice/slice-core/api/current.txt
index 6a6b471..bc4b051 100644
--- a/slice/slice-core/api/current.txt
+++ b/slice/slice-core/api/current.txt
@@ -1,87 +1,87 @@
// Signature format: 4.0
package androidx.slice {
- @RequiresApi(19) public final class Slice implements androidx.versionedparcelable.VersionedParcelable {
- method public java.util.List<java.lang.String!> getHints();
- method public java.util.List<androidx.slice.SliceItem!> getItems();
- method public android.net.Uri getUri();
- field public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
+ @Deprecated @RequiresApi(19) public final class Slice implements androidx.versionedparcelable.VersionedParcelable {
+ method @Deprecated public java.util.List<java.lang.String!> getHints();
+ method @Deprecated public java.util.List<androidx.slice.SliceItem!> getItems();
+ method @Deprecated public android.net.Uri getUri();
+ field @Deprecated public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
}
- @RequiresApi(28) public class SliceConvert {
- method public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
- method public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
+ @Deprecated @RequiresApi(28) public class SliceConvert {
+ method @Deprecated public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
+ method @Deprecated public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
}
- @RequiresApi(19) public final class SliceItem implements androidx.versionedparcelable.VersionedParcelable {
- method public static android.text.ParcelableSpan createSensitiveSpan();
- method public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
- method public android.app.PendingIntent? getAction();
- method public String getFormat();
- method public java.util.List<java.lang.String!> getHints();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method public int getInt();
- method public long getLong();
- method public CharSequence? getRedactedText();
- method public androidx.slice.Slice? getSlice();
- method public String? getSubType();
- method public CharSequence? getText();
- method public boolean hasHint(String);
- method public void onPostParceling();
- method public void onPreParceling(boolean);
+ @Deprecated @RequiresApi(19) public final class SliceItem implements androidx.versionedparcelable.VersionedParcelable {
+ method @Deprecated public static android.text.ParcelableSpan createSensitiveSpan();
+ method @Deprecated public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+ method @Deprecated public android.app.PendingIntent? getAction();
+ method @Deprecated public String getFormat();
+ method @Deprecated public java.util.List<java.lang.String!> getHints();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated public int getInt();
+ method @Deprecated public long getLong();
+ method @Deprecated public CharSequence? getRedactedText();
+ method @Deprecated public androidx.slice.Slice? getSlice();
+ method @Deprecated public String? getSubType();
+ method @Deprecated public CharSequence? getText();
+ method @Deprecated public boolean hasHint(String);
+ method @Deprecated public void onPostParceling();
+ method @Deprecated public void onPreParceling(boolean);
}
- @RequiresApi(19) public abstract class SliceManager {
- method public abstract int checkSlicePermission(android.net.Uri, int, int);
- method public static androidx.slice.SliceManager getInstance(android.content.Context);
- method public abstract java.util.List<android.net.Uri!> getPinnedSlices();
- method public abstract void grantSlicePermission(String, android.net.Uri);
- method public abstract void revokeSlicePermission(String, android.net.Uri);
+ @Deprecated @RequiresApi(19) public abstract class SliceManager {
+ method @Deprecated public abstract int checkSlicePermission(android.net.Uri, int, int);
+ method @Deprecated public static androidx.slice.SliceManager getInstance(android.content.Context);
+ method @Deprecated public abstract java.util.List<android.net.Uri!> getPinnedSlices();
+ method @Deprecated public abstract void grantSlicePermission(String, android.net.Uri);
+ method @Deprecated public abstract void revokeSlicePermission(String, android.net.Uri);
}
- public abstract class SliceProvider extends android.content.ContentProvider {
- ctor public SliceProvider();
- ctor public SliceProvider(java.lang.String!...);
- method public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
- method @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
- method public final int delete(android.net.Uri, String?, String![]?);
- method @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
- method public final String? getType(android.net.Uri);
- 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);
- method @RequiresApi(19) public void onSlicePinned(android.net.Uri);
- method @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
- method @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
- method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
- method @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
- method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+ @Deprecated public abstract class SliceProvider extends android.content.ContentProvider {
+ ctor @Deprecated public SliceProvider();
+ ctor @Deprecated public SliceProvider(java.lang.String!...);
+ method @Deprecated public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
+ method @Deprecated @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
+ method @Deprecated public final int delete(android.net.Uri, String?, String![]?);
+ method @Deprecated @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
+ method @Deprecated public final String? getType(android.net.Uri);
+ method @Deprecated public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+ method @Deprecated @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
+ method @Deprecated public final boolean onCreate();
+ method @Deprecated public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
+ method @Deprecated @RequiresApi(19) public abstract boolean onCreateSliceProvider();
+ method @Deprecated @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
+ method @Deprecated @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
+ method @Deprecated @RequiresApi(19) public void onSlicePinned(android.net.Uri);
+ method @Deprecated @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
+ method @Deprecated @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
+ method @Deprecated public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+ method @Deprecated @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+ method @Deprecated public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
}
}
package androidx.slice.core {
- @RequiresApi(19) public interface SliceAction {
- method public android.app.PendingIntent getAction();
- method public CharSequence? getContentDescription();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method public int getImageMode();
- method public String? getKey();
- method public int getPriority();
- method public CharSequence getTitle();
- method public boolean isActivity();
- method public boolean isChecked();
- method public boolean isDefaultToggle();
- method public boolean isToggle();
- method public androidx.slice.core.SliceAction setChecked(boolean);
- method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
- method public androidx.slice.core.SliceAction setKey(String);
- method public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
+ @Deprecated @RequiresApi(19) public interface SliceAction {
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated public int getImageMode();
+ method @Deprecated public String? getKey();
+ method @Deprecated public int getPriority();
+ method @Deprecated public CharSequence getTitle();
+ method @Deprecated public boolean isActivity();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isDefaultToggle();
+ method @Deprecated public boolean isToggle();
+ method @Deprecated public androidx.slice.core.SliceAction setChecked(boolean);
+ method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.core.SliceAction setKey(String);
+ method @Deprecated public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
}
}
diff --git a/slice/slice-core/api/restricted_current.txt b/slice/slice-core/api/restricted_current.txt
index 332afef..b000782 100644
--- a/slice/slice-core/api/restricted_current.txt
+++ b/slice/slice-core/api/restricted_current.txt
@@ -1,307 +1,307 @@
// Signature format: 4.0
package androidx.slice {
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface Clock {
- method public long currentTimeMillis();
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface Clock {
+ method @Deprecated public long currentTimeMillis();
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CornerDrawable extends android.graphics.drawable.InsetDrawable {
- ctor public CornerDrawable(android.graphics.drawable.Drawable?, float);
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CornerDrawable extends android.graphics.drawable.InsetDrawable {
+ ctor @Deprecated public CornerDrawable(android.graphics.drawable.Drawable?, float);
}
- @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, isCustom=true) public final class Slice extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.versionedparcelable.VersionedParcelable {
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>?);
- method public java.util.List<java.lang.String!> getHints();
- method public java.util.List<androidx.slice.SliceItem!> getItems();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceSpec? getSpec();
- method public android.net.Uri getUri();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasHint(String);
- field public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String SUBTYPE_RANGE_MODE = "range_mode";
+ @Deprecated @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, isCustom=true) public final class Slice extends androidx.versionedparcelable.CustomVersionedParcelable implements androidx.versionedparcelable.VersionedParcelable {
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>?);
+ method @Deprecated public java.util.List<java.lang.String!> getHints();
+ method @Deprecated public java.util.List<androidx.slice.SliceItem!> getItems();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceSpec? getSpec();
+ method @Deprecated public android.net.Uri getUri();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasHint(String);
+ field @Deprecated public static final String EXTRA_SELECTION = "android.app.slice.extra.SELECTION";
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String SUBTYPE_RANGE_MODE = "range_mode";
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class Slice.Builder {
- ctor public Slice.Builder(android.net.Uri);
- ctor public Slice.Builder(androidx.slice.Slice.Builder);
- method public androidx.slice.Slice.Builder addAction(android.app.PendingIntent, androidx.slice.Slice, String?);
- method public androidx.slice.Slice.Builder addAction(androidx.slice.Slice, String?, androidx.slice.SliceItem.ActionHandler);
- method public androidx.slice.Slice.Builder addHints(java.lang.String!...);
- method public androidx.slice.Slice.Builder addHints(java.util.List<java.lang.String!>);
- method public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.lang.String!...);
- method public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.util.List<java.lang.String!>);
- method public androidx.slice.Slice.Builder addInt(int, String?, java.lang.String!...);
- method public androidx.slice.Slice.Builder addInt(int, String?, java.util.List<java.lang.String!>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.slice.Slice.Builder addItem(androidx.slice.SliceItem);
- method public androidx.slice.Slice.Builder addLong(long, String?, java.lang.String!...);
- method public androidx.slice.Slice.Builder addLong(long, String?, java.util.List<java.lang.String!>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.lang.String!...);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.util.List<java.lang.String!>);
- method public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice);
- method public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice, String?);
- method public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.lang.String!...);
- method public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.util.List<java.lang.String!>);
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class Slice.Builder {
+ ctor @Deprecated public Slice.Builder(android.net.Uri);
+ ctor @Deprecated public Slice.Builder(androidx.slice.Slice.Builder);
+ method @Deprecated public androidx.slice.Slice.Builder addAction(android.app.PendingIntent, androidx.slice.Slice, String?);
+ method @Deprecated public androidx.slice.Slice.Builder addAction(androidx.slice.Slice, String?, androidx.slice.SliceItem.ActionHandler);
+ method @Deprecated public androidx.slice.Slice.Builder addHints(java.lang.String!...);
+ method @Deprecated public androidx.slice.Slice.Builder addHints(java.util.List<java.lang.String!>);
+ method @Deprecated public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.lang.String!...);
+ method @Deprecated public androidx.slice.Slice.Builder addIcon(androidx.core.graphics.drawable.IconCompat, String?, java.util.List<java.lang.String!>);
+ method @Deprecated public androidx.slice.Slice.Builder addInt(int, String?, java.lang.String!...);
+ method @Deprecated public androidx.slice.Slice.Builder addInt(int, String?, java.util.List<java.lang.String!>);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public androidx.slice.Slice.Builder addItem(androidx.slice.SliceItem);
+ method @Deprecated public androidx.slice.Slice.Builder addLong(long, String?, java.lang.String!...);
+ method @Deprecated public androidx.slice.Slice.Builder addLong(long, String?, java.util.List<java.lang.String!>);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.lang.String!...);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, String?, java.util.List<java.lang.String!>);
+ method @Deprecated public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice);
+ method @Deprecated public androidx.slice.Slice.Builder addSubSlice(androidx.slice.Slice, String?);
+ method @Deprecated public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.lang.String!...);
+ method @Deprecated public androidx.slice.Slice.Builder addText(CharSequence?, String?, java.util.List<java.lang.String!>);
method @Deprecated public androidx.slice.Slice.Builder! addTimestamp(long, String?, java.lang.String!...);
- method public androidx.slice.Slice.Builder addTimestamp(long, String?, java.util.List<java.lang.String!>);
- method public androidx.slice.Slice build();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder setSpec(androidx.slice.SliceSpec?);
+ method @Deprecated public androidx.slice.Slice.Builder addTimestamp(long, String?, java.util.List<java.lang.String!>);
+ method @Deprecated public androidx.slice.Slice build();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.Slice.Builder setSpec(androidx.slice.SliceSpec?);
}
- @RequiresApi(28) public class SliceConvert {
- method public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
- method public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
+ @Deprecated @RequiresApi(28) public class SliceConvert {
+ method @Deprecated public static android.app.slice.Slice? unwrap(androidx.slice.Slice?);
+ method @Deprecated public static androidx.slice.Slice? wrap(android.app.slice.Slice?, android.content.Context);
}
- @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, ignoreParcelables=true, isCustom=true) public final class SliceItem extends androidx.versionedparcelable.CustomVersionedParcelable {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem();
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(android.app.PendingIntent, androidx.slice.Slice?, String, String?, String![]);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(androidx.slice.SliceItem.ActionHandler, androidx.slice.Slice?, String, String?, String![]);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, String![]);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, java.util.List<java.lang.String!>);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void addHint(String);
- method public static android.text.ParcelableSpan createSensitiveSpan();
- method public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean fireActionInternal(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
- method public android.app.PendingIntent? getAction();
- method public String getFormat();
- method public java.util.List<java.lang.String!> getHints();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method public int getInt();
- method public long getLong();
- method public CharSequence? getRedactedText();
- method @RequiresApi(20) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.RemoteInput? getRemoteInput();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getSanitizedText();
- method public androidx.slice.Slice? getSlice();
- method public String? getSubType();
- method public CharSequence? getText();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public boolean hasAnyHints(java.lang.String!...);
- method public boolean hasHint(String);
+ @Deprecated @RequiresApi(19) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true, ignoreParcelables=true, isCustom=true) public final class SliceItem extends androidx.versionedparcelable.CustomVersionedParcelable {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem();
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(android.app.PendingIntent, androidx.slice.Slice?, String, String?, String![]);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(androidx.slice.SliceItem.ActionHandler, androidx.slice.Slice?, String, String?, String![]);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, String![]);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceItem(Object!, String, String?, java.util.List<java.lang.String!>);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public void addHint(String);
+ method @Deprecated public static android.text.ParcelableSpan createSensitiveSpan();
+ method @Deprecated public void fireAction(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean fireActionInternal(android.content.Context?, android.content.Intent?) throws android.app.PendingIntent.CanceledException;
+ method @Deprecated public android.app.PendingIntent? getAction();
+ method @Deprecated public String getFormat();
+ method @Deprecated public java.util.List<java.lang.String!> getHints();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated public int getInt();
+ method @Deprecated public long getLong();
+ method @Deprecated public CharSequence? getRedactedText();
+ method @Deprecated @RequiresApi(20) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public android.app.RemoteInput? getRemoteInput();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getSanitizedText();
+ method @Deprecated public androidx.slice.Slice? getSlice();
+ method @Deprecated public String? getSubType();
+ method @Deprecated public CharSequence? getText();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public boolean hasAnyHints(java.lang.String!...);
+ method @Deprecated public boolean hasHint(String);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SliceItem.ActionHandler {
- method public void onAction(androidx.slice.SliceItem, android.content.Context?, android.content.Intent?);
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static interface SliceItem.ActionHandler {
+ method @Deprecated public void onAction(androidx.slice.SliceItem, android.content.Context?, android.content.Intent?);
}
- @RequiresApi(19) public abstract class SliceManager {
- method @androidx.core.content.PermissionChecker.PermissionResult public abstract int checkSlicePermission(android.net.Uri, int, int);
- method public static androidx.slice.SliceManager getInstance(android.content.Context);
- method public abstract java.util.List<android.net.Uri!> getPinnedSlices();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract java.util.Set<androidx.slice.SliceSpec!> getPinnedSpecs(android.net.Uri);
- method public abstract void grantSlicePermission(String, android.net.Uri);
- method public abstract void revokeSlicePermission(String, android.net.Uri);
+ @Deprecated @RequiresApi(19) public abstract class SliceManager {
+ method @Deprecated @androidx.core.content.PermissionChecker.PermissionResult public abstract int checkSlicePermission(android.net.Uri, int, int);
+ method @Deprecated public static androidx.slice.SliceManager getInstance(android.content.Context);
+ method @Deprecated public abstract java.util.List<android.net.Uri!> getPinnedSlices();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public abstract java.util.Set<androidx.slice.SliceSpec!> getPinnedSpecs(android.net.Uri);
+ method @Deprecated public abstract void grantSlicePermission(String, android.net.Uri);
+ method @Deprecated public abstract void revokeSlicePermission(String, android.net.Uri);
}
- public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
- ctor public SliceProvider();
- ctor public SliceProvider(java.lang.String!...);
- method public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
- method @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
- method public final int delete(android.net.Uri, String?, String![]?);
- method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static androidx.slice.Clock? getClock();
- method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Set<androidx.slice.SliceSpec!>? getCurrentSpecs();
- method @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
- method public final String? getType(android.net.Uri);
+ @Deprecated public abstract class SliceProvider extends android.content.ContentProvider implements androidx.core.app.CoreComponentFactory.CompatWrapped {
+ ctor @Deprecated public SliceProvider();
+ ctor @Deprecated public SliceProvider(java.lang.String!...);
+ method @Deprecated public final int bulkInsert(android.net.Uri, android.content.ContentValues![]);
+ method @Deprecated @RequiresApi(19) public final android.net.Uri? canonicalize(android.net.Uri);
+ method @Deprecated public final int delete(android.net.Uri, String?, String![]?);
+ method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static androidx.slice.Clock? getClock();
+ method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Set<androidx.slice.SliceSpec!>? getCurrentSpecs();
+ method @Deprecated @RequiresApi(19) public java.util.List<android.net.Uri!> getPinnedSlices();
+ method @Deprecated public final String? getType(android.net.Uri);
method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public Object? getWrapper();
- 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);
- method @RequiresApi(19) public void onSlicePinned(android.net.Uri);
- method @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
- method @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
- method public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
- method @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
- method public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+ method @Deprecated public final android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+ method @Deprecated @RequiresApi(19) public abstract androidx.slice.Slice? onBindSlice(android.net.Uri);
+ method @Deprecated public final boolean onCreate();
+ method @Deprecated public android.app.PendingIntent? onCreatePermissionRequest(android.net.Uri, String);
+ method @Deprecated @RequiresApi(19) public abstract boolean onCreateSliceProvider();
+ method @Deprecated @RequiresApi(19) public java.util.Collection<android.net.Uri!> onGetSliceDescendants(android.net.Uri);
+ method @Deprecated @RequiresApi(19) public android.net.Uri onMapIntentToUri(android.content.Intent);
+ method @Deprecated @RequiresApi(19) public void onSlicePinned(android.net.Uri);
+ method @Deprecated @RequiresApi(19) public void onSliceUnpinned(android.net.Uri);
+ method @Deprecated @RequiresApi(28) public final android.database.Cursor? query(android.net.Uri, String![]?, android.os.Bundle?, android.os.CancellationSignal?);
+ method @Deprecated public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+ method @Deprecated @RequiresApi(16) public final android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?, android.os.CancellationSignal?);
+ method @Deprecated public final int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
- ctor public SliceSpec(String, int);
- method public boolean canRender(androidx.slice.SliceSpec);
- method public int getRevision();
- method public String getType();
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize(allowSerialization=true) public final class SliceSpec implements androidx.versionedparcelable.VersionedParcelable {
+ ctor @Deprecated public SliceSpec(String, int);
+ method @Deprecated public boolean canRender(androidx.slice.SliceSpec);
+ method @Deprecated public int getRevision();
+ method @Deprecated public String getType();
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceSpecs {
- field public static final androidx.slice.SliceSpec! BASIC;
- field public static final androidx.slice.SliceSpec! LIST;
- field public static final androidx.slice.SliceSpec! LIST_V2;
- field public static final androidx.slice.SliceSpec! MESSAGING;
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceSpecs {
+ field @Deprecated public static final androidx.slice.SliceSpec! BASIC;
+ field @Deprecated public static final androidx.slice.SliceSpec! LIST;
+ field @Deprecated public static final androidx.slice.SliceSpec! LIST_V2;
+ field @Deprecated public static final androidx.slice.SliceSpec! MESSAGING;
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SystemClock implements androidx.slice.Clock {
- ctor public SystemClock();
- method public long currentTimeMillis();
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SystemClock implements androidx.slice.Clock {
+ ctor @Deprecated public SystemClock();
+ method @Deprecated public long currentTimeMillis();
}
}
package androidx.slice.compat {
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CompatPermissionManager {
- ctor public CompatPermissionManager(android.content.Context, String, int, String![]);
- method public int checkSlicePermission(android.net.Uri, int, int);
- method public void grantSlicePermission(android.net.Uri, String);
- method public void revokeSlicePermission(android.net.Uri, String);
- field public static final String ALL_SUFFIX = "_all";
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class CompatPermissionManager {
+ ctor @Deprecated public CompatPermissionManager(android.content.Context, String, int, String![]);
+ method @Deprecated public int checkSlicePermission(android.net.Uri, int, int);
+ method @Deprecated public void grantSlicePermission(android.net.Uri, String);
+ method @Deprecated public void revokeSlicePermission(android.net.Uri, String);
+ field @Deprecated public static final String ALL_SUFFIX = "_all";
}
- public static class CompatPermissionManager.PermissionState {
- method public String getKey();
- method public boolean hasAccess(java.util.List<java.lang.String!>);
- method public boolean hasAllPermissions();
- method public java.util.Set<java.lang.String!> toPersistable();
+ @Deprecated public static class CompatPermissionManager.PermissionState {
+ method @Deprecated public String getKey();
+ method @Deprecated public boolean hasAccess(java.util.List<java.lang.String!>);
+ method @Deprecated public boolean hasAllPermissions();
+ method @Deprecated public java.util.Set<java.lang.String!> toPersistable();
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class SliceProviderCompat {
- ctor public SliceProviderCompat(androidx.slice.SliceProvider, androidx.slice.compat.CompatPermissionManager, android.content.Context);
- method public static void addSpecs(android.os.Bundle, java.util.Set<androidx.slice.SliceSpec!>);
- method public static androidx.slice.Slice? bindSlice(android.content.Context, android.content.Intent, java.util.Set<androidx.slice.SliceSpec!>);
- method public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
- method public android.os.Bundle? call(String, String?, android.os.Bundle);
- method public static int checkSlicePermission(android.content.Context, String?, android.net.Uri, int, int);
- method public String? getCallingPackage();
- method public static java.util.List<android.net.Uri!> getPinnedSlices(android.content.Context);
- method public static java.util.Set<androidx.slice.SliceSpec!>? getPinnedSpecs(android.content.Context, android.net.Uri);
- method public static java.util.Collection<android.net.Uri!> getSliceDescendants(android.content.Context, android.net.Uri);
- method public static java.util.Set<androidx.slice.SliceSpec!> getSpecs(android.os.Bundle);
- method public static void grantSlicePermission(android.content.Context, String?, String?, android.net.Uri);
- method public static android.net.Uri? mapIntentToUri(android.content.Context, android.content.Intent);
- method public static void pinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
- method public static void revokeSlicePermission(android.content.Context, String?, String?, android.net.Uri);
- method public static void unpinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
- field public static final String ARG_SUPPORTS_VERSIONED_PARCELABLE = "supports_versioned_parcelable";
- field public static final String EXTRA_BIND_URI = "slice_uri";
- field public static final String EXTRA_INTENT = "slice_intent";
- field public static final String EXTRA_PID = "pid";
- field public static final String EXTRA_PKG = "pkg";
- field public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
- field public static final String EXTRA_RESULT = "result";
- field public static final String EXTRA_SLICE = "slice";
- field public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
- field public static final String EXTRA_SUPPORTED_SPECS = "specs";
- field public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
- field public static final String EXTRA_UID = "uid";
- field public static final String METHOD_CHECK_PERMISSION = "check_perms";
- field public static final String METHOD_GET_DESCENDANTS = "get_descendants";
- field public static final String METHOD_GET_PINNED_SPECS = "get_specs";
- field public static final String METHOD_GRANT_PERMISSION = "grant_perms";
- field public static final String METHOD_MAP_INTENT = "map_slice";
- field public static final String METHOD_MAP_ONLY_INTENT = "map_only";
- field public static final String METHOD_PIN = "pin_slice";
- field public static final String METHOD_REVOKE_PERMISSION = "revoke_perms";
- field public static final String METHOD_SLICE = "bind_slice";
- field public static final String METHOD_UNPIN = "unpin_slice";
- field public static final String PERMS_PREFIX = "slice_perms_";
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class SliceProviderCompat {
+ ctor @Deprecated public SliceProviderCompat(androidx.slice.SliceProvider, androidx.slice.compat.CompatPermissionManager, android.content.Context);
+ method @Deprecated public static void addSpecs(android.os.Bundle, java.util.Set<androidx.slice.SliceSpec!>);
+ method @Deprecated public static androidx.slice.Slice? bindSlice(android.content.Context, android.content.Intent, java.util.Set<androidx.slice.SliceSpec!>);
+ method @Deprecated public static androidx.slice.Slice? bindSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+ method @Deprecated public android.os.Bundle? call(String, String?, android.os.Bundle);
+ method @Deprecated public static int checkSlicePermission(android.content.Context, String?, android.net.Uri, int, int);
+ method @Deprecated public String? getCallingPackage();
+ method @Deprecated public static java.util.List<android.net.Uri!> getPinnedSlices(android.content.Context);
+ method @Deprecated public static java.util.Set<androidx.slice.SliceSpec!>? getPinnedSpecs(android.content.Context, android.net.Uri);
+ method @Deprecated public static java.util.Collection<android.net.Uri!> getSliceDescendants(android.content.Context, android.net.Uri);
+ method @Deprecated public static java.util.Set<androidx.slice.SliceSpec!> getSpecs(android.os.Bundle);
+ method @Deprecated public static void grantSlicePermission(android.content.Context, String?, String?, android.net.Uri);
+ method @Deprecated public static android.net.Uri? mapIntentToUri(android.content.Context, android.content.Intent);
+ method @Deprecated public static void pinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+ method @Deprecated public static void revokeSlicePermission(android.content.Context, String?, String?, android.net.Uri);
+ method @Deprecated public static void unpinSlice(android.content.Context, android.net.Uri, java.util.Set<androidx.slice.SliceSpec!>);
+ field @Deprecated public static final String ARG_SUPPORTS_VERSIONED_PARCELABLE = "supports_versioned_parcelable";
+ field @Deprecated public static final String EXTRA_BIND_URI = "slice_uri";
+ field @Deprecated public static final String EXTRA_INTENT = "slice_intent";
+ field @Deprecated public static final String EXTRA_PID = "pid";
+ field @Deprecated public static final String EXTRA_PKG = "pkg";
+ field @Deprecated public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
+ field @Deprecated public static final String EXTRA_RESULT = "result";
+ field @Deprecated public static final String EXTRA_SLICE = "slice";
+ field @Deprecated public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
+ field @Deprecated public static final String EXTRA_SUPPORTED_SPECS = "specs";
+ field @Deprecated public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs";
+ field @Deprecated public static final String EXTRA_UID = "uid";
+ field @Deprecated public static final String METHOD_CHECK_PERMISSION = "check_perms";
+ field @Deprecated public static final String METHOD_GET_DESCENDANTS = "get_descendants";
+ field @Deprecated public static final String METHOD_GET_PINNED_SPECS = "get_specs";
+ field @Deprecated public static final String METHOD_GRANT_PERMISSION = "grant_perms";
+ field @Deprecated public static final String METHOD_MAP_INTENT = "map_slice";
+ field @Deprecated public static final String METHOD_MAP_ONLY_INTENT = "map_only";
+ field @Deprecated public static final String METHOD_PIN = "pin_slice";
+ field @Deprecated public static final String METHOD_REVOKE_PERMISSION = "revoke_perms";
+ field @Deprecated public static final String METHOD_SLICE = "bind_slice";
+ field @Deprecated public static final String METHOD_UNPIN = "unpin_slice";
+ field @Deprecated public static final String PERMS_PREFIX = "slice_perms_";
}
}
package androidx.slice.core {
- @RequiresApi(19) public interface SliceAction {
- method public android.app.PendingIntent getAction();
- method public CharSequence? getContentDescription();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
- method public String? getKey();
- method public int getPriority();
- method public CharSequence getTitle();
- method public boolean isActivity();
- method public boolean isChecked();
- method public boolean isDefaultToggle();
- method public boolean isToggle();
- method public androidx.slice.core.SliceAction setChecked(boolean);
- method public androidx.slice.core.SliceAction setContentDescription(CharSequence);
- method public androidx.slice.core.SliceAction setKey(String);
- method public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
+ @Deprecated @RequiresApi(19) public interface SliceAction {
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
+ method @Deprecated public String? getKey();
+ method @Deprecated public int getPriority();
+ method @Deprecated public CharSequence getTitle();
+ method @Deprecated public boolean isActivity();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isDefaultToggle();
+ method @Deprecated public boolean isToggle();
+ method @Deprecated public androidx.slice.core.SliceAction setChecked(boolean);
+ method @Deprecated public androidx.slice.core.SliceAction setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.core.SliceAction setKey(String);
+ method @Deprecated public androidx.slice.core.SliceAction setPriority(@IntRange(from=0) int);
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceActionImpl implements androidx.slice.core.SliceAction {
- ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, @androidx.slice.core.SliceHints.ImageMode int, CharSequence);
- ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence);
- ctor public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
- ctor public SliceActionImpl(android.app.PendingIntent, CharSequence, boolean);
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceActionImpl(androidx.slice.SliceItem);
- method public androidx.slice.Slice buildPrimaryActionSlice(androidx.slice.Slice.Builder);
- method public androidx.slice.Slice buildSlice(androidx.slice.Slice.Builder);
- method public android.app.PendingIntent getAction();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getActionItem();
- method public CharSequence? getContentDescription();
- method public androidx.core.graphics.drawable.IconCompat? getIcon();
- method @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
- method public String? getKey();
- method public int getPriority();
- method public androidx.slice.SliceItem? getSliceItem();
- method public String? getSubtype();
- method public CharSequence getTitle();
- method public boolean isActivity();
- method public boolean isChecked();
- method public boolean isDefaultToggle();
- method public boolean isToggle();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int parseImageMode(androidx.slice.SliceItem);
- method public void setActivity(boolean);
- method public androidx.slice.core.SliceActionImpl setChecked(boolean);
- method public androidx.slice.core.SliceAction? setContentDescription(CharSequence);
- method public androidx.slice.core.SliceActionImpl setKey(String);
- method public androidx.slice.core.SliceActionImpl setPriority(@IntRange(from=0) int);
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceActionImpl implements androidx.slice.core.SliceAction {
+ ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, @androidx.slice.core.SliceHints.ImageMode int, CharSequence);
+ ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence);
+ ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, androidx.core.graphics.drawable.IconCompat, CharSequence, boolean);
+ ctor @Deprecated public SliceActionImpl(android.app.PendingIntent, CharSequence, boolean);
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public SliceActionImpl(androidx.slice.SliceItem);
+ method @Deprecated public androidx.slice.Slice buildPrimaryActionSlice(androidx.slice.Slice.Builder);
+ method @Deprecated public androidx.slice.Slice buildSlice(androidx.slice.Slice.Builder);
+ method @Deprecated public android.app.PendingIntent getAction();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getActionItem();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getIcon();
+ method @Deprecated @androidx.slice.core.SliceHints.ImageMode public int getImageMode();
+ method @Deprecated public String? getKey();
+ method @Deprecated public int getPriority();
+ method @Deprecated public androidx.slice.SliceItem? getSliceItem();
+ method @Deprecated public String? getSubtype();
+ method @Deprecated public CharSequence getTitle();
+ method @Deprecated public boolean isActivity();
+ method @Deprecated public boolean isChecked();
+ method @Deprecated public boolean isDefaultToggle();
+ method @Deprecated public boolean isToggle();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static int parseImageMode(androidx.slice.SliceItem);
+ method @Deprecated public void setActivity(boolean);
+ method @Deprecated public androidx.slice.core.SliceActionImpl setChecked(boolean);
+ method @Deprecated public androidx.slice.core.SliceAction? setContentDescription(CharSequence);
+ method @Deprecated public androidx.slice.core.SliceActionImpl setKey(String);
+ method @Deprecated public androidx.slice.core.SliceActionImpl setPriority(@IntRange(from=0) int);
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceHints {
- field public static final int ACTION_WITH_LABEL = 6; // 0x6
- field public static final int DETERMINATE_RANGE = 0; // 0x0
- field public static final String HINT_ACTIVITY = "activity";
- field public static final String HINT_CACHED = "cached";
- field public static final String HINT_END_OF_SECTION = "end_of_section";
- field public static final String HINT_OVERLAY = "overlay";
- field public static final String HINT_RAW = "raw";
- field public static final String HINT_SELECTION_OPTION = "selection_option";
- field public static final String HINT_SHOW_LABEL = "show_label";
- field public static final int ICON_IMAGE = 0; // 0x0
- field public static final int INDETERMINATE_RANGE = 1; // 0x1
- field public static final long INFINITY = -1L; // 0xffffffffffffffffL
- field public static final int LARGE_IMAGE = 2; // 0x2
- field public static final int RAW_IMAGE_LARGE = 4; // 0x4
- field public static final int RAW_IMAGE_SMALL = 3; // 0x3
- field public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
- field public static final int SMALL_IMAGE = 1; // 0x1
- field public static final int STAR_RATING = 2; // 0x2
- field public static final String SUBTYPE_ACTION_KEY = "action_key";
- field public static final String SUBTYPE_DATE_PICKER = "date_picker";
- field public static final String SUBTYPE_HOST_EXTRAS = "host_extras";
- field public static final String SUBTYPE_MILLIS = "millis";
- field public static final String SUBTYPE_MIN = "min";
- field public static final String SUBTYPE_SELECTION = "selection";
- field public static final String SUBTYPE_SELECTION_OPTION_KEY = "selection_option_key";
- field public static final String SUBTYPE_SELECTION_OPTION_VALUE = "selection_option_value";
- field public static final String SUBTYPE_TIME_PICKER = "time_picker";
- field public static final int UNKNOWN_IMAGE = 5; // 0x5
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceHints {
+ field @Deprecated public static final int ACTION_WITH_LABEL = 6; // 0x6
+ field @Deprecated public static final int DETERMINATE_RANGE = 0; // 0x0
+ field @Deprecated public static final String HINT_ACTIVITY = "activity";
+ field @Deprecated public static final String HINT_CACHED = "cached";
+ field @Deprecated public static final String HINT_END_OF_SECTION = "end_of_section";
+ field @Deprecated public static final String HINT_OVERLAY = "overlay";
+ field @Deprecated public static final String HINT_RAW = "raw";
+ field @Deprecated public static final String HINT_SELECTION_OPTION = "selection_option";
+ field @Deprecated public static final String HINT_SHOW_LABEL = "show_label";
+ field @Deprecated public static final int ICON_IMAGE = 0; // 0x0
+ field @Deprecated public static final int INDETERMINATE_RANGE = 1; // 0x1
+ field @Deprecated public static final long INFINITY = -1L; // 0xffffffffffffffffL
+ field @Deprecated public static final int LARGE_IMAGE = 2; // 0x2
+ field @Deprecated public static final int RAW_IMAGE_LARGE = 4; // 0x4
+ field @Deprecated public static final int RAW_IMAGE_SMALL = 3; // 0x3
+ field @Deprecated public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
+ field @Deprecated public static final int SMALL_IMAGE = 1; // 0x1
+ field @Deprecated public static final int STAR_RATING = 2; // 0x2
+ field @Deprecated public static final String SUBTYPE_ACTION_KEY = "action_key";
+ field @Deprecated public static final String SUBTYPE_DATE_PICKER = "date_picker";
+ field @Deprecated public static final String SUBTYPE_HOST_EXTRAS = "host_extras";
+ field @Deprecated public static final String SUBTYPE_MILLIS = "millis";
+ field @Deprecated public static final String SUBTYPE_MIN = "min";
+ field @Deprecated public static final String SUBTYPE_SELECTION = "selection";
+ field @Deprecated public static final String SUBTYPE_SELECTION_OPTION_KEY = "selection_option_key";
+ field @Deprecated public static final String SUBTYPE_SELECTION_OPTION_VALUE = "selection_option_value";
+ field @Deprecated public static final String SUBTYPE_TIME_PICKER = "time_picker";
+ field @Deprecated public static final int UNKNOWN_IMAGE = 5; // 0x5
}
- @IntDef({androidx.slice.core.SliceHints.LARGE_IMAGE, androidx.slice.core.SliceHints.SMALL_IMAGE, androidx.slice.core.SliceHints.ICON_IMAGE, androidx.slice.core.SliceHints.RAW_IMAGE_SMALL, androidx.slice.core.SliceHints.RAW_IMAGE_LARGE, androidx.slice.core.SliceHints.UNKNOWN_IMAGE, androidx.slice.core.SliceHints.ACTION_WITH_LABEL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceHints.ImageMode {
+ @Deprecated @IntDef({androidx.slice.core.SliceHints.LARGE_IMAGE, androidx.slice.core.SliceHints.SMALL_IMAGE, androidx.slice.core.SliceHints.ICON_IMAGE, androidx.slice.core.SliceHints.RAW_IMAGE_SMALL, androidx.slice.core.SliceHints.RAW_IMAGE_LARGE, androidx.slice.core.SliceHints.UNKNOWN_IMAGE, androidx.slice.core.SliceHints.ACTION_WITH_LABEL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceHints.ImageMode {
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceQuery {
- method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?);
- method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String?, String?);
- method public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String![]?, String![]?);
- method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?);
- method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String?, String?);
- method public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String![]?, String![]?);
- method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String?, String?);
- method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String![]?, String![]?);
- method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?);
- method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String?, String?);
- method public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String![]?, String![]?);
- method public static androidx.slice.SliceItem? findItem(androidx.slice.Slice, android.net.Uri);
- method public static androidx.slice.SliceItem? findNotContaining(androidx.slice.SliceItem?, java.util.List<androidx.slice.SliceItem!>);
- method public static androidx.slice.SliceItem? findSubtype(androidx.slice.Slice?, String?, String?);
- method public static androidx.slice.SliceItem? findSubtype(androidx.slice.SliceItem?, String?, String?);
- method public static androidx.slice.SliceItem? findTopLevelItem(androidx.slice.Slice, String?, String?, String![]?, String![]?);
- method public static boolean hasAnyHints(androidx.slice.SliceItem, java.lang.String!...);
- method public static boolean hasHints(androidx.slice.Slice, java.lang.String!...);
- method public static boolean hasHints(androidx.slice.SliceItem, java.lang.String!...);
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SliceQuery {
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.Slice?, String?, String![]?, String![]?);
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? find(androidx.slice.SliceItem?, String?, String![]?, String![]?);
+ method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String?, String?);
+ method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.Slice, String?, String![]?, String![]?);
+ method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?);
+ method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String?, String?);
+ method @Deprecated public static java.util.List<androidx.slice.SliceItem!> findAll(androidx.slice.SliceItem, String?, String![]?, String![]?);
+ method @Deprecated public static androidx.slice.SliceItem? findItem(androidx.slice.Slice, android.net.Uri);
+ method @Deprecated public static androidx.slice.SliceItem? findNotContaining(androidx.slice.SliceItem?, java.util.List<androidx.slice.SliceItem!>);
+ method @Deprecated public static androidx.slice.SliceItem? findSubtype(androidx.slice.Slice?, String?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? findSubtype(androidx.slice.SliceItem?, String?, String?);
+ method @Deprecated public static androidx.slice.SliceItem? findTopLevelItem(androidx.slice.Slice, String?, String?, String![]?, String![]?);
+ method @Deprecated public static boolean hasAnyHints(androidx.slice.SliceItem, java.lang.String!...);
+ method @Deprecated public static boolean hasHints(androidx.slice.Slice, java.lang.String!...);
+ method @Deprecated public static boolean hasHints(androidx.slice.SliceItem, java.lang.String!...);
}
}
diff --git a/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java b/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
index e5dd970..82d9b0a 100644
--- a/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
+++ b/slice/slice-core/src/main/java/androidx/slice/ArrayUtils.java
@@ -27,6 +27,7 @@
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@RequiresApi(19)
+@Deprecated
class ArrayUtils {
public static <T> boolean contains(T[] array, T item) {
diff --git a/slice/slice-core/src/main/java/androidx/slice/Clock.java b/slice/slice-core/src/main/java/androidx/slice/Clock.java
index f68560c..fc5c059 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Clock.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Clock.java
@@ -25,6 +25,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public interface Clock {
long currentTimeMillis();
}
diff --git a/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java b/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
index 9ccb915..740cb1c 100644
--- a/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
+++ b/slice/slice-core/src/main/java/androidx/slice/CornerDrawable.java
@@ -32,6 +32,7 @@
*
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Deprecated
public class CornerDrawable extends InsetDrawable {
private float mCornerRadius;
private final Path mPath = new Path();
diff --git a/slice/slice-core/src/main/java/androidx/slice/Slice.java b/slice/slice-core/src/main/java/androidx/slice/Slice.java
index 8044c1e..b9ce51d 100644
--- a/slice/slice-core/src/main/java/androidx/slice/Slice.java
+++ b/slice/slice-core/src/main/java/androidx/slice/Slice.java
@@ -90,9 +90,14 @@
* <p>Slices are constructed using {@link androidx.slice.builders.TemplateSliceBuilder}s
* in a tree structure that provides the OS some information about how the content should be
* displayed.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@VersionedParcelize(allowSerialization = true, isCustom = true)
@RequiresApi(19)
+@Deprecated
public final class Slice extends CustomVersionedParcelable implements VersionedParcelable {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java b/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
index 3711e65..abae809e 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceConvert.java
@@ -43,8 +43,13 @@
/**
* Convert between {@link androidx.slice.Slice androidx.slice.Slice} and
* {@link android.app.slice.Slice android.app.slice.Slice}
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(28)
+@Deprecated
public class SliceConvert {
private static final String TAG = "SliceConvert";
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceItem.java b/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
index c35fdef..47d353c 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceItem.java
@@ -86,9 +86,14 @@
* The hints that a {@link SliceItem} are a set of strings which annotate
* the content. The hints that are guaranteed to be understood by the system
* are defined on {@link Slice}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@VersionedParcelize(allowSerialization = true, ignoreParcelables = true, isCustom = true)
@RequiresApi(19)
+@Deprecated
public final class SliceItem extends CustomVersionedParcelable {
private static final String HINTS = "hints";
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java b/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
index ab2ebba..4e822a3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceItemHolder.java
@@ -50,6 +50,7 @@
@VersionedParcelize(allowSerialization = true, ignoreParcelables = true,
factory = SliceItemHolder.SliceItemPool.class)
@RequiresApi(19)
+@Deprecated
public class SliceItemHolder implements VersionedParcelable {
public static final Object sSerializeLock = new Object();
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManager.java b/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
index 390f09a..eff8508 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManager.java
@@ -33,8 +33,13 @@
* Class to handle interactions with {@link Slice}s.
* <p>
* The SliceViewManager manages permissions and pinned state for slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public abstract class SliceManager {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java b/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
index 549d939..89b8ba8 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManagerCompat.java
@@ -32,6 +32,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
class SliceManagerCompat extends SliceManager {
private final Context mContext;
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java b/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
index d1bd08e..7d86844 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceManagerWrapper.java
@@ -36,6 +36,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(api = 28)
+@Deprecated
class SliceManagerWrapper extends SliceManager {
private final android.app.slice.SliceManager mManager;
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
index 9cd8ddc..399ba9f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceProvider.java
@@ -130,7 +130,12 @@
* </pre>
*
* @see Slice
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public abstract class SliceProvider extends ContentProvider implements
CoreComponentFactory.CompatWrapped {
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java b/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
index 13bacd9..5c7b61f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceSpec.java
@@ -45,6 +45,7 @@
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@VersionedParcelize(allowSerialization = true)
@RequiresApi(19)
+@Deprecated
public final class SliceSpec implements VersionedParcelable {
@ParcelField(1)
diff --git a/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java b/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
index 436d24b..e07a46f 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SliceSpecs.java
@@ -24,6 +24,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class SliceSpecs {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/SystemClock.java b/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
index 888d3e4..c3f5ac3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
+++ b/slice/slice-core/src/main/java/androidx/slice/SystemClock.java
@@ -25,6 +25,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class SystemClock implements Clock {
@Override
public long currentTimeMillis() {
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
index dc700dd..b5ee3db 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
@@ -40,6 +40,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@RequiresApi(19)
+@Deprecated
public class CompatPermissionManager {
public static final String ALL_SUFFIX = "_all";
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
index f9de140..67dea43 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/CompatPinnedList.java
@@ -41,6 +41,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class CompatPinnedList {
private static final String LAST_BOOT = "last_boot";
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java b/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
index 9d704cc..87711b7 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SlicePermissionActivity.java
@@ -43,6 +43,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SlicePermissionActivity extends AppCompatActivity implements OnClickListener,
OnDismissListener {
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
index a03e977..24a73f3 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderCompat.java
@@ -67,6 +67,7 @@
*/
@RestrictTo(Scope.LIBRARY_GROUP)
@RequiresApi(19)
+@Deprecated
public class SliceProviderCompat {
public static final String PERMS_PREFIX = "slice_perms_";
private static final String TAG = "SliceProviderCompat";
diff --git a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
index b24671f..69207a4 100644
--- a/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
+++ b/slice/slice-core/src/main/java/androidx/slice/compat/SliceProviderWrapperContainer.java
@@ -43,6 +43,7 @@
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Deprecated
public class SliceProviderWrapperContainer {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
index 6c47667..0df5949 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceAction.java
@@ -26,8 +26,13 @@
/**
* Interface for a slice action, supports tappable icons, custom toggle icons, and default toggles.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public interface SliceAction {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
index 503cdbf..cd69d0e 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceActionImpl.java
@@ -64,6 +64,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class SliceActionImpl implements SliceAction {
// Either mAction or mActionItem must be non-null.
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
index e08441b..29e0353 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceHints.java
@@ -31,6 +31,7 @@
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class SliceHints {
/**
diff --git a/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java b/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
index 3111e1b..f4d3121 100644
--- a/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
+++ b/slice/slice-core/src/main/java/androidx/slice/core/SliceQuery.java
@@ -40,6 +40,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class SliceQuery {
/**
diff --git a/slice/slice-view/api/current.txt b/slice/slice-view/api/current.txt
index 91918d9..0b172e8 100644
--- a/slice/slice-view/api/current.txt
+++ b/slice/slice-view/api/current.txt
@@ -1,235 +1,235 @@
// Signature format: 4.0
package androidx.slice {
- @RequiresApi(19) public class SliceMetadata {
- method public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
- method public long getExpiry();
- method public int getHeaderType();
- method public android.os.Bundle getHostExtras();
- method public android.app.PendingIntent? getInputRangeAction();
- method public long getLastUpdatedTime();
- method public int getLoadingState();
- method public androidx.slice.core.SliceAction? getPrimaryAction();
- method public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
- method public int getRangeValue();
- method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
- method public java.util.List<java.lang.String!>? getSliceKeywords();
- method public CharSequence? getSubtitle();
- method public CharSequence? getSummary();
- method public CharSequence? getTitle();
- method public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
- method public boolean hasLargeMode();
- method public boolean isCachedSlice();
- method public boolean isErrorSlice();
- method public boolean isPermissionSlice();
- method public boolean isSelection();
- method public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
- method public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
- field public static final int LOADED_ALL = 2; // 0x2
- field public static final int LOADED_NONE = 0; // 0x0
- field public static final int LOADED_PARTIAL = 1; // 0x1
+ @Deprecated @RequiresApi(19) public class SliceMetadata {
+ method @Deprecated public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
+ method @Deprecated public long getExpiry();
+ method @Deprecated public int getHeaderType();
+ method @Deprecated public android.os.Bundle getHostExtras();
+ method @Deprecated public android.app.PendingIntent? getInputRangeAction();
+ method @Deprecated public long getLastUpdatedTime();
+ method @Deprecated public int getLoadingState();
+ method @Deprecated public androidx.slice.core.SliceAction? getPrimaryAction();
+ method @Deprecated public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
+ method @Deprecated public int getRangeValue();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+ method @Deprecated public java.util.List<java.lang.String!>? getSliceKeywords();
+ method @Deprecated public CharSequence? getSubtitle();
+ method @Deprecated public CharSequence? getSummary();
+ method @Deprecated public CharSequence? getTitle();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
+ method @Deprecated public boolean hasLargeMode();
+ method @Deprecated public boolean isCachedSlice();
+ method @Deprecated public boolean isErrorSlice();
+ method @Deprecated public boolean isPermissionSlice();
+ method @Deprecated public boolean isSelection();
+ method @Deprecated public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
+ method @Deprecated public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
+ field @Deprecated public static final int LOADED_ALL = 2; // 0x2
+ field @Deprecated public static final int LOADED_NONE = 0; // 0x0
+ field @Deprecated public static final int LOADED_PARTIAL = 1; // 0x1
}
- @RequiresApi(19) public class SliceStructure {
- ctor public SliceStructure(androidx.slice.Slice!);
+ @Deprecated @RequiresApi(19) public class SliceStructure {
+ ctor @Deprecated public SliceStructure(androidx.slice.Slice!);
}
- @RequiresApi(19) public class SliceUtils {
- method public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
- method public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
- method public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
+ @Deprecated @RequiresApi(19) public class SliceUtils {
+ method @Deprecated public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
+ method @Deprecated public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
+ method @Deprecated public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
}
- public static class SliceUtils.SerializeOptions {
- ctor public SliceUtils.SerializeOptions();
- method public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
- method public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
- field public static final int MODE_CONVERT = 2; // 0x2
- field public static final int MODE_REMOVE = 1; // 0x1
- field public static final int MODE_THROW = 0; // 0x0
+ @Deprecated public static class SliceUtils.SerializeOptions {
+ ctor @Deprecated public SliceUtils.SerializeOptions();
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
+ field @Deprecated public static final int MODE_CONVERT = 2; // 0x2
+ field @Deprecated public static final int MODE_REMOVE = 1; // 0x1
+ field @Deprecated public static final int MODE_THROW = 0; // 0x0
}
- public static interface SliceUtils.SliceActionListener {
- method public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
+ @Deprecated public static interface SliceUtils.SliceActionListener {
+ method @Deprecated public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
}
- public static class SliceUtils.SliceParseException extends java.lang.Exception {
+ @Deprecated public static class SliceUtils.SliceParseException extends java.lang.Exception {
}
- @RequiresApi(19) public abstract class SliceViewManager {
- method public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
- method public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
- method public static androidx.slice.SliceViewManager getInstance(android.content.Context);
- method @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
- method public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
- method public abstract void pinSlice(android.net.Uri);
- method public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
- method public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
- method public abstract void unpinSlice(android.net.Uri);
- method public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+ @Deprecated @RequiresApi(19) public abstract class SliceViewManager {
+ method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
+ method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
+ method @Deprecated public static androidx.slice.SliceViewManager getInstance(android.content.Context);
+ method @Deprecated @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
+ method @Deprecated public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
+ method @Deprecated public abstract void pinSlice(android.net.Uri);
+ method @Deprecated public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+ method @Deprecated public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
+ method @Deprecated public abstract void unpinSlice(android.net.Uri);
+ method @Deprecated public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
}
- public static interface SliceViewManager.SliceCallback {
- method public void onSliceUpdated(androidx.slice.Slice?);
+ @Deprecated public static interface SliceViewManager.SliceCallback {
+ method @Deprecated public void onSliceUpdated(androidx.slice.Slice?);
}
}
package androidx.slice.widget {
- @RequiresApi(19) public class EventInfo {
- ctor public EventInfo(int, int, int, int);
- method public void setPosition(int, int, int);
- field public static final int ACTION_TYPE_BUTTON = 1; // 0x1
- field public static final int ACTION_TYPE_CONTENT = 3; // 0x3
- field public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
- field public static final int ACTION_TYPE_SELECTION = 5; // 0x5
- field public static final int ACTION_TYPE_SLIDER = 2; // 0x2
- field public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
- field public static final int POSITION_CELL = 2; // 0x2
- field public static final int POSITION_END = 1; // 0x1
- field public static final int POSITION_START = 0; // 0x0
- field public static final int ROW_TYPE_GRID = 1; // 0x1
- field public static final int ROW_TYPE_LIST = 0; // 0x0
- field public static final int ROW_TYPE_MESSAGING = 2; // 0x2
- field public static final int ROW_TYPE_PROGRESS = 5; // 0x5
- field public static final int ROW_TYPE_SELECTION = 6; // 0x6
- field public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
- field public static final int ROW_TYPE_SLIDER = 4; // 0x4
- field public static final int ROW_TYPE_TOGGLE = 3; // 0x3
- field public static final int STATE_OFF = 0; // 0x0
- field public static final int STATE_ON = 1; // 0x1
- field public int actionCount;
- field public int actionIndex;
- field public int actionPosition;
- field public int actionType;
- field public int rowIndex;
- field public int rowTemplateType;
- field public int sliceMode;
- field public int state;
+ @Deprecated @RequiresApi(19) public class EventInfo {
+ ctor @Deprecated public EventInfo(int, int, int, int);
+ method @Deprecated public void setPosition(int, int, int);
+ field @Deprecated public static final int ACTION_TYPE_BUTTON = 1; // 0x1
+ field @Deprecated public static final int ACTION_TYPE_CONTENT = 3; // 0x3
+ field @Deprecated public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
+ field @Deprecated public static final int ACTION_TYPE_SELECTION = 5; // 0x5
+ field @Deprecated public static final int ACTION_TYPE_SLIDER = 2; // 0x2
+ field @Deprecated public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
+ field @Deprecated public static final int POSITION_CELL = 2; // 0x2
+ field @Deprecated public static final int POSITION_END = 1; // 0x1
+ field @Deprecated public static final int POSITION_START = 0; // 0x0
+ field @Deprecated public static final int ROW_TYPE_GRID = 1; // 0x1
+ field @Deprecated public static final int ROW_TYPE_LIST = 0; // 0x0
+ field @Deprecated public static final int ROW_TYPE_MESSAGING = 2; // 0x2
+ field @Deprecated public static final int ROW_TYPE_PROGRESS = 5; // 0x5
+ field @Deprecated public static final int ROW_TYPE_SELECTION = 6; // 0x6
+ field @Deprecated public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
+ field @Deprecated public static final int ROW_TYPE_SLIDER = 4; // 0x4
+ field @Deprecated public static final int ROW_TYPE_TOGGLE = 3; // 0x3
+ field @Deprecated public static final int STATE_OFF = 0; // 0x0
+ field @Deprecated public static final int STATE_ON = 1; // 0x1
+ field @Deprecated public int actionCount;
+ field @Deprecated public int actionIndex;
+ field @Deprecated public int actionPosition;
+ field @Deprecated public int actionType;
+ field @Deprecated public int rowIndex;
+ field @Deprecated public int rowTemplateType;
+ field @Deprecated public int sliceMode;
+ field @Deprecated public int state;
}
- @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
- method public android.graphics.Point getFirstImageSize(android.content.Context);
- method public boolean isValid();
+ @Deprecated @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
+ method @Deprecated public android.graphics.Point getFirstImageSize(android.content.Context);
+ method @Deprecated public boolean isValid();
}
- @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
- ctor public GridRowView(android.content.Context);
- ctor public GridRowView(android.content.Context, android.util.AttributeSet?);
- method protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
- method protected int getExtraBottomPadding();
- method protected int getExtraTopPadding();
- method protected int getMaxCells();
- method protected int getTitleTextLayout();
+ @Deprecated @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
+ ctor @Deprecated public GridRowView(android.content.Context);
+ ctor @Deprecated public GridRowView(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
+ method @Deprecated protected int getExtraBottomPadding();
+ method @Deprecated protected int getExtraTopPadding();
+ method @Deprecated protected int getMaxCells();
+ method @Deprecated protected int getTitleTextLayout();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onClick(android.view.View);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public boolean onTouch(android.view.View, android.view.MotionEvent);
- method protected void populateViews();
- method public void resetView();
- method protected boolean scheduleMaxCellsUpdate();
+ method @Deprecated protected void populateViews();
+ method @Deprecated public void resetView();
+ method @Deprecated protected boolean scheduleMaxCellsUpdate();
}
- public interface RowStyleFactory {
- method @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
+ @Deprecated public interface RowStyleFactory {
+ method @Deprecated @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
}
- @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
- ctor public RowView(android.content.Context);
- method protected java.util.List<java.lang.String!> getEndItemKeys();
- method protected androidx.slice.SliceItem? getPrimaryActionItem();
- method protected String? getPrimaryActionKey();
- method public void onClick(android.view.View);
- method public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
- method public void onNothingSelected(android.widget.AdapterView<?>);
+ @Deprecated @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
+ ctor @Deprecated public RowView(android.content.Context);
+ method @Deprecated protected java.util.List<java.lang.String!> getEndItemKeys();
+ method @Deprecated protected androidx.slice.SliceItem? getPrimaryActionItem();
+ method @Deprecated protected String? getPrimaryActionKey();
+ method @Deprecated public void onClick(android.view.View);
+ method @Deprecated public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
+ method @Deprecated public void onNothingSelected(android.widget.AdapterView<?>);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
}
- @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
- ctor public SliceAdapter(android.content.Context);
- method public androidx.slice.widget.GridRowView getGridRowView();
- method public int getItemCount();
- method public androidx.slice.widget.RowView getRowView();
+ @Deprecated @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+ ctor @Deprecated public SliceAdapter(android.content.Context);
+ method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
+ method @Deprecated public int getItemCount();
+ method @Deprecated public androidx.slice.widget.RowView getRowView();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onBindViewHolder(androidx.slice.widget.SliceAdapter.SliceViewHolder, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public androidx.slice.widget.SliceAdapter.SliceViewHolder onCreateViewHolder(android.view.ViewGroup, int);
}
- @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
- ctor public SliceChildView(android.content.Context);
- ctor public SliceChildView(android.content.Context, android.util.AttributeSet?);
- method public abstract void resetView();
- method public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
+ @Deprecated @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
+ ctor @Deprecated public SliceChildView(android.content.Context);
+ ctor @Deprecated public SliceChildView(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public abstract void resetView();
+ method @Deprecated public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
}
- @RequiresApi(19) public class SliceContent {
- ctor public SliceContent(androidx.slice.Slice?);
+ @Deprecated @RequiresApi(19) public class SliceContent {
+ ctor @Deprecated public SliceContent(androidx.slice.Slice?);
}
- @RequiresApi(19) public final class SliceLiveData {
- method public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+ @Deprecated @RequiresApi(19) public final class SliceLiveData {
+ method @Deprecated public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
}
- public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
- method public void goLive();
- method public void parseStream();
+ @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+ method @Deprecated public void goLive();
+ method @Deprecated public void parseStream();
}
- public static interface SliceLiveData.OnErrorListener {
- method public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
- field public static final int ERROR_INVALID_INPUT = 3; // 0x3
- field public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
- field public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
- field public static final int ERROR_UNKNOWN = 0; // 0x0
+ @Deprecated public static interface SliceLiveData.OnErrorListener {
+ method @Deprecated public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
+ field @Deprecated public static final int ERROR_INVALID_INPUT = 3; // 0x3
+ field @Deprecated public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
+ field @Deprecated public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
+ field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
}
- @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
+ @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
}
- @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
- ctor public SliceView(android.content.Context!);
- ctor public SliceView(android.content.Context!, android.util.AttributeSet?);
- ctor public SliceView(android.content.Context!, android.util.AttributeSet?, int);
- ctor @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
- method protected void configureViewPolicy(int);
- method public int getHiddenItemCount();
- method public int getMode();
- method public androidx.slice.Slice? getSlice();
- method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
- method public boolean isScrollable();
- method public void onChanged(androidx.slice.Slice?);
- method public void onClick(android.view.View!);
- method public void setAccentColor(@ColorInt int);
- method public void setCurrentView(androidx.slice.widget.SliceChildView);
- method public void setMode(int);
- method public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
- method public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
- method public void setScrollable(boolean);
- method public void setShowActionDividers(boolean);
- method public void setShowHeaderDivider(boolean);
- method public void setShowTitleItems(boolean);
- method public void setSlice(androidx.slice.Slice?);
- method public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
- field public static final int MODE_LARGE = 2; // 0x2
- field public static final int MODE_SHORTCUT = 3; // 0x3
- field public static final int MODE_SMALL = 1; // 0x1
+ @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+ ctor @Deprecated public SliceView(android.content.Context!);
+ ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
+ ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
+ ctor @Deprecated @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
+ method @Deprecated protected void configureViewPolicy(int);
+ method @Deprecated public int getHiddenItemCount();
+ method @Deprecated public int getMode();
+ method @Deprecated public androidx.slice.Slice? getSlice();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+ method @Deprecated public boolean isScrollable();
+ method @Deprecated public void onChanged(androidx.slice.Slice?);
+ method @Deprecated public void onClick(android.view.View!);
+ method @Deprecated public void setAccentColor(@ColorInt int);
+ method @Deprecated public void setCurrentView(androidx.slice.widget.SliceChildView);
+ method @Deprecated public void setMode(int);
+ method @Deprecated public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
+ method @Deprecated public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
+ method @Deprecated public void setScrollable(boolean);
+ method @Deprecated public void setShowActionDividers(boolean);
+ method @Deprecated public void setShowHeaderDivider(boolean);
+ method @Deprecated public void setShowTitleItems(boolean);
+ method @Deprecated public void setSlice(androidx.slice.Slice?);
+ method @Deprecated public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
+ field @Deprecated public static final int MODE_LARGE = 2; // 0x2
+ field @Deprecated public static final int MODE_SHORTCUT = 3; // 0x3
+ field @Deprecated public static final int MODE_SMALL = 1; // 0x1
}
- public static interface SliceView.OnSliceActionListener {
- method public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
+ @Deprecated public static interface SliceView.OnSliceActionListener {
+ method @Deprecated public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
}
- @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
- ctor public TemplateView(android.content.Context);
- method public void onAttachedToWindow();
+ @Deprecated @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
+ ctor @Deprecated public TemplateView(android.content.Context);
+ method @Deprecated public void onAttachedToWindow();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
- method public void setAdapter(androidx.slice.widget.SliceAdapter);
+ method @Deprecated public void setAdapter(androidx.slice.widget.SliceAdapter);
}
}
diff --git a/slice/slice-view/api/removed_current.txt b/slice/slice-view/api/removed_current.txt
index 83b9996..c72daba 100644
--- a/slice/slice-view/api/removed_current.txt
+++ b/slice/slice-view/api/removed_current.txt
@@ -1,7 +1,7 @@
// Signature format: 4.0
package androidx.slice.widget {
- @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+ @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
method @Deprecated public void showActionDividers(boolean);
method @Deprecated public void showHeaderDivider(boolean);
method @Deprecated public void showTitleItems(boolean);
diff --git a/slice/slice-view/api/restricted_current.txt b/slice/slice-view/api/restricted_current.txt
index 9f3c43a..068889b 100644
--- a/slice/slice-view/api/restricted_current.txt
+++ b/slice/slice-view/api/restricted_current.txt
@@ -1,294 +1,294 @@
// Signature format: 4.0
package androidx.slice {
- @RequiresApi(19) public class SliceMetadata {
- method public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
- method public long getExpiry();
- method public int getHeaderType();
- method public android.os.Bundle getHostExtras();
- method public android.app.PendingIntent? getInputRangeAction();
- method public long getLastUpdatedTime();
- method public int getLoadingState();
- method public androidx.slice.core.SliceAction? getPrimaryAction();
- method public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
- method public int getRangeValue();
- method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
- method public java.util.List<java.lang.String!>? getSliceKeywords();
- method public CharSequence? getSubtitle();
- method public CharSequence? getSummary();
- method public CharSequence? getTitle();
- method public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
- method public boolean hasLargeMode();
- method public boolean isCachedSlice();
- method public boolean isErrorSlice();
- method public boolean isPermissionSlice();
- method public boolean isSelection();
- method public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
- method public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
- field public static final int LOADED_ALL = 2; // 0x2
- field public static final int LOADED_NONE = 0; // 0x0
- field public static final int LOADED_PARTIAL = 1; // 0x1
+ @Deprecated @RequiresApi(19) public class SliceMetadata {
+ method @Deprecated public static androidx.slice.SliceMetadata from(android.content.Context?, androidx.slice.Slice);
+ method @Deprecated public long getExpiry();
+ method @Deprecated public int getHeaderType();
+ method @Deprecated public android.os.Bundle getHostExtras();
+ method @Deprecated public android.app.PendingIntent? getInputRangeAction();
+ method @Deprecated public long getLastUpdatedTime();
+ method @Deprecated public int getLoadingState();
+ method @Deprecated public androidx.slice.core.SliceAction? getPrimaryAction();
+ method @Deprecated public androidx.core.util.Pair<java.lang.Integer!,java.lang.Integer!>? getRange();
+ method @Deprecated public int getRangeValue();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+ method @Deprecated public java.util.List<java.lang.String!>? getSliceKeywords();
+ method @Deprecated public CharSequence? getSubtitle();
+ method @Deprecated public CharSequence? getSummary();
+ method @Deprecated public CharSequence? getTitle();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggles();
+ method @Deprecated public boolean hasLargeMode();
+ method @Deprecated public boolean isCachedSlice();
+ method @Deprecated public boolean isErrorSlice();
+ method @Deprecated public boolean isPermissionSlice();
+ method @Deprecated public boolean isSelection();
+ method @Deprecated public boolean sendInputRangeAction(int) throws android.app.PendingIntent.CanceledException;
+ method @Deprecated public boolean sendToggleAction(androidx.slice.core.SliceAction!, boolean) throws android.app.PendingIntent.CanceledException;
+ field @Deprecated public static final int LOADED_ALL = 2; // 0x2
+ field @Deprecated public static final int LOADED_NONE = 0; // 0x0
+ field @Deprecated public static final int LOADED_PARTIAL = 1; // 0x1
}
- @RequiresApi(19) public class SliceStructure {
- ctor public SliceStructure(androidx.slice.Slice!);
+ @Deprecated @RequiresApi(19) public class SliceStructure {
+ ctor @Deprecated public SliceStructure(androidx.slice.Slice!);
}
- @RequiresApi(19) public class SliceUtils {
- method public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
- method public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
- method public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
+ @Deprecated @RequiresApi(19) public class SliceUtils {
+ method @Deprecated public static androidx.slice.Slice parseSlice(android.content.Context, java.io.InputStream, String, androidx.slice.SliceUtils.SliceActionListener) throws java.io.IOException, androidx.slice.SliceUtils.SliceParseException;
+ method @Deprecated public static void serializeSlice(androidx.slice.Slice, android.content.Context, java.io.OutputStream, androidx.slice.SliceUtils.SerializeOptions) throws java.lang.IllegalArgumentException;
+ method @Deprecated public static androidx.slice.Slice stripSlice(androidx.slice.Slice, int, boolean);
}
- public static class SliceUtils.SerializeOptions {
- ctor public SliceUtils.SerializeOptions();
- method public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
- method public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
- method public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
- field public static final int MODE_CONVERT = 2; // 0x2
- field public static final int MODE_REMOVE = 1; // 0x1
- field public static final int MODE_THROW = 0; // 0x0
+ @Deprecated public static class SliceUtils.SerializeOptions {
+ ctor @Deprecated public SliceUtils.SerializeOptions();
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setActionMode(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageConversionFormat(android.graphics.Bitmap.CompressFormat!, int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setImageMode(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageHeight(int);
+ method @Deprecated public androidx.slice.SliceUtils.SerializeOptions! setMaxImageWidth(int);
+ field @Deprecated public static final int MODE_CONVERT = 2; // 0x2
+ field @Deprecated public static final int MODE_REMOVE = 1; // 0x1
+ field @Deprecated public static final int MODE_THROW = 0; // 0x0
}
- public static interface SliceUtils.SliceActionListener {
- method public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
+ @Deprecated public static interface SliceUtils.SliceActionListener {
+ method @Deprecated public void onSliceAction(android.net.Uri!, android.content.Context!, android.content.Intent!);
}
- public static class SliceUtils.SliceParseException extends java.lang.Exception {
+ @Deprecated public static class SliceUtils.SliceParseException extends java.lang.Exception {
}
- @RequiresApi(19) public abstract class SliceViewManager {
- method public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
- method public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
- method public static androidx.slice.SliceViewManager getInstance(android.content.Context);
- method @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
- method public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
- method public abstract void pinSlice(android.net.Uri);
- method public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
- method public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
- method public abstract void unpinSlice(android.net.Uri);
- method public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+ @Deprecated @RequiresApi(19) public abstract class SliceViewManager {
+ method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.content.Intent);
+ method @Deprecated public abstract androidx.slice.Slice? bindSlice(android.net.Uri);
+ method @Deprecated public static androidx.slice.SliceViewManager getInstance(android.content.Context);
+ method @Deprecated @WorkerThread public abstract java.util.Collection<android.net.Uri!> getSliceDescendants(android.net.Uri);
+ method @Deprecated public abstract android.net.Uri? mapIntentToUri(android.content.Intent);
+ method @Deprecated public abstract void pinSlice(android.net.Uri);
+ method @Deprecated public abstract void registerSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
+ method @Deprecated public abstract void registerSliceCallback(android.net.Uri, java.util.concurrent.Executor, androidx.slice.SliceViewManager.SliceCallback);
+ method @Deprecated public abstract void unpinSlice(android.net.Uri);
+ method @Deprecated public abstract void unregisterSliceCallback(android.net.Uri, androidx.slice.SliceViewManager.SliceCallback);
}
- public static interface SliceViewManager.SliceCallback {
- method public void onSliceUpdated(androidx.slice.Slice?);
+ @Deprecated public static interface SliceViewManager.SliceCallback {
+ method @Deprecated public void onSliceUpdated(androidx.slice.Slice?);
}
}
package androidx.slice.widget {
- @RequiresApi(19) public class EventInfo {
- ctor public EventInfo(int, int, int, int);
- method public void setPosition(int, int, int);
- field public static final int ACTION_TYPE_BUTTON = 1; // 0x1
- field public static final int ACTION_TYPE_CONTENT = 3; // 0x3
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_DATE_PICK = 6; // 0x6
- field public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
- field public static final int ACTION_TYPE_SELECTION = 5; // 0x5
- field public static final int ACTION_TYPE_SLIDER = 2; // 0x2
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_TIME_PICK = 7; // 0x7
- field public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
- field public static final int POSITION_CELL = 2; // 0x2
- field public static final int POSITION_END = 1; // 0x1
- field public static final int POSITION_START = 0; // 0x0
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_DATE_PICK = 7; // 0x7
- field public static final int ROW_TYPE_GRID = 1; // 0x1
- field public static final int ROW_TYPE_LIST = 0; // 0x0
- field public static final int ROW_TYPE_MESSAGING = 2; // 0x2
- field public static final int ROW_TYPE_PROGRESS = 5; // 0x5
- field public static final int ROW_TYPE_SELECTION = 6; // 0x6
- field public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
- field public static final int ROW_TYPE_SLIDER = 4; // 0x4
- field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_TIME_PICK = 8; // 0x8
- field public static final int ROW_TYPE_TOGGLE = 3; // 0x3
- field public static final int STATE_OFF = 0; // 0x0
- field public static final int STATE_ON = 1; // 0x1
- field public int actionCount;
- field public int actionIndex;
- field public int actionPosition;
- field public int actionType;
- field public int rowIndex;
- field public int rowTemplateType;
- field public int sliceMode;
- field public int state;
+ @Deprecated @RequiresApi(19) public class EventInfo {
+ ctor @Deprecated public EventInfo(int, int, int, int);
+ method @Deprecated public void setPosition(int, int, int);
+ field @Deprecated public static final int ACTION_TYPE_BUTTON = 1; // 0x1
+ field @Deprecated public static final int ACTION_TYPE_CONTENT = 3; // 0x3
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_DATE_PICK = 6; // 0x6
+ field @Deprecated public static final int ACTION_TYPE_SEE_MORE = 4; // 0x4
+ field @Deprecated public static final int ACTION_TYPE_SELECTION = 5; // 0x5
+ field @Deprecated public static final int ACTION_TYPE_SLIDER = 2; // 0x2
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ACTION_TYPE_TIME_PICK = 7; // 0x7
+ field @Deprecated public static final int ACTION_TYPE_TOGGLE = 0; // 0x0
+ field @Deprecated public static final int POSITION_CELL = 2; // 0x2
+ field @Deprecated public static final int POSITION_END = 1; // 0x1
+ field @Deprecated public static final int POSITION_START = 0; // 0x0
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_DATE_PICK = 7; // 0x7
+ field @Deprecated public static final int ROW_TYPE_GRID = 1; // 0x1
+ field @Deprecated public static final int ROW_TYPE_LIST = 0; // 0x0
+ field @Deprecated public static final int ROW_TYPE_MESSAGING = 2; // 0x2
+ field @Deprecated public static final int ROW_TYPE_PROGRESS = 5; // 0x5
+ field @Deprecated public static final int ROW_TYPE_SELECTION = 6; // 0x6
+ field @Deprecated public static final int ROW_TYPE_SHORTCUT = -1; // 0xffffffff
+ field @Deprecated public static final int ROW_TYPE_SLIDER = 4; // 0x4
+ field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final int ROW_TYPE_TIME_PICK = 8; // 0x8
+ field @Deprecated public static final int ROW_TYPE_TOGGLE = 3; // 0x3
+ field @Deprecated public static final int STATE_OFF = 0; // 0x0
+ field @Deprecated public static final int STATE_ON = 1; // 0x1
+ field @Deprecated public int actionCount;
+ field @Deprecated public int actionIndex;
+ field @Deprecated public int actionPosition;
+ field @Deprecated public int actionType;
+ field @Deprecated public int rowIndex;
+ field @Deprecated public int rowTemplateType;
+ field @Deprecated public int sliceMode;
+ field @Deprecated public int state;
}
- @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
- ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public GridContent(androidx.slice.SliceItem, int);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getContentIntent();
- method public android.graphics.Point getFirstImageSize(android.content.Context);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.slice.widget.GridContent.CellContent!> getGridContent();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean getIsLastIndex();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getLargestImageMode();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMaxCellLineCount();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getSeeMoreItem();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getTitle();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasImage();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isAllImages();
- method public boolean isValid();
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setIsLastIndex(boolean);
+ @Deprecated @RequiresApi(19) public class GridContent extends androidx.slice.widget.SliceContent {
+ ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public GridContent(androidx.slice.SliceItem, int);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getContentIntent();
+ method @Deprecated public android.graphics.Point getFirstImageSize(android.content.Context);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public java.util.ArrayList<androidx.slice.widget.GridContent.CellContent!> getGridContent();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean getIsLastIndex();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getLargestImageMode();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public int getMaxCellLineCount();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.slice.SliceItem? getSeeMoreItem();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public CharSequence? getTitle();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean hasImage();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public boolean isAllImages();
+ method @Deprecated public boolean isValid();
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public void setIsLastIndex(boolean);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class GridContent.CellContent {
- ctor public GridContent.CellContent(androidx.slice.SliceItem);
- method public java.util.ArrayList<androidx.slice.SliceItem!> getCellItems();
- method public CharSequence? getContentDescription();
- method public androidx.slice.SliceItem? getContentIntent();
- method public androidx.core.graphics.drawable.IconCompat? getImageIcon();
- method public int getImageMode();
- method public androidx.slice.SliceItem? getOverlayItem();
- method public androidx.slice.SliceItem? getPicker();
- method public int getTextCount();
- method public androidx.slice.SliceItem? getTitleItem();
- method public androidx.slice.SliceItem? getToggleItem();
- method public boolean hasImage();
- method public boolean isImageOnly();
- method public boolean isValid();
- method public boolean populate(androidx.slice.SliceItem);
+ @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static class GridContent.CellContent {
+ ctor @Deprecated public GridContent.CellContent(androidx.slice.SliceItem);
+ method @Deprecated public java.util.ArrayList<androidx.slice.SliceItem!> getCellItems();
+ method @Deprecated public CharSequence? getContentDescription();
+ method @Deprecated public androidx.slice.SliceItem? getContentIntent();
+ method @Deprecated public androidx.core.graphics.drawable.IconCompat? getImageIcon();
+ method @Deprecated public int getImageMode();
+ method @Deprecated public androidx.slice.SliceItem? getOverlayItem();
+ method @Deprecated public androidx.slice.SliceItem? getPicker();
+ method @Deprecated public int getTextCount();
+ method @Deprecated public androidx.slice.SliceItem? getTitleItem();
+ method @Deprecated public androidx.slice.SliceItem? getToggleItem();
+ method @Deprecated public boolean hasImage();
+ method @Deprecated public boolean isImageOnly();
+ method @Deprecated public boolean isValid();
+ method @Deprecated public boolean populate(androidx.slice.SliceItem);
}
- @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
- ctor public GridRowView(android.content.Context);
- ctor public GridRowView(android.content.Context, android.util.AttributeSet?);
- method protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
- method protected int getExtraBottomPadding();
- method protected int getExtraTopPadding();
- method protected int getMaxCells();
- method protected int getTitleTextLayout();
+ @Deprecated @RequiresApi(19) public class GridRowView extends androidx.slice.widget.SliceChildView implements android.view.View.OnClickListener android.view.View.OnTouchListener {
+ ctor @Deprecated public GridRowView(android.content.Context);
+ ctor @Deprecated public GridRowView(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated protected boolean addImageItem(androidx.slice.SliceItem, androidx.slice.SliceItem?, int, android.view.ViewGroup, boolean);
+ method @Deprecated protected int getExtraBottomPadding();
+ method @Deprecated protected int getExtraTopPadding();
+ method @Deprecated protected int getMaxCells();
+ method @Deprecated protected int getTitleTextLayout();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onClick(android.view.View);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public boolean onTouch(android.view.View, android.view.MotionEvent);
- method protected void populateViews();
- method public void resetView();
- method protected boolean scheduleMaxCellsUpdate();
+ method @Deprecated protected void populateViews();
+ method @Deprecated public void resetView();
+ method @Deprecated protected boolean scheduleMaxCellsUpdate();
}
- @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RowContent extends androidx.slice.widget.SliceContent {
- ctor public RowContent(androidx.slice.SliceItem!, int);
- method public java.util.List<androidx.slice.SliceItem!>! getEndItems();
- method public androidx.slice.SliceItem? getInputRangeThumb();
- method public boolean getIsHeader();
- method public int getLineCount();
- method public androidx.slice.SliceItem? getPrimaryAction();
- method public androidx.slice.SliceItem? getRange();
- method public androidx.slice.SliceItem? getSelection();
- method public androidx.slice.SliceItem? getStartItem();
- method public androidx.slice.SliceItem? getSubtitleItem();
- method public androidx.slice.SliceItem? getSummaryItem();
- method public androidx.slice.SliceItem? getTitleItem();
- method public java.util.List<androidx.slice.core.SliceAction!>! getToggleItems();
- method public boolean hasActionDivider();
- method public boolean hasBottomDivider();
- method public boolean hasTitleItems();
- method public boolean isDefaultSeeMore();
- method public boolean isValid();
- method public void setIsHeader(boolean);
- method public void showActionDivider(boolean);
- method public void showBottomDivider(boolean);
- method public void showTitleItems(boolean);
+ @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class RowContent extends androidx.slice.widget.SliceContent {
+ ctor @Deprecated public RowContent(androidx.slice.SliceItem!, int);
+ method @Deprecated public java.util.List<androidx.slice.SliceItem!>! getEndItems();
+ method @Deprecated public androidx.slice.SliceItem? getInputRangeThumb();
+ method @Deprecated public boolean getIsHeader();
+ method @Deprecated public int getLineCount();
+ method @Deprecated public androidx.slice.SliceItem? getPrimaryAction();
+ method @Deprecated public androidx.slice.SliceItem? getRange();
+ method @Deprecated public androidx.slice.SliceItem? getSelection();
+ method @Deprecated public androidx.slice.SliceItem? getStartItem();
+ method @Deprecated public androidx.slice.SliceItem? getSubtitleItem();
+ method @Deprecated public androidx.slice.SliceItem? getSummaryItem();
+ method @Deprecated public androidx.slice.SliceItem? getTitleItem();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>! getToggleItems();
+ method @Deprecated public boolean hasActionDivider();
+ method @Deprecated public boolean hasBottomDivider();
+ method @Deprecated public boolean hasTitleItems();
+ method @Deprecated public boolean isDefaultSeeMore();
+ method @Deprecated public boolean isValid();
+ method @Deprecated public void setIsHeader(boolean);
+ method @Deprecated public void showActionDivider(boolean);
+ method @Deprecated public void showBottomDivider(boolean);
+ method @Deprecated public void showTitleItems(boolean);
}
- public interface RowStyleFactory {
- method @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
+ @Deprecated public interface RowStyleFactory {
+ method @Deprecated @StyleRes public int getRowStyleRes(androidx.slice.SliceItem);
}
- @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
- ctor public RowView(android.content.Context);
- method protected java.util.List<java.lang.String!> getEndItemKeys();
- method protected androidx.slice.SliceItem? getPrimaryActionItem();
- method protected String? getPrimaryActionKey();
- method public void onClick(android.view.View);
- method public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
- method public void onNothingSelected(android.widget.AdapterView<?>);
+ @Deprecated @RequiresApi(19) public class RowView extends androidx.slice.widget.SliceChildView implements android.widget.AdapterView.OnItemSelectedListener android.view.View.OnClickListener {
+ ctor @Deprecated public RowView(android.content.Context);
+ method @Deprecated protected java.util.List<java.lang.String!> getEndItemKeys();
+ method @Deprecated protected androidx.slice.SliceItem? getPrimaryActionItem();
+ method @Deprecated protected String? getPrimaryActionKey();
+ method @Deprecated public void onClick(android.view.View);
+ method @Deprecated public void onItemSelected(android.widget.AdapterView<?>, android.view.View, int, long);
+ method @Deprecated public void onNothingSelected(android.widget.AdapterView<?>);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
}
- @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
- ctor public SliceAdapter(android.content.Context);
- method public androidx.slice.widget.GridRowView getGridRowView();
- method public int getItemCount();
- method public androidx.slice.widget.RowView getRowView();
+ @Deprecated @RequiresApi(19) public class SliceAdapter extends androidx.recyclerview.widget.RecyclerView.Adapter<androidx.slice.widget.SliceAdapter.SliceViewHolder> {
+ ctor @Deprecated public SliceAdapter(android.content.Context);
+ method @Deprecated public androidx.slice.widget.GridRowView getGridRowView();
+ method @Deprecated public int getItemCount();
+ method @Deprecated public androidx.slice.widget.RowView getRowView();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void onBindViewHolder(androidx.slice.widget.SliceAdapter.SliceViewHolder, int);
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public androidx.slice.widget.SliceAdapter.SliceViewHolder onCreateViewHolder(android.view.ViewGroup, int);
}
- @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
- ctor public SliceChildView(android.content.Context);
- ctor public SliceChildView(android.content.Context, android.util.AttributeSet?);
- method public abstract void resetView();
- method public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
+ @Deprecated @RequiresApi(19) public abstract class SliceChildView extends android.widget.FrameLayout {
+ ctor @Deprecated public SliceChildView(android.content.Context);
+ ctor @Deprecated public SliceChildView(android.content.Context, android.util.AttributeSet?);
+ method @Deprecated public abstract void resetView();
+ method @Deprecated public void setSliceItem(androidx.slice.widget.SliceContent?, boolean, int, int, androidx.slice.widget.SliceView.OnSliceActionListener?);
}
- @RequiresApi(19) public class SliceContent {
- ctor public SliceContent(androidx.slice.Slice?);
+ @Deprecated @RequiresApi(19) public class SliceContent {
+ ctor @Deprecated public SliceContent(androidx.slice.Slice?);
}
- @RequiresApi(19) public final class SliceLiveData {
- method public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
- method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromStream(android.content.Context, androidx.slice.SliceViewManager!, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
- method public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+ @Deprecated @RequiresApi(19) public final class SliceLiveData {
+ method @Deprecated public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromCachedSlice(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromIntent(android.content.Context, android.content.Intent, androidx.slice.widget.SliceLiveData.OnErrorListener?);
+ method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.slice.widget.SliceLiveData.CachedSliceLiveData fromStream(android.content.Context, androidx.slice.SliceViewManager!, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromStream(android.content.Context, java.io.InputStream, androidx.slice.widget.SliceLiveData.OnErrorListener!);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri);
+ method @Deprecated public static androidx.lifecycle.LiveData<androidx.slice.Slice!> fromUri(android.content.Context, android.net.Uri, androidx.slice.widget.SliceLiveData.OnErrorListener?);
}
- public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
- method public void goLive();
- method public void parseStream();
+ @Deprecated public static class SliceLiveData.CachedSliceLiveData extends androidx.lifecycle.LiveData<androidx.slice.Slice> {
+ method @Deprecated public void goLive();
+ method @Deprecated public void parseStream();
}
- public static interface SliceLiveData.OnErrorListener {
- method public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
- field public static final int ERROR_INVALID_INPUT = 3; // 0x3
- field public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
- field public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
- field public static final int ERROR_UNKNOWN = 0; // 0x0
+ @Deprecated public static interface SliceLiveData.OnErrorListener {
+ method @Deprecated public void onSliceError(@androidx.slice.widget.SliceLiveData.OnErrorListener.ErrorType int, Throwable?);
+ field @Deprecated public static final int ERROR_INVALID_INPUT = 3; // 0x3
+ field @Deprecated public static final int ERROR_SLICE_NO_LONGER_PRESENT = 2; // 0x2
+ field @Deprecated public static final int ERROR_STRUCTURE_CHANGED = 1; // 0x1
+ field @Deprecated public static final int ERROR_UNKNOWN = 0; // 0x0
}
- @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
+ @Deprecated @IntDef({androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_UNKNOWN, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_STRUCTURE_CHANGED, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_SLICE_NO_LONGER_PRESENT, androidx.slice.widget.SliceLiveData.OnErrorListener.ERROR_INVALID_INPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface SliceLiveData.OnErrorListener.ErrorType {
}
- @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
- ctor public SliceView(android.content.Context!);
- ctor public SliceView(android.content.Context!, android.util.AttributeSet?);
- ctor public SliceView(android.content.Context!, android.util.AttributeSet?, int);
- ctor @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
- method protected void configureViewPolicy(int);
- method public int getHiddenItemCount();
- method public int getMode();
- method public androidx.slice.Slice? getSlice();
- method public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
- method public boolean isScrollable();
- method public void onChanged(androidx.slice.Slice?);
- method public void onClick(android.view.View!);
- method public void setAccentColor(@ColorInt int);
- method public void setCurrentView(androidx.slice.widget.SliceChildView);
- method public void setMode(int);
- method public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
- method public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
- method public void setScrollable(boolean);
- method public void setShowActionDividers(boolean);
- method public void setShowHeaderDivider(boolean);
- method public void setShowTitleItems(boolean);
- method public void setSlice(androidx.slice.Slice?);
- method public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
- field public static final int MODE_LARGE = 2; // 0x2
- field public static final int MODE_SHORTCUT = 3; // 0x3
- field public static final int MODE_SMALL = 1; // 0x1
+ @Deprecated @RequiresApi(19) public class SliceView extends android.view.ViewGroup implements androidx.lifecycle.Observer<androidx.slice.Slice> android.view.View.OnClickListener {
+ ctor @Deprecated public SliceView(android.content.Context!);
+ ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?);
+ ctor @Deprecated public SliceView(android.content.Context!, android.util.AttributeSet?, int);
+ ctor @Deprecated @RequiresApi(21) public SliceView(android.content.Context!, android.util.AttributeSet!, int, int);
+ method @Deprecated protected void configureViewPolicy(int);
+ method @Deprecated public int getHiddenItemCount();
+ method @Deprecated public int getMode();
+ method @Deprecated public androidx.slice.Slice? getSlice();
+ method @Deprecated public java.util.List<androidx.slice.core.SliceAction!>? getSliceActions();
+ method @Deprecated public boolean isScrollable();
+ method @Deprecated public void onChanged(androidx.slice.Slice?);
+ method @Deprecated public void onClick(android.view.View!);
+ method @Deprecated public void setAccentColor(@ColorInt int);
+ method @Deprecated public void setCurrentView(androidx.slice.widget.SliceChildView);
+ method @Deprecated public void setMode(int);
+ method @Deprecated public void setOnSliceActionListener(androidx.slice.widget.SliceView.OnSliceActionListener?);
+ method @Deprecated public void setRowStyleFactory(androidx.slice.widget.RowStyleFactory?);
+ method @Deprecated public void setScrollable(boolean);
+ method @Deprecated public void setShowActionDividers(boolean);
+ method @Deprecated public void setShowHeaderDivider(boolean);
+ method @Deprecated public void setShowTitleItems(boolean);
+ method @Deprecated public void setSlice(androidx.slice.Slice?);
+ method @Deprecated public void setSliceActions(java.util.List<androidx.slice.core.SliceAction!>?);
+ field @Deprecated public static final int MODE_LARGE = 2; // 0x2
+ field @Deprecated public static final int MODE_SHORTCUT = 3; // 0x3
+ field @Deprecated public static final int MODE_SMALL = 1; // 0x1
}
- public static interface SliceView.OnSliceActionListener {
- method public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
+ @Deprecated public static interface SliceView.OnSliceActionListener {
+ method @Deprecated public void onSliceAction(androidx.slice.widget.EventInfo, androidx.slice.SliceItem);
}
- @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
- ctor public TemplateView(android.content.Context);
- method public void onAttachedToWindow();
+ @Deprecated @RequiresApi(19) public class TemplateView extends androidx.slice.widget.SliceChildView {
+ ctor @Deprecated public TemplateView(android.content.Context);
+ method @Deprecated public void onAttachedToWindow();
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public void resetView();
- method public void setAdapter(androidx.slice.widget.SliceAdapter);
+ method @Deprecated public void setAdapter(androidx.slice.widget.SliceAdapter);
}
}
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
index edbc303..45009c3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceMetadata.java
@@ -71,8 +71,13 @@
/**
* Utility class to parse a {@link Slice} and provide access to information around its contents.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceMetadata {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java b/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
index f4f1f27..4b9f7cd 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceStructure.java
@@ -37,8 +37,13 @@
* specific content such as text or icons.
*
* Two structures can be compared using {@link #equals(Object)}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceStructure {
private final String mStructure;
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java b/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
index 46f1d35..d6209ce 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceUtils.java
@@ -63,8 +63,13 @@
/**
* Utilities for dealing with slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceUtils {
private SliceUtils() {
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
index 95c0d6d..3ebb7de 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManager.java
@@ -34,8 +34,13 @@
* Class to handle interactions with {@link Slice}s.
* <p>
* The SliceViewManager manages permissions and pinned state for slices.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public abstract class SliceViewManager {
/**
@@ -183,7 +188,12 @@
/**
* Class that listens to changes in {@link Slice}s.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public interface SliceCallback {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
index 217d0f17d..b7a5750 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerBase.java
@@ -37,6 +37,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public abstract class SliceViewManagerBase extends SliceViewManager {
private final ArrayMap<Pair<Uri, SliceCallback>, SliceListenerImpl> mListenerLookup =
new ArrayMap<>();
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
index 1c1d436..02bfec0 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerCompat.java
@@ -36,6 +36,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
class SliceViewManagerCompat extends SliceViewManagerBase {
SliceViewManagerCompat(Context context) {
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
index c4364d9..58a354a 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceViewManagerWrapper.java
@@ -43,6 +43,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(api = 28)
+@Deprecated
class SliceViewManagerWrapper extends SliceViewManagerBase {
private static final String TAG = "SliceViewManagerWrapper"; // exactly 23
diff --git a/slice/slice-view/src/main/java/androidx/slice/SliceXml.java b/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
index 1251afb..b09499e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
+++ b/slice/slice-view/src/main/java/androidx/slice/SliceXml.java
@@ -59,6 +59,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
class SliceXml {
private static final String NAMESPACE = null;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java b/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
index 15f0ac0..c32f815 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ActionRow.java
@@ -54,6 +54,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class ActionRow extends FrameLayout {
private static final int MAX_ACTIONS = 5;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java b/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
index 4b5ee95..154d842 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/DisplayedListItems.java
@@ -25,6 +25,7 @@
*
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+@Deprecated
class DisplayedListItems {
private final List<SliceContent> mDisplayedItems;
private final int mHiddenItemCount;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java b/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
index db5231f..5e315a2 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/EventInfo.java
@@ -25,8 +25,13 @@
/**
* Represents information associated with a logged event on {@link SliceView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class EventInfo {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
index ee38115..1c8036e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/GridContent.java
@@ -55,8 +55,13 @@
/**
* Extracts information required to present content in a grid format from a slice.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class GridContent extends SliceContent {
private boolean mAllImages;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java b/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
index 9bbd5de..b48c559 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/GridRowView.java
@@ -86,6 +86,12 @@
import java.util.Iterator;
import java.util.List;
+/**
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
+ */
+@Deprecated
@RequiresApi(19)
public class GridRowView extends SliceChildView implements View.OnClickListener,
View.OnTouchListener {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
index 8151152..8f28a05 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ListContent.java
@@ -54,6 +54,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class ListContent extends SliceContent {
private SliceAction mPrimaryAction;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java b/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
index 0404633..b7c5639 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/LocationBasedViewTracker.java
@@ -34,6 +34,7 @@
* Utility class to track view based on relative location to the parent.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
+@Deprecated
public class LocationBasedViewTracker implements Runnable, View.OnLayoutChangeListener {
private static final SelectionLogic INPUT_FOCUS = new SelectionLogic() {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java b/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
index 8e44e18..d14fbdb 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/MessageView.java
@@ -40,6 +40,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class MessageView extends SliceChildView {
private TextView mDetails;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java b/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
index 09392c9..5e89e45 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RemoteInputView.java
@@ -63,6 +63,7 @@
@SuppressWarnings("AppCompatCustomView")
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(21)
+@Deprecated
public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
private static final String TAG = "RemoteInput";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
index eed88be..dcd403c 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowContent.java
@@ -60,6 +60,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
@RequiresApi(19)
+@Deprecated
public class RowContent extends SliceContent {
private static final String TAG = "RowContent";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
index 02416ac..960bfcc 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyle.java
@@ -31,6 +31,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class RowStyle {
public static final int UNBOUNDED = -1;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
index 39e9b74..4ada731 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowStyleFactory.java
@@ -22,7 +22,12 @@
/**
* Factory to return different styles for child views of a slice.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+@Deprecated
public interface RowStyleFactory {
/**
* Returns the style resource to use for this child.
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java b/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
index 34d23e5..84f7712 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/RowView.java
@@ -119,8 +119,13 @@
/**
* Row item is in small template format and can be used to construct list items for use
* with {@link TemplateView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class RowView extends SliceChildView implements View.OnClickListener,
AdapterView.OnItemSelectedListener {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java b/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
index 89ae3d0..5fa1625 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/ShortcutView.java
@@ -42,6 +42,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class ShortcutView extends SliceChildView {
private static final String TAG = "ShortcutView";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
index e5b20d0..0ffdf27 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceActionView.java
@@ -56,6 +56,7 @@
@SuppressWarnings("AppCompatCustomView")
@RestrictTo(LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SliceActionView extends FrameLayout implements View.OnClickListener,
CompoundButton.OnCheckedChangeListener {
private static final String TAG = "SliceActionView";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
index f8ae73a..4fc45ab 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceAdapter.java
@@ -47,9 +47,14 @@
/**
* RecyclerView.Adapter for the Slice components.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@SuppressWarnings("HiddenSuperclass")
@RequiresApi(19)
+@Deprecated
public class SliceAdapter extends RecyclerView.Adapter<SliceAdapter.SliceViewHolder>
implements SliceActionView.SliceActionLoadingListener {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
index b0795bf..9b1b11b 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceChildView.java
@@ -35,8 +35,13 @@
/**
* Base class for children views of {@link SliceView}.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public abstract class SliceChildView extends FrameLayout {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
index a56e1e9..0d03a82 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceContent.java
@@ -53,8 +53,13 @@
/**
* Base class representing content that can be displayed.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceContent {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
index 8fc3a85..ee2ca9e 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceLiveData.java
@@ -55,8 +55,13 @@
*
* @see #fromUri(Context, Uri)
* @see LiveData
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public final class SliceLiveData {
private static final String TAG = "SliceLiveData";
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
index faa5e10..30178d3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetrics.java
@@ -29,6 +29,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
class SliceMetrics {
public static @Nullable SliceMetrics getInstance(@NonNull Context context, @NonNull Uri uri) {
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
index adb43d8..fe4abcca 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceMetricsWrapper.java
@@ -27,6 +27,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(api = 28)
+@Deprecated
class SliceMetricsWrapper extends SliceMetrics {
private final android.app.slice.SliceMetrics mSliceMetrics;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
index 2440e67..7d86b57 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceStyle.java
@@ -44,6 +44,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SliceStyle {
private int mTintColor = -1;
private final int mTitleColor;
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
index 8c6fd44..43cbaf8 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceView.java
@@ -94,8 +94,13 @@
*
* @see Slice
* @see SliceLiveData
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@RequiresApi(19)
+@Deprecated
public class SliceView extends ViewGroup implements Observer<Slice>, View.OnClickListener {
private static final String TAG = "SliceView";
@@ -104,7 +109,12 @@
* Implement this interface to be notified of interactions with the slice displayed
* in this view.
* @see EventInfo
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
+ @Deprecated
public interface OnSliceActionListener {
/**
* Called when an interaction has occurred with an element in this view.
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
index 57dcf89..3c649ac 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewPolicy.java
@@ -27,6 +27,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SliceViewPolicy {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
index f4f0499..bb0eef3 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/SliceViewUtil.java
@@ -54,6 +54,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
+@Deprecated
public class SliceViewUtil {
/**
diff --git a/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java b/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
index b26282b..fca31f0 100644
--- a/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
+++ b/slice/slice-view/src/main/java/androidx/slice/widget/TemplateView.java
@@ -36,9 +36,14 @@
/**
* Slice template containing all view components.
+ *
+ * @deprecated Slice framework has been deprecated, it will not receive any updates moving
+ * forward. If you are looking for a framework that handles communication across apps,
+ * consider using {@link android.app.appsearch.AppSearchManager}.
*/
@SuppressWarnings("HiddenSuperclass")
@RequiresApi(19)
+@Deprecated
public class TemplateView extends SliceChildView implements
SliceViewPolicy.PolicyChangeListener {
diff --git a/transition/transition/build.gradle b/transition/transition/build.gradle
index 165c99b..693fbbd 100644
--- a/transition/transition/build.gradle
+++ b/transition/transition/build.gradle
@@ -10,7 +10,7 @@
api("androidx.annotation:annotation:1.2.0")
api("androidx.core:core:1.12.0")
implementation("androidx.collection:collection:1.1.0")
- compileOnly("androidx.fragment:fragment:1.2.5")
+ compileOnly(projectOrArtifact(":fragment:fragment"))
compileOnly("androidx.appcompat:appcompat:1.0.1")
implementation("androidx.dynamicanimation:dynamicanimation:1.0.0")
diff --git a/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
new file mode 100644
index 0000000..868a511
--- /dev/null
+++ b/transition/transition/src/androidTest/java/androidx/transition/FragmentTransitionSeekingTest.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2023 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.transition
+
+import android.os.Build
+import android.window.BackEvent
+import androidx.activity.BackEventCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.waitForExecution
+import androidx.transition.test.R
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class FragmentTransitionSeekingTest {
+
+ @Suppress("DEPRECATION")
+ @get:Rule
+ val activityRule = androidx.test.rule.ActivityTestRule(
+ FragmentTransitionTestActivity::class.java
+ )
+
+ @Test
+ fun replaceOperationWithTransitionsThenGestureBack() {
+ val fm1 = activityRule.activity.supportFragmentManager
+
+ var startedEnter = false
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragment1.setReenterTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedEnter = true
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ val startedExitCountDownLatch = CountDownLatch(1)
+ val fragment2 = TransitionFragment()
+ fragment2.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2, "2")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val dispatcher = activityRule.activity.onBackPressedDispatcher
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackProgressed(
+ BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+ )
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(startedEnter).isTrue()
+ assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ activityRule.runOnUiThread {
+ dispatcher.onBackPressed()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ fragment1.waitForTransition()
+
+ assertThat(fragment2.isAdded).isFalse()
+ assertThat(fm1.findFragmentByTag("2"))
+ .isEqualTo(null)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment1.requireView().parent).isNotNull()
+ }
+
+ @Test
+ fun replaceOperationWithTransitionsThenBackCancelled() {
+ val fm1 = activityRule.activity.supportFragmentManager
+
+ var startedEnter = false
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragment1.setReenterTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedEnter = true
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ val startedExitCountDownLatch = CountDownLatch(1)
+ val fragment2 = TransitionFragment()
+ fragment2.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2, "2")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val dispatcher = activityRule.activity.onBackPressedDispatcher
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackProgressed(
+ BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+ )
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(startedEnter).isTrue()
+ assertThat(startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackCancelled()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ fragment1.waitForTransition()
+
+ assertThat(fragment2.isAdded).isTrue()
+ assertThat(fm1.findFragmentByTag("2")).isEqualTo(fragment2)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment2.requireView()).isNotNull()
+ }
+
+ @Test
+ fun replaceOperationWithTransitionsThenGestureBackTwice() {
+ val fm1 = activityRule.activity.supportFragmentManager
+
+ var startedEnter = false
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragment1.setReenterTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedEnter = true
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ val fragment2startedExitCountDownLatch = CountDownLatch(1)
+ val fragment2 = TransitionFragment()
+ fragment2.setReenterTransition(Fade().apply { duration = 300 })
+ fragment2.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ fragment2startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2, "2")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val fragment3startedExitCountDownLatch = CountDownLatch(1)
+ val fragment3 = TransitionFragment()
+ fragment3.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ fragment3startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3, "3")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ assertThat(fragment3.startTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+ // We need to wait for the exit animation to end
+ assertThat(fragment2.endTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+
+ val dispatcher = activityRule.activity.onBackPressedDispatcher
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackProgressed(
+ BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+ )
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(fragment3startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ activityRule.runOnUiThread {
+ dispatcher.onBackPressed()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ fragment2.waitForTransition()
+ fragment3.waitForTransition()
+
+ assertThat(fragment3.isAdded).isFalse()
+ assertThat(fm1.findFragmentByTag("3")).isEqualTo(null)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment2.requireView().parent).isNotNull()
+
+ val fragment2ResumedLatch = CountDownLatch(1)
+ activityRule.runOnUiThread {
+ fragment2.lifecycle.addObserver(
+ object : LifecycleEventObserver {
+ override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+ if (event.targetState == Lifecycle.State.RESUMED) {
+ fragment2ResumedLatch.countDown()
+ }
+ }
+ }
+ )
+ }
+
+ assertThat(fragment2ResumedLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackProgressed(
+ BackEventCompat(0.2F, 0.2F, 0.2F, BackEvent.EDGE_LEFT)
+ )
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(startedEnter).isTrue()
+ assertThat(fragment2startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ activityRule.runOnUiThread {
+ dispatcher.onBackPressed()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ fragment1.waitForTransition()
+
+ assertThat(fragment2.isAdded).isFalse()
+ assertThat(fm1.findFragmentByTag("2")).isEqualTo(null)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment1.requireView().parent).isNotNull()
+ }
+
+ @Test
+ fun replaceOperationWithTransitionsThenOnBackPressedTwice() {
+ val fm1 = activityRule.activity.supportFragmentManager
+
+ var startedEnter = false
+ val fragment1 = TransitionFragment(R.layout.scene1)
+ fragment1.setReenterTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ startedEnter = true
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment1, "1")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.waitForExecution()
+
+ val fragment2startedExitCountDownLatch = CountDownLatch(1)
+ val fragment2 = TransitionFragment()
+ fragment2.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ fragment2startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment2, "2")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ fragment1.waitForTransition()
+ fragment2.waitForTransition()
+
+ val fragment3startedExitCountDownLatch = CountDownLatch(1)
+ val fragment3 = TransitionFragment()
+ fragment3.setReturnTransition(Fade().apply {
+ duration = 300
+ addListener(object : TransitionListenerAdapter() {
+ override fun onTransitionStart(transition: Transition) {
+ fragment3startedExitCountDownLatch.countDown()
+ }
+ })
+ })
+
+ fm1.beginTransaction()
+ .replace(R.id.fragmentContainer, fragment3, "3")
+ .setReorderingAllowed(true)
+ .addToBackStack(null)
+ .commit()
+ activityRule.executePendingTransactions()
+
+ assertThat(fragment3.startTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+ // We need to wait for the exit animation to end
+ assertThat(fragment2.endTransitionCountDownLatch.await(1000, TimeUnit.MILLISECONDS))
+ .isTrue()
+
+ val dispatcher = activityRule.activity.onBackPressedDispatcher
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.onBackPressed()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(fragment3startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ fragment2.waitForTransition()
+ fragment3.waitForTransition()
+
+ assertThat(fragment3.isAdded).isFalse()
+ assertThat(fm1.findFragmentByTag("3")).isEqualTo(null)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment2.requireView().parent).isNotNull()
+
+ activityRule.runOnUiThread {
+ dispatcher.dispatchOnBackStarted(BackEventCompat(0.1F, 0.1F, 0.1F, BackEvent.EDGE_LEFT))
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ activityRule.runOnUiThread {
+ dispatcher.onBackPressed()
+ }
+ activityRule.executePendingTransactions(fm1)
+
+ assertThat(startedEnter).isTrue()
+ assertThat(fragment2startedExitCountDownLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue()
+
+ fragment1.waitForTransition()
+
+ assertThat(fragment2.isAdded).isFalse()
+ assertThat(fm1.findFragmentByTag("2")).isEqualTo(null)
+
+ // Make sure the original fragment was correctly readded to the container
+ assertThat(fragment1.requireView().parent).isNotNull()
+ }
+}
diff --git a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index 217c891..6a0d6c9 100644
--- a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -20,6 +20,7 @@
import android.annotation.SuppressLint;
import android.graphics.Rect;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -228,6 +229,54 @@
}
@Override
+ public boolean isSeekingSupported(@NonNull Object transition) {
+ boolean supported = ((Transition) transition).isSeekingSupported();
+ if (!supported) {
+ Log.v("FragmentManager",
+ "Predictive back not available for AndroidX Transition "
+ + transition + ". Please enable seeking support for the designated "
+ + "transition by overriding isSeekingSupported().");
+ }
+ return supported;
+ }
+
+ @Override
+ @Nullable
+ public Object controlDelayedTransition(@NonNull ViewGroup sceneRoot,
+ @NonNull Object transition) {
+ return TransitionManager.controlDelayedTransition(sceneRoot, (Transition) transition);
+ }
+
+ @Override
+ public void setCurrentPlayTime(@NonNull Object transitionController, float progress) {
+ TransitionSeekController controller = (TransitionSeekController) transitionController;
+ if (controller.isReady()) {
+ long time = (long) (progress * controller.getDurationMillis());
+ // We cannot let the time get to 0 or the totalDuration to avoid
+ // completing the operation accidentally.
+ if (time == 0L) {
+ time = 1L;
+ }
+ if (time == controller.getDurationMillis()) {
+ time = controller.getDurationMillis() - 1;
+ }
+ controller.setCurrentPlayTimeMillis(time);
+ }
+ }
+
+ @Override
+ public void animateToEnd(@NonNull Object transitionController) {
+ TransitionSeekController controller = (TransitionSeekController) transitionController;
+ controller.animateToEnd();
+ }
+
+ @Override
+ public void animateToStart(@NonNull Object transitionController) {
+ TransitionSeekController controller = (TransitionSeekController) transitionController;
+ controller.animateToStart();
+ }
+
+ @Override
public void scheduleRemoveTargets(final @NonNull Object overallTransitionObj,
final @Nullable Object enterTransition, final @Nullable ArrayList<View> enteringViews,
final @Nullable Object exitTransition, final @Nullable ArrayList<View> exitingViews,
@@ -269,11 +318,23 @@
public void setListenerForTransitionEnd(@NonNull final Fragment outFragment,
@NonNull final Object transition, @NonNull final CancellationSignal signal,
@NonNull final Runnable transitionCompleteRunnable) {
+ setListenerForTransitionEnd(outFragment, transition, signal,
+ null, transitionCompleteRunnable);
+ }
+
+ @Override
+ public void setListenerForTransitionEnd(@NonNull Fragment outFragment,
+ @NonNull Object transition, @NonNull CancellationSignal signal,
+ @Nullable Runnable cancelRunnable, @NonNull Runnable transitionCompleteRunnable) {
final Transition realTransition = ((Transition) transition);
signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
@Override
public void onCancel() {
- realTransition.cancel();
+ if (cancelRunnable == null) {
+ realTransition.cancel();
+ } else {
+ cancelRunnable.run();
+ }
}
});
realTransition.addListener(new Transition.TransitionListener() {
diff --git a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
index 1557ac8..c9d1f31 100644
--- a/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
+++ b/tv/integration-tests/playground/src/main/java/androidx/tv/integration/playground/NavigationDrawer.kt
@@ -16,9 +16,7 @@
package androidx.tv.integration.playground
-import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -26,38 +24,30 @@
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectableGroup
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
+import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.semantics.selected
-import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.zIndex
-import androidx.tv.material3.DrawerValue
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Icon
import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.NavigationDrawerItem
+import androidx.tv.material3.NavigationDrawerItemDefaults
+import androidx.tv.material3.NavigationDrawerScope
import androidx.tv.material3.Text
@OptIn(ExperimentalTvMaterial3Api::class)
@@ -68,14 +58,7 @@
CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
Row(Modifier.fillMaxSize()) {
Box(modifier = Modifier.height(400.dp)) {
- NavigationDrawer(
- drawerContent = { drawerValue ->
- Sidebar(
- drawerValue = drawerValue,
- direction = direction,
- )
- }
- ) {
+ NavigationDrawer(drawerContent = { Sidebar(direction = direction) }) {
CommonBackground()
}
}
@@ -92,12 +75,7 @@
Row(Modifier.fillMaxSize()) {
Box(modifier = Modifier.height(400.dp)) {
androidx.tv.material3.ModalNavigationDrawer(
- drawerContent = { drawerValue ->
- Sidebar(
- drawerValue = drawerValue,
- direction = direction,
- )
- }
+ drawerContent = { Sidebar(direction = direction) }
) {
CommonBackground()
}
@@ -115,10 +93,7 @@
@OptIn(ExperimentalTvMaterial3Api::class)
@Composable
-private fun Sidebar(
- drawerValue: DrawerValue,
- direction: MutableState<LayoutDirection>,
-) {
+private fun NavigationDrawerScope.Sidebar(direction: MutableState<LayoutDirection>) {
val selectedIndex = remember { mutableStateOf(0) }
LaunchedEffect(selectedIndex.value) {
@@ -132,81 +107,52 @@
modifier = Modifier
.fillMaxHeight()
.background(pageColor)
+ .padding(12.dp)
.selectableGroup(),
- horizontalAlignment = Alignment.CenterHorizontally,
+ horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
- NavigationItem(
- imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
- text = "LTR",
- drawerValue = drawerValue,
- selectedIndex = selectedIndex,
- index = 0
- )
- NavigationItem(
- imageVector = Icons.AutoMirrored.Default.KeyboardArrowLeft,
- text = "RTL",
- drawerValue = drawerValue,
- selectedIndex = selectedIndex,
- index = 1
- )
- }
-}
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-private fun NavigationItem(
- imageVector: ImageVector,
- text: String,
- drawerValue: DrawerValue,
- selectedIndex: MutableState<Int>,
- index: Int,
- modifier: Modifier = Modifier,
-) {
- var isFocused by remember { mutableStateOf(false) }
-
- Box(
- modifier = modifier
- .clip(RoundedCornerShape(10.dp))
- .onFocusChanged { isFocused = it.isFocused }
- .background(if (isFocused) Color.White else Color.Transparent)
- .semantics(mergeDescendants = true) {
- selected = selectedIndex.value == index
- }
- .clickable {
- selectedIndex.value = index
- }
- ) {
- Box(modifier = Modifier.padding(10.dp)) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(5.dp),
- ) {
+ NavigationDrawerItem(
+ selected = true,
+ onClick = { },
+ leadingContent = {
Icon(
- imageVector = imageVector,
- tint = if (isFocused) pageColor else Color.White,
+ imageVector = Icons.Default.Settings,
contentDescription = null,
)
- AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
- Text(
- text = text,
- modifier = Modifier,
- softWrap = false,
- color = if (isFocused) pageColor else Color.White,
- )
- }
+ },
+ supportingContent = {
+ Text("Switch account")
+ },
+ trailingContent = {
+ NavigationDrawerItemDefaults.TrailingBadge("NEW")
}
- if (selectedIndex.value == index) {
- Box(
- modifier = Modifier
- .width(10.dp)
- .height(3.dp)
- .offset(y = 5.dp)
- .align(Alignment.BottomCenter)
- .background(Color.Red)
- .zIndex(10f)
+ ) {
+ Text(text = "Hi there")
+ }
+ NavigationDrawerItem(
+ selected = selectedIndex.value == 0,
+ onClick = { selectedIndex.value = 0 },
+ leadingContent = {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.KeyboardArrowRight,
+ contentDescription = null,
)
- }
+ },
+ ) {
+ Text(text = "Left to right")
+ }
+ NavigationDrawerItem(
+ selected = selectedIndex.value == 1,
+ onClick = { selectedIndex.value = 1 },
+ leadingContent = {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.KeyboardArrowLeft,
+ contentDescription = null,
+ )
+ },
+ ) {
+ Text(text = "Right to left")
}
}
}
diff --git a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
index dc061ec..e96c9d9 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
@@ -17,62 +17,79 @@
package androidx.tv.samples
import androidx.annotation.Sampled
-import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.tv.material3.DrawerValue
import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Icon
import androidx.tv.material3.ModalNavigationDrawer
import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.NavigationDrawerItem
import androidx.tv.material3.Text
@OptIn(ExperimentalTvMaterial3Api::class)
@Sampled
@Composable
fun SampleNavigationDrawer() {
- val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
- { drawerValue, color, text ->
- Row(Modifier.padding(10.dp).focusable()) {
- Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
- AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
- Text(
- text = text,
- softWrap = false,
- modifier = Modifier.padding(15.dp).width(50.dp),
- textAlign = TextAlign.Center
- )
- }
- }
- }
+ var selectedIndex by remember { mutableIntStateOf(0) }
+
+ val items = listOf(
+ "Home" to Icons.Default.Home,
+ "Settings" to Icons.Default.Settings,
+ "Favourites" to Icons.Default.Favorite,
+ )
NavigationDrawer(
drawerContent = {
- Column(Modifier.background(Color.Gray).fillMaxHeight()) {
- navigationRow(it, Color.Red, "Red")
- navigationRow(it, Color.Blue, "Blue")
- navigationRow(it, Color.Yellow, "Yellow")
+ Column(
+ Modifier
+ .background(Color.Gray)
+ .fillMaxHeight()
+ .padding(12.dp)
+ .selectableGroup(),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ items.forEachIndexed { index, item ->
+ val (text, icon) = item
+
+ NavigationDrawerItem(
+ selected = selectedIndex == index,
+ onClick = { selectedIndex = index },
+ leadingContent = {
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ )
+ }
+ ) {
+ Text(text)
+ }
+ }
}
}
) {
- Button(modifier = Modifier
- .height(100.dp)
- .fillMaxWidth(), onClick = {}) {
+ Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
Text("BUTTON")
}
}
@@ -82,33 +99,45 @@
@Sampled
@Composable
fun SampleModalNavigationDrawerWithSolidScrim() {
- val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
- { drawerValue, color, text ->
- Row(Modifier.padding(10.dp).focusable()) {
- Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
- AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
- Text(
- text = text,
- softWrap = false,
- modifier = Modifier.padding(15.dp).width(50.dp),
- textAlign = TextAlign.Center
- )
- }
- }
- }
+ var selectedIndex by remember { mutableIntStateOf(0) }
+
+ val items = listOf(
+ "Home" to Icons.Default.Home,
+ "Settings" to Icons.Default.Settings,
+ "Favourites" to Icons.Default.Favorite,
+ )
ModalNavigationDrawer(
drawerContent = {
- Column(Modifier.background(Color.Gray).fillMaxHeight()) {
- navigationRow(it, Color.Red, "Red")
- navigationRow(it, Color.Blue, "Blue")
- navigationRow(it, Color.Yellow, "Yellow")
+ Column(
+ Modifier
+ .background(Color.Gray)
+ .fillMaxHeight()
+ .padding(12.dp)
+ .selectableGroup(),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ items.forEachIndexed { index, item ->
+ val (text, icon) = item
+
+ NavigationDrawerItem(
+ selected = selectedIndex == index,
+ onClick = { selectedIndex = index },
+ leadingContent = {
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ )
+ }
+ ) {
+ Text(text)
+ }
+ }
}
}
) {
- Button(modifier = Modifier
- .height(100.dp)
- .fillMaxWidth(), onClick = {}) {
+ Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
Text("BUTTON")
}
}
@@ -118,34 +147,46 @@
@Sampled
@Composable
fun SampleModalNavigationDrawerWithGradientScrim() {
- val navigationRow: @Composable (drawerValue: DrawerValue, color: Color, text: String) -> Unit =
- { drawerValue, color, text ->
- Row(Modifier.padding(10.dp).focusable()) {
- Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
- AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
- Text(
- text = text,
- softWrap = false,
- modifier = Modifier.padding(15.dp).width(50.dp),
- textAlign = TextAlign.Center
- )
- }
- }
- }
+ var selectedIndex by remember { mutableIntStateOf(0) }
- androidx.tv.material3.ModalNavigationDrawer(
+ val items = listOf(
+ "Home" to Icons.Default.Home,
+ "Settings" to Icons.Default.Settings,
+ "Favourites" to Icons.Default.Favorite,
+ )
+
+ ModalNavigationDrawer(
drawerContent = {
- Column(Modifier.fillMaxHeight()) {
- navigationRow(it, Color.Red, "Red")
- navigationRow(it, Color.Blue, "Blue")
- navigationRow(it, Color.Yellow, "Yellow")
+ Column(
+ Modifier
+ .background(Color.Gray)
+ .fillMaxHeight()
+ .padding(12.dp)
+ .selectableGroup(),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ items.forEachIndexed { index, item ->
+ val (text, icon) = item
+
+ NavigationDrawerItem(
+ selected = selectedIndex == index,
+ onClick = { selectedIndex = index },
+ leadingContent = {
+ Icon(
+ imageVector = icon,
+ contentDescription = null,
+ )
+ }
+ ) {
+ Text(text)
+ }
+ }
}
},
scrimBrush = Brush.horizontalGradient(listOf(Color.DarkGray, Color.Transparent))
) {
- Button(modifier = Modifier
- .height(100.dp)
- .fillMaxWidth(), onClick = {}) {
+ Button(modifier = Modifier.height(100.dp).fillMaxWidth(), onClick = {}) {
Text("BUTTON")
}
}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
index 967d1c2f..cc97ac8 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItem.kt
@@ -113,25 +113,25 @@
leadingContent()
}
},
- trailingContent = if (trailingContent == null) null else {
+ trailingContent = trailingContent?.let {
{
AnimatedVisibility(
visible = doesNavigationDrawerHaveFocus,
enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
exit = NavigationDrawerItemDefaults.ContentAnimationExit,
) {
- trailingContent()
+ it()
}
}
},
- supportingContent = if (supportingContent == null) null else {
+ supportingContent = supportingContent?.let {
{
AnimatedVisibility(
visible = doesNavigationDrawerHaveFocus,
enter = NavigationDrawerItemDefaults.ContentAnimationEnter,
exit = NavigationDrawerItemDefaults.ContentAnimationExit,
) {
- supportingContent()
+ it()
}
}
},
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
index 44de75e..debfd6f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemDefaults.kt
@@ -38,27 +38,27 @@
import androidx.tv.material3.tokens.Elevation
/**
- * Contains the default values used by selectable NavigationDrawerItem
+ * Contains the default values used by selectable [NavigationDrawerItem]
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
object NavigationDrawerItemDefaults {
/**
- * The default Icon size used by NavigationDrawerItem
+ * The default Icon size used by [NavigationDrawerItem]
*/
val IconSize = 24.dp
/**
- * The size of the NavigationDrawerItem when the drawer is collapsed
+ * The size of the [NavigationDrawerItem] when the drawer is collapsed
*/
val CollapsedDrawerItemWidth = 56.dp
/**
- * The size of the NavigationDrawerItem when the drawer is expanded
+ * The size of the [NavigationDrawerItem] when the drawer is expanded
*/
val ExpandedDrawerItemWidth = 256.dp
/**
- * The default content padding [PaddingValues] used by NavigationDrawerItem when the drawer
+ * The default content padding [PaddingValues] used by [NavigationDrawerItem] when the drawer
* is expanded
*/
@@ -66,7 +66,7 @@
val ContainerHeightTwoLine = 64.dp
/**
- * The default elevation used by NavigationDrawerItem
+ * The default elevation used by [NavigationDrawerItem]
*/
val NavigationDrawerItemElevation = Elevation.Level0
@@ -81,7 +81,7 @@
val ContentAnimationExit = fadeOut() + slideOut { IntOffset(0, 0) }
/**
- * Default border used by NavigationDrawerItem
+ * Default border used by [NavigationDrawerItem]
*/
val DefaultBorder
@ReadOnlyComposable
@@ -93,28 +93,28 @@
)
/**
- * The default container color used by NavigationDrawerItem's trailing badge
+ * The default container color used by [NavigationDrawerItem]'s trailing badge
*/
val TrailingBadgeContainerColor
@ReadOnlyComposable
@Composable get() = MaterialTheme.colorScheme.tertiary
/**
- * The default text style used by NavigationDrawerItem's trailing badge
+ * The default text style used by [NavigationDrawerItem]'s trailing badge
*/
val TrailingBadgeTextStyle
@ReadOnlyComposable
@Composable get() = MaterialTheme.typography.labelSmall
/**
- * The default content color used by NavigationDrawerItem's trailing badge
+ * The default content color used by [NavigationDrawerItem]'s trailing badge
*/
val TrailingBadgeContentColor
@ReadOnlyComposable
@Composable get() = MaterialTheme.colorScheme.onTertiary
/**
- * Creates a trailing badge for NavigationDrawerItem
+ * Creates a trailing badge for [NavigationDrawerItem]
*/
@Composable
fun TrailingBadge(
@@ -138,18 +138,18 @@
/**
* Creates a [NavigationDrawerItemShape] that represents the default container shapes
- * used in a selectable NavigationDrawerItem
+ * used in a selectable [NavigationDrawerItem]
*
- * @param shape the default shape used when the NavigationDrawerItem is enabled
- * @param focusedShape the shape used when the NavigationDrawerItem is enabled and focused
- * @param pressedShape the shape used when the NavigationDrawerItem is enabled and pressed
- * @param selectedShape the shape used when the NavigationDrawerItem is enabled and selected
- * @param disabledShape the shape used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param shape the default shape used when the [NavigationDrawerItem] is enabled
+ * @param focusedShape the shape used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedShape the shape used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedShape the shape used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledShape the shape used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledShape the shape used when the NavigationDrawerItem is not enabled
+ * @param focusedDisabledShape the shape used when the [NavigationDrawerItem] is not enabled
* and focused
- * @param pressedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
fun shape(
@@ -174,38 +174,38 @@
/**
* Creates a [NavigationDrawerItemColors] that represents the default container &
- * content colors used in a selectable NavigationDrawerItem
+ * content colors used in a selectable [NavigationDrawerItem]
*
- * @param containerColor the default container color used when the NavigationDrawerItem is
+ * @param containerColor the default container color used when the [NavigationDrawerItem] is
* enabled
- * @param contentColor the default content color used when the NavigationDrawerItem is enabled
+ * @param contentColor the default content color used when the [NavigationDrawerItem] is enabled
* @param inactiveContentColor the content color used when none of the navigation items have
* focus
- * @param focusedContainerColor the container color used when the NavigationDrawerItem is
+ * @param focusedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and focused
- * @param focusedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param focusedContentColor the content color used when the [NavigationDrawerItem] is enabled
* and focused
- * @param pressedContainerColor the container color used when the NavigationDrawerItem is
+ * @param pressedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and pressed
- * @param pressedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param pressedContentColor the content color used when the [NavigationDrawerItem] is enabled
* and pressed
- * @param selectedContainerColor the container color used when the NavigationDrawerItem is
+ * @param selectedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and selected
- * @param selectedContentColor the content color used when the NavigationDrawerItem is
+ * @param selectedContentColor the content color used when the [NavigationDrawerItem] is
* enabled and selected
- * @param disabledContainerColor the container color used when the NavigationDrawerItem is
+ * @param disabledContainerColor the container color used when the [NavigationDrawerItem] is
* not enabled
- * @param disabledContentColor the content color used when the NavigationDrawerItem is not
+ * @param disabledContentColor the content color used when the [NavigationDrawerItem] is not
* enabled
* @param disabledInactiveContentColor the content color used when none of the navigation items
* have focus and this item is disabled
* @param focusedSelectedContainerColor the container color used when the
* NavigationDrawerItem is enabled, focused and selected
- * @param focusedSelectedContentColor the content color used when the NavigationDrawerItem
+ * @param focusedSelectedContentColor the content color used when the [NavigationDrawerItem]
* is enabled, focused and selected
* @param pressedSelectedContainerColor the container color used when the
- * NavigationDrawerItem is enabled, pressed and selected
- * @param pressedSelectedContentColor the content color used when the NavigationDrawerItem is
+ * [NavigationDrawerItem] is enabled, pressed and selected
+ * @param pressedSelectedContentColor the content color used when the [NavigationDrawerItem] is
* enabled, pressed and selected
*/
@ReadOnlyComposable
@@ -249,22 +249,22 @@
/**
* Creates a [NavigationDrawerItemScale] that represents the default scales used in a
- * selectable NavigationDrawerItem
+ * selectable [NavigationDrawerItem]
*
* scales are used to modify the size of a composable in different [Interaction] states
* e.g. `1f` (original) in default state, `1.2f` (scaled up) in focused state, `0.8f` (scaled
* down) in pressed state, etc.
*
- * @param scale the scale used when the NavigationDrawerItem is enabled
- * @param focusedScale the scale used when the NavigationDrawerItem is enabled and focused
- * @param pressedScale the scale used when the NavigationDrawerItem is enabled and pressed
- * @param selectedScale the scale used when the NavigationDrawerItem is enabled and selected
- * @param disabledScale the scale used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param scale the scale used when the [NavigationDrawerItem] is enabled
+ * @param focusedScale the scale used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedScale the scale used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedScale the scale used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledScale the scale used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledScale the scale used when the NavigationDrawerItem is not enabled and
+ * @param focusedDisabledScale the scale used when the [NavigationDrawerItem] is not enabled and
* focused
- * @param pressedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
fun scale(
@@ -289,19 +289,19 @@
/**
* Creates a [NavigationDrawerItemBorder] that represents the default [Border]s
- * applied on a selectable NavigationDrawerItem in different [Interaction] states
+ * applied on a selectable [NavigationDrawerItem] in different [Interaction] states
*
- * @param border the default [Border] used when the NavigationDrawerItem is enabled
- * @param focusedBorder the [Border] used when the NavigationDrawerItem is enabled and focused
- * @param pressedBorder the [Border] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedBorder the [Border] used when the NavigationDrawerItem is enabled and
+ * @param border the default [Border] used when the [NavigationDrawerItem] is enabled
+ * @param focusedBorder the [Border] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedBorder the [Border] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedBorder the [Border] used when the [NavigationDrawerItem] is enabled and
* selected
- * @param disabledBorder the [Border] used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param disabledBorder the [Border] used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledBorder the [Border] used when the NavigationDrawerItem is not
+ * @param focusedDisabledBorder the [Border] used when the [NavigationDrawerItem] is not
* enabled and focused
- * @param pressedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
@ReadOnlyComposable
@@ -328,16 +328,16 @@
/**
* Creates a [NavigationDrawerItemGlow] that represents the default [Glow]s used in a
- * selectable NavigationDrawerItem
+ * selectable [NavigationDrawerItem]
*
- * @param glow the [Glow] used when the NavigationDrawerItem is enabled, and has no other
+ * @param glow the [Glow] used when the [NavigationDrawerItem] is enabled, and has no other
* [Interaction]s
- * @param focusedGlow the [Glow] used when the NavigationDrawerItem is enabled and focused
- * @param pressedGlow the [Glow] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedGlow the [Glow] used when the NavigationDrawerItem is enabled and selected
- * @param focusedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param focusedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and selected
+ * @param focusedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param pressedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
fun glow(
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
index 027ac27..f08e865 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerItemStyles.kt
@@ -23,21 +23,21 @@
import androidx.compose.ui.graphics.Shape
/**
- * Defines [Shape] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Shape] for all TV [Indication] states of a [NavigationDrawerItem]
*
- * @constructor create an instance with arbitrary shape. See NavigationDrawerItemDefaults.shape
- * for the default shape used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary shape. See [NavigationDrawerItemDefaults.shape]
+ * for the default shape used in a [NavigationDrawerItem]
*
- * @param shape the default shape used when the NavigationDrawerItem is enabled
- * @param focusedShape the shape used when the NavigationDrawerItem is enabled and focused
- * @param pressedShape the shape used when the NavigationDrawerItem is enabled and pressed
- * @param selectedShape the shape used when the NavigationDrawerItem is enabled and selected
- * @param disabledShape the shape used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param shape the default shape used when the [NavigationDrawerItem] is enabled
+ * @param focusedShape the shape used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedShape the shape used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedShape the shape used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledShape the shape used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledShape the shape used when the NavigationDrawerItem is not enabled
+ * @param focusedDisabledShape the shape used when the [NavigationDrawerItem] is not enabled
* and focused
- * @param pressedSelectedShape the shape used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedShape the shape used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -95,41 +95,41 @@
/**
* Defines container & content color [Color] for all TV [Indication] states of a
- * NavigationDrawerItem
+ * [NavigationDrawerItem]
*
- * @constructor create an instance with arbitrary colors. See NavigationDrawerItemDefaults.colors
- * for the default colors used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary colors. See [NavigationDrawerItemDefaults.colors]
+ * for the default colors used in a [NavigationDrawerItem]
*
- * @param containerColor the default container color used when the NavigationDrawerItem is
+ * @param containerColor the default container color used when the [NavigationDrawerItem] is
* enabled
- * @param contentColor the default content color used when the NavigationDrawerItem is enabled
+ * @param contentColor the default content color used when the [NavigationDrawerItem] is enabled
* @param inactiveContentColor the content color used when none of the navigation items have
* focus
- * @param focusedContainerColor the container color used when the NavigationDrawerItem is
+ * @param focusedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and focused
- * @param focusedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param focusedContentColor the content color used when the [NavigationDrawerItem] is enabled
* and focused
- * @param pressedContainerColor the container color used when the NavigationDrawerItem is
+ * @param pressedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and pressed
- * @param pressedContentColor the content color used when the NavigationDrawerItem is enabled
+ * @param pressedContentColor the content color used when the [NavigationDrawerItem] is enabled
* and pressed
- * @param selectedContainerColor the container color used when the NavigationDrawerItem is
+ * @param selectedContainerColor the container color used when the [NavigationDrawerItem] is
* enabled and selected
- * @param selectedContentColor the content color used when the NavigationDrawerItem is
+ * @param selectedContentColor the content color used when the [NavigationDrawerItem] is
* enabled and selected
- * @param disabledContainerColor the container color used when the NavigationDrawerItem is
+ * @param disabledContainerColor the container color used when the [NavigationDrawerItem] is
* not enabled
- * @param disabledContentColor the content color used when the NavigationDrawerItem is not
+ * @param disabledContentColor the content color used when the [NavigationDrawerItem] is not
* enabled
* @param disabledInactiveContentColor the content color used when none of the navigation items
* have focus and this item is disabled
* @param focusedSelectedContainerColor the container color used when the
- * NavigationDrawerItem is enabled, focused and selected
- * @param focusedSelectedContentColor the content color used when the NavigationDrawerItem
+ * [NavigationDrawerItem] is enabled, focused and selected
+ * @param focusedSelectedContentColor the content color used when the [NavigationDrawerItem]
* is enabled, focused and selected
* @param pressedSelectedContainerColor the container color used when the
- * NavigationDrawerItem is enabled, pressed and selected
- * @param pressedSelectedContentColor the content color used when the NavigationDrawerItem is
+ * [NavigationDrawerItem] is enabled, pressed and selected
+ * @param pressedSelectedContentColor the content color used when the [NavigationDrawerItem] is
* enabled, pressed and selected
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -215,21 +215,21 @@
}
/**
- * Defines the scale for all TV [Indication] states of a NavigationDrawerItem
+ * Defines the scale for all TV [Indication] states of a [NavigationDrawerItem]
*
- * @constructor create an instance with arbitrary scale. See NavigationDrawerItemDefaults.scale
- * for the default scale used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary scale. See [NavigationDrawerItemDefaults.scale]
+ * for the default scale used in a [NavigationDrawerItem]
*
- * @param scale the scale used when the NavigationDrawerItem is enabled
- * @param focusedScale the scale used when the NavigationDrawerItem is enabled and focused
- * @param pressedScale the scale used when the NavigationDrawerItem is enabled and pressed
- * @param selectedScale the scale used when the NavigationDrawerItem is enabled and selected
- * @param disabledScale the scale used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param scale the scale used when the [NavigationDrawerItem] is enabled
+ * @param focusedScale the scale used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedScale the scale used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedScale the scale used when the [NavigationDrawerItem] is enabled and selected
+ * @param disabledScale the scale used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledScale the scale used when the NavigationDrawerItem is not enabled and
+ * @param focusedDisabledScale the scale used when the [NavigationDrawerItem] is not enabled and
* focused
- * @param pressedSelectedScale the scale used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedScale the scale used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -286,7 +286,7 @@
companion object {
/**
- * Signifies the absence of a [ScaleIndication] in NavigationDrawerItem
+ * Signifies the absence of a [ScaleIndication] in [NavigationDrawerItem]
*/
val None = NavigationDrawerItemScale(
scale = 1f,
@@ -302,22 +302,22 @@
}
/**
- * Defines [Border] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Border] for all TV [Indication] states of a [NavigationDrawerItem]
*
- * @constructor create an instance with arbitrary border. See NavigationDrawerItemDefaults.border
- * for the default border used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary border. See [NavigationDrawerItemDefaults.border]
+ * for the default border used in a [NavigationDrawerItem]
*
- * @param border the default [Border] used when the NavigationDrawerItem is enabled
- * @param focusedBorder the [Border] used when the NavigationDrawerItem is enabled and focused
- * @param pressedBorder the [Border] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedBorder the [Border] used when the NavigationDrawerItem is enabled and
+ * @param border the default [Border] used when the [NavigationDrawerItem] is enabled
+ * @param focusedBorder the [Border] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedBorder the [Border] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedBorder the [Border] used when the [NavigationDrawerItem] is enabled and
* selected
- * @param disabledBorder the [Border] used when the NavigationDrawerItem is not enabled
- * @param focusedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param disabledBorder the [Border] used when the [NavigationDrawerItem] is not enabled
+ * @param focusedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param focusedDisabledBorder the [Border] used when the NavigationDrawerItem is not
+ * @param focusedDisabledBorder the [Border] used when the [NavigationDrawerItem] is not
* enabled and focused
- * @param pressedSelectedBorder the [Border] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedBorder the [Border] used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
@@ -374,18 +374,18 @@
}
/**
- * Defines [Glow] for all TV [Indication] states of a NavigationDrawerItem
+ * Defines [Glow] for all TV [Indication] states of a [NavigationDrawerItem]
*
- * @constructor create an instance with arbitrary glow. See NavigationDrawerItemDefaults.glow
- * for the default glow used in a NavigationDrawerItem
+ * @constructor create an instance with arbitrary glow. See [NavigationDrawerItemDefaults.glow]
+ * for the default glow used in a [NavigationDrawerItem]
*
- * @param glow the [Glow] used when the NavigationDrawerItem is enabled
- * @param focusedGlow the [Glow] used when the NavigationDrawerItem is enabled and focused
- * @param pressedGlow the [Glow] used when the NavigationDrawerItem is enabled and pressed
- * @param selectedGlow the [Glow] used when the NavigationDrawerItem is enabled and selected
- * @param focusedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param glow the [Glow] used when the [NavigationDrawerItem] is enabled
+ * @param focusedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and focused
+ * @param pressedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and pressed
+ * @param selectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled and selected
+ * @param focusedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
* focused and selected
- * @param pressedSelectedGlow the [Glow] used when the NavigationDrawerItem is enabled,
+ * @param pressedSelectedGlow the [Glow] used when the [NavigationDrawerItem] is enabled,
* pressed and selected
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
index 27dccf0..85434a3 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawerScope.kt
@@ -17,7 +17,7 @@
package androidx.tv.material3
/**
- * [NavigationDrawerScope] is used to provide the isActivated state to the NavigationDrawerItem
+ * [NavigationDrawerScope] is used to provide the isActivated state to the [NavigationDrawerItem]
* composable
*/
@ExperimentalTvMaterial3Api // TODO (b/263353219): Remove this before launching beta
diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
index b12573c..77b9215 100644
--- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
+++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableWatchFaceServiceTest.kt
@@ -102,6 +102,7 @@
private var surfaceHolderOverride: SurfaceHolder,
) : ListenableWatchFaceRuntimeService() {
lateinit var lastResourceOnlyWatchFacePackageName: String
+ val lastResourceOnlyWatchFacePackageNameLatch = CountDownLatch(1)
init {
attachBaseContext(testContext)
@@ -117,6 +118,7 @@
resourceOnlyWatchFacePackageName: String
): ListenableFuture<WatchFace> {
lastResourceOnlyWatchFacePackageName = resourceOnlyWatchFacePackageName
+ lastResourceOnlyWatchFacePackageNameLatch.countDown()
val future = SettableFuture.create<WatchFace>()
// Post a task to resolve the future.
@@ -210,9 +212,12 @@
val client = awaitWithTimeout(deferredClient)
- // To avoid a race condition, we need to wait for the watchface to be fully created, which
- // this does.
- client.complicationSlotsState
+ Assert.assertTrue(
+ service.lastResourceOnlyWatchFacePackageNameLatch.await(
+ TIME_OUT_MILLIS,
+ TimeUnit.MILLISECONDS
+ )
+ )
assertThat(service.lastResourceOnlyWatchFacePackageName).isEqualTo("com.example.wf")
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 568894e..74af498 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -1367,6 +1367,16 @@
)
writer.println("currentUserStyleRepository.schema=${currentUserStyleRepository.schema}")
writer.println("editorObscuresWatchFace=$editorObscuresWatchFace")
+ writer.println("additionalContentDescriptionLabels:")
+ writer.increaseIndent()
+ for (label in renderer.additionalContentDescriptionLabels) {
+ if (Build.TYPE.equals("userdebug")) {
+ writer.println("${label.first}: ${label.second}")
+ } else {
+ writer.println("${label.first}: Redacted")
+ }
+ }
+ writer.decreaseIndent()
overlayStyle.dump(writer)
watchState.dump(writer)
complicationSlotsManager.dump(writer)