Funkcje i interfejsy API

Android 17 wprowadza nowe, świetne funkcje i interfejsy API dla deweloperów. W sekcjach poniżej podsumowujemy te funkcje, aby ułatwić Ci rozpoczęcie korzystania z powiązanych interfejsów API.

Szczegółową listę nowych, zmodyfikowanych i usuniętych interfejsów API znajdziesz w raporcie o różnicach w interfejsach API. Szczegółowe informacje o nowych interfejsach API znajdziesz w dokumentacji interfejsów API Androida – nowe interfejsy API są wyróżnione, aby były lepiej widoczne.

Sprawdź też obszary, w których zmiany na platformie mogą mieć wpływ na Twoje aplikacje. Więcej informacji znajdziesz na tych stronach:

Główna funkcja

Android 17 wprowadza te nowe funkcje związane z podstawowymi funkcjami Androida.

Nowe aktywatory ProfilingManager

Android 17 dodaje do ProfilingManager kilka nowych aktywatorów systemowych, które pomagają zbierać szczegółowe dane na potrzeby debugowania problemów z wydajnością.

Nowe aktywatory to:

  • TRIGGER_TYPE_COLD_START: aktywacja następuje podczas uruchamiania aplikacji na zimno. W odpowiedzi podaje zarówno próbkę stosu wywołań, jak i śledzenie systemu.
  • TRIGGER_TYPE_OOM: aktywator uruchamia się, gdy aplikacja zgłasza błąd OutOfMemoryError, i w odpowiedzi udostępnia zrzut stosu Java.
  • TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: aktywuje się, gdy aplikacja zostanie zamknięta z powodu nieprawidłowego i nadmiernego wykorzystania procesora. W odpowiedzi podaje próbkę stosu wywołań.
  • TRIGGER_TYPE_ANOMALY: wykrywanie anomalii w wydajności systemu, takich jak nadmierne wywołania binderów i nadmierne wykorzystanie pamięci.

Aby dowiedzieć się, jak skonfigurować aktywator systemowy, zapoznaj się z dokumentacją dotyczącą profilowania opartego na aktywatorach oraz pobierania i analizowania danych profilowania.

Aktywowanie profilowania w przypadku anomalii w aplikacji

Android 17 wprowadza usługę wykrywania anomalii na urządzeniu, która monitoruje zachowania wymagające dużej ilości zasobów i potencjalne problemy ze zgodnością. Dzięki integracji z ProfilingManager ta usługa umożliwia aplikacji odbieranie artefaktów profilowania wywoływanych przez określone zdarzenia wykryte przez system.

Użyj aktywatora TRIGGER_TYPE_ANOMALY, aby wykrywać problemy z wydajnością systemu, takie jak nadmierne wywołania binderów i nadmierne wykorzystanie pamięci. Gdy aplikacja przekroczy limity pamięci określone przez system operacyjny, aktywator anomalii umożliwia deweloperom otrzymywanie zrzutów sterty dotyczących konkretnej aplikacji, co pomaga w identyfikowaniu i rozwiązywaniu problemów z pamięcią. Dodatkowo w przypadku nadmiernego spamu w binderze aktywator anomalii udostępnia profil próbkowania stosu w transakcjach bindera.

To wywołanie zwrotne interfejsu API następuje przed zastosowaniem jakichkolwiek wymuszonych przez system zasad. Może na przykład pomóc deweloperom w gromadzeniu danych debugowania, zanim aplikacja zostanie zamknięta przez system z powodu przekroczenia limitów pamięci.

val profilingManager =
    applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList<ProfilingTrigger>()
triggers.add(ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer<ProfilingResult> { profilingResult ->
    if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
        // upload profile result to server for further analysis
        setupProfileUploadWorker(profilingResult.resultFilePath)
    }
    profilingManager.registerForAllProfilingResults(mainExecutor,
                                                    resultCallback)
    profilingManager.addProfilingTriggers(triggers)
}

Interfejsy API JobDebugInfo

Android 17 wprowadza nowe interfejsy API JobDebugInfo, które pomagają deweloperom debugować zadania JobScheduler – dlaczego nie działają, jak długo działały i inne zagregowane dane.

Pierwszą metodą rozszerzonych interfejsów API JobDebugInfo jest getPendingJobReasonStats(), która zwraca mapę powodów, dla których zadanie było w stanie oczekiwania na wykonanie, oraz ich skumulowany czas oczekiwania. Ta metoda łączy metody getPendingJobReasonsHistory() i getPendingJobReasons(), aby umożliwić Ci sprawdzenie, dlaczego zaplanowane zadanie nie działa zgodnie z oczekiwaniami, ale upraszcza pobieranie informacji, udostępniając zarówno czas trwania, jak i przyczynę zadania w jednej metodzie.

Na przykład dla określonego jobId metoda może zwrócić PENDING_JOB_REASON_CONSTRAINT_CHARGING i czas trwania 60 000 ms, co oznacza, że zadanie oczekiwało przez 60 000 ms, ponieważ nie zostało spełnione ograniczenie dotyczące ładowania.

Ograniczanie blokad uśpienia dzięki obsłudze detektorów w alarmach typu „zezwól w trybie bezczynności”

Android 17 wprowadza nowy wariant AlarmManager.setExactAndAllowWhileIdle, który akceptuje OnAlarmListener zamiast PendingIntent. Ten nowy mechanizm oparty na wywołaniach zwrotnych jest idealny w przypadku aplikacji, które obecnie korzystają z ciągłych blokad uśpienia do wykonywania zadań okresowych, takich jak aplikacje do obsługi wiadomości utrzymujące połączenia z gniazdami.

Prywatność

Android 17 zawiera te nowe funkcje, które zwiększają prywatność użytkowników.

Obsługa platformy Encrypted Client Hello (ECH)

Android 17 wprowadza obsługę platformy Encrypted Client Hello (ECH), co znacznie zwiększa prywatność komunikacji sieciowej. ECH to rozszerzenie protokołu TLS 1.3, które szyfruje rozszerzenie SNI (Server Name Indication) podczas początkowego uzgadniania połączenia TLS. Szyfrowanie to pomaga chronić prywatność użytkowników, ponieważ utrudnia pośrednikom sieciowym identyfikowanie konkretnej domeny, z którą łączy się aplikacja.

Platforma zawiera teraz interfejsy API niezbędne do implementacji ECH w bibliotekach sieciowych. Obejmuje to nowe funkcje w DnsResolver, które umożliwiają wysyłanie zapytań o rekordy DNS HTTPS zawierające konfiguracje ECH, oraz nowe metody w klasach SSLEngines i SSLSockets biblioteki Conscrypt, które umożliwiają włączenie ECH przez przekazywanie tych konfiguracji podczas łączenia się z domeną. Deweloperzy mogą konfigurować ustawienia ECH, np. włączać je w miarę możliwości lub wymagać ich używania, za pomocą nowego elementu <domainEncryption> w pliku konfiguracji zabezpieczeń sieciowych, który ma zastosowanie globalnie lub w przypadku poszczególnych domen.

Popularne biblioteki sieciowe, takie jak HttpEngine, WebView i OkHttp, mają w przyszłych aktualizacjach zintegrować te interfejsy API platformy, co ułatwi aplikacjom wdrażanie ECH i zwiększanie prywatności użytkowników.

Więcej informacji znajdziesz w dokumentacji Encrypted Client Hello.

Selektor kontaktów na Androidzie

Selektor kontaktów na Androidzie to ustandaryzowany, możliwy do przeglądania interfejs, który umożliwia użytkownikom udostępnianie kontaktów aplikacji. Jest on dostępny na urządzeniach z Androidem 17 (interfejs API na poziomie 37) lub nowszym i stanowi chroniącą prywatność alternatywę dla szerokiego uprawnienia READ_CONTACTS. Zamiast prosić o dostęp do całej książki adresowej użytkownika, aplikacja określa pola danych, których potrzebuje, takie jak numery telefonów lub adresy e-mail, a użytkownik wybiera konkretne kontakty do udostępnienia. Dzięki temu aplikacja ma dostęp do odczytu tylko wybranych danych, co zapewnia szczegółową kontrolę i spójne wrażenia użytkownika dzięki wbudowanym funkcjom wyszukiwania, przełączania profili w wyszukiwarce i wyboru wielu elementów bez konieczności tworzenia i utrzymywania interfejsu.

Więcej informacji znajdziesz w dokumentacji selektora kontaktów.

Bezpieczeństwo

Android 17 wprowadza nowe funkcje, które zwiększają bezpieczeństwo urządzenia i aplikacji.

Tryb ochrony zaawansowanej na Androidzie (AAPM)

Tryb ochrony zaawansowanej na Androidzie oferuje użytkownikom tego systemu nowy, zaawansowany zestaw funkcji zabezpieczeń, co stanowi ważny krok w ochronie użytkowników – zwłaszcza tych bardziej narażonych – przed zaawansowanymi atakami. AAPM to funkcja, którą można włączyć. Aktywuje się ją za pomocą jednego ustawienia konfiguracyjnego, które użytkownicy mogą włączyć w dowolnym momencie, aby zastosować zestaw zabezpieczeń.

Te podstawowe konfiguracje obejmują blokowanie instalacji aplikacji z nieznanych źródeł (instalowanie z zewnątrz), ograniczanie sygnalizacji danych przez USB i wymaganie skanowania przez Google Play Protect, co znacznie zmniejsza obszar podatny na ataki. Deweloperzy mogą zintegrować tę funkcję za pomocą interfejsu AdvancedProtectionManager API, aby wykrywać stan trybu, co umożliwia aplikacjom automatyczne przyjmowanie wzmocnionych zabezpieczeń lub ograniczanie funkcji wysokiego ryzyka, gdy użytkownik wyrazi na to zgodę.

Podpisywanie plików APK za pomocą kryptografii postkwantowej

Android obsługuje teraz hybrydowy schemat podpisywania plików APK, aby chronić tożsamość podpisu aplikacji przed potencjalnymi atakami z wykorzystaniem komputerów kwantowych. Ta funkcja wprowadza nowy schemat podpisu pliku APK, który umożliwia sparowanie klasycznego klucza podpisywania (takiego jak RSA lub EC) z nowym algorytmem kryptografii postkwantowej (PQC) (ML-DSA).

To hybrydowe podejście zapewnia bezpieczeństwo aplikacji przed przyszłymi atakami kwantowymi, zachowując jednocześnie pełną zgodność wsteczną ze starszymi wersjami Androida i urządzeniami, które korzystają z klasycznej weryfikacji podpisu.

Wpływ na deweloperów

  • Aplikacje korzystające z podpisywania aplikacji przez Google Play: jeśli korzystasz z podpisywania aplikacji przez Google Play, możesz poczekać, aż Google Play udostępni Ci opcję uaktualnienia podpisu hybrydowego za pomocą klucza PQC wygenerowanego przez Google Play. Dzięki temu Twoja aplikacja będzie chroniona bez konieczności ręcznego zarządzania kluczami.
  • Aplikacje korzystające z kluczy zarządzanych samodzielnie: deweloperzy, którzy zarządzają własnymi kluczami podpisywania, mogą używać zaktualizowanych narzędzi do tworzenia aplikacji na Androida (takich jak apksigner), aby przejść na tożsamość hybrydową, łącząc klucz PQC z nowym kluczem klasycznym. (Musisz utworzyć nowy klucz klasyczny, nie możesz użyć starego).

Połączenia

Android 17 wprowadza te funkcje, które poprawiają łączność urządzenia i aplikacji.

Ograniczone sieci satelitarne

Wprowadza optymalizacje, które umożliwiają skuteczne działanie aplikacji w sieciach satelitarnych o niskiej przepustowości.

Interfejs użytkownika i systemowy

Android 17 zawiera te zmiany, które poprawiają wrażenia użytkowników.

Dedykowany strumień głośności Asystenta

Android 17 wprowadza dedykowany strumień głośności asystenta dla aplikacji asystujących, który umożliwia odtwarzanie za pomocą USAGE_ASSISTANT. Ta zmiana oddziela dźwięk Asystenta od standardowego strumienia multimediów, co zapewnia użytkownikom niezależną kontrolę nad głośnością obu tych elementów. Umożliwia to na przykład wyciszenie odtwarzania multimediów przy zachowaniu słyszalności odpowiedzi Asystenta i odwrotnie.

Aplikacje asystujące z dostępem do nowego trybu audio MODE_ASSISTANT_CONVERSATION mogą jeszcze bardziej poprawić spójność sterowania głośnością. Aplikacje asystujące mogą używać tego trybu, aby przekazać systemowi wskazówkę dotyczącą aktywnej sesji asystenta, dzięki czemu strumień asystenta może być kontrolowany poza aktywnym odtwarzaniem USAGE_ASSISTANT lub za pomocą połączonych urządzeń peryferyjnych Bluetooth.

Handoff

Przekazywanie to nowa funkcja i interfejs API, które pojawią się w Androidzie 17. Deweloperzy aplikacji będą mogli je zintegrować, aby zapewnić użytkownikom ciągłość działania na różnych urządzeniach. Umożliwia użytkownikowi rozpoczęcie aktywności w aplikacji na jednym urządzeniu z Androidem i przeniesienie jej na inne urządzenie z Androidem. Przekazywanie działa w tle na urządzeniu użytkownika i wyświetla dostępne aktywności z innych urządzeń użytkownika w pobliżu za pomocą różnych punktów wejścia, takich jak menu z aplikacjami i pasek zadań, na urządzeniu odbierającym.

Aplikacje mogą wyznaczyć funkcję Handoff do uruchamiania tej samej natywnej aplikacji na Androida, jeśli jest ona zainstalowana i dostępna na urządzeniu odbierającym. W tym procesie między aplikacjami użytkownik jest przekierowywany do wyznaczonej aktywności za pomocą precyzyjnego linku. Można też zaoferować przekazywanie z aplikacji do przeglądarki jako opcję zastępczą lub wdrożyć je bezpośrednio za pomocą przekazywania adresu URL.

Obsługa funkcji Handoff jest wdrażana na poziomie poszczególnych aktywności. Aby włączyć funkcję Handoff, wywołaj metodę setHandoffEnabled() dla aktywności. Wraz z przekazaniem może być konieczne przekazanie dodatkowych danych, aby odtworzona aktywność na urządzeniu odbierającym mogła przywrócić odpowiedni stan. Zaimplementuj wywołanie zwrotne onHandoffActivityDataRequested(), aby zwrócić obiekt HandoffActivityData zawierający szczegółowe informacje o tym, jak funkcja Handoff powinna obsługiwać i odtwarzać aktywność na urządzeniu odbierającym.

Aktualizacja na żywo – interfejs API kolorów semantycznych

W Androidzie 17 Live Update wprowadza interfejsy Semantic Coloring API, które obsługują kolory o uniwersalnym znaczeniu.

Te klasy obsługują kolorowanie semantyczne:

Kolorowanki

  • Zielony: kojarzy się z bezpieczeństwem. Ten kolor powinien być używany w przypadku, gdy chcesz poinformować innych, że jesteś w bezpiecznej sytuacji.
  • Pomarańczowy: do oznaczania ostrzeżeń i zagrożeń fizycznych. Ten kolor powinien być używany w sytuacji, gdy użytkownicy muszą zwrócić uwagę na ustawienie lepszej ochrony.
  • Czerwony: zwykle oznacza niebezpieczeństwo, zatrzymanie. Powinien być wyświetlany w przypadkach, gdy trzeba pilnie zwrócić uwagę użytkowników.
  • Niebieski: neutralny kolor dla treści informacyjnych, które powinny wyróżniać się na tle innych.

Poniższy przykład pokazuje, jak zastosować style semantyczne do tekstu w powiadomieniu:

  val ssb = SpannableStringBuilder()
        .append("Colors: ")
        .append("NONE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_UNSPECIFIED), 0)
        .append(", ")
        .append("INFO", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_INFO), 0)
        .append(", ")
        .append("SAFE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_SAFE), 0)
        .append(", ")
        .append("CAUTION", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_CAUTION), 0)
        .append(", ")
        .append("DANGER", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_DANGER), 0)

    Notification.Builder(context, channelId)
          .setSmallIcon(R.drawable.ic_icon)
          .setContentTitle("Hello World!")
          .setContentText(ssb)
          .setOngoing(true)
              .setRequestPromotedOngoing(true)

Interfejs API UWB Downlink-TDoA na Androida 17

Pomiar odległości metodą Downlink Time Difference of Arrival (DL-TDoA) pozwala urządzeniu określić swoją pozycję względem wielu punktów odniesienia przez pomiar względnego czasu nadejścia sygnałów.

Poniższy fragment kodu pokazuje, jak zainicjować menedżera zakresu, sprawdzić możliwości urządzenia i rozpocząć sesję DL-TDoA:

Kotlin

class RangingApp {

    fun initDlTdoa(context: Context) {
        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Register for device capabilities
        val capabilitiesCallback = object : RangingManager.RangingCapabilitiesCallback {
            override fun onRangingCapabilities(capabilities: RangingCapabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.uwbCapabilities != null && capabilities.uwbCapabilities!!.isDlTdoaSupported) {
                    startDlTDoASession(context)
                }
            }
        }
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback)
    }

    fun startDlTDoASession(context: Context) {

        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Create session and configure parameters
        val executor = Executors.newSingleThreadExecutor()
        val rangingSession = rangingManager.createRangingSession(executor, RangingSessionCallback())
        val rangingRoundIndexes = byteArrayOf(0)
        val config: ByteArray = byteArrayOf() // OOB config data
        val params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes)

        val rangingDevice = RangingDevice.Builder().build()
        val rawTagDevice = RawRangingDevice.Builder()
            .setRangingDevice(rangingDevice)
            .setDlTdoaRangingParams(params)
            .build()

        val dtTagConfig = RawDtTagRangingConfig.Builder(rawTagDevice).build()

        val preference = RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
            .setSessionConfig(SessionConfig.Builder().build())
            .build()

        // Start the ranging session
        rangingSession.start(preference)
    }
}

private class RangingSessionCallback : RangingSession.Callback {
    override fun onDlTdoaResults(peer: RangingDevice, measurement: DlTdoaMeasurement) {
        // Process measurement results here
    }
}

Java

public class RangingApp {

    public void initDlTdoa(Context context) {

        // Initialize the Ranging Manager
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Register for device capabilities
        RangingManager.CapabilitiesCallback capabilitiesCallback = new RangingManager.RangingCapabilitiesCallback() {
            @Override
            public void onRangingCapabilities(RangingCapabilities capabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.getUwbCapabilities() != null && capabilities.getUwbCapabilities().isDlTdoaSupported()) {
                    startDlTDoASession(context);
                }
            }
        };
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback);
    }

    public void startDlTDoASession(Context context) {
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Create session and configure parameters
        Executor executor = Executors.newSingleThreadExecutor();
        RangingSession rangingSession = rangingManager.createRangingSession(executor, new RangingSessionCallback());
        byte[] rangingRoundIndexes = new byte[] {0};
        byte[] config = new byte[0]; // OOB config data
        DlTdoaRangingParams params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes);

        RangingDevice rangingDevice = new RangingDevice.Builder().build();
        RawRangingDevice rawTagDevice = new RawRangingDevice.Builder()
                .setRangingDevice(rangingDevice)
                .setDlTdoaRangingParams(params)
                .build();

        RawDtTagRangingConfig dtTagConfig = new RawDtTagRangingConfig.Builder(rawTagDevice).build();

        RangingPreference preference = new RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
                .setSessionConfig(new SessionConfig.Builder().build())
                .build();

        // Start the ranging session
        rangingSession.start(preference);
    }

    private static class RangingSessionCallback implements RangingSession.Callback {

        @Override
        public void onDlTdoaResults(RangingDevice peer, DlTdoaMeasurement measurement) {
            // Process measurement results here
        }
    }
}

Konfiguracje poza pasmem (OOB)

Poniższy fragment kodu zawiera przykładowe dane konfiguracji DL-TDoA OOB dla Wi-Fi i BLE:

Java

// Wifi Configuration
byte[] wifiConfig = {
    (byte) 0xDD, (byte) 0x2D, (byte) 0x5A, (byte) 0x18, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

// BLE Configuration
byte[] bleConfig = {
    (byte) 0x2D, (byte) 0x16, (byte) 0xF4, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

Jeśli nie możesz użyć konfiguracji OOB, ponieważ jej brakuje, lub jeśli chcesz zmienić wartości domyślne, których nie ma w konfiguracji OOB, możesz utworzyć parametry za pomocą DlTdoaRangingParams.Builder, jak pokazano w tym fragmencie kodu. Zamiast DlTdoaRangingParams.createFromFiraConfigPacket() możesz użyć tych parametrów:

Kotlin

val dlTdoaParams = DlTdoaRangingParams.Builder(1)
    .setComplexChannel(UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(byteArrayOf(0x01, 0x02, 0x03, 0x04))
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(byteArrayOf(0x01, 0x05))
    .build()

Java

DlTdoaRangingParams dlTdoaParams = new DlTdoaRangingParams.Builder(1)
    .setComplexChannel(new UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(new byte[]{0x01, 0x02, 0x03, 0x04})
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(new byte[]{0x01, 0x05})
    .build();