Merge "[GH] Import JVM annotations" into androidx-main
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
index 72087b1..a2f8213 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/MainActivity.kt
@@ -77,7 +77,7 @@
val navController = findNavController(R.id.nav_host_fragment_activity_main)
val appBarConfiguration = AppBarConfiguration(
- setOf(R.id.navigation_home, R.id.navigation_scanner, R.id.navigation_advertiser)
+ setOf(R.id.navigation_scanner, R.id.navigation_advertiser)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/BluetoothLe.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/BluetoothLe.kt
index 54d477c..91afd29 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/BluetoothLe.kt
+++ b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/BluetoothLe.kt
@@ -23,15 +23,8 @@
import android.bluetooth.BluetoothGattServerCallback
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothManager
-import android.bluetooth.le.AdvertiseCallback
-import android.bluetooth.le.AdvertiseData
-import android.bluetooth.le.AdvertiseSettings
-import android.bluetooth.le.ScanCallback
-import android.bluetooth.le.ScanResult
-import android.bluetooth.le.ScanSettings
import android.content.Context
import android.util.Log
-import java.util.UUID
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
@@ -48,92 +41,6 @@
private val bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
- // Permissions are handled by MainActivity requestBluetoothPermissions
- @SuppressLint("MissingPermission")
- fun scan(
- settings: ScanSettings
- ): Flow<ScanResult> =
- callbackFlow {
- val callback = object : ScanCallback() {
- override fun onScanResult(callbackType: Int, result: ScanResult) {
- trySend(result)
- }
-
- override fun onScanFailed(errorCode: Int) {
- Log.d(TAG, "onScanFailed() called with: errorCode = $errorCode")
- }
- }
-
- val bluetoothAdapter = bluetoothManager?.adapter
- val bleScanner = bluetoothAdapter?.bluetoothLeScanner
-
- bleScanner?.startScan(null, settings, callback)
-
- awaitClose {
- Log.d(TAG, "awaitClose() called")
- bleScanner?.stopScan(callback)
- }
- }
-
- // Permissions are handled by MainActivity requestBluetoothPermissions
- @SuppressLint("MissingPermission")
- fun advertise(
- settings: AdvertiseSettings,
- data: AdvertiseData
- ): Flow<AdvertiseResult> =
- callbackFlow {
- val callback = object : AdvertiseCallback() {
- override fun onStartFailure(errorCode: Int) {
- // TODO(ofy) Map to proper errorCodes
- Log.d(TAG, "onStartFailure() called with: errorCode = $errorCode")
- trySend(AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR)
- }
-
- override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
- trySend(AdvertiseResult.ADVERTISE_STARTED)
- }
- }
-
- val bluetoothAdapter = bluetoothManager?.adapter
- val bleAdvertiser = bluetoothAdapter?.bluetoothLeAdvertiser
-
- bleAdvertiser?.startAdvertising(settings, data, callback)
-
- awaitClose {
- Log.d(TAG, "awaitClose() called")
- bleAdvertiser?.stopAdvertising(callback)
- }
- }
-
- interface GattClientScope {
-
- fun getServices(): List<BluetoothGattService>
- fun getService(uuid: UUID): BluetoothGattService?
-
- suspend fun readCharacteristic(characteristic: BluetoothGattCharacteristic):
- Result<ByteArray>
- suspend fun writeCharacteristic(
- characteristic: BluetoothGattCharacteristic,
- value: ByteArray,
- writeType: Int
- ): Result<Unit>
- suspend fun readDescriptor(descriptor: BluetoothGattDescriptor): Result<ByteArray>
- suspend fun writeDescriptor(
- descriptor: BluetoothGattDescriptor,
- value: ByteArray
- ): Result<Unit>
- fun subscribeToCharacteristic(characteristic: BluetoothGattCharacteristic): Flow<ByteArray>
- suspend fun awaitClose(onClosed: () -> Unit)
- }
-
- suspend fun <R> connectGatt(
- context: Context,
- device: BluetoothDevice,
- block: suspend GattClientScope.() -> R
- ): R? {
- return GattClientImpl().connect(context, device, block)
- }
-
@SuppressLint("MissingPermission")
fun openGattServer(
services: List<BluetoothGattService> = emptyList()
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/GattClientImpl.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/GattClientImpl.kt
deleted file mode 100644
index 4c61aa6..0000000
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/GattClientImpl.kt
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * 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.bluetooth.integration.testapp.experimental
-
-import android.annotation.SuppressLint
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothGatt
-import android.bluetooth.BluetoothGatt.GATT_SUCCESS
-import android.bluetooth.BluetoothGattCallback
-import android.bluetooth.BluetoothGattCharacteristic
-import android.bluetooth.BluetoothGattDescriptor
-import android.bluetooth.BluetoothGattService
-import android.content.Context
-import android.util.Log
-import androidx.collection.arrayMapOf
-import java.util.UUID
-import kotlin.coroutines.cancellation.CancellationException
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.job
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-
-internal class GattClientImpl {
-
- private companion object {
- private const val TAG = "GattClientImpl"
- private const val GATT_MAX_MTU = 517
- private val CCCD_UID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
- }
-
- private sealed interface CallbackResult {
- class OnCharacteristicRead(
- val characteristic: BluetoothGattCharacteristic,
- val value: ByteArray,
- val status: Int
- ) : CallbackResult
-
- class OnCharacteristicWrite(
- val characteristic: BluetoothGattCharacteristic,
- val status: Int
- ) : CallbackResult
-
- class OnDescriptorRead(
- val descriptor: BluetoothGattDescriptor,
- val value: ByteArray,
- val status: Int
- ) : CallbackResult
- class OnDescriptorWrite(
- val descriptor: BluetoothGattDescriptor,
- val status: Int
- ) : CallbackResult
- }
-
- private interface SubscribeListener {
- fun onCharacteristicNotification(value: ByteArray)
- fun finish()
- }
-
- @SuppressLint("MissingPermission")
- suspend fun <R> connect(
- context: Context,
- device: BluetoothDevice,
- block: suspend BluetoothLe.GattClientScope.() -> R
- ): R? = coroutineScope {
- val connectResult = CompletableDeferred<Boolean>(parent = coroutineContext.job)
- val finished = Job(parent = coroutineContext.job)
- val callbackResultsFlow = MutableSharedFlow<CallbackResult>(
- extraBufferCapacity = Int.MAX_VALUE)
- val subscribeMap: MutableMap<BluetoothGattCharacteristic, SubscribeListener> = arrayMapOf()
- val subscribeMutex = Mutex()
-
- val callback = object : BluetoothGattCallback() {
- override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
- if (newState == BluetoothGatt.STATE_CONNECTED) {
- gatt?.requestMtu(GATT_MAX_MTU)
- } else {
- connectResult.complete(false)
- // TODO(b/270492198): throw precise exception
- finished.completeExceptionally(IllegalStateException("connect failed"))
- }
- }
-
- override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
- Log.d(TAG, "onMtuChanged() called with: gatt = $gatt, mtu = $mtu, status = $status")
- if (status == GATT_SUCCESS) {
- gatt?.discoverServices()
- } else {
- connectResult.complete(false)
- // TODO(b/270492198): throw precise exception
- finished.completeExceptionally(IllegalStateException("mtu request failed"))
- }
- }
-
- override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
- connectResult.complete(status == GATT_SUCCESS)
- }
-
- override fun onCharacteristicRead(
- gatt: BluetoothGatt,
- characteristic: BluetoothGattCharacteristic,
- value: ByteArray,
- status: Int
- ) {
- callbackResultsFlow.tryEmit(
- CallbackResult.OnCharacteristicRead(characteristic, value, status))
- }
-
- override fun onCharacteristicWrite(
- gatt: BluetoothGatt,
- characteristic: BluetoothGattCharacteristic,
- status: Int
- ) {
- callbackResultsFlow.tryEmit(
- CallbackResult.OnCharacteristicWrite(characteristic, status))
- }
-
- override fun onDescriptorRead(
- gatt: BluetoothGatt,
- descriptor: BluetoothGattDescriptor,
- status: Int,
- value: ByteArray
- ) {
- callbackResultsFlow.tryEmit(
- CallbackResult.OnDescriptorRead(descriptor, value, status))
- }
-
- override fun onDescriptorWrite(
- gatt: BluetoothGatt,
- descriptor: BluetoothGattDescriptor,
- status: Int
- ) {
- callbackResultsFlow.tryEmit(
- CallbackResult.OnDescriptorWrite(descriptor, status))
- }
-
- override fun onCharacteristicChanged(
- gatt: BluetoothGatt,
- characteristic: BluetoothGattCharacteristic,
- value: ByteArray
- ) {
- launch {
- subscribeMutex.withLock {
- subscribeMap[characteristic]?.onCharacteristicNotification(value)
- }
- }
- }
- }
- val bluetoothGatt = device.connectGatt(context, /*autoConnect=*/false, callback)
-
- if (!connectResult.await()) {
- Log.w(TAG, "Failed to connect to the remote GATT server")
- return@coroutineScope null
- }
- val gattScope = object : BluetoothLe.GattClientScope {
- val taskMutex = Mutex()
- suspend fun<R> runTask(block: suspend () -> R): R {
- taskMutex.withLock {
- return block()
- }
- }
-
- override fun getServices(): List<BluetoothGattService> {
- return bluetoothGatt.services
- }
-
- override fun getService(uuid: UUID): BluetoothGattService? {
- return bluetoothGatt.getService(uuid)
- }
-
- override suspend fun readCharacteristic(characteristic: BluetoothGattCharacteristic):
- Result<ByteArray> {
- return runTask {
- bluetoothGatt.readCharacteristic(characteristic)
- val res = takeMatchingResult<CallbackResult.OnCharacteristicRead>(
- callbackResultsFlow) {
- it.characteristic == characteristic
- }
-
- if (res.status == GATT_SUCCESS) Result.success(res.value)
- else Result.failure(RuntimeException("fail"))
- }
- }
-
- override suspend fun writeCharacteristic(
- characteristic: BluetoothGattCharacteristic,
- value: ByteArray,
- writeType: Int
- ): Result<Unit> {
- return runTask {
- bluetoothGatt.writeCharacteristic(characteristic, value, writeType)
- val res = takeMatchingResult<CallbackResult.OnCharacteristicWrite>(
- callbackResultsFlow) {
- it.characteristic == characteristic
- }
- if (res.status == GATT_SUCCESS) Result.success(Unit)
- else Result.failure(RuntimeException("fail"))
- }
- }
-
- override suspend fun readDescriptor(descriptor: BluetoothGattDescriptor):
- Result<ByteArray> {
- return runTask {
- bluetoothGatt.readDescriptor(descriptor)
- val res = takeMatchingResult<CallbackResult.OnDescriptorRead>(
- callbackResultsFlow) {
- it.descriptor == descriptor
- }
-
- if (res.status == GATT_SUCCESS) Result.success(res.value)
- else Result.failure(RuntimeException("fail"))
- }
- }
-
- override suspend fun writeDescriptor(
- descriptor: BluetoothGattDescriptor,
- value: ByteArray
- ): Result<Unit> {
- return runTask {
- bluetoothGatt.writeDescriptor(descriptor, value)
- val res = takeMatchingResult<CallbackResult.OnDescriptorWrite>(
- callbackResultsFlow) {
- it.descriptor == descriptor
- }
- if (res.status == GATT_SUCCESS) Result.success(Unit)
- else Result.failure(RuntimeException("fail"))
- }
- }
-
- override fun subscribeToCharacteristic(characteristic: BluetoothGattCharacteristic):
- Flow<ByteArray> {
- val cccd = characteristic.getDescriptor(CCCD_UID) ?: return emptyFlow()
-
- return callbackFlow {
- val listener = object : SubscribeListener {
- override fun onCharacteristicNotification(value: ByteArray) {
- trySend(value)
- }
- override fun finish() {
- cancel("finished")
- }
- }
- if (!registerSubscribeListener(characteristic, listener)) {
- cancel("already subscribed")
- }
-
- runTask {
- bluetoothGatt.setCharacteristicNotification(characteristic, /*enable=*/true)
- bluetoothGatt.writeDescriptor(
- cccd,
- BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
- )
- val res = takeMatchingResult<CallbackResult.OnDescriptorWrite>(
- callbackResultsFlow) {
- it.descriptor == cccd
- }
- if (res.status != GATT_SUCCESS) {
- cancel(CancellationException("failed to set notification"))
- }
- }
-
- this.awaitClose {
- launch {
- unregisterSubscribeListener(characteristic)
- }
- bluetoothGatt.setCharacteristicNotification(characteristic,
- /*enable=*/false)
- bluetoothGatt.writeDescriptor(
- cccd,
- BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
- )
- }
- }
- }
-
- override suspend fun awaitClose(onClosed: () -> Unit) {
- try {
- // Wait for queued tasks done
- taskMutex.withLock {
- subscribeMutex.withLock {
- subscribeMap.values.forEach { it.finish() }
- }
- }
- } finally {
- onClosed()
- }
- }
-
- private suspend fun registerSubscribeListener(
- characteristic: BluetoothGattCharacteristic,
- callback: SubscribeListener
- ): Boolean {
- subscribeMutex.withLock {
- if (subscribeMap.containsKey(characteristic)) {
- return false
- }
- subscribeMap[characteristic] = callback
- return true
- }
- }
-
- private suspend fun unregisterSubscribeListener(
- characteristic: BluetoothGattCharacteristic
- ) {
- subscribeMutex.withLock {
- subscribeMap.remove(characteristic)
- }
- }
- }
- gattScope.block()
- }
-
- private suspend inline fun<reified R : CallbackResult> takeMatchingResult(
- flow: SharedFlow<CallbackResult>,
- crossinline predicate: (R) -> Boolean
- ): R {
- return flow.filter { it is R && predicate(it) }.first() as R
- }
-}
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeFragment.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeFragment.kt
deleted file mode 100644
index bb72d7c..0000000
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeFragment.kt
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * 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.bluetooth.integration.testapp.ui.home
-
-import android.annotation.SuppressLint
-import android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY
-import android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ
-import android.bluetooth.le.AdvertiseData
-import android.bluetooth.le.AdvertiseSettings
-import android.bluetooth.le.ScanResult
-import android.bluetooth.le.ScanSettings
-import android.os.Bundle
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Toast
-import androidx.bluetooth.integration.testapp.R
-import androidx.bluetooth.integration.testapp.databinding.FragmentHomeBinding
-import androidx.bluetooth.integration.testapp.experimental.AdvertiseResult
-import androidx.bluetooth.integration.testapp.experimental.BluetoothLe
-import androidx.bluetooth.integration.testapp.experimental.GattServerCallback
-import androidx.bluetooth.integration.testapp.ui.common.ScanResultAdapter
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-class HomeFragment : Fragment() {
-
- companion object {
- private const val TAG = "HomeFragment"
- }
-
- private var scanResultAdapter: ScanResultAdapter? = null
-
- private lateinit var bluetoothLe: BluetoothLe
-
- private val viewModel: HomeViewModel by viewModels()
-
- private var _binding: FragmentHomeBinding? = null
- private val binding get() = _binding!!
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentHomeBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- bluetoothLe = BluetoothLe(requireContext())
-
- scanResultAdapter = ScanResultAdapter { scanResult -> onClickScanResult(scanResult) }
- binding.recyclerView.adapter = scanResultAdapter
-
- binding.buttonScan.setOnClickListener {
- if (scanJob?.isActive == true) {
- scanJob?.cancel()
- binding.buttonScan.text = getString(R.string.scan_using_androidx_bluetooth)
- } else {
- startScan()
- }
- }
-
- binding.switchAdvertise.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) startAdvertise()
- else advertiseJob?.cancel()
- }
-
- binding.switchGattServer.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) openGattServer()
- else gattServerJob?.cancel()
- }
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- scanJob?.cancel()
- advertiseJob?.cancel()
- gattServerJob?.cancel()
- }
-
- private val scanScope = CoroutineScope(Dispatchers.Main + Job())
- private var scanJob: Job? = null
-
- private val connectScope = CoroutineScope(Dispatchers.Default + Job())
- private var connectJob: Job? = null
-
- private fun startScan() {
- Log.d(TAG, "startScan() called")
-
- val scanSettings = ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
- .build()
-
- scanJob = scanScope.launch {
- Toast.makeText(context, getString(R.string.scan_start_message), Toast.LENGTH_SHORT)
- .show()
-
- binding.buttonScan.text = getString(R.string.stop_scanning)
-
- bluetoothLe.scan(scanSettings)
- .collect {
- Log.d(TAG, "ScanResult collected: $it")
-
- if (it.scanRecord?.serviceUuids?.isEmpty() == false)
- viewModel.scanResults[it.device.address] = it
- scanResultAdapter?.submitList(viewModel.scanResults.values.toMutableList())
- scanResultAdapter?.notifyItemInserted(viewModel.scanResults.size)
- }
- }
- }
-
- private fun onClickScanResult(scanResult: ScanResult) {
- scanJob?.cancel()
- connectJob?.cancel()
- connectJob = connectScope.launch {
- bluetoothLe.connectGatt(requireContext(), scanResult.device) {
- for (srv in getServices()) {
- for (char in srv.characteristics) {
- if (char.properties.and(PROPERTY_READ) == 0) continue
- launch {
- val value = readCharacteristic(char).getOrNull()
- if (value != null) {
- Log.d(TAG, "Successfully read characteristic value=$value")
- }
- }
- launch {
- if (char.properties.and(PROPERTY_NOTIFY) != 0) {
- val value = subscribeToCharacteristic(char).first()
- Log.d(TAG, "Successfully get characteristic value=$value")
- }
- }
- }
- }
- awaitClose {
- Log.d(TAG, "GATT client is closed")
- connectJob = null
- }
- }
- }
- }
-
- private val advertiseScope = CoroutineScope(Dispatchers.Main + Job())
- private var advertiseJob: Job? = null
-
- // Permissions are handled by MainActivity requestBluetoothPermissions
- @SuppressLint("MissingPermission")
- private fun startAdvertise() {
- Log.d(TAG, "startAdvertise() called")
-
- val advertiseSettings = AdvertiseSettings.Builder()
- .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
- .setTimeout(0)
- .build()
-
- val advertiseData = AdvertiseData.Builder()
- .setIncludeDeviceName(true)
- .build()
-
- advertiseJob = advertiseScope.launch {
- bluetoothLe.advertise(advertiseSettings, advertiseData)
- .collect {
- Log.d(TAG, "advertiseResult received: $it")
-
- when (it) {
- AdvertiseResult.ADVERTISE_STARTED -> {
- Toast.makeText(
- context,
- getString(R.string.advertise_start_message), Toast.LENGTH_SHORT
- )
- .show()
- }
- AdvertiseResult.ADVERTISE_FAILED_ALREADY_STARTED -> {
- Log.d(
- TAG, "advertise onStartFailure() called with: " +
- "${AdvertiseResult.ADVERTISE_FAILED_ALREADY_STARTED}"
- )
- }
- AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE -> {
- Log.d(
- TAG, "advertise onStartFailure() called with: " +
- "${AdvertiseResult.ADVERTISE_FAILED_DATA_TOO_LARGE}"
- )
- }
- AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> {
- Log.d(
- TAG, "advertise onStartFailure() called with: " +
- "${AdvertiseResult.ADVERTISE_FAILED_FEATURE_UNSUPPORTED}"
- )
- }
- AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR -> {
- Log.d(
- TAG, "advertise onStartFailure() called with: " +
- "${AdvertiseResult.ADVERTISE_FAILED_INTERNAL_ERROR}"
- )
- }
- AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> {
- Log.d(
- TAG, "advertise onStartFailure() called with: " +
- "${AdvertiseResult.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS}"
- )
- }
- }
- }
- }
- }
-
- private val gattServerScope = CoroutineScope(Dispatchers.Main + Job())
- private var gattServerJob: Job? = null
-
- // Permissions are handled by MainActivity requestBluetoothPermissions
- @SuppressLint("MissingPermission")
- private fun openGattServer() {
- Log.d(TAG, "openGattServer() called")
-
- gattServerJob = gattServerScope.launch {
- bluetoothLe.openGattServer().collect { gattServerCallback ->
- when (gattServerCallback) {
- is GattServerCallback.OnCharacteristicReadRequest -> {
- val onCharacteristicReadRequest:
- GattServerCallback.OnCharacteristicReadRequest = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onCharacteristicReadRequest = $onCharacteristicReadRequest"
- )
- }
- is GattServerCallback.OnCharacteristicWriteRequest -> {
- val onCharacteristicWriteRequest:
- GattServerCallback.OnCharacteristicWriteRequest = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onCharacteristicWriteRequest = $onCharacteristicWriteRequest"
- )
- }
- is GattServerCallback.OnConnectionStateChange -> {
- val onConnectionStateChange:
- GattServerCallback.OnConnectionStateChange = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onConnectionStateChange = $onConnectionStateChange"
- )
- }
- is GattServerCallback.OnDescriptorReadRequest -> {
- val onDescriptorReadRequest:
- GattServerCallback.OnDescriptorReadRequest = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onDescriptorReadRequest = $onDescriptorReadRequest"
- )
- }
- is GattServerCallback.OnDescriptorWriteRequest -> {
- val onDescriptorWriteRequest:
- GattServerCallback.OnDescriptorWriteRequest = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onDescriptorWriteRequest = $onDescriptorWriteRequest"
- )
- }
- is GattServerCallback.OnExecuteWrite -> {
- val onExecuteWrite:
- GattServerCallback.OnExecuteWrite = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onExecuteWrite = $onExecuteWrite"
- )
- }
- is GattServerCallback.OnMtuChanged -> {
- val onMtuChanged:
- GattServerCallback.OnMtuChanged = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onMtuChanged = $onMtuChanged"
- )
- }
- is GattServerCallback.OnNotificationSent -> {
- val onNotificationSent:
- GattServerCallback.OnNotificationSent = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onNotificationSent = $onNotificationSent"
- )
- }
- is GattServerCallback.OnPhyRead -> {
- val onPhyRead:
- GattServerCallback.OnPhyRead = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onPhyRead = $onPhyRead"
- )
- }
- is GattServerCallback.OnPhyUpdate -> {
- val onPhyUpdate:
- GattServerCallback.OnPhyUpdate = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onPhyUpdate = $onPhyUpdate"
- )
- }
- is GattServerCallback.OnServiceAdded -> {
- val onServiceAdded:
- GattServerCallback.OnServiceAdded = gattServerCallback
- Log.d(
- TAG,
- "openGattServer() called with: " +
- "onServiceAdded = $onServiceAdded"
- )
- }
- }
- }
- }
- }
-}
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeViewModel.kt b/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeViewModel.kt
deleted file mode 100644
index 88b1c1c..0000000
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/ui/home/HomeViewModel.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.bluetooth.integration.testapp.ui.home
-
-import android.bluetooth.le.ScanResult
-import androidx.lifecycle.ViewModel
-
-class HomeViewModel : ViewModel() {
-
- private companion object {
- private const val TAG = "HomeViewModel"
- }
-
- val scanResults = mutableMapOf<String, ScanResult>()
-}
diff --git a/bluetooth/integration-tests/testapp/src/main/res/drawable/ic_bluetooth_24.xml b/bluetooth/integration-tests/testapp/src/main/res/drawable/ic_bluetooth_24.xml
deleted file mode 100644
index 34e9311..0000000
--- a/bluetooth/integration-tests/testapp/src/main/res/drawable/ic_bluetooth_24.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<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="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/>
-</vector>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_home.xml b/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_home.xml
deleted file mode 100644
index 9b242ce..0000000
--- a/bluetooth/integration-tests/testapp/src/main/res/layout/fragment_home.xml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 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.
- -->
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".ui.home.HomeFragment">
-
- <Button
- android:id="@+id/button_scan"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:text="@string/scan_using_androidx_bluetooth"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <androidx.appcompat.widget.SwitchCompat
- android:id="@+id/switch_advertise"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:text="@string/advertise_using_androidx_bluetooth"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/button_scan" />
-
- <androidx.appcompat.widget.SwitchCompat
- android:id="@+id/switch_gatt_server"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:text="@string/open_gatt_server_using_androidx_bluetooth"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/switch_advertise" />
-
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/recycler_view"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- app:layoutManager="LinearLayoutManager"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/switch_gatt_server"
- tools:itemCount="3"
- tools:listitem="@layout/scan_result_item" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/menu/bottom_nav_menu.xml b/bluetooth/integration-tests/testapp/src/main/res/menu/bottom_nav_menu.xml
index 68f6353..d3c5b8f 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/menu/bottom_nav_menu.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/menu/bottom_nav_menu.xml
@@ -17,11 +17,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
- android:id="@+id/navigation_home"
- android:icon="@drawable/ic_bluetooth_24"
- android:title="@string/title_home" />
-
- <item
android:id="@+id/navigation_scanner"
android:icon="@drawable/baseline_bluetooth_searching_24"
android:title="@string/title_scanner" />
diff --git a/bluetooth/integration-tests/testapp/src/main/res/navigation/nav_graph.xml b/bluetooth/integration-tests/testapp/src/main/res/navigation/nav_graph.xml
index b3c74a1..488e0f06 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/navigation/nav_graph.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/navigation/nav_graph.xml
@@ -18,24 +18,18 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
- app:startDestination="@id/navigation_home">
-
- <fragment
- android:id="@+id/navigation_home"
- android:name="androidx.bluetooth.integration.testapp.ui.home.HomeFragment"
- android:label="@string/title_home"
- tools:layout="@layout/fragment_home" />
+ app:startDestination="@id/navigation_scanner">
<fragment
android:id="@+id/navigation_scanner"
android:name="androidx.bluetooth.integration.testapp.ui.scanner.ScannerFragment"
android:label="@string/title_scanner"
- tools:layout="@layout/fragment_home" />
+ tools:layout="@layout/fragment_scanner" />
<fragment
android:id="@+id/navigation_advertiser"
android:name="androidx.bluetooth.integration.testapp.ui.advertiser.AdvertiserFragment"
android:label="@string/title_advertiser"
- tools:layout="@layout/fragment_home" />
+ tools:layout="@layout/fragment_advertiser" />
</navigation>
diff --git a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
index 7c47b75..06dffe7 100644
--- a/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
+++ b/bluetooth/integration-tests/testapp/src/main/res/values/donottranslate-strings.xml
@@ -17,7 +17,6 @@
<resources>
<string name="app_name">AndroidX Bluetooth Test App</string>
- <string name="title_home">AndroidX Bluetooth</string>
<string name="title_scanner">Scanner</string>
<string name="title_advertiser">Advertiser</string>
@@ -70,13 +69,4 @@
<string name="gatt_server">Gatt Server</string>
<string name="open_gatt_server">Open Gatt Server</string>
<string name="stop_gatt_server">Stop Gatt Server</string>
-
- <string name="scan_using_androidx_bluetooth">Scan using AndroidX Bluetooth APIs</string>
- <string name="scan_start_message">Scan started. Results are in Logcat</string>
-
- <string name="advertise_using_androidx_bluetooth">Advertise using AndroidX Bluetooth APIs</string>
- <string name="advertise_start_message">Advertise started</string>
-
- <string name="open_gatt_server_using_androidx_bluetooth">Open GATT Server using AndroidX Bluetooth APIs</string>
- <string name="gatt_server_open">GATT Server open</string>
</resources>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index 0b63a70..16d7e8d 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -104,6 +104,7 @@
task.referenceApi.set(checkApiRelease!!.flatMap { it.referenceApi })
task.baselines.set(checkApiRelease!!.flatMap { it.baselines })
task.api.set(builtApiLocation)
+ task.version.set(version)
task.dependencyClasspath = javaCompileInputs.dependencyClasspath
task.bootClasspath = javaCompileInputs.bootClasspath
task.k2UastEnabled.set(extension.metalavaK2UastEnabled)
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
index 9e82b95..9e91631 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/metalava/UpdateBaselineTasks.kt
@@ -16,6 +16,7 @@
package androidx.build.metalava
+import androidx.build.Version
import androidx.build.checkapi.ApiBaselinesLocation
import androidx.build.checkapi.ApiLocation
import java.io.File
@@ -92,6 +93,10 @@
@get:Input
abstract val baselines: Property<ApiBaselinesLocation>
+ // Version for the current API surface.
+ @get:Input
+ abstract val version: Property<Version>
+
@[InputFiles PathSensitive(PathSensitivity.RELATIVE)]
fun getTaskInputs(): List<File> {
val referenceApiLocation = referenceApi.get()
@@ -115,18 +120,23 @@
fun exec() {
check(bootClasspath.files.isNotEmpty()) { "Android boot classpath not set." }
+ val apiLocation = api.get()
+ val referenceApiLocation = referenceApi.get()
+ val freezeApis = shouldFreezeApis(referenceApiLocation.version(), version.get())
updateBaseline(
- api.get().publicApiFile,
- referenceApi.get().publicApiFile,
+ apiLocation.publicApiFile,
+ referenceApiLocation.publicApiFile,
baselines.get().publicApiFile,
- false
+ false,
+ freezeApis
)
- if (referenceApi.get().restrictedApiFile.exists()) {
+ if (referenceApiLocation.restrictedApiFile.exists()) {
updateBaseline(
- api.get().restrictedApiFile,
- referenceApi.get().restrictedApiFile,
+ apiLocation.restrictedApiFile,
+ referenceApiLocation.restrictedApiFile,
baselines.get().restrictedApiFile,
- true
+ true,
+ freezeApis
)
}
}
@@ -137,7 +147,8 @@
api: File,
prevApi: File,
baselineFile: File,
- processRestrictedApis: Boolean
+ processRestrictedApis: Boolean,
+ freezeApis: Boolean,
) {
val args = getCommonBaselineUpdateArgs(
bootClasspath,
@@ -152,6 +163,12 @@
"--source-files",
api.toString()
)
+ if (freezeApis) {
+ args += listOf(
+ "--error-category",
+ "Compatibility"
+ )
+ }
if (processRestrictedApis) {
args += listOf(
"--show-annotation",
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index dba781b..6c21ada 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -49,6 +49,7 @@
import androidx.camera.core.impl.UseCaseConfigFactory;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.internal.CameraUseCaseAdapter;
+import androidx.camera.core.internal.compat.workaround.CaptureFailedRetryEnabler;
import androidx.camera.testing.CoreAppTestUtil;
import androidx.camera.testing.fakes.FakeCamera;
import androidx.camera.testing.fakes.FakeCameraCaptureResult;
@@ -536,6 +537,9 @@
// Act.
// Complete the picture taken, then new flash mode should be applied.
+ CaptureFailedRetryEnabler retryEnabler = new CaptureFailedRetryEnabler();
+ // Because of retry in some devices, we may need to notify capture failures multiple times.
+ addExtraFailureNotificationsForRetry(fakeCameraControl, retryEnabler.getRetryCount());
fakeCameraControl.notifyAllRequestsOnCaptureFailed();
// Assert.
@@ -543,6 +547,16 @@
assertThat(fakeCameraControl.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_ON);
}
+ private void addExtraFailureNotificationsForRetry(FakeCameraControl cameraControl,
+ int retryCount) {
+ if (retryCount > 0) {
+ cameraControl.setOnNewCaptureRequestListener(captureConfigs -> {
+ addExtraFailureNotificationsForRetry(cameraControl, retryCount - 1);
+ cameraControl.notifyAllRequestsOnCaptureFailed();
+ });
+ }
+ }
+
@Test
public void correctViewPortRectInResolutionInfo_withCropAspectRatioSetting() {
ImageCapture imageCapture = new ImageCapture.Builder()
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
index 58ff19d..4903752 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RequestWithCallback.java
@@ -149,13 +149,19 @@
// Fail silently if the request has been aborted.
return;
}
- if (mTakePictureRequest.decrementRetryCounter()) {
- mRetryControl.retryRequest(mTakePictureRequest);
- } else {
+
+ boolean isRetryAllowed = mTakePictureRequest.decrementRetryCounter();
+ if (!isRetryAllowed) {
onFailure(imageCaptureException);
}
markComplete();
mCaptureCompleter.setException(imageCaptureException);
+
+ if (isRetryAllowed) {
+ // retry after all the cleaning up works are done via mCaptureCompleter.setException,
+ // e.g. removing previous request from CaptureNode, SingleBundlingNode etc.
+ mRetryControl.retryRequest(mTakePictureRequest);
+ }
}
@MainThread
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureManager.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureManager.java
index 3ba10d1..e2c6ae5 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureManager.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureManager.java
@@ -37,6 +37,7 @@
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Logger;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.core.util.Pair;
@@ -120,8 +121,11 @@
@Override
public void retryRequest(@NonNull TakePictureRequest request) {
checkMainThread();
+ Logger.d(TAG, "Add a new request for retrying.");
// Insert the request to the front of the queue.
mNewRequests.addFirst(request);
+ // Try to issue the newly added request in case condition allows.
+ issueNextRequest();
}
/**
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeImageCaptureControl.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeImageCaptureControl.kt
index d0b5089..85043d9 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeImageCaptureControl.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeImageCaptureControl.kt
@@ -39,11 +39,17 @@
// Flip this flag to return a custom result using pendingResultCompleter.
var shouldUsePendingResult = false
lateinit var pendingResultCompleter: CallbackToFutureAdapter.Completer<Void>
- var pendingResult = CallbackToFutureAdapter.getFuture { completer ->
+ var pendingResult = createPendingResult()
+
+ private fun createPendingResult() = CallbackToFutureAdapter.getFuture { completer ->
pendingResultCompleter = completer
"FakeImageCaptureControl's pendingResult"
}
+ fun resetPendingResult() {
+ pendingResult = createPendingResult()
+ }
+
override fun lockFlashMode() {
actions.add(Action.LOCK_FLASH)
}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/TakePictureManagerTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/TakePictureManagerTest.kt
index 2a6cf328..2f8d74f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/TakePictureManagerTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/TakePictureManagerTest.kt
@@ -24,10 +24,14 @@
import androidx.camera.core.ImageCapture.ERROR_CAPTURE_FAILED
import androidx.camera.core.ImageCapture.OutputFileResults
import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.imagecapture.FakeImageCaptureControl.Action.SUBMIT_REQUESTS
import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.internal.compat.quirk.CaptureFailedRetryQuirk
+import androidx.camera.core.internal.compat.quirk.DeviceQuirks
import androidx.camera.testing.fakes.FakeImageInfo
import androidx.camera.testing.fakes.FakeImageProxy
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +39,7 @@
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.ShadowBuild
/**
* Unit tests for [TakePictureManager].
@@ -133,7 +138,7 @@
// Assert: one request is sent.
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
// Both request are aborted.
@@ -184,7 +189,7 @@
// Assert: only one request is sent.
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
@@ -195,10 +200,10 @@
// Assert: 2nd request is sent too.
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
}
@@ -275,7 +280,7 @@
// Assert:
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
assertThat(imageCaptureControl.latestCaptureConfigs).isEqualTo(response1)
@@ -288,10 +293,10 @@
// Assert: imageCaptureControl was invoked in the exact given order.
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
assertThat(imageCaptureControl.latestCaptureConfigs).isEqualTo(response2)
@@ -399,7 +404,7 @@
assertThat(takePictureManager.mNewRequests.size).isEqualTo(0)
assertThat(imageCaptureControl.actions).containsExactly(
FakeImageCaptureControl.Action.LOCK_FLASH,
- FakeImageCaptureControl.Action.SUBMIT_REQUESTS,
+ SUBMIT_REQUESTS,
FakeImageCaptureControl.Action.UNLOCK_FLASH,
).inOrder()
}
@@ -443,4 +448,74 @@
// Assert. new request can be issued after the capture failure of the first request
takePictureManager.offerRequest(request2)
}
-}
\ No newline at end of file
+
+ @Test
+ fun requestFailure_failureReportedIfQuirkDisabled() {
+ // Arrange: use the real ImagePipeline implementation to do the test
+ takePictureManager.mImagePipeline =
+ ImagePipeline(Utils.createEmptyImageCaptureConfig(), Size(640, 480))
+
+ // Create a request and offer it to the manager.
+ imageCaptureControl.shouldUsePendingResult = true
+ val request = FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)
+ takePictureManager.offerRequest(request)
+
+ // Act: make the request fail once.
+ imageCaptureControl.pendingResultCompleter.setException(
+ ImageCaptureException(
+ ERROR_CAPTURE_FAILED, "", null
+ )
+ )
+ shadowOf(getMainLooper()).idle()
+
+ // Assert: failure exception received and no more retry possible.
+ assertThat((request.exceptionReceived as ImageCaptureException).imageCaptureError)
+ .isEqualTo(ERROR_CAPTURE_FAILED)
+ assertThat(request.remainingRetries).isEqualTo(0)
+ // Only 1 request submitted to camera
+ assertThat(imageCaptureControl.actions.count { it == SUBMIT_REQUESTS }).isEqualTo(1)
+ }
+
+ @Test
+ fun requestFailure_retriedIfQuirkEnabled() {
+ // Arrange: enable retry related quirk.
+ ShadowBuild.setBrand("SAMSUNG")
+ ShadowBuild.setModel("SM-G981U1")
+
+ val captureFailedRetryQuirk = DeviceQuirks.get(CaptureFailedRetryQuirk::class.java)
+
+ assertWithMessage("CaptureFailedRetryQuirk not enabled!")
+ .that(captureFailedRetryQuirk).isNotNull()
+
+ // Use the real ImagePipeline implementation to do the test
+ takePictureManager.mImagePipeline =
+ ImagePipeline(Utils.createEmptyImageCaptureConfig(), Size(640, 480))
+
+ // Create a request and offer it to the manager.
+ imageCaptureControl.shouldUsePendingResult = true
+ val request = FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)
+ takePictureManager.offerRequest(request)
+
+ // Act: make the request fail once and then successful if retried later.
+ imageCaptureControl.pendingResultCompleter.setException(
+ ImageCaptureException(
+ ERROR_CAPTURE_FAILED, "", null
+ )
+ )
+ imageCaptureControl.resetPendingResult()
+
+ // complete the new capture successfully
+ imageCaptureControl.pendingResultCompleter.set(null)
+ shadowOf(getMainLooper()).idle()
+
+ // Assert: retry count decremented without any failure.
+ assertThat(request.remainingRetries).isEqualTo(captureFailedRetryQuirk!!.retryCount - 1)
+ assertThat(request.exceptionReceived).isNull()
+ // 2 requests submitted to camera
+ assertThat(imageCaptureControl.actions.count { it == SUBMIT_REQUESTS }).isEqualTo(2)
+
+ // Clean-up brands and models set for enabling quirk.
+ ShadowBuild.setBrand("")
+ ShadowBuild.setModel("")
+ }
+}
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/AudioSourceTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/AudioSourceTest.kt
index 47dadac..eeaf74b 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/AudioSourceTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/internal/audio/AudioSourceTest.kt
@@ -33,6 +33,7 @@
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit.NANOSECONDS
import org.junit.After
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -62,6 +63,7 @@
}
}
+ @Ignore("b/289918974")
@Test
fun canStartAndStopAudioSource() {
// Arrange.
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
index 23b0547..5a638f2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/pager/PagerNestedScrollContentTest.kt
@@ -36,6 +36,7 @@
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
@@ -54,6 +55,7 @@
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.test.assertTrue
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -384,7 +386,6 @@
// Assert: Check we're settled.
rule.runOnIdle {
- assertThat(pagerState.currentPage).isEqualTo(5)
assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
}
@@ -395,6 +396,7 @@
rule.runOnIdle { assertThat(focusItems).contains("page=5-item=3") }
// Act: Move focus in inner scrollable
+ val previousPage = pagerState.currentPage
rule.runOnIdle {
assertTrue {
if (vertical) {
@@ -408,7 +410,7 @@
// Assert: Check we actually scrolled, but didn't move pages.
rule.runOnIdle {
assertThat(focusItems).contains("page=5-item=4")
- assertThat(pagerState.currentPage).isEqualTo(5)
+ assertThat(pagerState.currentPage).isEqualTo(previousPage)
assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
}
@@ -428,11 +430,63 @@
// Assert: Check we moved pages.
rule.runOnIdle {
- assertThat(pagerState.currentPage).isEqualTo(6)
+ assertThat(focusItems).contains("page=6-item=0")
assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
}
}
+ @Test
+ fun focusableContentInPage_focusMoveShouldNotLeavePagesInIntermediateState() {
+ lateinit var pagerFocusRequester: FocusRequester
+
+ createPager(
+ modifier = Modifier.fillMaxSize(),
+ pageCount = { DefaultPageCount },
+ initialPage = 3
+ ) { page ->
+ val focusRequester = remember {
+ FocusRequester().apply {
+ if (page == 5) pagerFocusRequester = this
+ }
+ }
+
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Box(
+ modifier = Modifier
+ .size(64.dp)
+ .focusRequester(focusRequester)
+ .focusable()
+ )
+ }
+ }
+
+ // Assert: Pager is settled
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ assertThat(pagerState.currentPage).isEqualTo(3)
+
+ // Scroll to a page
+ rule.runOnIdle {
+ scope.launch {
+ pagerState.scrollToPage(5)
+ }
+ }
+
+ // Assert: Pager is settled
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ assertThat(pagerState.currentPage).isEqualTo(5)
+
+ // Act: Request focus.
+ rule.runOnIdle {
+ pagerFocusRequester.requestFocus()
+ }
+
+ // Assert: Pager is settled
+ rule.runOnIdle {
+ assertThat(pagerState.currentPageOffsetFraction).isEqualTo(0.0f)
+ assertThat(pagerState.currentPage).isEqualTo(5)
+ }
+ }
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
index ffb2dea..9f49b62 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/LazyLayoutPager.kt
@@ -135,6 +135,8 @@
orientation == Orientation.Vertical
)
+ val pagerBringIntoViewScroller = remember(state) { PagerBringIntoViewScroller(state) }
+
LazyLayout(
modifier = modifier
.then(state.remeasurementModifier)
@@ -167,7 +169,7 @@
state = state,
overscrollEffect = overscrollEffect,
enabled = userScrollEnabled,
- bringIntoViewScroller = PagerBringIntoViewScroller
+ bringIntoViewScroller = pagerBringIntoViewScroller
)
.dragDirectionDetector(state)
.nestedScroll(pageNestedScrollConnection),
@@ -286,26 +288,37 @@
}
@OptIn(ExperimentalFoundationApi::class)
-private val PagerBringIntoViewScroller = object : BringIntoViewScroller {
+private class PagerBringIntoViewScroller(val pagerState: PagerState) : BringIntoViewScroller {
override val scrollAnimationSpec: AnimationSpec<Float> = spring()
+ /**
+ * [calculateScrollDistance] for Pager behaves differently than in a normal list. We must
+ * always respect the snapped pages over bringing a child into view. The logic here will
+ * behave like so:
+ *
+ * 1) If a child is outside of the view, start bringing it into view.
+ * 2) If a child's trailing edge is outside of the page bounds and the child is smaller than
+ * the page, scroll until the trailing edge is in view.
+ * 3) Once a child is fully in view, if it is smaller than the page, scroll until the page is
+ * settled.
+ * 4) If the child is larger than the page, scroll until it is partially in view and continue
+ * scrolling until the page is settled.
+ */
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float {
- val trailingEdge = offset + size
- val leadingEdge = offset
-
- val sizeOfItemRequestingFocus = (trailingEdge - leadingEdge).absoluteValue
- val childSmallerThanParent = sizeOfItemRequestingFocus <= containerSize
- val initialTargetForLeadingEdge = 0.0f
- val spaceAvailableToShowItem = containerSize - initialTargetForLeadingEdge
-
- val targetForLeadingEdge =
- if (childSmallerThanParent && spaceAvailableToShowItem < sizeOfItemRequestingFocus) {
- containerSize - sizeOfItemRequestingFocus
+ return if (offset >= containerSize || offset < 0) {
+ offset
+ } else {
+ if (size <= containerSize && (offset + size) > containerSize) {
+ offset // bring into view
} else {
- initialTargetForLeadingEdge
+ // are we in a settled position?
+ if (pagerState.currentPageOffsetFraction.absoluteValue == 0.0f) {
+ 0f
+ } else {
+ offset
+ }
}
-
- return leadingEdge - targetForLeadingEdge
+ }
}
}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
index 764e75d..d382069 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/TestUtils.kt
@@ -298,6 +298,7 @@
updateCoordinator(coordinator)
if (coordinator.isAttached) {
markAsAttached()
+ runAttachLifecycle()
}
}
@@ -307,6 +308,7 @@
coordinator.isAttached = false
}
if (isAttached) {
+ runDetachLifecycle()
markAsDetached()
}
}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAttachOrderTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAttachOrderTest.kt
index c9a48d3..7bcca13 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAttachOrderTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/ModifierNodeAttachOrderTest.kt
@@ -17,7 +17,10 @@
package androidx.compose.ui.node
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.ReusableContentHost
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -299,4 +302,40 @@
log.clear()
}
}
+
+ // Regression test for b/289461011
+ @Test
+ fun reusable_nodes_in_movable_content() {
+ var active by mutableStateOf(true)
+ var inBox by mutableStateOf(true)
+ val content = movableContentOf {
+ ReusableContentHost(active = active) {
+ BasicText("Hello World")
+ }
+ }
+
+ rule.setContent {
+ if (inBox) {
+ Box {
+ content()
+ }
+ } else {
+ content()
+ }
+ }
+
+ rule.runOnIdle {
+ active = false
+ }
+
+ rule.runOnIdle {
+ inBox = false
+ }
+
+ rule.runOnIdle {
+ active = true
+ }
+
+ rule.waitForIdle()
+ }
}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
index 5616a28..fefc50a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt
@@ -221,6 +221,8 @@
private set
internal var insertedNodeAwaitingAttachForInvalidation = false
internal var updatedNodeAwaitingAttachForInvalidation = false
+ private var onAttachRunExpected = false
+ private var onDetachRunExpected = false
/**
* Indicates that the node is attached to a [androidx.compose.ui.layout.Layout] which is
* part of the UI tree.
@@ -262,21 +264,34 @@
check(!isAttached) { "node attached multiple times" }
check(coordinator != null) { "attach invoked on a node without a coordinator" }
isAttached = true
+ onAttachRunExpected = true
}
internal open fun runAttachLifecycle() {
check(isAttached) { "Must run markAsAttached() prior to runAttachLifecycle" }
+ check(onAttachRunExpected) { "Must run runAttachLifecycle() only once after " +
+ "markAsAttached()"
+ }
+ onAttachRunExpected = false
onAttach()
+ onDetachRunExpected = true
}
internal open fun runDetachLifecycle() {
check(isAttached) { "node detached multiple times" }
check(coordinator != null) { "detach invoked on a node without a coordinator" }
+ check(onDetachRunExpected) {
+ "Must run runDetachLifecycle() once after runAttachLifecycle() and before " +
+ "markAsDetached()"
+ }
+ onDetachRunExpected = false
onDetach()
}
internal open fun markAsDetached() {
check(isAttached) { "Cannot detach a node that is not attached" }
+ check(!onAttachRunExpected) { "Must run runAttachLifecycle() before markAsDetached()" }
+ check(!onDetachRunExpected) { "Must run runDetachLifecycle() before markAsDetached()" }
isAttached = false
scope?.let {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index fc26143..2fa7171 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -474,11 +474,15 @@
// is a virtual lookahead root
lookaheadRoot = _foldedParent?.lookaheadRoot ?: lookaheadRoot
}
- nodes.markAsAttached()
+ if (!deactivated) {
+ nodes.markAsAttached()
+ }
_foldedChildren.forEach { child ->
child.attach(owner)
}
- nodes.runAttachLifecycle()
+ if (!deactivated) {
+ nodes.runAttachLifecycle()
+ }
invalidateMeasurements()
parent?.invalidateMeasurements()
@@ -487,7 +491,9 @@
onAttach?.invoke(owner)
layoutDelegate.updateParentData()
- invalidateFocusOnAttach()
+ if (!deactivated) {
+ invalidateFocusOnAttach()
+ }
}
/**
@@ -1317,6 +1323,7 @@
private var deactivated = false
override fun onReuse() {
+ require(isAttached) { "onReuse is only expected on attached node" }
interopViewFactoryHolder?.onReuse()
if (deactivated) {
deactivated = false
diff --git a/core/core/src/androidTest/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompatTest.java b/core/core/src/androidTest/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompatTest.java
index 3b7b2d8..f1e265b 100644
--- a/core/core/src/androidTest/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompatTest.java
@@ -324,7 +324,7 @@
equalTo(accessibilityNodeInfoCompat.unwrap().getExtraRenderingInfo()));
}
- @SdkSuppress(minSdkVersion = 33)
+ @SdkSuppress(minSdkVersion = 19)
@SmallTest
@Test
public void testSetGetTextSelectable() {
diff --git a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
index 6e63749..f6e288e 100644
--- a/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/core/core/src/main/java/androidx/core/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -96,8 +96,8 @@
* </p>
* <p class="note">
* <strong>Note:</strong> Views which support these actions should invoke
- * {@link View#setImportantForAccessibility(int)} with
- * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an
+ * {@link ViewCompat#setImportantForAccessibility(View, int)} with
+ * {@link ViewCompat#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an
* {@link android.accessibilityservice.AccessibilityService} can discover the set of supported
* actions.
* </p>
@@ -1397,6 +1397,7 @@
private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x00000004;
private static final int BOOLEAN_PROPERTY_IS_TEXT_ENTRY_KEY = 0x00000008;
private static final int BOOLEAN_PROPERTY_HAS_REQUEST_INITIAL_ACCESSIBILITY_FOCUS = 1 << 5;
+ private static final int BOOLEAN_PROPERTY_TEXT_SELECTABLE = 1 << 23;
private static final int BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING = 1 << 26;
private final AccessibilityNodeInfo mInfo;
@@ -2833,7 +2834,11 @@
/**
* Gets if the node supports granular scrolling.
- *
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>Api < 19: Returns false.</li>
+ * </ul>
* @return True if all scroll actions that could support
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
*/
@@ -2849,7 +2854,11 @@
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
- *
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>Api < 19: No-op.</li>
+ * </ul>
* @param granularScrollingSupported True if the node supports granular scrolling, false
* otherwise.
*
@@ -2867,11 +2876,12 @@
* Services should use {@link #ACTION_SET_SELECTION} for selection. Editable text nodes must
* also be selectable. But not all UIs will populate this field, so services should consider
* 'isTextSelectable | isEditable' to ensure they don't miss nodes with selectable text.
- * Compatibility:
- * <ul>
- * <li>Api < 33: Returns false.</li>
- * </ul>
* </p>
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>Api < 19: Returns false.</li>
+ * </ul>
*
* @see #isEditable
* @return True if the node has selectable text.
@@ -2880,7 +2890,7 @@
if (Build.VERSION.SDK_INT >= 33) {
return Api33Impl.isTextSelectable(mInfo);
} else {
- return false;
+ return getBooleanProperty(BOOLEAN_PROPERTY_TEXT_SELECTABLE);
}
}
@@ -2890,10 +2900,12 @@
* <strong>Note:</strong> Cannot be called from an
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
- * Compatibility:
- * <ul>
- * <li>Api < 33: Does not operate.</li>
- * </ul>
+ * </p>
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>Api < 19: Does not operate.</li>
+ * </ul>
* </p>
*
* @param selectableText True if the node has selectable text, false otherwise.
@@ -2903,6 +2915,8 @@
public void setTextSelectable(boolean selectableText) {
if (Build.VERSION.SDK_INT >= 33) {
Api33Impl.setTextSelectable(mInfo, selectableText);
+ } else {
+ setBooleanProperty(BOOLEAN_PROPERTY_TEXT_SELECTABLE, selectableText);
}
}
@@ -3167,8 +3181,8 @@
* than 19.
*/
public @Nullable CharSequence getStateDescription() {
- if (BuildCompat.isAtLeastR()) {
- return mInfo.getStateDescription();
+ if (Build.VERSION.SDK_INT >= 30) {
+ return Api30Impl.getStateDescription(mInfo);
} else if (Build.VERSION.SDK_INT >= 19) {
return Api19Impl.getExtras(mInfo).getCharSequence(STATE_DESCRIPTION_KEY);
}
@@ -3202,8 +3216,8 @@
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setStateDescription(@Nullable CharSequence stateDescription) {
- if (BuildCompat.isAtLeastR()) {
- mInfo.setStateDescription(stateDescription);
+ if (Build.VERSION.SDK_INT >= 30) {
+ Api30Impl.setStateDescription(mInfo, stateDescription);
} else if (Build.VERSION.SDK_INT >= 19) {
Api19Impl.getExtras(mInfo).putCharSequence(STATE_DESCRIPTION_KEY, stateDescription);
}
@@ -3215,10 +3229,9 @@
* @return the unique id or null if android version smaller
* than 19.
*/
- @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
public @Nullable String getUniqueId() {
- if (BuildCompat.isAtLeastT()) {
- return mInfo.getUniqueId();
+ if (Build.VERSION.SDK_INT >= 33) {
+ return Api33Impl.getUniqueId(mInfo);
} else if (Build.VERSION.SDK_INT >= 19) {
return Api19Impl.getExtras(mInfo).getString(UNIQUE_ID_KEY);
}
@@ -3236,10 +3249,9 @@
* @param uniqueId the unique id of this node.
* @throws IllegalStateException If called from an AccessibilityService.
*/
- @OptIn(markerClass = BuildCompat.PrereleaseSdkCheck.class)
public void setUniqueId(@Nullable String uniqueId) {
- if (BuildCompat.isAtLeastT()) {
- mInfo.setUniqueId(uniqueId);
+ if (Build.VERSION.SDK_INT >= 33) {
+ Api33Impl.setUniqueId(mInfo, uniqueId);
} else if (Build.VERSION.SDK_INT >= 19) {
Api19Impl.getExtras(mInfo).putString(UNIQUE_ID_KEY, uniqueId);
}
@@ -4338,7 +4350,7 @@
* Returns whether node represents a heading.
* <p><strong>Note:</strong> Returns {@code true} if either {@link #setHeading(boolean)}
* marks this node as a heading or if the node has a {@link CollectionItemInfoCompat} that marks
- * it as such, to accomodate apps that use the now-deprecated API.</p>
+ * it as such, to accommodate apps that use the now-deprecated API.</p>
*
* @return {@code true} if the node is a heading, {@code false} otherwise.
*/
@@ -4594,8 +4606,12 @@
builder.append("; packageName: ").append(getPackageName());
builder.append("; className: ").append(getClassName());
builder.append("; text: ").append(getText());
+ builder.append("; error: ").append(getError());
+ builder.append("; maxTextLength: ").append(getMaxTextLength());
+ builder.append("; stateDescription: ").append(getStateDescription());
builder.append("; contentDescription: ").append(getContentDescription());
- builder.append("; viewId: ").append(getViewIdResourceName());
+ builder.append("; tooltipText: ").append(getTooltipText());
+ builder.append("; viewIdResName: ").append(getViewIdResourceName());
builder.append("; uniqueId: ").append(getUniqueId());
builder.append("; checkable: ").append(isCheckable());
@@ -4605,9 +4621,14 @@
builder.append("; selected: ").append(isSelected());
builder.append("; clickable: ").append(isClickable());
builder.append("; longClickable: ").append(isLongClickable());
+ builder.append("; contextClickable: ").append(isContextClickable());
builder.append("; enabled: ").append(isEnabled());
builder.append("; password: ").append(isPassword());
builder.append("; scrollable: " + isScrollable());
+ builder.append("; granularScrollingSupported: ").append(isGranularScrollingSupported());
+ builder.append("; importantForAccessibility: ").append(isImportantForAccessibility());
+ builder.append("; visible: ").append(isVisibleToUser());
+ builder.append("; isTextSelectable: ").append(isTextSelectable());
builder.append("; [");
if (Build.VERSION.SDK_INT >= 21) {
@@ -4748,6 +4769,24 @@
}
}
+ @RequiresApi(30)
+ private static class Api30Impl {
+ private Api30Impl() {
+ // This class is non instantiable.
+ }
+
+ @DoNotInline
+ public static void setStateDescription(AccessibilityNodeInfo info,
+ CharSequence stateDescription) {
+ info.setStateDescription(stateDescription);
+ }
+
+ @DoNotInline
+ public static CharSequence getStateDescription(AccessibilityNodeInfo info) {
+ return info.getStateDescription();
+ }
+ }
+
@RequiresApi(33)
private static class Api33Impl {
private Api33Impl() {
@@ -4769,6 +4808,16 @@
public static void setTextSelectable(AccessibilityNodeInfo info, boolean selectable) {
info.setTextSelectable(selectable);
}
+
+ @DoNotInline
+ public static String getUniqueId(AccessibilityNodeInfo info) {
+ return info.getUniqueId();
+ }
+
+ @DoNotInline
+ public static void setUniqueId(AccessibilityNodeInfo info, String uniqueId) {
+ info.setUniqueId(uniqueId);
+ }
}
@RequiresApi(19)
diff --git a/health/connect/connect-client/lint-baseline.xml b/health/connect/connect-client/lint-baseline.xml
index dfb1ec9..d0e77cb 100644
--- a/health/connect/connect-client/lint-baseline.xml
+++ b/health/connect/connect-client/lint-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="BanSynchronizedMethods"
@@ -11,42 +11,6 @@
</issue>
<issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/health/connect/client/PermissionController.kt"/>
- </issue>
-
- <issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/health/connect/client/PermissionController.kt"/>
- </issue>
-
- <issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/health/connect/client/PermissionController.kt"/>
- </issue>
-
- <issue
- id="PrereleaseSdkCoreDependency"
- message="Prelease SDK check isAtLeastU cannot be called as this project has a versioned dependency on androidx.core:core"
- errorLine1=" if (BuildCompat.isAtLeastU()) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/health/connect/client/PermissionController.kt"/>
- </issue>
-
- <issue
id="RequireUnstableAidlAnnotation"
message="Unstable AIDL files must be annotated with `@RequiresOptIn` marker"
errorLine1="parcelable AggregateDataRequest;"
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/package-info.java b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/package-info.java
index c5b8a8e..597a6c1 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/package-info.java
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/records/package-info.java
@@ -16,8 +16,6 @@
/**
* Helps with conversions to the platform record and API objects.
- *
- * @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
package androidx.health.connect.client.impl.platform.records;
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/package-info.java b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/package-info.java
index f8b9cb7..a743964 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/package-info.java
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/platform/response/package-info.java
@@ -16,8 +16,6 @@
/**
* Helps with conversions to the platform record and API objects.
- *
- * @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
package androidx.health.connect.client.impl.platform.response;
diff --git a/libraryversions.toml b/libraryversions.toml
index 386c109..3c22280 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -89,7 +89,7 @@
LOADER = "1.2.0-alpha01"
MEDIA = "1.7.0-alpha02"
MEDIA2 = "1.3.0-alpha01"
-MEDIAROUTER = "1.6.0-alpha05"
+MEDIAROUTER = "1.6.0-beta01"
METRICS = "1.0.0-alpha05"
NAVIGATION = "2.7.0-beta02"
PAGING = "3.3.0-alpha01"
diff --git a/mediarouter/mediarouter-testing/api/1.6.0-beta01.txt b/mediarouter/mediarouter-testing/api/1.6.0-beta01.txt
new file mode 100644
index 0000000..14e1df6
--- /dev/null
+++ b/mediarouter/mediarouter-testing/api/1.6.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.mediarouter.testing {
+
+ public class MediaRouterTestHelper {
+ method @MainThread public static void resetMediaRouter();
+ }
+
+}
+
diff --git a/mediarouter/mediarouter-testing/api/res-1.6.0-beta01.txt b/mediarouter/mediarouter-testing/api/res-1.6.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mediarouter/mediarouter-testing/api/res-1.6.0-beta01.txt
diff --git a/mediarouter/mediarouter-testing/api/restricted_1.6.0-beta01.txt b/mediarouter/mediarouter-testing/api/restricted_1.6.0-beta01.txt
new file mode 100644
index 0000000..14e1df6
--- /dev/null
+++ b/mediarouter/mediarouter-testing/api/restricted_1.6.0-beta01.txt
@@ -0,0 +1,9 @@
+// Signature format: 4.0
+package androidx.mediarouter.testing {
+
+ public class MediaRouterTestHelper {
+ method @MainThread public static void resetMediaRouter();
+ }
+
+}
+
diff --git a/mediarouter/mediarouter/api/1.6.0-beta01.txt b/mediarouter/mediarouter/api/1.6.0-beta01.txt
new file mode 100644
index 0000000..00e0b0ae6
--- /dev/null
+++ b/mediarouter/mediarouter/api/1.6.0-beta01.txt
@@ -0,0 +1,621 @@
+// Signature format: 4.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton onCreateMediaRouteButton();
+ method @Deprecated public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?, int);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method @Deprecated public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable?);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context);
+ ctor public MediaRouteChooserDialog(android.content.Context, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog onCreateChooserDialog(android.content.Context, android.os.Bundle?);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context);
+ ctor public MediaRouteControllerDialog(android.content.Context, int);
+ method public android.view.View? getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View? onCreateMediaControlView(android.os.Bundle?);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog onCreateControllerDialog(android.content.Context, android.os.Bundle?);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback? onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public final class SystemOutputSwitcherDialogController {
+ method public static boolean showDialog(android.content.Context);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus? fromBundle(android.os.Bundle?);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle? getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus);
+ ctor public MediaItemStatus.Builder(int);
+ method public androidx.mediarouter.media.MediaItemStatus build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor? fromBundle(android.os.Bundle?);
+ method public java.util.Set<java.lang.String!> getAllowedPackages();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!> getControlFilters();
+ method public java.util.Set<java.lang.String!> getDeduplicationIds();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri? getIconUri();
+ method public String getId();
+ method public String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender? getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ method public boolean isVisibilityPublic();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+ ctor public MediaRouteDescriptor.Builder(String, String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilter(android.content.IntentFilter);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilters(java.util.Collection<android.content.IntentFilter!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder clearControlFilters();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeduplicationIds(java.util.Set<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDescription(String?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIconUri(android.net.Uri);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setId(String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setName(String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setSettingsActivity(android.content.IntentSender?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityPublic();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityRestricted(java.util.Set<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector, boolean);
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest? fromBundle(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaRouteSelector getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setSelectionState(int);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName getComponentName();
+ method public String getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method @Deprecated public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor? fromBundle(android.os.Bundle?);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoute(androidx.mediarouter.media.MediaRouteDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
+ method public android.os.IBinder? onBind(android.content.Intent);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
+ method public static androidx.mediarouter.media.MediaRouteSelector? fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String!> getControlCategories();
+ method public boolean hasControlCategory(String?);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>?);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback);
+ method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method @MainThread public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method @Deprecated @MainThread public void addRemoteControlClient(Object);
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo? getBluetoothRoute();
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method @MainThread public static androidx.mediarouter.media.MediaRouter getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSessionToken();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!> getProviders();
+ method @MainThread public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method @MainThread public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method @MainThread public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method @MainThread public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method @MainThread public void removeRemoteControlClient(Object);
+ method @MainThread public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @MainThread public void setMediaSession(Object?);
+ method @MainThread public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
+ method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+ method @MainThread public void setRouteListingPreference(androidx.mediarouter.media.RouteListingPreference?);
+ method @MainThread public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+ method @MainThread public void unselect(int);
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String?, android.os.Bundle?);
+ method public void onResult(android.os.Bundle?);
+ }
+
+ public static interface MediaRouter.OnPrepareTransferListener {
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName getComponentName();
+ method public String getPackageName();
+ method @MainThread public androidx.mediarouter.media.MediaRouteProvider getProviderInstance();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!> getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri? getIconUri();
+ method public String getId();
+ method public String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method @MainThread public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @MainThread public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method @MainThread public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method @MainThread public boolean isSelected();
+ method @MainThread public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method @MainThread public void requestSetVolume(int);
+ method @MainThread public void requestUpdateVolume(int);
+ method @MainThread public void select();
+ method @MainThread public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method @MainThread public boolean supportsControlAction(String, String);
+ method @MainThread public boolean supportsControlCategory(String);
+ method @MainThread public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+ field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+ field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+ field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+ field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+ field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+ field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public class MediaRouterParams {
+ method public int getDialogType();
+ method public boolean isMediaTransferReceiverEnabled();
+ method public boolean isOutputSwitcherEnabled();
+ method public boolean isTransferToLocalEnabled();
+ field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+ field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+ field public static final String ENABLE_GROUP_VOLUME_UX = "androidx.mediarouter.media.MediaRouterParams.ENABLE_GROUP_VOLUME_UX";
+ }
+
+ public static final class MediaRouterParams.Builder {
+ ctor public MediaRouterParams.Builder();
+ ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+ method public androidx.mediarouter.media.MediaRouterParams build();
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus? fromBundle(android.os.Bundle?);
+ method public android.os.Bundle? getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus);
+ ctor public MediaSessionStatus.Builder(int);
+ method public androidx.mediarouter.media.MediaSessionStatus build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setTimestamp(long);
+ }
+
+ public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaTransferReceiver();
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void endSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void enqueue(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public String? getSessionId();
+ method public void getSessionStatus(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void getStatus(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void play(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void release();
+ method public void remove(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void resume(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void seek(String, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void sendMessage(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener?);
+ method public void setSessionId(String?);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback?);
+ method public void startSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void stop(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String?, int, android.os.Bundle?);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String, android.os.Bundle?);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+ method public void onSessionChanged(String?);
+ method public void onSessionStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?);
+ }
+
+ public final class RouteListingPreference {
+ method public java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!> getItems();
+ method public android.content.ComponentName? getLinkedItemComponentName();
+ method public boolean getUseSystemOrdering();
+ field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+ field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+ }
+
+ public static final class RouteListingPreference.Builder {
+ ctor public RouteListingPreference.Builder();
+ method public androidx.mediarouter.media.RouteListingPreference build();
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setItems(java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!>);
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setLinkedItemComponentName(android.content.ComponentName?);
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
+ }
+
+ public static final class RouteListingPreference.Item {
+ method public CharSequence? getCustomSubtextMessage();
+ method public int getFlags();
+ method public String getRouteId();
+ method public int getSelectionBehavior();
+ method public int getSubText();
+ field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
+ field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
+ field public static final int FLAG_SUGGESTED = 4; // 0x4
+ field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+ field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+ field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4; // 0x4
+ field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+ field public static final int SUBTEXT_DEVICE_LOW_POWER = 5; // 0x5
+ field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int SUBTEXT_NONE = 0; // 0x0
+ field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2; // 0x2
+ field public static final int SUBTEXT_TRACK_UNSUPPORTED = 7; // 0x7
+ field public static final int SUBTEXT_UNAUTHORIZED = 6; // 0x6
+ }
+
+ public static final class RouteListingPreference.Item.Builder {
+ ctor public RouteListingPreference.Item.Builder(String);
+ method public androidx.mediarouter.media.RouteListingPreference.Item build();
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(CharSequence?);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setFlags(int);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSubText(int);
+ }
+
+}
+
diff --git a/mediarouter/mediarouter/api/res-1.6.0-beta01.txt b/mediarouter/mediarouter/api/res-1.6.0-beta01.txt
new file mode 100644
index 0000000..620c3fe
--- /dev/null
+++ b/mediarouter/mediarouter/api/res-1.6.0-beta01.txt
@@ -0,0 +1,4 @@
+dimen mediarouter_chooser_list_item_padding_bottom
+dimen mediarouter_chooser_list_item_padding_end
+dimen mediarouter_chooser_list_item_padding_start
+dimen mediarouter_chooser_list_item_padding_top
diff --git a/mediarouter/mediarouter/api/restricted_1.6.0-beta01.txt b/mediarouter/mediarouter/api/restricted_1.6.0-beta01.txt
new file mode 100644
index 0000000..00e0b0ae6
--- /dev/null
+++ b/mediarouter/mediarouter/api/restricted_1.6.0-beta01.txt
@@ -0,0 +1,621 @@
+// Signature format: 4.0
+package androidx.mediarouter.app {
+
+ public class MediaRouteActionProvider extends androidx.core.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.app.MediaRouteButton? getMediaRouteButton();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View onCreateActionView();
+ method public androidx.mediarouter.app.MediaRouteButton onCreateMediaRouteButton();
+ method @Deprecated public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet?, int);
+ method @Deprecated public void enableDynamicGroup();
+ method public androidx.mediarouter.app.MediaRouteDialogFactory getDialogFactory();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method @Deprecated public void setAlwaysVisible(boolean);
+ method public void setDialogFactory(androidx.mediarouter.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable?);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends androidx.appcompat.app.AppCompatDialog {
+ ctor public MediaRouteChooserDialog(android.content.Context);
+ ctor public MediaRouteChooserDialog(android.content.Context, int);
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public androidx.mediarouter.app.MediaRouteChooserDialog onCreateChooserDialog(android.content.Context, android.os.Bundle?);
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteControllerDialog extends androidx.appcompat.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context);
+ ctor public MediaRouteControllerDialog(android.content.Context, int);
+ method public android.view.View? getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSession();
+ method public androidx.mediarouter.media.MediaRouter.RouteInfo getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View? onCreateMediaControlView(android.os.Bundle?);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends androidx.fragment.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialog onCreateControllerDialog(android.content.Context, android.os.Bundle?);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static androidx.mediarouter.app.MediaRouteDialogFactory getDefault();
+ method public androidx.mediarouter.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public androidx.mediarouter.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends androidx.fragment.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public androidx.mediarouter.media.MediaRouter getMediaRouter();
+ method public androidx.mediarouter.media.MediaRouteSelector getRouteSelector();
+ method public androidx.mediarouter.media.MediaRouter.Callback? onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(androidx.mediarouter.media.MediaRouteSelector);
+ }
+
+ public final class SystemOutputSwitcherDialogController {
+ method public static boolean showDialog(android.content.Context);
+ }
+
+}
+
+package androidx.mediarouter.media {
+
+ public final class MediaControlIntent {
+ field public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+ field public static final String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
+ field public static final String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaItemStatus? fromBundle(android.os.Bundle?);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle? getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(androidx.mediarouter.media.MediaItemStatus);
+ ctor public MediaItemStatus.Builder(int);
+ method public androidx.mediarouter.media.MediaItemStatus build();
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setContentDuration(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setContentPosition(long);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setPlaybackState(int);
+ method public androidx.mediarouter.media.MediaItemStatus.Builder setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static androidx.mediarouter.media.MediaRouteDescriptor? fromBundle(android.os.Bundle?);
+ method public java.util.Set<java.lang.String!> getAllowedPackages();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!> getControlFilters();
+ method public java.util.Set<java.lang.String!> getDeduplicationIds();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri? getIconUri();
+ method public String getId();
+ method public String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender? getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @Deprecated public boolean isConnecting();
+ method public boolean isDynamicGroupRoute();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ method public boolean isVisibilityPublic();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+ ctor public MediaRouteDescriptor.Builder(String, String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilter(android.content.IntentFilter);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder addControlFilters(java.util.Collection<android.content.IntentFilter!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder clearControlFilters();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setCanDisconnect(boolean);
+ method @Deprecated public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnecting(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setConnectionState(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeduplicationIds(java.util.Set<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDescription(String?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setDeviceType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIconUri(android.net.Uri);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setId(String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setIsDynamicGroupRoute(boolean);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setName(String);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackStream(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPlaybackType(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setPresentationDisplayId(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setSettingsActivity(android.content.IntentSender?);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityPublic();
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVisibilityRestricted(java.util.Set<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolume(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeHandling(int);
+ method public androidx.mediarouter.media.MediaRouteDescriptor.Builder setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(androidx.mediarouter.media.MediaRouteSelector, boolean);
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaRouteDiscoveryRequest? fromBundle(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaRouteSelector getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context getContext();
+ method public final androidx.mediarouter.media.MediaRouteProviderDescriptor? getDescriptor();
+ method public final androidx.mediarouter.media.MediaRouteDiscoveryRequest? getDiscoveryRequest();
+ method public final android.os.Handler getHandler();
+ method public final androidx.mediarouter.media.MediaRouteProvider.ProviderMetadata getMetadata();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController? onCreateDynamicGroupRouteController(String);
+ method public androidx.mediarouter.media.MediaRouteProvider.RouteController? onCreateRouteController(String);
+ method public void onDiscoveryRequestChanged(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ method public final void setCallback(androidx.mediarouter.media.MediaRouteProvider.Callback?);
+ method public final void setDescriptor(androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ method public final void setDiscoveryRequest(androidx.mediarouter.media.MediaRouteDiscoveryRequest?);
+ }
+
+ public abstract static class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(androidx.mediarouter.media.MediaRouteProvider, androidx.mediarouter.media.MediaRouteProviderDescriptor?);
+ }
+
+ public abstract static class MediaRouteProvider.DynamicGroupRouteController extends androidx.mediarouter.media.MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.DynamicGroupRouteController();
+ method public String? getGroupableSelectionTitle();
+ method public String? getTransferableSectionTitle();
+ method public final void notifyDynamicRoutesChanged(androidx.mediarouter.media.MediaRouteDescriptor, java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method @Deprecated public final void notifyDynamicRoutesChanged(java.util.Collection<androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor!>);
+ method public abstract void onAddMemberRoute(String);
+ method public abstract void onRemoveMemberRoute(String);
+ method public abstract void onUpdateMemberRoutes(java.util.List<java.lang.String!>?);
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor {
+ method public androidx.mediarouter.media.MediaRouteDescriptor getRouteDescriptor();
+ method public int getSelectionState();
+ method public boolean isGroupable();
+ method public boolean isTransferable();
+ method public boolean isUnselectable();
+ field public static final int SELECTED = 3; // 0x3
+ field public static final int SELECTING = 2; // 0x2
+ field public static final int UNSELECTED = 1; // 0x1
+ field public static final int UNSELECTING = 0; // 0x0
+ }
+
+ public static final class MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder {
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteDescriptor);
+ ctor public MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder(androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsGroupable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsTransferable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setIsUnselectable(boolean);
+ method public androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController.DynamicRouteDescriptor.Builder setSelectionState(int);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName getComponentName();
+ method public String getPackageName();
+ }
+
+ public abstract static class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method @Deprecated public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaRouteProviderDescriptor? fromBundle(android.os.Bundle?);
+ method public java.util.List<androidx.mediarouter.media.MediaRouteDescriptor!> getRoutes();
+ method public boolean isValid();
+ method public boolean supportsDynamicGroupRoute();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(androidx.mediarouter.media.MediaRouteProviderDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoute(androidx.mediarouter.media.MediaRouteDescriptor);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder addRoutes(java.util.Collection<androidx.mediarouter.media.MediaRouteDescriptor!>);
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor build();
+ method public androidx.mediarouter.media.MediaRouteProviderDescriptor.Builder setSupportsDynamicGroupRoute(boolean);
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public androidx.mediarouter.media.MediaRouteProvider? getMediaRouteProvider();
+ method public android.os.IBinder? onBind(android.content.Intent);
+ method public abstract androidx.mediarouter.media.MediaRouteProvider? onCreateMediaRouteProvider();
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle asBundle();
+ method public boolean contains(androidx.mediarouter.media.MediaRouteSelector);
+ method public static androidx.mediarouter.media.MediaRouteSelector? fromBundle(android.os.Bundle?);
+ method public java.util.List<java.lang.String!> getControlCategories();
+ method public boolean hasControlCategory(String?);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter!>?);
+ field public static final androidx.mediarouter.media.MediaRouteSelector! EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String!>);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addControlCategory(String);
+ method public androidx.mediarouter.media.MediaRouteSelector.Builder addSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method public androidx.mediarouter.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback);
+ method @MainThread public void addCallback(androidx.mediarouter.media.MediaRouteSelector, androidx.mediarouter.media.MediaRouter.Callback, int);
+ method @MainThread public void addProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method @Deprecated @MainThread public void addRemoteControlClient(Object);
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo? getBluetoothRoute();
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getDefaultRoute();
+ method @MainThread public static androidx.mediarouter.media.MediaRouter getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token? getMediaSessionToken();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.ProviderInfo!> getProviders();
+ method @MainThread public androidx.mediarouter.media.MediaRouterParams? getRouterParams();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo getSelectedRoute();
+ method @MainThread public boolean isRouteAvailable(androidx.mediarouter.media.MediaRouteSelector, int);
+ method @MainThread public void removeCallback(androidx.mediarouter.media.MediaRouter.Callback);
+ method @MainThread public void removeProvider(androidx.mediarouter.media.MediaRouteProvider);
+ method @MainThread public void removeRemoteControlClient(Object);
+ method @MainThread public void selectRoute(androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @MainThread public void setMediaSession(Object?);
+ method @MainThread public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat?);
+ method @MainThread public void setOnPrepareTransferListener(androidx.mediarouter.media.MediaRouter.OnPrepareTransferListener?);
+ method @MainThread public void setRouteListingPreference(androidx.mediarouter.media.RouteListingPreference?);
+ method @MainThread public void setRouterParams(androidx.mediarouter.media.MediaRouterParams?);
+ method @MainThread public void unselect(int);
+ method @MainThread public androidx.mediarouter.media.MediaRouter.RouteInfo updateSelectedRoute(androidx.mediarouter.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public abstract static class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onProviderChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onProviderRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.ProviderInfo);
+ method public void onRouteAdded(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRoutePresentationDisplayChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteRemoved(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteSelected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method @Deprecated public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void onRouteUnselected(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo, int);
+ method public void onRouteVolumeChanged(androidx.mediarouter.media.MediaRouter, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public abstract static class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(String?, android.os.Bundle?);
+ method public void onResult(android.os.Bundle?);
+ }
+
+ public static interface MediaRouter.OnPrepareTransferListener {
+ method @MainThread public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!>? onPrepareTransfer(androidx.mediarouter.media.MediaRouter.RouteInfo, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName getComponentName();
+ method public String getPackageName();
+ method @MainThread public androidx.mediarouter.media.MediaRouteProvider getProviderInstance();
+ method @MainThread public java.util.List<androidx.mediarouter.media.MediaRouter.RouteInfo!> getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter!> getControlFilters();
+ method public String? getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle? getExtras();
+ method public android.net.Uri? getIconUri();
+ method public String getId();
+ method public String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method @MainThread public android.view.Display? getPresentationDisplay();
+ method public androidx.mediarouter.media.MediaRouter.ProviderInfo getProvider();
+ method public android.content.IntentSender? getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method @MainThread public boolean isBluetooth();
+ method @Deprecated public boolean isConnecting();
+ method @MainThread public boolean isDefault();
+ method public boolean isDeviceSpeaker();
+ method public boolean isEnabled();
+ method @MainThread public boolean isSelected();
+ method @MainThread public boolean matchesSelector(androidx.mediarouter.media.MediaRouteSelector);
+ method @MainThread public void requestSetVolume(int);
+ method @MainThread public void requestUpdateVolume(int);
+ method @MainThread public void select();
+ method @MainThread public void sendControlRequest(android.content.Intent, androidx.mediarouter.media.MediaRouter.ControlRequestCallback?);
+ method @MainThread public boolean supportsControlAction(String, String);
+ method @MainThread public boolean supportsControlCategory(String);
+ method @MainThread public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_AUDIO_VIDEO_RECEIVER = 4; // 0x4
+ field public static final int DEVICE_TYPE_CAR = 9; // 0x9
+ field public static final int DEVICE_TYPE_COMPUTER = 7; // 0x7
+ field public static final int DEVICE_TYPE_GAME_CONSOLE = 8; // 0x8
+ field public static final int DEVICE_TYPE_GROUP = 1000; // 0x3e8
+ field public static final int DEVICE_TYPE_SMARTWATCH = 10; // 0xa
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TABLET = 5; // 0x5
+ field public static final int DEVICE_TYPE_TABLET_DOCKED = 6; // 0x6
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public class MediaRouterParams {
+ method public int getDialogType();
+ method public boolean isMediaTransferReceiverEnabled();
+ method public boolean isOutputSwitcherEnabled();
+ method public boolean isTransferToLocalEnabled();
+ field public static final int DIALOG_TYPE_DEFAULT = 1; // 0x1
+ field public static final int DIALOG_TYPE_DYNAMIC_GROUP = 2; // 0x2
+ field public static final String ENABLE_GROUP_VOLUME_UX = "androidx.mediarouter.media.MediaRouterParams.ENABLE_GROUP_VOLUME_UX";
+ }
+
+ public static final class MediaRouterParams.Builder {
+ ctor public MediaRouterParams.Builder();
+ ctor public MediaRouterParams.Builder(androidx.mediarouter.media.MediaRouterParams);
+ method public androidx.mediarouter.media.MediaRouterParams build();
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setDialogType(int);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setMediaTransferReceiverEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setOutputSwitcherEnabled(boolean);
+ method public androidx.mediarouter.media.MediaRouterParams.Builder setTransferToLocalEnabled(boolean);
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle asBundle();
+ method public static androidx.mediarouter.media.MediaSessionStatus? fromBundle(android.os.Bundle?);
+ method public android.os.Bundle? getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(androidx.mediarouter.media.MediaSessionStatus);
+ ctor public MediaSessionStatus.Builder(int);
+ method public androidx.mediarouter.media.MediaSessionStatus build();
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setExtras(android.os.Bundle?);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setQueuePaused(boolean);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setSessionState(int);
+ method public androidx.mediarouter.media.MediaSessionStatus.Builder setTimestamp(long);
+ }
+
+ public final class MediaTransferReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaTransferReceiver();
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context, androidx.mediarouter.media.MediaRouter.RouteInfo);
+ method public void endSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void enqueue(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public String? getSessionId();
+ method public void getSessionStatus(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void getStatus(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public boolean hasSession();
+ method public boolean isMessagingSupported();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void play(android.net.Uri, String?, android.os.Bundle?, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void release();
+ method public void remove(String, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void resume(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void seek(String, long, android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.ItemActionCallback?);
+ method public void sendMessage(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void setOnMessageReceivedListener(androidx.mediarouter.media.RemotePlaybackClient.OnMessageReceivedListener?);
+ method public void setSessionId(String?);
+ method public void setStatusCallback(androidx.mediarouter.media.RemotePlaybackClient.StatusCallback?);
+ method public void startSession(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ method public void stop(android.os.Bundle?, androidx.mediarouter.media.RemotePlaybackClient.SessionActionCallback?);
+ }
+
+ public abstract static class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(String?, int, android.os.Bundle?);
+ }
+
+ public abstract static class RemotePlaybackClient.ItemActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+ }
+
+ public static interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public void onMessageReceived(String, android.os.Bundle?);
+ }
+
+ public abstract static class RemotePlaybackClient.SessionActionCallback extends androidx.mediarouter.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle, String, androidx.mediarouter.media.MediaSessionStatus?);
+ }
+
+ public abstract static class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?, String, androidx.mediarouter.media.MediaItemStatus);
+ method public void onSessionChanged(String?);
+ method public void onSessionStatusChanged(android.os.Bundle?, String, androidx.mediarouter.media.MediaSessionStatus?);
+ }
+
+ public final class RouteListingPreference {
+ method public java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!> getItems();
+ method public android.content.ComponentName? getLinkedItemComponentName();
+ method public boolean getUseSystemOrdering();
+ field public static final String ACTION_TRANSFER_MEDIA = "android.media.action.TRANSFER_MEDIA";
+ field public static final String EXTRA_ROUTE_ID = "android.media.extra.ROUTE_ID";
+ }
+
+ public static final class RouteListingPreference.Builder {
+ ctor public RouteListingPreference.Builder();
+ method public androidx.mediarouter.media.RouteListingPreference build();
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setItems(java.util.List<androidx.mediarouter.media.RouteListingPreference.Item!>);
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setLinkedItemComponentName(android.content.ComponentName?);
+ method public androidx.mediarouter.media.RouteListingPreference.Builder setUseSystemOrdering(boolean);
+ }
+
+ public static final class RouteListingPreference.Item {
+ method public CharSequence? getCustomSubtextMessage();
+ method public int getFlags();
+ method public String getRouteId();
+ method public int getSelectionBehavior();
+ method public int getSubText();
+ field public static final int FLAG_ONGOING_SESSION = 1; // 0x1
+ field public static final int FLAG_ONGOING_SESSION_MANAGED = 2; // 0x2
+ field public static final int FLAG_SUGGESTED = 4; // 0x4
+ field public static final int SELECTION_BEHAVIOR_GO_TO_APP = 2; // 0x2
+ field public static final int SELECTION_BEHAVIOR_NONE = 0; // 0x0
+ field public static final int SELECTION_BEHAVIOR_TRANSFER = 1; // 0x1
+ field public static final int SUBTEXT_AD_ROUTING_DISALLOWED = 4; // 0x4
+ field public static final int SUBTEXT_CUSTOM = 10000; // 0x2710
+ field public static final int SUBTEXT_DEVICE_LOW_POWER = 5; // 0x5
+ field public static final int SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED = 3; // 0x3
+ field public static final int SUBTEXT_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int SUBTEXT_NONE = 0; // 0x0
+ field public static final int SUBTEXT_SUBSCRIPTION_REQUIRED = 2; // 0x2
+ field public static final int SUBTEXT_TRACK_UNSUPPORTED = 7; // 0x7
+ field public static final int SUBTEXT_UNAUTHORIZED = 6; // 0x6
+ }
+
+ public static final class RouteListingPreference.Item.Builder {
+ ctor public RouteListingPreference.Item.Builder(String);
+ method public androidx.mediarouter.media.RouteListingPreference.Item build();
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setCustomSubtextMessage(CharSequence?);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setFlags(int);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSelectionBehavior(int);
+ method public androidx.mediarouter.media.RouteListingPreference.Item.Builder setSubText(int);
+ }
+
+}
+
diff --git a/navigation/navigation-common/api/2.7.0-beta02.txt b/navigation/navigation-common/api/2.7.0-beta02.txt
index 51a77a6..95166e9 100644
--- a/navigation/navigation-common/api/2.7.0-beta02.txt
+++ b/navigation/navigation-common/api/2.7.0-beta02.txt
@@ -128,6 +128,10 @@
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
property public androidx.lifecycle.ViewModelStore viewModelStore;
+ field public static final androidx.navigation.NavBackStackEntry.Companion Companion;
+ }
+
+ public static final class NavBackStackEntry.Companion {
}
public final class NavDeepLink {
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index 51a77a6..95166e9 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -128,6 +128,10 @@
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
property public androidx.lifecycle.ViewModelStore viewModelStore;
+ field public static final androidx.navigation.NavBackStackEntry.Companion Companion;
+ }
+
+ public static final class NavBackStackEntry.Companion {
}
public final class NavDeepLink {
diff --git a/navigation/navigation-common/api/restricted_2.7.0-beta02.txt b/navigation/navigation-common/api/restricted_2.7.0-beta02.txt
index 51a77a6..95166e9 100644
--- a/navigation/navigation-common/api/restricted_2.7.0-beta02.txt
+++ b/navigation/navigation-common/api/restricted_2.7.0-beta02.txt
@@ -128,6 +128,10 @@
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
property public androidx.lifecycle.ViewModelStore viewModelStore;
+ field public static final androidx.navigation.NavBackStackEntry.Companion Companion;
+ }
+
+ public static final class NavBackStackEntry.Companion {
}
public final class NavDeepLink {
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index 51a77a6..95166e9 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -128,6 +128,10 @@
property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
property public androidx.lifecycle.ViewModelStore viewModelStore;
+ field public static final androidx.navigation.NavBackStackEntry.Companion Companion;
+ }
+
+ public static final class NavBackStackEntry.Companion {
}
public final class NavDeepLink {
diff --git a/navigation/navigation-common/lint-baseline.xml b/navigation/navigation-common/lint-baseline.xml
deleted file mode 100644
index af90ea4..0000000
--- a/navigation/navigation-common/lint-baseline.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
-
- <issue
- id="BanHideAnnotation"
- message="@hide is not allowed in Javadoc"
- errorLine1=" public companion object {"
- errorLine2=" ~~~~~~">
- <location
- file="src/main/java/androidx/navigation/NavBackStackEntry.kt"/>
- </issue>
-
-</issues>
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index 0cf2b96..0d605ee 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -84,10 +84,6 @@
maxLifecycle = entry.maxLifecycle
}
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public companion object {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun create(
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
index 536688d..76f77a0 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/HintHandler.kt
@@ -22,7 +22,7 @@
import androidx.paging.LoadType.APPEND
import androidx.paging.LoadType.PREPEND
import co.touchlab.stately.concurrency.Lock
-import kotlin.concurrent.withLock
+import co.touchlab.stately.concurrency.withLock
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
index fdeb52f..e23d525 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/InvalidateCallbackTracker.kt
@@ -18,7 +18,7 @@
import androidx.annotation.VisibleForTesting
import co.touchlab.stately.concurrency.Lock
-import kotlin.concurrent.withLock
+import co.touchlab.stately.concurrency.withLock
/**
* Helper class for thread-safe invalidation callback tracking + triggering on registration.
@@ -74,7 +74,7 @@
internal fun invalidate(): Boolean {
if (invalid) return false
- var callbacksToInvoke: List<T>?
+ var callbacksToInvoke: List<T>? = null
lock.withLock {
if (invalid) return false
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataDiffer.kt
index 448c904..41e3d5f 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -17,7 +17,6 @@
package androidx.paging
import androidx.annotation.IntRange
-import androidx.annotation.MainThread
import androidx.annotation.RestrictTo
import androidx.paging.LoadType.APPEND
import androidx.paging.LoadType.PREPEND
diff --git a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
index b6f5b1a..f2725a1 100644
--- a/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/RemoteMediatorAccessor.kt
@@ -21,7 +21,7 @@
import androidx.paging.AccessorState.BlockState.UNBLOCKED
import androidx.paging.RemoteMediator.MediatorResult
import co.touchlab.stately.concurrency.Lock
-import kotlin.concurrent.withLock
+import co.touchlab.stately.concurrency.withLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt b/paging/paging-common/src/commonMain/kotlin/androidx/paging/annotation.kt
similarity index 66%
rename from bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
rename to paging/paging-common/src/commonMain/kotlin/androidx/paging/annotation.kt
index f2fb27d..5d73136 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
+++ b/paging/paging-common/src/commonMain/kotlin/androidx/paging/annotation.kt
@@ -14,13 +14,6 @@
* limitations under the License.
*/
-package androidx.bluetooth.integration.testapp.experimental
+package androidx.paging
-enum class AdvertiseResult {
- ADVERTISE_STARTED,
- ADVERTISE_FAILED_ALREADY_STARTED,
- ADVERTISE_FAILED_DATA_TOO_LARGE,
- ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
- ADVERTISE_FAILED_INTERNAL_ERROR,
- ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
-}
+public expect annotation class MainThread()
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MainThread.kt
similarity index 66%
copy from bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
copy to paging/paging-common/src/jvmMain/kotlin/androidx/paging/MainThread.kt
index f2fb27d..7705b0d 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
+++ b/paging/paging-common/src/jvmMain/kotlin/androidx/paging/MainThread.kt
@@ -14,13 +14,6 @@
* limitations under the License.
*/
-package androidx.bluetooth.integration.testapp.experimental
+package androidx.paging
-enum class AdvertiseResult {
- ADVERTISE_STARTED,
- ADVERTISE_FAILED_ALREADY_STARTED,
- ADVERTISE_FAILED_DATA_TOO_LARGE,
- ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
- ADVERTISE_FAILED_INTERNAL_ERROR,
- ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
-}
+public actual typealias MainThread = androidx.annotation.MainThread
diff --git a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/annotation.kt
similarity index 66%
copy from bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
copy to paging/paging-common/src/nativeMain/kotlin/androidx/paging/annotation.kt
index f2fb27d..ce1d1a6 100644
--- a/bluetooth/integration-tests/testapp/src/main/java/androidx/bluetooth/integration/testapp/experimental/AdvertiseResult.kt
+++ b/paging/paging-common/src/nativeMain/kotlin/androidx/paging/annotation.kt
@@ -14,13 +14,6 @@
* limitations under the License.
*/
-package androidx.bluetooth.integration.testapp.experimental
+package androidx.paging
-enum class AdvertiseResult {
- ADVERTISE_STARTED,
- ADVERTISE_FAILED_ALREADY_STARTED,
- ADVERTISE_FAILED_DATA_TOO_LARGE,
- ADVERTISE_FAILED_FEATURE_UNSUPPORTED,
- ADVERTISE_FAILED_INTERNAL_ERROR,
- ADVERTISE_FAILED_TOO_MANY_ADVERTISERS
-}
+public actual annotation class MainThread()
diff --git a/preference/preference/lint-baseline.xml b/preference/preference/lint-baseline.xml
index 4b4ccf8..217ddcd 100644
--- a/preference/preference/lint-baseline.xml
+++ b/preference/preference/lint-baseline.xml
@@ -1,14 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-alpha07" type="baseline" client="gradle" dependencies="false" name="AGP (8.0.0-beta03)" variant="all" version="8.1.0-alpha07">
-
- <issue
- id="BanHideAnnotation"
- message="@hide is not allowed in Javadoc"
- errorLine1=" public static PreferenceViewHolder createInstanceForTests(@NonNull View itemView) {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/preference/PreferenceViewHolder.java"/>
- </issue>
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="BanThreadSleep"
diff --git a/preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java b/preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java
index 95635c5..e3129a8 100644
--- a/preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java
+++ b/preference/preference/src/main/java/androidx/preference/PreferenceViewHolder.java
@@ -25,6 +25,7 @@
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -60,7 +61,7 @@
}
}
- /** @hide */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@VisibleForTesting
@NonNull
public static PreferenceViewHolder createInstanceForTests(@NonNull View itemView) {
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
index 82a15d5..2808373 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/QueryInterceptorTest.kt
@@ -167,6 +167,20 @@
}
@Test
+ fun testExecSQLWithBindArgs() {
+ mDatabase.openHelper.writableDatabase.execSQL(
+ "INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
+ "VALUES (?,?)",
+ arrayOf("3", "Description")
+ )
+ assertQueryLogged(
+ "INSERT OR ABORT INTO `queryInterceptorTestDatabase` (`id`,`description`) " +
+ "VALUES (?,?)",
+ listOf("3", "Description")
+ )
+ }
+
+ @Test
fun testNullBindArgument() {
mDatabase.openHelper.writableDatabase.query(
SimpleSQLiteQuery(
diff --git a/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt b/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
index d6a4107..0f1ec17 100644
--- a/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
+++ b/room/room-runtime/src/main/java/androidx/room/QueryInterceptorDatabase.kt
@@ -136,11 +136,10 @@
// and it can't be renamed.
@Suppress("AcronymName")
override fun execSQL(sql: String, bindArgs: Array<out Any?>) {
- val inputArguments = mutableListOf<Any>()
- inputArguments.addAll(listOf(bindArgs))
+ val inputArguments = buildList { addAll(bindArgs) }
queryCallbackExecutor.execute {
queryCallback.onQuery(sql, inputArguments)
}
- delegate.execSQL(sql, arrayOf(inputArguments))
+ delegate.execSQL(sql, inputArguments.toTypedArray())
}
}
diff --git a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
index 820d26a..d4c24aa 100644
--- a/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
+++ b/sqlite/sqlite/src/main/java/androidx/sqlite/db/SupportSQLiteDatabase.kt
@@ -1,4 +1,3 @@
-@file:JvmName("SupportSQLiteDatabaseKt")
/*
* Copyright (C) 2016 The Android Open Source Project
*
diff --git a/test/uiautomator/uiautomator/lint-baseline.xml b/test/uiautomator/uiautomator/lint-baseline.xml
index a62bf81..6476f95 100644
--- a/test/uiautomator/uiautomator/lint-baseline.xml
+++ b/test/uiautomator/uiautomator/lint-baseline.xml
@@ -1,14 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.1.0-beta02" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta02)" variant="all" version="8.1.0-beta02">
-
- <issue
- id="BanHideAnnotation"
- message="@hide is not allowed in Javadoc"
- errorLine1=" public String executeShellCommand(@NonNull String cmd) throws IOException {"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~">
- <location
- file="src/main/java/androidx/test/uiautomator/UiDevice.java"/>
- </issue>
+<issues format="6" by="lint 8.1.0-beta05" type="baseline" client="gradle" dependencies="false" name="AGP (8.1.0-beta05)" variant="all" version="8.1.0-beta05">
<issue
id="BanUncheckedReflection"
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
index dc72558..8dbc93d 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/UiDevice.java
@@ -48,6 +48,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import androidx.test.uiautomator.util.Traces;
import androidx.test.uiautomator.util.Traces.Section;
@@ -1115,8 +1116,9 @@
* @param cmd the command to run
* @return the standard output of the command
* @throws IOException
- * @hide legacy hidden method, kept for compatibility with existing tests.
+ * Legacy hidden method, kept for compatibility with existing tests.
*/
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(21)
@NonNull
public String executeShellCommand(@NonNull String cmd) throws IOException {
diff --git a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToDismissBoxTest.kt b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToDismissBoxTest.kt
index dfcca4b..956835f 100644
--- a/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToDismissBoxTest.kt
+++ b/wear/compose/compose-foundation/src/androidTest/kotlin/androidx/wear/compose/foundation/SwipeToDismissBoxTest.kt
@@ -18,6 +18,7 @@
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -36,6 +37,8 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
@@ -53,6 +56,8 @@
import androidx.compose.ui.test.swipeRight
import java.lang.Math.sin
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -419,6 +424,58 @@
}
}
+ @OptIn(ExperimentalWearFoundationApi::class)
+ @Test
+ fun partial_swipe_maintains_focus() {
+ var focusedBackground by mutableStateOf(false)
+ var focusedContent by mutableStateOf(false)
+
+ rule.setContent {
+ val state = rememberSwipeToDismissBoxState()
+ SwipeToDismissBox(
+ state = state,
+ modifier = Modifier.testTag(TEST_TAG),
+ ) { isBackground ->
+ if (isBackground) {
+ val focusRequester = rememberActiveFocusRequester()
+ BasicText(
+ BACKGROUND_MESSAGE,
+ Modifier
+ .onFocusChanged { focusedBackground = it.isFocused }
+ .focusRequester(focusRequester)
+ .focusable())
+ } else {
+ val focusRequester = rememberActiveFocusRequester()
+ BasicText(
+ CONTENT_MESSAGE,
+ Modifier
+ .onFocusChanged { focusedContent = it.isFocused }
+ .focusRequester(focusRequester)
+ .focusable())
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertTrue(focusedContent)
+ assertFalse(focusedBackground)
+ }
+
+ // Click down and drag across 1/4 of the screen to start a swipe,
+ // but don't release the finger, so that the screen can be inspected
+ // (note that swipeRight would release the finger and does not pause time midway).
+ rule.onNodeWithTag(TEST_TAG).performTouchInput {
+ down(Offset(x = 0f, y = height / 2f))
+ moveTo(Offset(x = width / 4f, y = height / 2f))
+ }
+
+ // We started showing the background, but focus hasn't changed.
+ rule.runOnIdle {
+ assertTrue(focusedContent)
+ assertFalse(focusedBackground)
+ }
+ }
+
private fun testBothDirectionScroll(
initialTouch: Long,
duration: Long,
diff --git a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToDismissBox.kt b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToDismissBox.kt
index b776940..f4e9c56 100644
--- a/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToDismissBox.kt
+++ b/wear/compose/compose-foundation/src/main/java/androidx/wear/compose/foundation/SwipeToDismissBox.kt
@@ -203,13 +203,15 @@
if (!isBackground ||
(userSwipeEnabled && (state.swipeableState.offset?.roundToInt() ?: 0) > 0)
) {
- Box(contentModifier) {
- // We use the repeat loop above and call content at this location
- // for both background and foreground so that any persistence
- // within the content composable has the same call stack which is used
- // as part of the hash identity for saveable state.
- content(isBackground)
- Box(modifier = scrimModifier)
+ HierarchicalFocusCoordinator(requiresFocus = { !isBackground }) {
+ Box(contentModifier) {
+ // We use the repeat loop above and call content at this location
+ // for both background and foreground so that any persistence
+ // within the content composable has the same call stack which is used
+ // as part of the hash identity for saveable state.
+ content(isBackground)
+ Box(modifier = scrimModifier)
+ }
}
}
}
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
index 0323d67..d546a2e 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/ButtonDemo.kt
@@ -16,13 +16,17 @@
package androidx.wear.compose.material3.demos
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ButtonDefaults
@@ -227,4 +231,159 @@
)
}
}
+}
+
+@Composable
+fun MultilineButtonDemo() {
+ ScalingLazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ item {
+ ListHeader {
+ Text("3 line label")
+ }
+ }
+ item {
+ MultilineButton(enabled = true)
+ }
+ item {
+ MultilineButton(enabled = false)
+ }
+ item {
+ MultilineButton(enabled = true, icon = { StandardIcon() })
+ }
+ item {
+ MultilineButton(enabled = false, icon = { StandardIcon() })
+ }
+ item {
+ ListHeader {
+ Text("5 line button")
+ }
+ }
+ item {
+ Multiline3SlotButton(enabled = true)
+ }
+ item {
+ Multiline3SlotButton(enabled = false)
+ }
+ item {
+ Multiline3SlotButton(enabled = true, icon = { StandardIcon() })
+ }
+ item {
+ Multiline3SlotButton(enabled = false, icon = { StandardIcon() })
+ }
+ }
+}
+
+@Composable
+fun AvatarButtonDemo() {
+ ScalingLazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ item {
+ ListHeader {
+ Text("Label + Avatar")
+ }
+ }
+ item {
+ AvatarButton(enabled = true)
+ }
+ item {
+ AvatarButton(enabled = false)
+ }
+ item {
+ ListHeader {
+ Text("Primary/Secondary + Avatar")
+ }
+ }
+ item {
+ Avatar3SlotButton(enabled = true)
+ }
+ item {
+ Avatar3SlotButton(enabled = false)
+ }
+ }
+}
+
+@Composable
+private fun AvatarButton(enabled: Boolean) =
+ MultilineButton(
+ enabled = enabled, icon = { AvatarIcon() }, label = { Text("Primary text") }
+ )
+
+@Composable
+private fun Avatar3SlotButton(enabled: Boolean) =
+ Multiline3SlotButton(
+ enabled = enabled,
+ icon = { AvatarIcon() },
+ label = { Text("Primary text") },
+ secondaryLabel = { Text("Secondary label") }
+ )
+
+@Composable
+private fun MultilineButton(
+ enabled: Boolean,
+ icon: (@Composable BoxScope.() -> Unit)? = null,
+ label: @Composable RowScope.() -> Unit = {
+ Text(
+ text = "Multiline label that include a lot of text and stretches to third line",
+ maxLines = 3,
+ overflow = TextOverflow.Ellipsis,
+ )
+ },
+) {
+ Button(
+ onClick = { /* Do something */ },
+ icon = icon,
+ label = label,
+ enabled = enabled
+ )
+}
+
+@Composable
+private fun Multiline3SlotButton(
+ enabled: Boolean,
+ icon: (@Composable BoxScope.() -> Unit)? = null,
+ label: @Composable RowScope.() -> Unit = {
+ Text(
+ text = "Multiline label that include a lot of text and stretches to third line",
+ maxLines = 3,
+ overflow = TextOverflow.Ellipsis,
+ )
+ },
+ secondaryLabel: @Composable RowScope.() -> Unit = {
+ Text(
+ text = "Secondary label over two lines",
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis,
+ )
+ },
+) {
+ Button(
+ onClick = { /* Do something */ },
+ icon = icon,
+ label = label,
+ secondaryLabel = secondaryLabel,
+ enabled = enabled
+ )
+}
+
+@Composable
+private fun StandardIcon() {
+ Icon(
+ Icons.Filled.Favorite,
+ contentDescription = "Favorite icon",
+ modifier = Modifier.size(ButtonDefaults.IconSize)
+ )
+}
+
+@Composable
+private fun AvatarIcon() {
+ Icon(
+ Icons.Filled.AccountCircle,
+ contentDescription = "Account",
+ modifier = Modifier.size(ButtonDefaults.LargeIconSize)
+ )
}
\ No newline at end of file
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SliderDemo.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SliderDemo.kt
index 4ad6082..b17cb00 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SliderDemo.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/SliderDemo.kt
@@ -42,7 +42,6 @@
import androidx.wear.compose.material3.InlineSlider
import androidx.wear.compose.material3.InlineSliderColors
import androidx.wear.compose.material3.InlineSliderDefaults
-import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.samples.InlineSliderSample
import androidx.wear.compose.material3.samples.InlineSliderSegmentedSample
@@ -99,9 +98,7 @@
autoCentering = AutoCenteringParams(itemIndex = 0)
) {
item {
- ListHeader {
- Text("Enabled Slider, value = $enabledValue")
- }
+ Text("Enabled Slider, value = $enabledValue")
}
item {
DefaultInlineSlider(
@@ -114,9 +111,7 @@
)
}
item {
- ListHeader {
- Text("Disabled Slider, value = $disabledValue")
- }
+ Text("Disabled Slider, value = $disabledValue")
}
item {
DefaultInlineSlider(
@@ -146,9 +141,7 @@
autoCentering = AutoCenteringParams(itemIndex = 0)
) {
item {
- ListHeader {
- Text("No segments, value = $valueWithoutSegments")
- }
+ Text("No segments, value = $valueWithoutSegments")
}
item {
DefaultInlineSlider(
@@ -158,9 +151,7 @@
onValueChange = { valueWithoutSegments = it })
}
item {
- ListHeader {
- Text("With segments, value = $valueWithSegments")
- }
+ Text("With segments, value = $valueWithSegments")
}
item {
DefaultInlineSlider(
diff --git a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
index acb77e0..d4df8fc 100644
--- a/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
+++ b/wear/compose/compose-material3/integration-tests/src/main/java/androidx/wear/compose/material3/demos/WearMaterial3Demos.kt
@@ -52,7 +52,13 @@
},
ComposableDemo("Child Button") {
ChildButtonDemo()
- }
+ },
+ ComposableDemo("Multiline Button") {
+ MultilineButtonDemo()
+ },
+ ComposableDemo("Avatar Button") {
+ AvatarButtonDemo()
+ },
)
),
DemoCategory(
diff --git a/wear/compose/compose-navigation/api/current.txt b/wear/compose/compose-navigation/api/current.txt
index 30cd692..e27dd9d 100644
--- a/wear/compose/compose-navigation/api/current.txt
+++ b/wear/compose/compose-navigation/api/current.txt
@@ -11,8 +11,10 @@
}
public final class SwipeDismissableNavHostKt {
- method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
- method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
+ method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional boolean userSwipeEnabled, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
+ method @Deprecated @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional boolean userSwipeEnabled, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.wear.compose.navigation.SwipeDismissableNavHostState rememberSwipeDismissableNavHostState(optional androidx.wear.compose.foundation.SwipeToDismissBoxState swipeToDismissBoxState);
method @Deprecated @androidx.compose.runtime.Composable public static androidx.wear.compose.navigation.SwipeDismissableNavHostState rememberSwipeDismissableNavHostState(optional androidx.wear.compose.material.SwipeToDismissBoxState swipeToDismissBoxState);
}
diff --git a/wear/compose/compose-navigation/api/restricted_current.txt b/wear/compose/compose-navigation/api/restricted_current.txt
index 30cd692..e27dd9d 100644
--- a/wear/compose/compose-navigation/api/restricted_current.txt
+++ b/wear/compose/compose-navigation/api/restricted_current.txt
@@ -11,8 +11,10 @@
}
public final class SwipeDismissableNavHostKt {
- method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
- method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @Deprecated @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
+ method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, androidx.navigation.NavGraph graph, optional androidx.compose.ui.Modifier modifier, optional boolean userSwipeEnabled, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state);
+ method @Deprecated @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
+ method @androidx.compose.runtime.Composable public static void SwipeDismissableNavHost(androidx.navigation.NavHostController navController, String startDestination, optional androidx.compose.ui.Modifier modifier, optional boolean userSwipeEnabled, optional androidx.wear.compose.navigation.SwipeDismissableNavHostState state, optional String? route, kotlin.jvm.functions.Function1<? super androidx.navigation.NavGraphBuilder,kotlin.Unit> builder);
method @androidx.compose.runtime.Composable public static androidx.wear.compose.navigation.SwipeDismissableNavHostState rememberSwipeDismissableNavHostState(optional androidx.wear.compose.foundation.SwipeToDismissBoxState swipeToDismissBoxState);
method @Deprecated @androidx.compose.runtime.Composable public static androidx.wear.compose.navigation.SwipeDismissableNavHostState rememberSwipeDismissableNavHostState(optional androidx.wear.compose.material.SwipeToDismissBoxState swipeToDismissBoxState);
}
diff --git a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
index 655e7bc..360df14 100644
--- a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
+++ b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
@@ -101,6 +101,21 @@
}
@Test
+ fun does_not_navigate_back_to_previous_level_when_swipe_disabled() {
+ rule.setContentWithTheme {
+ SwipeDismissWithNavigation(userSwipeEnabled = false)
+ }
+
+ // Click to move to next destination then swipe to dismiss.
+ rule.onNodeWithText(START).performClick()
+ rule.onNodeWithTag(TEST_TAG).performTouchInput { swipeRight() }
+
+ // Should still display "next".
+ rule.onNodeWithText(NEXT).assertExists()
+ rule.onNodeWithText(START).assertDoesNotExist()
+ }
+
+ @Test
fun navigates_back_to_previous_level_with_back_button() {
val onBackPressedDispatcher = OnBackPressedDispatcher()
val dispatcherOwner =
@@ -423,12 +438,14 @@
@Composable
fun SwipeDismissWithNavigation(
- navController: NavHostController = rememberSwipeDismissableNavController()
+ navController: NavHostController = rememberSwipeDismissableNavController(),
+ userSwipeEnabled: Boolean = true
) {
SwipeDismissableNavHost(
navController = navController,
startDestination = START,
modifier = Modifier.testTag(TEST_TAG),
+ userSwipeEnabled = userSwipeEnabled
) {
composable(START) {
CompactChip(
diff --git a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
index c373ef9..377b170 100644
--- a/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
+++ b/wear/compose/compose-navigation/src/main/java/androidx/wear/compose/navigation/SwipeDismissableNavHost.kt
@@ -76,6 +76,7 @@
* @param navController The navController for this host
* @param startDestination The route for the start destination
* @param modifier The modifier to be applied to the layout
+ * @param userSwipeEnabled [Boolean] Whether swipe-to-dismiss gesture is enabled.
* @param state State containing information about ongoing swipe and animation.
* @param route The route for the graph
* @param builder The builder used to construct the graph
@@ -85,6 +86,7 @@
navController: NavHostController,
startDestination: String,
modifier: Modifier = Modifier,
+ userSwipeEnabled: Boolean = true,
state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
route: String? = null,
builder: NavGraphBuilder.() -> Unit
@@ -95,6 +97,7 @@
navController.createGraph(startDestination, route, builder)
},
modifier,
+ userSwipeEnabled,
state = state,
)
@@ -122,6 +125,7 @@
* @param navController [NavHostController] for this host
* @param graph Graph for this host
* @param modifier [Modifier] to be applied to the layout
+ * @param userSwipeEnabled [Boolean] Whether swipe-to-dismiss gesture is enabled.
* @param state State containing information about ongoing swipe and animation.
*
* @throws IllegalArgumentException if no WearNavigation.Destination is on the navigation backstack.
@@ -131,6 +135,7 @@
navController: NavHostController,
graph: NavGraph,
modifier: Modifier = Modifier,
+ userSwipeEnabled: Boolean = true,
state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
) {
val lifecycleOwner = LocalLifecycleOwner.current
@@ -217,7 +222,7 @@
SwipeToDismissBox(
state = swipeState,
modifier = Modifier,
- userSwipeEnabled = previous != null,
+ userSwipeEnabled = userSwipeEnabled && previous != null,
backgroundKey = previous?.id ?: SwipeToDismissKeys.Background,
contentKey = current?.id ?: SwipeToDismissKeys.Content,
content = { isBackground ->
@@ -243,6 +248,104 @@
}
/**
+ * Provides a place in the Compose hierarchy for self-contained navigation to occur,
+ * with backwards navigation provided by a swipe gesture.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * The builder passed into this method is [remember]ed. This means that for this NavHost, the
+ * contents of the builder cannot be changed.
+ *
+ * Content is displayed within a [SwipeToDismissBox], showing the current navigation level.
+ * During a swipe-to-dismiss gesture, the previous navigation level (if any) is shown in
+ * the background. BackgroundScrimColor and ContentScrimColor of it are taken from
+ * [LocalSwipeToDismissBackgroundScrimColor] and [LocalSwipeToDismissContentScrimColor].
+ *
+ * Example of a [SwipeDismissableNavHost] alternating between 2 screens:
+ * @sample androidx.wear.compose.navigation.samples.SimpleNavHost
+ *
+ * Example of a [SwipeDismissableNavHost] for which a destination has a named argument:
+ * @sample androidx.wear.compose.navigation.samples.NavHostWithNamedArgument
+ *
+ * @param navController The navController for this host
+ * @param startDestination The route for the start destination
+ * @param modifier The modifier to be applied to the layout
+ * @param state State containing information about ongoing swipe and animation.
+ * @param route The route for the graph
+ * @param builder The builder used to construct the graph
+ */
+@Deprecated(
+ "This overload is provided for backwards compatibility. " +
+ "A newer overload is available with an additional userSwipeEnabled param.",
+ level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun SwipeDismissableNavHost(
+ navController: NavHostController,
+ startDestination: String,
+ modifier: Modifier = Modifier,
+ state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
+ route: String? = null,
+ builder: NavGraphBuilder.() -> Unit
+) = SwipeDismissableNavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = modifier,
+ userSwipeEnabled = true,
+ state = state,
+ route = route,
+ builder = builder
+)
+
+/**
+ * Provides a place in the Compose hierarchy for self-contained navigation to occur,
+ * with backwards navigation provided by a swipe gesture.
+ *
+ * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from
+ * the provided [navController].
+ *
+ * The builder passed into this method is [remember]ed. This means that for this NavHost, the
+ * contents of the builder cannot be changed.
+ *
+ * Content is displayed within a [SwipeToDismissBox], showing the current navigation level.
+ * During a swipe-to-dismiss gesture, the previous navigation level (if any) is shown in
+ * the background. BackgroundScrimColor and ContentScrimColor of it are taken from
+ * [LocalSwipeToDismissBackgroundScrimColor] and [LocalSwipeToDismissContentScrimColor].
+ *
+ * Example of a [SwipeDismissableNavHost] alternating between 2 screens:
+ * @sample androidx.wear.compose.navigation.samples.SimpleNavHost
+ *
+ * Example of a [SwipeDismissableNavHost] for which a destination has a named argument:
+ * @sample androidx.wear.compose.navigation.samples.NavHostWithNamedArgument
+ *
+ * @param navController [NavHostController] for this host
+ * @param graph Graph for this host
+ * @param modifier [Modifier] to be applied to the layout
+ * @param state State containing information about ongoing swipe and animation.
+ *
+ * @throws IllegalArgumentException if no WearNavigation.Destination is on the navigation backstack.
+ */
+@Deprecated(
+ "This overload is provided for backwards compatibility. " +
+ "A newer overload is available with an additional userSwipeEnabled param.",
+ level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun SwipeDismissableNavHost(
+ navController: NavHostController,
+ graph: NavGraph,
+ modifier: Modifier = Modifier,
+ state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
+) = SwipeDismissableNavHost(
+ navController = navController,
+ graph = graph,
+ modifier = modifier,
+ userSwipeEnabled = true,
+ state = state
+)
+
+/**
* State for [SwipeDismissableNavHost]
*
* @param swipeToDismissBoxState State for [SwipeToDismissBox], which is used to support the
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index fa0c223..36e7808d 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -26,8 +26,8 @@
applicationId "androidx.wear.compose.integration.demos"
minSdk 25
targetSdk 30
- versionCode 15
- versionName "1.15"
+ versionCode 16
+ versionName "1.16"
// Change the APK name to match the *testapp regex we use to pick up APKs for testing as
// part of CI.
archivesBaseName = "wear-compose-demos-testapp"
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index 02fcfac..d06de43 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
import androidx.wear.compose.foundation.SwipeToDismissBox
import androidx.wear.compose.foundation.SwipeToDismissBoxState
import androidx.wear.compose.foundation.SwipeToDismissKeys
@@ -54,6 +55,7 @@
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.lazy.ScalingParams
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.foundation.rememberActiveFocusRequester
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.integration.demos.common.ActivityDemo
import androidx.wear.compose.integration.demos.common.ComposableDemo
@@ -133,7 +135,9 @@
) {
ScalingLazyColumnWithRSB(
horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.fillMaxWidth().testTag(DemoListTag),
+ modifier = Modifier
+ .fillMaxWidth()
+ .testTag(DemoListTag),
) {
item {
ListHeader {
@@ -169,7 +173,9 @@
) {
Text(
text = description,
- modifier = Modifier.fillMaxWidth().align(Alignment.Center),
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.Center),
textAlign = TextAlign.Center
)
}
@@ -259,13 +265,15 @@
true
}.let {
if (focusRequester != null) {
- it.focusRequester(focusRequester)
+ it
+ .focusRequester(focusRequester)
.focusable()
} else it
}
}
}
+@OptIn(ExperimentalWearFoundationApi::class)
@Composable
fun ScalingLazyColumnWithRSB(
modifier: Modifier = Modifier,
@@ -284,7 +292,7 @@
val flingBehavior = if (snap) ScalingLazyColumnDefaults.snapFlingBehavior(
state = state
) else ScrollableDefaults.flingBehavior()
- val focusRequester = remember { FocusRequester() }
+ val focusRequester = rememberActiveFocusRequester()
ScalingLazyColumn(
modifier = modifier.rsbScroll(
scrollableState = state,
@@ -300,7 +308,4 @@
autoCentering = autoCentering,
content = content
)
- LaunchedEffect(Unit) {
- focusRequester.requestFocus()
- }
}
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
index e36eedb..1ecdca3 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/TimeFormatText.java
@@ -39,6 +39,7 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class TimeFormatText implements TimeDependentText {
+ private static final Date sDate = new Date();
@Override
public boolean equals(Object o) {
@@ -48,13 +49,12 @@
return mStyle == that.mStyle
&& mTimePrecision == that.mTimePrecision
&& Objects.equals(mDateFormat, that.mDateFormat)
- && Objects.equals(mTimeZone, that.mTimeZone)
- && Objects.equals(mDate.toString(), that.mDate.toString());
+ && Objects.equals(mTimeZone, that.mTimeZone);
}
@Override
public int hashCode() {
- return Objects.hash(mDateFormat, mStyle, mTimeZone, mDate, mTimePrecision);
+ return Objects.hash(mDateFormat, mStyle, mTimeZone, mTimePrecision);
}
@NonNull
@@ -69,8 +69,6 @@
+ mStyle
+ ", mTimeZone="
+ mTimeZone
- + ", mDate="
- + mDate
+ ", mTimePrecision="
+ mTimePrecision
+ '}';
@@ -98,7 +96,6 @@
@ComplicationText.TimeFormatStyle private final int mStyle;
private final TimeZone mTimeZone;
- private final Date mDate;
private long mTimePrecision;
public TimeFormatText(
@@ -117,7 +114,6 @@
} else {
mTimeZone = mDateFormat.getTimeZone();
}
- mDate = new Date();
}
TimeFormatText(
@@ -128,7 +124,6 @@
mDateFormat = dateFormat;
mStyle = style;
mTimeZone = timeZone;
- mDate = new Date();
mTimePrecision = timePrecision;
}
@@ -229,8 +224,8 @@
}
private long getOffset(long date) {
- mDate.setTime(date);
- if (mTimeZone.inDaylightTime(mDate)) {
+ sDate.setTime(date);
+ if (mTimeZone.inDaylightTime(sDate)) {
return (long) mTimeZone.getRawOffset() + mTimeZone.getDSTSavings();
}
return mTimeZone.getRawOffset();
@@ -277,7 +272,6 @@
this.mStyle = in.readInt();
this.mTimeZone = (TimeZone) in.readSerializable();
this.mTimePrecision = -1;
- this.mDate = new Date();
}
public static final Creator<TimeFormatText> CREATOR =
diff --git a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
index 9255883..e7e8235 100644
--- a/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
+++ b/wear/watchface/watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditorSessionTest.kt
@@ -632,6 +632,8 @@
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
+ override var editorObscuresWatchFace: Boolean = false
+
override val userStyleSchema = userStyleRepository.schema
override var userStyle: UserStyle
get() = userStyleRepository.userStyle.value
diff --git a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index 70b05b1..55da8af 100644
--- a/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/watchface/watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -427,6 +427,7 @@
) : EditorSession {
protected var closed: Boolean = false
protected var forceClosed: Boolean = false
+ protected open var editorObscuresWatchFace = false
private val editorSessionTraceEvent = AsyncTraceEvent("EditorSession")
private val closeCallback =
@@ -522,6 +523,10 @@
"Can't configure fixed complication ID $complicationSlotId"
}
+ // Don't animate the watch face while the provider is running, because that makes
+ // hardware rendering of the complication preview images very much slower.
+ editorObscuresWatchFace = true
+
val deferredResult = CompletableDeferred<ComplicationDataSourceChooserResult?>()
synchronized(this) {
@@ -552,6 +557,8 @@
synchronized(this) { pendingComplicationDataSourceChooserResult = null }
}
+ editorObscuresWatchFace = false
+
// If deferredResult was null then the user canceled so return null.
if (complicationDataSourceChooserResult == null) {
return null
@@ -844,6 +851,12 @@
internal val wrappedUserStyle by lazy { MutableStateFlow(editorDelegate.userStyle) }
+ override var editorObscuresWatchFace: Boolean
+ get() = editorDelegate.editorObscuresWatchFace
+ set(value) {
+ editorDelegate.editorObscuresWatchFace = value
+ }
+
// Unfortunately a dynamic proxy is the only way we can reasonably validate the UserStyle,
// exceptions thrown within a coroutine are lost and the MutableStateFlow interface includes
// internal unstable methods so we can't use a static proxy...
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/Renderer.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/Renderer.kt
index fba8756..85b31e1 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/Renderer.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/Renderer.kt
@@ -22,6 +22,7 @@
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
+import android.graphics.Picture
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
@@ -39,6 +40,7 @@
import androidx.annotation.IntDef
import androidx.annotation.IntRange
import androidx.annotation.Px
+import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
@@ -78,8 +80,8 @@
* to a software canvas.
*
* NOTE the system takes screenshots for use in the watch face picker UI and these will be
- * taken using software rendering. This means [Bitmap]s with [Bitmap.Config.HARDWARE] must
- * be avoided.
+ * taken using software rendering for API level 27 and below. This means on API level 27 and
+ * below [Bitmap]s with [Bitmap.Config.HARDWARE] must be avoided.
*/
public const val HARDWARE: Int = 1
}
@@ -605,22 +607,39 @@
renderParameters: RenderParameters
): Bitmap =
TraceEvent("CanvasRenderer.takeScreenshot").use {
- val bitmap =
- Bitmap.createBitmap(
- screenBounds.width(),
- screenBounds.height(),
- Bitmap.Config.ARGB_8888
- )
val prevRenderParameters = this.renderParameters
val originalIsForScreenshot = renderParameters.isForScreenshot
renderParameters.isForScreenshot = true
this.renderParameters = renderParameters
- renderAndComposite(Canvas(bitmap), zonedDateTime)
- this.renderParameters = prevRenderParameters
- renderParameters.isForScreenshot = originalIsForScreenshot
- return bitmap
+ if (Build.VERSION.SDK_INT >= 28) {
+ val picture = Picture()
+ renderAndComposite(
+ picture.beginRecording(screenBounds.width(), screenBounds.height()),
+ zonedDateTime
+ )
+ picture.endRecording()
+ this.renderParameters = prevRenderParameters
+ renderParameters.isForScreenshot = originalIsForScreenshot
+ return Api28CreateBitmapHelper.createBitmap(
+ picture,
+ screenBounds.width(),
+ screenBounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
+ } else {
+ val bitmap =
+ Bitmap.createBitmap(
+ screenBounds.width(),
+ screenBounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
+ renderAndComposite(Canvas(bitmap), zonedDateTime)
+ this.renderParameters = prevRenderParameters
+ renderParameters.isForScreenshot = originalIsForScreenshot
+ return bitmap
+ }
}
internal override fun renderScreenshotToSurface(
@@ -653,17 +672,34 @@
// Render and composite the HighlightLayer
val highlightLayer = renderParameters.highlightLayer
if (highlightLayer != null) {
- val highlightLayerBitmap =
- Bitmap.createBitmap(
+ val highlightLayerBitmap: Bitmap
+ if (Build.VERSION.SDK_INT >= 28) {
+ val picture = Picture()
+ val highlightCanvas =
+ picture.beginRecording(screenBounds.width(), screenBounds.height())
+ if (clearWithBackgroundTintBeforeRenderingHighlightLayer) {
+ highlightCanvas.drawColor(highlightLayer.backgroundTint)
+ }
+ renderHighlightLayer(highlightCanvas, screenBounds, zonedDateTime)
+ picture.endRecording()
+ highlightLayerBitmap = Api28CreateBitmapHelper.createBitmap(
+ picture,
screenBounds.width(),
screenBounds.height(),
Bitmap.Config.ARGB_8888
)
- val highlightCanvas = Canvas(highlightLayerBitmap)
- if (clearWithBackgroundTintBeforeRenderingHighlightLayer) {
- highlightCanvas.drawColor(highlightLayer.backgroundTint)
+ } else {
+ highlightLayerBitmap = Bitmap.createBitmap(
+ screenBounds.width(),
+ screenBounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
+ val highlightCanvas = Canvas(highlightLayerBitmap)
+ if (clearWithBackgroundTintBeforeRenderingHighlightLayer) {
+ highlightCanvas.drawColor(highlightLayer.backgroundTint)
+ }
+ renderHighlightLayer(highlightCanvas, screenBounds, zonedDateTime)
}
- renderHighlightLayer(highlightCanvas, screenBounds, zonedDateTime)
canvas.drawBitmap(highlightLayerBitmap, 0f, 0f, HIGHLIGHT_LAYER_COMPOSITE_PAINT)
highlightLayerBitmap.recycle()
}
@@ -1761,3 +1797,14 @@
}
}
}
+
+/** Helper to allow class verification. */
+@RequiresApi(28)
+internal object Api28CreateBitmapHelper {
+ fun createBitmap(
+ picture: Picture,
+ width: Int,
+ height: Int,
+ config: Bitmap.Config
+ ) = Bitmap.createBitmap(picture, width, height, config)
+}
\ No newline at end of file
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 33bfea4..20f9460 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
@@ -25,6 +25,7 @@
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Picture
import android.graphics.PixelFormat
import android.graphics.Rect
import android.os.Build
@@ -50,6 +51,7 @@
import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.complications.data.toApiComplicationData
import androidx.wear.watchface.control.HeadlessWatchFaceImpl
+import androidx.wear.watchface.control.InteractiveInstanceManager
import androidx.wear.watchface.control.RemoteWatchFaceView
import androidx.wear.watchface.control.WatchFaceControlService
import androidx.wear.watchface.control.data.ComplicationRenderParams
@@ -281,6 +283,12 @@
public val complicationRationaleDialogIntent: Intent?
/**
+ * Allows the delegate to inform the watch face if it's obscured by the editor UI. If the
+ * watch face is not visible, it will stop animating.
+ */
+ public var editorObscuresWatchFace: Boolean
+
+ /**
* Renders the watchface to a [Bitmap] with the [CurrentUserStyleRepository]'s [UserStyle].
*/
public fun renderWatchFaceToBitmap(
@@ -564,6 +572,16 @@
internal val broadcastsObserver: BroadcastsObserver,
internal var broadcastsReceiver: BroadcastsReceiver?
) {
+ internal var editorObscuresWatchFace = false
+ set(value) {
+ field = value
+
+ // Start animating again if needed.
+ if (!value) {
+ scheduleDraw()
+ }
+ }
+
internal companion object {
internal const val NO_DEFAULT_DATA_SOURCE = SystemDataSources.NO_DATA_SOURCE
@@ -854,6 +872,15 @@
override val complicationRationaleDialogIntent
get() = watchFaceHostApi.getComplicationRationaleIntent()
+ override var editorObscuresWatchFace: Boolean
+ get() = InteractiveInstanceManager
+ .getCurrentInteractiveInstance()?.engine?.editorObscuresWatchFace ?: false
+ set(value) {
+ InteractiveInstanceManager.getCurrentInteractiveInstance()?.engine?.let {
+ it.editorObscuresWatchFace = value
+ }
+ }
+
override fun renderWatchFaceToBitmap(
renderParameters: RenderParameters,
instant: Instant,
@@ -902,6 +929,9 @@
@SuppressLint("NewApi") // release
override fun onDestroy(): Unit =
TraceEvent("WFEditorDelegate.onDestroy").use {
+ InteractiveInstanceManager.getCurrentInteractiveInstance()?.engine?.let {
+ it.editorObscuresWatchFace = false
+ }
if (watchState.isHeadless) {
headlessWatchFaceImpl!!.release()
[email protected]()
@@ -943,7 +973,9 @@
private fun scheduleDraw() {
// Separate calls are issued to deliver the state of isAmbient and isVisible, so during init
// we might not yet know the state of both (which is required by the shouldAnimate logic).
- if (!watchState.isAmbient.hasValue() || !watchState.isVisible.hasValue()) {
+ // If the editor is obscuring the watch face, there's no need to schedule a frame.
+ if (!watchState.isAmbient.hasValue() || !watchState.isVisible.hasValue() ||
+ editorObscuresWatchFace) {
return
}
@@ -992,7 +1024,8 @@
renderer.renderInternal(startTime)
lastDrawTimeMillis = startTimeMillis
- if (renderer.shouldAnimate()) {
+ // If the editor is obscuring the watch face, there's no need to draw.
+ if (renderer.shouldAnimate() && !editorObscuresWatchFace) {
val currentTimeMillis = systemTimeProvider.getSystemTimeMillis()
var delayMillis =
computeDelayTillNextFrame(startTimeMillis, currentTimeMillis, Instant.now())
@@ -1218,8 +1251,6 @@
}
val bounds = it.computeBounds(renderer.screenBounds)
- val complicationBitmap =
- Bitmap.createBitmap(bounds.width(), bounds.height(), Bitmap.Config.ARGB_8888)
var prevData: ComplicationData? = null
val screenshotComplicationData = params.complicationData
@@ -1232,13 +1263,38 @@
)
}
- it.renderer.render(
- Canvas(complicationBitmap),
- Rect(0, 0, bounds.width(), bounds.height()),
- zonedDateTime,
- RenderParameters(params.renderParametersWireFormat),
- params.complicationSlotId
- )
+ val complicationBitmap: Bitmap
+ val picture = Picture()
+ if (Build.VERSION.SDK_INT >= 28) {
+ it.renderer.render(
+ picture.beginRecording(bounds.width(), bounds.height()),
+ Rect(0, 0, bounds.width(), bounds.height()),
+ zonedDateTime,
+ RenderParameters(params.renderParametersWireFormat),
+ params.complicationSlotId
+ )
+ picture.endRecording()
+ complicationBitmap = Api28CreateBitmapHelper.createBitmap(
+ picture,
+ bounds.width(),
+ bounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
+ } else {
+ complicationBitmap =
+ Bitmap.createBitmap(
+ bounds.width(),
+ bounds.height(),
+ Bitmap.Config.ARGB_8888
+ )
+ it.renderer.render(
+ Canvas(complicationBitmap),
+ Rect(0, 0, bounds.width(), bounds.height()),
+ zonedDateTime,
+ RenderParameters(params.renderParametersWireFormat),
+ params.complicationSlotId
+ )
+ }
// No point in restoring the old style and complication if this is headless.
if (!watchState.isHeadless) {
@@ -1257,7 +1313,9 @@
}
}
- SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
+ val bundle = SharedMemoryImage.ashmemWriteImageBundle(complicationBitmap)
+ complicationBitmap.recycle()
+ bundle
}
}
@@ -1276,6 +1334,7 @@
"currentUserStyleRepository.userStyle=${currentUserStyleRepository.userStyle.value}"
)
writer.println("currentUserStyleRepository.schema=${currentUserStyleRepository.schema}")
+ writer.println("editorObscuresWatchFace=$editorObscuresWatchFace")
overlayStyle.dump(writer)
watchState.dump(writer)
complicationSlotsManager.dump(writer)
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 537923c..d9c04a97 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -1224,6 +1224,12 @@
*/
private var frameCallbackPending = false
+ internal var editorObscuresWatchFace = false
+ set(value) {
+ getWatchFaceImplOrNull()?.editorObscuresWatchFace = value
+ field = value
+ }
+
private val frameCallback =
object : Choreographer.FrameCallback {
@SuppressWarnings("SyntheticAccessor")
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
index d6e5cdc..07232b0 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
@@ -163,6 +163,15 @@
}
}
+ fun getCurrentInteractiveInstance(): InteractiveWatchFaceImpl? {
+ synchronized(pendingWallpaperInteractiveWatchFaceInstanceLock) {
+ if (instances.size == 1) {
+ return instances.entries.first().value.impl
+ }
+ }
+ return null
+ }
+
/** Can be called on any thread. */
@SuppressLint("SyntheticAccessor")
fun getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
diff --git a/work/work-lint/src/main/java/androidx/work/lint/IdleBatteryChargingConstraintsDetector.kt b/work/work-lint/src/main/java/androidx/work/lint/IdleBatteryChargingConstraintsDetector.kt
index 975a5a8..d9d3f47 100644
--- a/work/work-lint/src/main/java/androidx/work/lint/IdleBatteryChargingConstraintsDetector.kt
+++ b/work/work-lint/src/main/java/androidx/work/lint/IdleBatteryChargingConstraintsDetector.kt
@@ -34,6 +34,7 @@
import org.jetbrains.uast.UQualifiedReferenceExpression
import org.jetbrains.uast.USimpleNameReferenceExpression
import org.jetbrains.uast.getParentOfType
+import org.jetbrains.uast.skipParenthesizedExprDown
import org.jetbrains.uast.toUElement
import org.jetbrains.uast.visitor.AbstractUastVisitor
@@ -110,9 +111,10 @@
}
fun UCallExpression.identifierName(): String? {
- var current = receiver
+ var current = receiver?.skipParenthesizedExprDown()
while (current != null && current !is USimpleNameReferenceExpression) {
- current = (current as? UQualifiedReferenceExpression)?.receiver
+ current =
+ (current as? UQualifiedReferenceExpression)?.receiver?.skipParenthesizedExprDown()
}
if (current != null && current is USimpleNameReferenceExpression) {
return current.identifier
diff --git a/work/work-lint/src/main/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetector.kt b/work/work-lint/src/main/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetector.kt
index 6404e126cc..eec241d 100644
--- a/work/work-lint/src/main/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetector.kt
+++ b/work/work-lint/src/main/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetector.kt
@@ -32,6 +32,9 @@
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.getParameterForArgument
+import org.jetbrains.uast.skipParenthesizedExprDown
/**
* Ensures a valid interval duration for a `PeriodicWorkRequest`.
@@ -65,11 +68,20 @@
constructor: PsiMethod
) {
if (node.valueArgumentCount >= 2) {
- val type = node.valueArguments[1].getExpressionType()?.canonicalText
+ // TestMode.PARENTHESIZED wraps Duration call in parenthesizes
+ val repeatInterval = node.valueArguments.find {
+ node.getParameterForArgument(it)?.name == "repeatInterval"
+ }?.skipParenthesizedExprDown()
+
+ val timeUnit = node.valueArguments.find {
+ node.getParameterForArgument(it)?.name == "repeatIntervalTimeUnit"
+ }?.skipParenthesizedExprDown()
+
+ val type = repeatInterval?.getExpressionType()?.canonicalText
if ("long" == type) {
- val value = node.valueArguments[1].evaluate() as? Long
+ val value = repeatInterval.evaluate() as? Long
// TimeUnit
- val units = node.valueArguments[2].evaluate() as? Pair<ClassId, Name>
+ val units = timeUnit?.evaluate() as? Pair<ClassId, Name>
if (value != null && units != null) {
val (_, timeUnitType) = units
val interval: Long? = when (timeUnitType.identifier) {
@@ -94,21 +106,26 @@
}
}
} else if ("java.time.Duration" == type) {
- val source = node.valueArguments[1].asSourceString()
// Look for the most common Duration specification
// Example: Duration.ofMinutes(15)
- val regexp = Regex("Duration.of(\\w+)\\((\\d+)\\)")
- val matchResult = regexp.matchEntire(source)
- if (matchResult != null) {
- val unit = matchResult.groupValues[1]
- val value = matchResult.groupValues[2].toLong()
+
+ val callExpression: UCallExpression? = when (repeatInterval) {
+ // ofMinutes(...)
+ is UCallExpression -> repeatInterval
+ // Duration.ofMinutes(...)
+ is UQualifiedReferenceExpression -> repeatInterval.selector as? UCallExpression
+ else -> null
+ }
+ val unit = callExpression?.methodName
+ val value = callExpression?.valueArguments?.firstOrNull()?.evaluate() as? Long
+ if (value != null) {
val interval: Long? = when (unit) {
- "Nanos" -> TimeUnit.MINUTES.convert(value, TimeUnit.NANOSECONDS)
- "Millis" -> TimeUnit.MINUTES.convert(value, TimeUnit.MILLISECONDS)
- "Seconds" -> TimeUnit.MINUTES.convert(value, TimeUnit.SECONDS)
- "Minutes" -> value
- "Hours" -> TimeUnit.MINUTES.convert(value, TimeUnit.HOURS)
- "Days" -> TimeUnit.MINUTES.convert(value, TimeUnit.DAYS)
+ "ofNanos" -> TimeUnit.MINUTES.convert(value, TimeUnit.NANOSECONDS)
+ "ofMillis" -> TimeUnit.MINUTES.convert(value, TimeUnit.MILLISECONDS)
+ "ofSeconds" -> TimeUnit.MINUTES.convert(value, TimeUnit.SECONDS)
+ "ofMinutes" -> value
+ "ofHours" -> TimeUnit.MINUTES.convert(value, TimeUnit.HOURS)
+ "ofDays" -> TimeUnit.MINUTES.convert(value, TimeUnit.DAYS)
else -> null
}
if (interval != null && interval < 15) {
diff --git a/work/work-lint/src/test/java/androidx/work/lint/IdleBatteryChargingConstraintsDetectorTest.kt b/work/work-lint/src/test/java/androidx/work/lint/IdleBatteryChargingConstraintsDetectorTest.kt
index 02ae6d1..16babb0 100644
--- a/work/work-lint/src/test/java/androidx/work/lint/IdleBatteryChargingConstraintsDetectorTest.kt
+++ b/work/work-lint/src/test/java/androidx/work/lint/IdleBatteryChargingConstraintsDetectorTest.kt
@@ -19,7 +19,6 @@
import androidx.work.lint.Stubs.CONSTRAINTS
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import org.junit.Ignore
import org.junit.Test
class IdleBatteryChargingConstraintsDetectorTest {
@@ -147,7 +146,6 @@
.expectClean()
}
- @Ignore("b/196831196")
@Test
fun noWarningsWhenSeparateConstraints() {
val customApplication = kotlin(
diff --git a/work/work-lint/src/test/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetectorTest.kt b/work/work-lint/src/test/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetectorTest.kt
index b109b25..bfa5e82 100644
--- a/work/work-lint/src/test/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetectorTest.kt
+++ b/work/work-lint/src/test/java/androidx/work/lint/InvalidPeriodicWorkRequestIntervalDetectorTest.kt
@@ -20,7 +20,6 @@
import androidx.work.lint.Stubs.PERIODIC_WORK_REQUEST
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import org.junit.Ignore
import org.junit.Test
class InvalidPeriodicWorkRequestIntervalDetectorTest {
@@ -33,12 +32,10 @@
import androidx.work.ListenableWorker
- class TestWorker: ListenableWorker() {
-
- }
+ class TestWorker: ListenableWorker()
"""
).indented().within("src")
-
+ /* ktlint-disable max-line-length */
val snippet = kotlin(
"com/example/Test.kt",
"""
@@ -50,14 +47,11 @@
class Test {
fun enqueue() {
- val worker = TestWorker()
- val builder = PeriodicWorkRequest.Builder(worker, 15L, TimeUnit.MILLISECONDS)
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, 15L, TimeUnit.MILLISECONDS)
}
}
"""
).indented().within("src")
-
- /* ktlint-disable max-line-length */
lint().files(
LISTENABLE_WORKER,
PERIODIC_WORK_REQUEST,
@@ -67,9 +61,9 @@
.run()
.expect(
"""
- src/com/example/Test.kt:10: Error: Interval duration for `PeriodicWorkRequest`s must be at least 15 minutes. [InvalidPeriodicWorkRequestInterval]
- val builder = PeriodicWorkRequest.Builder(worker, 15L, TimeUnit.MILLISECONDS)
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/com/example/Test.kt:9: Error: Interval duration for `PeriodicWorkRequest`s must be at least 15 minutes. [InvalidPeriodicWorkRequestInterval]
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, 15L, TimeUnit.MILLISECONDS)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
@@ -85,12 +79,11 @@
import androidx.work.ListenableWorker
- class TestWorker: ListenableWorker() {
-
- }
+ class TestWorker: ListenableWorker()
"""
).indented().within("src")
+ /* ktlint-disable max-line-length */
val snippet = kotlin(
"com/example/Test.kt",
"""
@@ -103,11 +96,12 @@
class Test {
fun enqueue() {
val worker = TestWorker()
- val builder = PeriodicWorkRequest.Builder(worker, 15L, TimeUnit.MINUTES)
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, 15L, TimeUnit.MINUTES)
}
}
"""
).indented().within("src")
+ /* ktlint-enable max-line-length */
lint().files(
LISTENABLE_WORKER,
@@ -119,7 +113,54 @@
.expectClean()
}
- @Ignore("b/196831196")
+ @Test
+ fun testWithInvalidDurationTypeStaticImport() {
+ val worker = kotlin(
+ "com/example/TestWorker.kt",
+ """
+ package com.example
+
+ import androidx.work.ListenableWorker
+
+ class TestWorker: ListenableWorker()
+ """
+ ).indented().within("src")
+ /* ktlint-disable max-line-length */
+ val snippet = kotlin(
+ "com/example/Test.kt",
+ """
+ package com.example
+
+ import androidx.work.PeriodicWorkRequest
+ import com.example.TestWorker
+ import java.time.Duration.ofNanos
+
+ class Test {
+ fun enqueue() {
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, ofNanos(10L))
+ }
+ }
+ """
+ ).indented().within("src")
+
+ lint().files(
+ LISTENABLE_WORKER,
+ PERIODIC_WORK_REQUEST,
+ worker,
+ snippet
+ ).issues(InvalidPeriodicWorkRequestIntervalDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/com/example/Test.kt:9: Error: Interval duration for `PeriodicWorkRequest`s must be at least 15 minutes. [InvalidPeriodicWorkRequestInterval]
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, ofNanos(10L))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 1 errors, 0 warnings
+ """.trimIndent()
+ )
+ /* ktlint-enable max-line-length */
+ }
+
@Test
fun testWithInvalidDurationType() {
val worker = kotlin(
@@ -129,12 +170,10 @@
import androidx.work.ListenableWorker
- class TestWorker: ListenableWorker() {
-
- }
+ class TestWorker: ListenableWorker()
"""
).indented().within("src")
-
+ /* ktlint-disable max-line-length */
val snippet = kotlin(
"com/example/Test.kt",
"""
@@ -146,14 +185,12 @@
class Test {
fun enqueue() {
- val worker = TestWorker()
- val builder = PeriodicWorkRequest.Builder(worker, Duration.ofNanos(15))
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, Duration.ofSeconds(10L))
}
}
"""
).indented().within("src")
- /* ktlint-disable max-line-length */
lint().files(
LISTENABLE_WORKER,
PERIODIC_WORK_REQUEST,
@@ -163,9 +200,9 @@
.run()
.expect(
"""
- src/com/example/Test.kt:10: Error: Interval duration for `PeriodicWorkRequest`s must be at least 15 minutes. [InvalidPeriodicWorkRequestInterval]
- val builder = PeriodicWorkRequest.Builder(worker, Duration.ofNanos(15))
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/com/example/Test.kt:9: Error: Interval duration for `PeriodicWorkRequest`s must be at least 15 minutes. [InvalidPeriodicWorkRequestInterval]
+ val builder = PeriodicWorkRequest.Builder(TestWorker::class.java, Duration.ofSeconds(10L))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 errors, 0 warnings
""".trimIndent()
)
diff --git a/work/work-lint/src/test/java/androidx/work/lint/SpecifyForegroundServiceTypeIssueDetectorTest.kt b/work/work-lint/src/test/java/androidx/work/lint/SpecifyForegroundServiceTypeIssueDetectorTest.kt
index b2a8fd2..fcc25223 100644
--- a/work/work-lint/src/test/java/androidx/work/lint/SpecifyForegroundServiceTypeIssueDetectorTest.kt
+++ b/work/work-lint/src/test/java/androidx/work/lint/SpecifyForegroundServiceTypeIssueDetectorTest.kt
@@ -21,11 +21,9 @@
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin
import com.android.tools.lint.checks.infrastructure.LintDetectorTest.manifest
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
-import org.junit.Ignore
import org.junit.Test
class SpecifyForegroundServiceTypeIssueDetectorTest {
- @Ignore("b/196831196")
@Test
fun failWhenServiceTypeIsNotSpecified() {
val application = kotlin(
@@ -64,7 +62,6 @@
/* ktlint-enable max-line-length */
}
- @Ignore("b/196831196")
@Test
fun failWhenSpecifiedServiceTypeIsInSufficient() {
val manifest = manifest(
diff --git a/work/work-lint/src/test/java/androidx/work/lint/Stubs.kt b/work/work-lint/src/test/java/androidx/work/lint/Stubs.kt
index 45855a3..904f95b 100644
--- a/work/work-lint/src/test/java/androidx/work/lint/Stubs.kt
+++ b/work/work-lint/src/test/java/androidx/work/lint/Stubs.kt
@@ -110,36 +110,40 @@
"""
).indented().within("src")
- val PERIODIC_WORK_REQUEST: TestFile = java(
- "androidx/work/PeriodicWorkRequest.java",
+ val PERIODIC_WORK_REQUEST: TestFile = kotlin(
+ "androidx/work/PeriodicWorkRequest.kt",
"""
- package androidx.work;
+ package androidx.work
- import androidx.work.ListenableWorker;
- import java.time.Duration;
- import java.util.concurrent.TimeUnit;
+ import androidx.work.ListenableWorker
+ import java.time.Duration
+ import java.util.concurrent.TimeUnit
- class PeriodicWorkRequest extends WorkRequest {
- static class Builder {
- public Builder(ListenableWorker worker, long interval, TimeUnit unit) {
-
- }
- public Builder(ListenableWorker worker, Duration duration) {
-
- }
- public Builder(
- ListenableWorker worker,
- long interval, TimeUnit intervalUnit,
- long flex,
- TimeUnit flexUnits) {
-
- }
- public Builder(
- ListenableWorker worker,
- Duration intervalDuration,
- Duration flexDuration) {
+ class PeriodicWorkRequest: WorkRequest {
+ class Builder {
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Duration
+ )
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit
+ ){}
- }
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Long,
+ repeatIntervalTimeUnit: TimeUnit,
+ flexInterval: Long,
+ flexIntervalTimeUnit: TimeUnit
+ )
+
+ constructor(
+ workerClass: Class<out ListenableWorker?>,
+ repeatInterval: Duration,
+ flexInterval: Duration
+ )
}
}
"""
@@ -184,17 +188,18 @@
"""
).indented().within("src")
- val FOREGROUND_INFO: TestFile = kotlin(
- "androidx/work/ForegroundInfo.kt",
+ val FOREGROUND_INFO: TestFile = java(
+ "androidx/work/ForegroundInfo.java",
"""
- package androidx.work
+ package androidx.work;
- import android.app.Notification
+ import android.app.Notification;
- class ForegroundInfo(id: Int, notification: Notification, serviceType: Int) {
- constructor(id: Int, notification: Notification) {
- this(id, notification, 0)
- }
+ public class ForegroundInfo {
+ public ForegroundInfo(
+ int notificationId,
+ Notification notification,
+ int foregroundServiceType) { }
}
"""
).indented().within("src")
diff --git a/work/work-runtime/src/main/java/androidx/work/ForegroundInfo.java b/work/work-runtime/src/main/java/androidx/work/ForegroundInfo.java
index 2e2b727..b77c79e 100644
--- a/work/work-runtime/src/main/java/androidx/work/ForegroundInfo.java
+++ b/work/work-runtime/src/main/java/androidx/work/ForegroundInfo.java
@@ -24,6 +24,8 @@
* The information required when a {@link ListenableWorker} runs in the context of a foreground
* service.
*/
+// NOTE: once this file is migrated to Kotlin, corresponding stub in lint rules should be migrated.
+// As a result lint checks should start relying on parameter names instead.
public final class ForegroundInfo {
private final int mNotificationId;