Biblioteka aplikacji Android for Cars umożliwia zabranie w samochodzie aplikacji do nawigacji, ciekawych miejsc i internetu (IOT). Zawiera zestaw szablonów zaprojektowany z myślą o wymaganiach dotyczących rozpraszania uwagi kierowcy oraz uwzględniający takie szczegóły jak różne czynniki wyświetlane w samochodzie i różne sposoby wprowadzania danych.
Ten przewodnik zawiera omówienie głównych funkcji i zagadnień związanych z biblioteką. Przeprowadzi Cię on także przez proces konfigurowania podstawowej aplikacji.
Zanim zaczniesz
- Przejrzyj strony Projektowanie do samochodów dotyczące biblioteki aplikacji Car.
- Informacje o kategoriach aplikacji nawigacyjnych i innych aplikacji związanych z prowadzeniem samochodu
- Omówienie tworzenia aplikacji za pomocą szablonów
- Elementy składowe obejmujące Szablony i Komponenty szablonów
- Przykładowe przepływy prezentujące typowe wzorce UX
- Wymagania dotyczące aplikacji utworzonych na podstawie szablonów
- Zapoznaj się z kluczowymi terminami i pojęciami w kolejnej sekcji.
- Zapoznaj się z interfejsem systemu Android Auto i projektem systemu operacyjnego Android Automotive.
- Zapoznaj się z informacjami o wersji.
- Przejrzyj Sample.
Kluczowe terminy i koncepcje
- Modele i szablony
- Interfejs jest reprezentowany przez wykres obiektów modelu, które można umieszczać w różnych miejscach zgodnie z szablonem, do którego należą. Szablony to podzbiór modeli, które mogą stanowić podstawę dla tych wykresów. Modele obejmują informacje, które mają być wyświetlane użytkownikowi w postaci tekstu i obrazów, a także atrybuty określające aspekty wizualnego wyglądu takich informacji, np. kolory tekstu lub rozmiary obrazów. Gospodarz konwertuje modele na widoki zaprojektowane tak, aby spełniały standardy w zakresie rozpraszania uwagi kierowcy, i dba o takie szczegóły jak różnorodne parametry ekranu samochodu i modalności danych wejściowych.
- Host
- Host to komponent backendu, który implementuje funkcje oferowane przez interfejsy API biblioteki, aby umożliwić działanie aplikacji w samochodzie. Obowiązki gospodarza są różne: od odkrycia aplikacji i zarządzania jej cyklem życia po przekształcanie modeli w widoki i powiadamianie aplikacji o interakcjach użytkowników. Na urządzeniach mobilnych ten host jest implementowany przez Androida Auto. W systemie operacyjnym Android Automotive ten host jest instalowany jako aplikacja systemowa.
- Ograniczenia szablonów
- Różne szablony wymuszają ograniczenia dotyczące treści modeli. Na przykład szablony list mają ograniczoną liczbę elementów, które można przedstawić użytkownikowi. Szablony mają też ograniczenia związane z ich łączeniem w celu utworzenia przepływu zadań. Na przykład aplikacja może przekazać maksymalnie 5 szablonów do stosu ekranu. Więcej informacji znajdziesz w sekcji Ograniczenia dotyczące szablonów.
Screen
Screen
to klasa udostępniana przez bibliotekę, którą wdraża aplikacje w celu zarządzania prezentowanym użytkownikowi. ElementScreen
ma cykl życia i umożliwia aplikacji wysyłanie szablonu do wyświetlenia, gdy ekran jest widoczny. Instancje (Screen
) można też przenosić do stosuScreen
lub z niego łączyć, dzięki czemu są one zgodne z ograniczeniami przepływu szablonów.CarAppService
CarAppService
to abstrakcyjna klasaService
, którą musi wdrożyć i wyeksportować Twoja aplikacja, aby umożliwić jej wykrywanie i zarządzanie przez hosta. KluczCarAppService
Twojej aplikacji odpowiada za sprawdzanie, czy połączenie z hostem jest godne zaufania, za pomocącreateHostValidator
, a następnie za udostępnianie instancjiSession
dla każdego połączenia z wykorzystaniemonCreateSession
.Session
Session
to klasa abstrakcyjna, którą aplikacja musi wdrożyć i zwrócić przy użyciuCarAppService.onCreateSession
. Służy jako punkt dostępu do wyświetlania informacji na ekranie samochodu. Zawiera on cykl życia, który informuje o bieżącym stanie aplikacji na ekranie samochodu, np. o tym, kiedy jest widoczna lub ukryta.Po uruchomieniu
Session
, np. przy pierwszym uruchomieniu aplikacji, host wysyła żądanie wyświetlenia początkowegoScreen
za pomocą metodyonCreateScreen
.
Instalowanie biblioteki aplikacji samochodu
Instrukcje dodawania biblioteki do aplikacji znajdziesz na stronie wersji biblioteki Jetpack.
Skonfiguruj pliki manifestu aplikacji
Zanim utworzysz aplikację samochodową, skonfiguruj jej pliki manifestu w następujący sposób.
Deklarowanie CarAppService
Host łączy się z Twoją aplikacją przez implementację CarAppService
. Tę usługę deklarujesz w pliku manifestu, aby host mógł wykrywać Twoją aplikację i się z nią łączyć.
Musisz też zadeklarować kategorię aplikacji w elemencie <category>
w filtrze intencji aplikacji. Wartości dozwolone w przypadku tego elementu znajdziesz na liście obsługiwanych kategorii aplikacji.
Ten fragment kodu pokazuje, jak w pliku manifestu zadeklarować usługę samochodową dla aplikacji z ciekawych miejsc:
<application>
...
<service
...
android:name=".MyCarAppService"
android:exported="true">
<intent-filter>
<action android:name="androidx.car.app.CarAppService"/>
<category android:name="androidx.car.app.category.POI"/>
</intent-filter>
</service>
...
<application>
Obsługiwane kategorie aplikacji
Zadeklaruj kategorię aplikacji, dodając co najmniej 1 z tych wartości kategorii w filtrze intencji podczas zadeklarowania CarAppService
w sposób opisany w poprzedniej sekcji:
androidx.car.app.category.NAVIGATION
: aplikacja ze szczegółowymi wskazówkami nawigacji. Więcej informacji o tej kategorii znajdziesz w artykule o tworzeniu aplikacji nawigacyjnych do samochodów.androidx.car.app.category.POI
: aplikacja, która umożliwia znajdowanie ciekawych miejsc, takich jak miejsca parkingowe, stacje ładowania i stacje paliw. Dodatkową dokumentację na temat tej kategorii znajdziesz w aplikacjach do tworzenia ciekawych miejsc w samochodach.androidx.car.app.category.IOT
: aplikacja, która umożliwia użytkownikom wykonywanie odpowiednich działań na połączonych urządzeniach z poziomu samochodu. Więcej informacji o tej kategorii znajdziesz w artykule Tworzenie internetu rzeczy w samochodach.
W artykule Jakość aplikacji na Androida w samochodach znajdziesz szczegółowe opisy poszczególnych kategorii i kryteriów, do których aplikacje mają należeć.
Określ nazwę i ikonę aplikacji
Musisz podać nazwę i ikonę aplikacji, za pomocą których host może ją reprezentować w interfejsie systemu.
Możesz określić nazwę i ikonę aplikacji, które będą reprezentować Twoją aplikację za pomocą atrybutów label
i icon
CarAppService
:
...
<service
android:name=".MyCarAppService"
android:exported="true"
android:label="@string/my_app_name"
android:icon="@drawable/my_app_icon">
...
</service>
...
Jeśli w elemencie <service>
nie zadeklarowano etykiety ani ikony, host wraca do wartości określonych dla elementu <application>
.
Ustawianie motywu niestandardowego
Aby ustawić niestandardowy motyw aplikacji samochodowej, dodaj element <meta-data>
w pliku manifestu w ten sposób:
<meta-data android:name="androidx.car.app.theme" android:resource="@style/MyCarAppTheme />
Następnie zadeklaruj zasób stylu, aby ustawić te atrybuty niestandardowego motywu aplikacji samochodowej:
<resources> <style name="MyCarAppTheme"> <item name="carColorPrimary">@layout/my_primary_car_color</item> <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item> <item name="carColorSecondary">@layout/my_secondary_car_color</item> <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item> <item name="carPermissionActivityLayout">@layout/my_custom_background</item> </style> </resources>
Poziom interfejsu Car App API
Biblioteka aplikacji samochodowych definiuje własne poziomy interfejsu API, dzięki czemu można sprawdzić, które funkcje biblioteki są obsługiwane przez hosta szablonu w pojeździe.
Aby pobrać najwyższy poziom interfejsu Car App API obsługiwany przez hosta, użyj metody getCarAppApiLevel()
.
W pliku AndroidManifest.xml
zadeklaruj minimalny poziom interfejsu Car App API obsługiwany przez Twoją aplikację:
<manifest ...>
<application ...>
<meta-data
android:name="androidx.car.app.minCarApiLevel"
android:value="1"/>
</application>
</manifest>
Szczegółowe informacje o tym, jak zachować zgodność wsteczną i zadeklarować minimalny poziom interfejsu API wymagany do używania funkcji, znajdziesz w dokumentacji adnotacji RequiresCarApi
. Definicję poziomu interfejsu API wymaganego do korzystania z danej funkcji biblioteki aplikacji samochodowych znajdziesz w dokumentacji referencyjnej CarAppApiLevels
.
Tworzenie usługi CarAppService i sesji
Aplikacja musi rozszerzyć klasę CarAppService
i zaimplementować jej metodę onCreateSession
, która zwraca wystąpienie Session
odpowiadające bieżącemu połączeniu z hostem:
Kotlin
class HelloWorldService : CarAppService() { ... override fun onCreateSession(): Session { return HelloWorldSession() } ... }
Java
public final class HelloWorldService extends CarAppService { ... @Override @NonNull public Session onCreateSession() { return new HelloWorldSession(); } ... }
Instancja Session
jest odpowiedzialna za zwrot instancji Screen
w celu użycia przy pierwszym uruchomieniu aplikacji:
Kotlin
class HelloWorldSession : Session() { ... override fun onCreateScreen(intent: Intent): Screen { return HelloWorldScreen(carContext) } ... }
Java
public final class HelloWorldSession extends Session { ... @Override @NonNull public Screen onCreateScreen(@NonNull Intent intent) { return new HelloWorldScreen(getCarContext()); } ... }
Aby radzić sobie w sytuacjach, w których aplikacja samochodowa musi się uruchamiać na innym ekranie (np. w przypadku obsługi precyzyjnych linków), możesz wstępnie wypełnić wsteczny stos ekranów za pomocą funkcji ScreenManager.push
, a potem wrócić z onCreateScreen
.
Wstępne wypełnianie umożliwia użytkownikom przechodzenie do poprzednich ekranów z pierwszego ekranu, na którym wyświetla się aplikacja.
Utwórz ekran startowy
Ekrany wyświetlane w aplikacji tworzysz, definiując klasy, które rozszerzają klasę Screen
, i implementując jej metodę onGetTemplate
, która zwraca wystąpienie Template
reprezentujące stan interfejsu wyświetlany na ekranie w samochodzie.
Ten fragment kodu pokazuje, jak zadeklarować Screen
, który korzysta z szablonu PaneTemplate
do wyświetlania prostego ciągu znaków „Hello world!”:
Kotlin
class HelloWorldScreen(carContext: CarContext) : Screen(carContext) { override fun onGetTemplate(): Template { val row = Row.Builder().setTitle("Hello world!").build() val pane = Pane.Builder().addRow(row).build() return PaneTemplate.Builder(pane) .setHeaderAction(Action.APP_ICON) .build() } }
Java
public class HelloWorldScreen extends Screen { @NonNull @Override public Template onGetTemplate() { Row row = new Row.Builder().setTitle("Hello world!").build(); Pane pane = new Pane.Builder().addRow(row).build(); return new PaneTemplate.Builder(pane) .setHeaderAction(Action.APP_ICON) .build(); } }
Klasa CarContext
Klasa CarContext
jest podklasą ContextWrapper
dostępną do instancji Session
i Screen
. Zapewnia dostęp do usług samochodowych, takich jak ScreenManager
do zarządzania stosem ekranu, AppManager
do ogólnych funkcji związanych z aplikacjami, takich jak dostęp do obiektu Surface
, np. do rysowania mapy aplikacji do nawigacji, oraz NavigationManager
do komunikacji z hostami nawigacji zakręt po zakręcie do komunikacji i innych metadanych.
Pełną listę funkcji biblioteki dostępnych dla aplikacji nawigacyjnych znajdziesz w artykule Uzyskiwanie dostępu do szablonów nawigacji.
CarContext
oferuje też inne funkcje, takie jak ładowanie możliwych do rysowania zasobów przy użyciu konfiguracji na ekranie samochodu, uruchamianie aplikacji w samochodzie przy użyciu intencji czy sygnalizowanie, czy aplikacja do nawigacji powinna wyświetlić swoją mapę w trybie ciemnym.
Wdrażanie nawigacji po ekranie
Aplikacje często mają wiele różnych ekranów, z których każdy może korzystać z odmiennych szablonów, z których użytkownik może korzystać podczas interakcji z wyświetlanym interfejsem.
Klasa ScreenManager
udostępnia stos ekranu, który umożliwia otwieranie ekranów automatycznie po kliknięciu przez użytkownika przycisku Wstecz na ekranie samochodu lub przy użyciu sprzętowego przycisku Wstecz, który jest dostępny w niektórych samochodach.
Ten fragment kodu pokazuje, jak dodać działanie wsteczne do szablonu wiadomości oraz działanie, które po wybraniu przez użytkownika powoduje wypchnięcie nowego ekranu:
Kotlin
val template = MessageTemplate.Builder("Hello world!") .setHeaderAction(Action.BACK) .addAction( Action.Builder() .setTitle("Next screen") .setOnClickListener { screenManager.push(NextScreen(carContext)) } .build()) .build()
Java
MessageTemplate template = new MessageTemplate.Builder("Hello world!") .setHeaderAction(Action.BACK) .addAction( new Action.Builder() .setTitle("Next screen") .setOnClickListener( () -> getScreenManager().push(new NextScreen(getCarContext()))) .build()) .build();
Obiekt Action.BACK
to standardowy obiekt Action
, który automatycznie wywołuje ScreenManager.pop
.
To działanie można zastąpić za pomocą instancji OnBackPressedDispatcher
dostępnej w CarContext
.
Aby można było bezpiecznie używać aplikacji podczas jazdy, stos ekranu może mieć maksymalnie 5 ekranów. Więcej informacji znajdziesz w sekcji Ograniczenia szablonu.
Odświeżanie zawartości szablonu
Aplikacja może zażądać unieważnienia zawartości pola Screen
, wywołując metodę Screen.invalidate
.
Następnie host łączy się z metodą Screen.onGetTemplate
Twojej aplikacji, aby pobrać szablon z nową zawartością.
Podczas odświeżania elementu Screen
ważne jest, aby poznać konkretną zawartość szablonu, którą można aktualizować, aby host nie wliczał nowego szablonu do limitu szablonów.
Więcej informacji znajdziesz w sekcji Ograniczenia dotyczące szablonów.
Zalecamy uporządkowanie ekranów w taki sposób, aby istniało mapowanie 1:1 między obiektem Screen
a typem szablonu, który zwraca w ramach implementacji onGetTemplate
.
Interakcja z użytkownikiem
Aplikacja może wchodzić w interakcję z użytkownikiem za pomocą wzorców podobnych do tych w aplikacji mobilnej.
Obsługa danych wejściowych użytkownika
Aplikacja może reagować na działania użytkowników, przekazując odpowiednie detektory do modeli, które je obsługują. Ten fragment kodu pokazuje, jak utworzyć model Action
, który ustawia OnClickListener
, który wywołuje metodę określoną w kodzie aplikacji:
Kotlin
val action = Action.Builder() .setTitle("Navigate") .setOnClickListener(::onClickNavigate) .build()
Java
Action action = new Action.Builder() .setTitle("Navigate") .setOnClickListener(this::onClickNavigate) .build();
Metoda onClickNavigate
może następnie uruchomić domyślną aplikację do nawigacji za pomocą metody CarContext.startCarApp
:
Kotlin
private fun onClickNavigate() { val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address)) carContext.startCarApp(intent) }
Java
private void onClickNavigate() { Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address)); getCarContext().startCarApp(intent); }
Więcej informacji o uruchamianiu aplikacji, w tym o formacie intencji ACTION_NAVIGATE
, znajdziesz w sekcji Uruchamianie aplikacji samochodowej z intencją.
Niektóre działania, na przykład te, które wymagają polecenia użytkownikowi kontynuowania interakcji na urządzeniu mobilnym, są dozwolone tylko wtedy, gdy samochód jest zaparkowany.
Do wykonania tych działań możesz użyć elementu ParkedOnlyOnClickListener
. Jeśli samochód nie jest zaparkowany, host wyświetla użytkownikowi informację, że w tym przypadku dana czynność jest niedozwolona. Gdy samochód jest zaparkowany, kod działa normalnie. Ten fragment kodu pokazuje, jak za pomocą ParkedOnlyOnClickListener
otworzyć ekran ustawień na urządzeniu mobilnym:
Kotlin
val row = Row.Builder() .setTitle("Open Settings") .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone)) .build()
Java
Row row = new Row.Builder() .setTitle("Open Settings") .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone)) .build();
Wyświetl powiadomienia
Powiadomienia wysyłane na urządzenie mobilne wyświetlają się na ekranie samochodu tylko wtedy, gdy są rozszerzone przy użyciu CarAppExtender
.
Niektóre atrybuty powiadomień, takie jak tytuł treści, tekst, ikona i działania, można ustawić w CarAppExtender
. Zastępują one atrybuty powiadomienia wyświetlanego na ekranie samochodu.
Ten fragment kodu pokazuje, jak wysłać na ekran samochodu powiadomienie z innym tytułem niż ten wyświetlany na urządzeniu mobilnym:
Kotlin
val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(titleOnThePhone) .extend( CarAppExtender.Builder() .setContentTitle(titleOnTheCar) ... .build()) .build()
Java
Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) .setContentTitle(titleOnThePhone) .extend( new CarAppExtender.Builder() .setContentTitle(titleOnTheCar) ... .build()) .build();
Powiadomienia mogą mieć wpływ na następujące części interfejsu użytkownika:
- Użytkownik może zobaczyć powiadomienie z ostrzeżeniem (HUN).
- W centrum powiadomień można dodać wpis wraz z plakietką widoczną na pasku powiadomień.
- W przypadku aplikacji nawigacyjnych powiadomienie może być wyświetlane w widżecie linii kolejowej zgodnie z opisem w sekcji Powiadomienia zakręt po zakręcie.
Możesz określić, jak powiadomienia aplikacji mają wpływać na każdy z tych elementów interfejsu, korzystając z priorytetu powiadomienia, zgodnie z opisem w dokumentacji CarAppExtender
.
Jeśli funkcja NotificationCompat.Builder.setOnlyAlertOnce
zostanie wywołana z wartością true
, powiadomienie o wysokim priorytecie wyświetla się tylko raz.
Więcej informacji o projektowaniu powiadomień z aplikacji samochodowej znajdziesz w przewodniku Google Design for Driving na temat powiadomień.
Wyświetlaj powiadomienia
Aplikacja może wyświetlać tost za pomocą parametru CarToast
, tak jak w tym fragmencie:
Kotlin
CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()
Java
CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();
Poproś o uprawnienia
Jeśli aplikacja wymaga dostępu do danych lub działań objętych ograniczeniami – np. do lokalizacji – obowiązują standardowe reguły uprawnień Androida. Aby poprosić o uprawnienia, możesz użyć metody CarContext.requestPermissions()
.
Zaletą użycia CarContext.requestPermissions()
w przeciwieństwie do standardowych interfejsów API Androida jest to, że nie musisz uruchamiać własnego Activity
, aby utworzyć okno uprawnień. Możesz też użyć tego samego kodu w systemie operacyjnym Android Auto i Android Automotive, zamiast tworzyć procesy zależne od platformy.
Określanie stylu okna uprawnień w Androidzie Auto
W Androidzie Auto na telefonie pojawi się okno uprawnień użytkownika.
Domyślnie za oknem nie ma tła. Aby ustawić niestandardowe tło, zadeklaruj motyw aplikacji samochodowej w pliku AndroidManifest.xml
i ustaw atrybut carPermissionActivityLayout
dla motywu aplikacji samochodowej.
<meta-data android:name="androidx.car.app.theme" android:resource="@style/MyCarAppTheme />
Następnie ustaw atrybut carPermissionActivityLayout
dla motywu aplikacji samochodu:
<resources> <style name="MyCarAppTheme"> <item name="carPermissionActivityLayout">@layout/my_custom_background</item> </style> </resources>
Uruchamianie aplikacji samochodowej z zamiarem
Możesz wywołać metodę CarContext.startCarApp
, aby wykonać jedno z tych działań:
- Otwórz aplikację Telefon, by zadzwonić.
- Uruchom szczegółową nawigację do lokalizacji w domyślnej aplikacji do nawigacji samochodowej.
- Uruchom własną aplikację z planem.
Z przykładu poniżej dowiesz się, jak utworzyć powiadomienie z działaniem powodującym otwarcie aplikacji i wyświetlenie ekranu ze szczegółami rezerwacji miejsca parkingowego.
Rozszerzasz wystąpienie powiadomienia za pomocą intencji treści, która zawiera element PendingIntent
pakujący intencję w stosunku do działania aplikacji:
Kotlin
val notification = notificationBuilder ... .extend( CarAppExtender.Builder() .setContentIntent( PendingIntent.getBroadcast( context, ACTION_VIEW_PARKING_RESERVATION.hashCode(), Intent(ACTION_VIEW_PARKING_RESERVATION) .setComponent(ComponentName(context, MyNotificationReceiver::class.java)), 0)) .build())
Java
Notification notification = notificationBuilder ... .extend( new CarAppExtender.Builder() .setContentIntent( PendingIntent.getBroadcast( context, ACTION_VIEW_PARKING_RESERVATION.hashCode(), new Intent(ACTION_VIEW_PARKING_RESERVATION) .setComponent(new ComponentName(context, MyNotificationReceiver.class)), 0)) .build());
Aplikacja musi też zadeklarować obiekt BroadcastReceiver
, który jest wywoływany do przetworzenia intencji, gdy użytkownik wybierze działanie w interfejsie powiadomień i wywoła CarContext.startCarApp
z intencją zawierającą identyfikator URI danych:
Kotlin
class MyNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val intentAction = intent.action if (ACTION_VIEW_PARKING_RESERVATION == intentAction) { CarContext.startCarApp( intent, Intent(Intent.ACTION_VIEW) .setComponent(ComponentName(context, MyCarAppService::class.java)) .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction))) } } }
Java
public class MyNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String intentAction = intent.getAction(); if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) { CarContext.startCarApp( intent, new Intent(Intent.ACTION_VIEW) .setComponent(new ComponentName(context, MyCarAppService.class)) .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction))); } } }
Wreszcie metoda Session.onNewIntent
w aplikacji obsługuje tę intencję, wypychając ekran rezerwacji miejsca parkingowego na stos, jeśli jeszcze nie znajduje się na wierzchu:
Kotlin
override fun onNewIntent(intent: Intent) { val screenManager = carContext.getCarService(ScreenManager::class.java) val uri = intent.data if (uri != null && MY_URI_SCHEME == uri.scheme && MY_URI_HOST == uri.schemeSpecificPart && ACTION_VIEW_PARKING_RESERVATION == uri.fragment ) { val top = screenManager.top if (top !is ParkingReservationScreen) { screenManager.push(ParkingReservationScreen(carContext)) } } }
Java
@Override public void onNewIntent(@NonNull Intent intent) { ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class); Uri uri = intent.getData(); if (uri != null && MY_URI_SCHEME.equals(uri.getScheme()) && MY_URI_HOST.equals(uri.getSchemeSpecificPart()) && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment()) ) { Screen top = screenManager.getTop(); if (!(top instanceof ParkingReservationScreen)) { screenManager.push(new ParkingReservationScreen(getCarContext())); } } }
Więcej informacji o obsłudze powiadomień z aplikacji samochodowej znajdziesz w sekcji Wyświetlanie powiadomień.
Ograniczenia szablonów
Host ogranicza liczbę szablonów wyświetlanych w ramach danego zadania do maksymalnie 5. Ostatni szablon musi być jednym z tych typów:
Pamiętaj, że ten limit dotyczy liczby szablonów, a nie liczby instancji Screen
w stosie. Jeśli na przykład aplikacja wyśle 2 szablony na ekranie A, a potem wyświetli ekran B, może wysłać 3 kolejne szablony. Jeśli każdy ekran ma strukturę umożliwiającą wysyłanie 1 szablonu, aplikacja może też umieścić 5 instancji ekranu na stos ScreenManager
.
Obowiązują szczególne przypadki, w których obowiązują te ograniczenia: odświeżanie szablonu oraz operacje przywracania i resetowania.
Odświeżanie szablonu
Niektóre aktualizacje treści nie wliczają się do limitu szablonów. Ogólnie, jeśli aplikacja przesyła nowy szablon tego samego typu i zawierający tę samą główną treść co poprzedni szablon, nowy szablon nie jest wliczany do limitu. Na przykład zaktualizowanie stanu przełączania wiersza w elemencie ListTemplate
nie powoduje wliczania się do limitu. W dokumentacji poszczególnych szablonów dowiesz się, jakie typy aktualizacji treści można uznać za odświeżone.
Operacje wstecz
Aby włączyć procesy podrzędne w ramach zadania, host wykrywa, kiedy aplikacja wyrzuca Screen
ze stosu ScreenManager
, i aktualizuje pozostały limit na podstawie liczby szablonów, o które aplikacja cofa się do poprzedniego kroku.
Jeśli na przykład na ekranie A aplikacja wyśle 2 szablony, a potem przesunie ekran B i wyśle jeszcze 2 szablony, aplikacja będzie mieć jeszcze 1 limit. Jeśli aplikacja wróci do ekranu A, host resetuje limit do 3, ponieważ aplikacja cofnęła się o 2 szablony.
Pamiętaj, że po powrocie na ekran aplikacja musi wysłać szablon tego samego typu co ostatni wysłany przez ten ekran. Wysyłanie innego typu szablonu powoduje błąd. Dopóki jednak typ pozostaje taki sam podczas operacji wstecz, aplikacja może dowolnie modyfikować zawartość szablonu bez wpływu na limit.
Operacje resetowania
Niektóre szablony mają specjalną semantykę, która oznacza koniec zadania. Na przykład widok NavigationTemplate
powinien pozostawać na ekranie i odświeżać się za pomocą nowych, szczegółowych instrukcji wykorzystania przez użytkownika. Gdy dojdzie do jednego z tych szablonów, host resetuje limit szablonów, traktując go jako pierwszy krok nowego zadania. Dzięki temu aplikacja będzie mogła rozpocząć nowe zadanie.
W dokumentacji poszczególnych szablonów możesz sprawdzić, które z nich powodują resetowanie na hoście.
Jeśli host otrzyma zamiar uruchomienia aplikacji z poziomu powiadomienia lub programu uruchamiającego, limit też zostanie zresetowany. Ten mechanizm umożliwia aplikacji rozpoczynanie nowego przepływu zadań z poziomu powiadomień i działa nawet wtedy, gdy aplikacja jest już powiązana i działa na pierwszym planie.
Więcej informacji o tym, jak wyświetlać powiadomienia z aplikacji na ekranie samochodu, znajdziesz w sekcji Wyświetlanie powiadomień. Informacje o tym, jak uruchomić aplikację z poziomu powiadomienia, znajdziesz w sekcji Uruchamianie aplikacji samochodowej z intencją.
Interfejs API połączenia
Aby sprawdzić, czy Twoja aplikacja działa na systemie operacyjnym Android Auto czy Android Automotive, pobierz informacje o połączeniu w czasie działania za pomocą interfejsu CarConnection
API.
Na przykład w Session
aplikacji samochodowej zainicjuj CarConnection
i zasubskrybuj aktualizacje LiveData
:
Kotlin
CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)
Java
new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);
Korzystając z obserwatora, możesz reagować na zmiany stanu połączenia:
Kotlin
fun onConnectionStateUpdated(connectionState: Int) { val message = when(connectionState) { CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit" CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS" CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto" else -> "Unknown car connection type" } CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show() }
Java
private void onConnectionStateUpdated(int connectionState) { String message; switch(connectionState) { case CarConnection.CONNECTION_TYPE_NOT_CONNECTED: message = "Not connected to a head unit"; break; case CarConnection.CONNECTION_TYPE_NATIVE: message = "Connected to Android Automotive OS"; break; case CarConnection.CONNECTION_TYPE_PROJECTION: message = "Connected to Android Auto"; break; default: message = "Unknown car connection type"; break; } CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show(); }
Interfejs API ograniczeń
Różne samochody mogą zezwalać na wyświetlanie użytkownikowi różnych instancji Item
naraz. Aby sprawdzić limit treści w czasie działania i ustawić odpowiednią liczbę elementów w szablonach, użyj komponentu ConstraintManager
.
Zacznij od uzyskania ConstraintManager
od: CarContext
:
Kotlin
val manager = carContext.getCarService(ConstraintManager::class.java)
Java
ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);
Następnie możesz przesłać zapytanie do pobranego obiektu ConstraintManager
, aby określić odpowiedni limit treści. Aby na przykład uzyskać liczbę elementów, które można wyświetlić w siatce, wywołaj metodę getContentLimit
z parametrem CONTENT_LIMIT_TYPE_GRID
:
Kotlin
val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)
Java
int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);
Dodaj proces logowania
Jeśli Twoja aplikacja umożliwia logowanie się użytkowników, możesz używać szablonów takich jak SignInTemplate
i LongMessageTemplate
z interfejsem Car App API na poziomie 2 lub wyższym, aby logować się w niej na konsoli centralnej samochodu.
Aby utworzyć SignInTemplate
, zdefiniuj SignInMethod
. Biblioteka
aplikacji samochodowych obsługuje obecnie te metody logowania:
InputSignInMethod
, aby zalogować się za pomocą nazwy użytkownika i hasła.PinSignInMethod
– logowanie za pomocą kodu PIN, gdy użytkownik łączy swoje konto na telefonie za pomocą kodu PIN wyświetlanego na jednostce głównej.ProviderSignInMethod
w przypadku logowania dostawcy, np. logowania przez Google i jednego dotknięcia.QRCodeSignInMethod
, aby zalogować się za pomocą kodu QR, czyli skanować kod QR, aby zalogować się na telefonie. Ta funkcja jest dostępna w Car API na poziomie 4 i nowszych.
Aby np. wdrożyć szablon zbierający hasło użytkownika, zacznij od utworzenia InputCallback
do przetwarzania i weryfikowania danych wejściowych użytkownika:
Kotlin
val callback = object : InputCallback { override fun onInputSubmitted(text: String) { // You will receive this callback when the user presses Enter on the keyboard. } override fun onInputTextChanged(text: String) { // You will receive this callback as the user is typing. The update // frequency is determined by the host. } }
Java
InputCallback callback = new InputCallback() { @Override public void onInputSubmitted(@NonNull String text) { // You will receive this callback when the user presses Enter on the keyboard. } @Override public void onInputTextChanged(@NonNull String text) { // You will receive this callback as the user is typing. The update // frequency is determined by the host. } };
Wymagany jest element InputCallback
dla elementu: InputSignInMethod
Builder
.
Kotlin
val passwordInput = InputSignInMethod.Builder(callback) .setHint("Password") .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD) ... .build()
Java
InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback) .setHint("Password") .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD) ... .build();
Na koniec użyj nowego urządzenia InputSignInMethod
do utworzenia SignInTemplate
.
Kotlin
SignInTemplate.Builder(passwordInput) .setTitle("Sign in with username and password") .setInstructions("Enter your password") .setHeaderAction(Action.BACK) ... .build()
Java
new SignInTemplate.Builder(passwordInput) .setTitle("Sign in with username and password") .setInstructions("Enter your password") .setHeaderAction(Action.BACK) ... .build();
Użyj menedżera konta
Aplikacje na system operacyjny Android Automotive, które mają uwierzytelnianie, muszą używać usługi AccountManager z tych powodów:
- Większa wygoda użytkowania i łatwiejsze zarządzanie kontem: użytkownicy mogą łatwo zarządzać wszystkimi swoimi kontami za pomocą menu kont w ustawieniach systemu. Obejmuje to logowanie się i wylogowywanie.
- Funkcje „Gość”: samochody są urządzeniami współdzielonymi, dlatego OEM może włączyć w pojeździe funkcje obsługi gości, których kont nie można dodawać.
Dodawanie wariantów ciągu tekstowego
Na ekranach różnych urządzeń w samochodzie może wyświetlać się różna ilość tekstu. Interfejs Car App API na poziomie 2 lub wyższym pozwala określić wiele wariantów ciągu tekstowego, które najlepiej pasują do ekranu. Aby dowiedzieć się, gdzie akceptowane są warianty tekstu, znajdź szablony i komponenty, które akceptują CarText
.
Do elementu CarText
możesz dodać warianty ciągu tekstowego za pomocą metody CarText.Builder.addVariant()
:
Kotlin
val itemTitle = CarText.Builder("This is a very long string") .addVariant("Shorter string") ... .build()
Java
CarText itemTitle = new CarText.Builder("This is a very long string") .addVariant("Shorter string") ... .build();
Następnie możesz użyć tego elementu CarText
, np. jako głównego tekstu strony GridItem
.
Kotlin
GridItem.Builder() .addTitle(itemTitle) ... .build()
Java
new GridItem.Builder() .addTitle(itemTitle) ... build();
Dodaj ciągi znaków w kolejności od największej do najmniejszej, np. od najdłuższego do najkrótszego. Gospodarz wybiera ciąg znaków o odpowiedniej długości w zależności od ilości miejsca dostępnego na ekranie w samochodzie.
Dodaj wbudowane obiekty CarIcon dla wierszy
Za pomocą CarIconSpan
możesz dodawać ikony w tekście, aby uatrakcyjnić wygląd aplikacji.
Więcej informacji o tworzeniu tych spanów znajdziesz w dokumentacji CarIconSpan.create
. Informacje o tym, jak działają style tekstu za pomocą rozpiętości, znajdziesz w artykule o stylizowaniu tekstu ze spacjami za pomocą zakresów.
Kotlin
val rating = SpannableString("Rating: 4.5 stars") rating.setSpan( CarIconSpan.create( // Create a CarIcon with an image of four and a half stars CarIcon.Builder(...).build(), // Align the CarIcon to the baseline of the text CarIconSpan.ALIGN_BASELINE ), // The start index of the span (index of the character '4') 8, // The end index of the span (index of the last 's' in "stars") 16, Spanned.SPAN_INCLUSIVE_INCLUSIVE ) val row = Row.Builder() ... .addText(rating) .build()
Java
SpannableString rating = new SpannableString("Rating: 4.5 stars"); rating.setSpan( CarIconSpan.create( // Create a CarIcon with an image of four and a half stars new CarIcon.Builder(...).build(), // Align the CarIcon to the baseline of the text CarIconSpan.ALIGN_BASELINE ), // The start index of the span (index of the character '4') 8, // The end index of the span (index of the last 's' in "stars") 16, Spanned.SPAN_INCLUSIVE_INCLUSIVE ); Row row = new Row.Builder() ... .addText(rating) .build();
Interfejsy API do sprzętu samochodowego
Począwszy od poziomu 3 interfejsu Car App API, Biblioteka aplikacji samochodowych zawiera interfejsy API umożliwiające dostęp do właściwości pojazdu i czujników.
Wymagania
Aby używać interfejsów API w Androidzie Auto, zacznij od dodania zależności androidx.car.app:app-projected
do pliku build.gradle
modułu Androida Auto. W przypadku systemu operacyjnego Android Automotive dodaj zależność androidx.car.app:app-automotive
do pliku build.gradle
modułu systemu operacyjnego Android Automotive.
Dodatkowo w pliku AndroidManifest.xml
musisz zadeklarować odpowiednie uprawnienia potrzebne do wysłania prośby o dostęp do danych samochodu, których chcesz użyć. Pamiętaj, że te uprawnienia musi Ci też przyznać użytkownik. Zamiast tworzyć procesy zależne od platformy, możesz użyć tego samego kodu zarówno w systemie operacyjnym Android Auto, jak i Android Automotive. Potrzebne są jednak inne uprawnienia.
Informacje o samochodzie
W tej tabeli opisujemy właściwości udostępniane przez interfejsy CarInfo
API oraz uprawnienia, których potrzebujesz, aby ich używać:
Metody | Właściwości | Uprawnienia Androida Auto | Uprawnienia systemu operacyjnego Android Automotive |
---|---|---|---|
fetchModel |
Marka, model, rok produkcji | android.car.permission.CAR_INFO |
|
fetchEnergyProfile |
Typy złączy EV, typy paliwa | com.google.android.gms.permission.CAR_FUEL |
android.car.permission.CAR_INFO |
addTollListener
removeTollListener |
Stan karty płatnej, typ karty płatnej | ||
addEnergyLevelListener
removeEnergyLevelListener |
Poziom baterii, poziom paliwa, niski poziom paliwa, pozostały zasięg | com.google.android.gms.permission.CAR_FUEL |
android.car.permission.CAR_ENERGY ,android.car.permission.CAR_ENERGY_PORTS ,android.car.permission.READ_CAR_DISPLAY_UNITS |
addSpeedListener
removeSpeedListener |
Prędkość nieprzetworzona i prędkość wyświetlania (widoczne na wyświetlaczu klastra w samochodzie) | com.google.android.gms.permission.CAR_SPEED |
android.car.permission.CAR_SPEED ,android.car.permission.READ_CAR_DISPLAY_UNITS |
addMileageListener
removeMileageListener |
Odległość drogomierza | com.google.android.gms.permission.CAR_MILEAGE |
Te dane nie są dostępne w systemie operacyjnym Android Automotive w aplikacjach zainstalowanych ze Sklepu Play. |
Aby np. uzyskać pozostały zakres, utwórz instancję obiektu CarInfo
, a następnie utwórz i zarejestruj OnCarDataAvailableListener
:
Kotlin
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo val listener = OnCarDataAvailableListener<EnergyLevel> { data -> if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) { val rangeRemaining = data.rangeRemainingMeters.value } else { // Handle error } } carInfo.addEnergyLevelListener(carContext.mainExecutor, listener) … // Unregister the listener when you no longer need updates carInfo.removeEnergyLevelListener(listener)
Java
CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo(); OnCarDataAvailableListener<EnergyLevel> listener = (data) -> { if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) { float rangeRemaining = data.getRangeRemainingMeters().getValue(); } else { // Handle error } }; carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener); … // Unregister the listener when you no longer need updates carInfo.removeEnergyLevelListener(listener);
Nie zakładaj, że dane z samochodu są przez cały czas dostępne.
Jeśli pojawi się błąd, sprawdź stan żądanej wartości, aby lepiej zrozumieć, dlaczego nie udało się pobrać żądanych danych. Pełną definicję klasy CarInfo
znajdziesz w dokumentacji referencyjnej.
Czujniki samochodowe
Klasa CarSensors
zapewnia dostęp do danych z akcelerometru, żyroskopu, kompasu i lokalizacji pojazdu. Dostępność tych wartości może zależeć od producenta OEM. Format danych z akcelerometru, żyroskopu i kompasu jest taki sam jak w przypadku interfejsu API SensorManager
. Na przykład, aby sprawdzić kierunek pojazdu:
Kotlin
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val listener = OnCarDataAvailableListener<Compass> { data -> if (data.orientations.status == CarValue.STATUS_SUCCESS) { val orientation = data.orientations.value } else { // Data not available, handle error } } carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener) … // Unregister the listener when you no longer need updates carSensors.removeCompassListener(listener)
Java
CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors(); OnCarDataAvailableListener<Compass> listener = (data) -> { if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) { List<Float> orientations = data.getOrientations().getValue(); } else { // Data not available, handle error } }; carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(), listener); … // Unregister the listener when you no longer need updates carSensors.removeCompassListener(listener);
Aby uzyskać dostęp do danych o lokalizacji z samochodu, musisz też zadeklarować i poprosić o uprawnienia android.permission.ACCESS_FINE_LOCATION
.
Testowanie
Aby symulować dane z czujników podczas testowania na Androidzie Auto, zapoznaj się z sekcjami dotyczącymi czujników i konfiguracji czujników w przewodniku po jednostce głównej na komputerze. Aby symulować dane z czujników podczas testowania w systemie operacyjnym Android Automotive, zapoznaj się z sekcją Emuluj stan sprzętu w przewodniku po emulatorze systemu operacyjnego Android Automotive.
Cykle życia usług CarAppService i sesji oraz ekranu
Klasy Session
i Screen
implementują interfejs LifecycleOwner
. Gdy użytkownik wchodzi w interakcję z aplikacją, wywoływane są wywołania zwrotne cyklu życia obiektów Session
i Screen
, zgodnie z opisem na tych diagramach.
Cykle życia usług CarAppService i sesji
Szczegółowe informacje znajdziesz w dokumentacji metody Session.getLifecycle
.
Cykl życia ekranu
Szczegółowe informacje znajdziesz w dokumentacji metody Screen.getLifecycle
.
Nagrywanie z mikrofonu w samochodzie
Za pomocą CarAppService
i interfejsu API CarAudioRecord
możesz przyznać aplikacji dostęp do mikrofonu samochodowego użytkownika. Użytkownicy muszą zezwolić aplikacji
na dostęp do mikrofonu w samochodzie. Aplikacja może rejestrować i przetwarzać dane wejściowe użytkownika.
Uprawnienia do nagrywania
Przed nagraniem dźwięku musisz najpierw zadeklarować, że masz pozwolenie na nagrywanie w urządzeniu AndroidManifest.xml
, i poprosić o to użytkownika.
<manifest ...>
...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
...
</manifest>
Musisz poprosić o uprawnienia do nagrywania w czasie działania. W sekcji Prośba o uprawnienia znajdziesz szczegółowe informacje o tym, jak poprosić o uprawnienia w aplikacji samochodowej.
Nagrywanie dźwięku
Gdy użytkownik zezwoli na nagrywanie, będziesz mieć możliwość nagrywania dźwięku i przetwarzania nagrania.
Kotlin
val carAudioRecord = CarAudioRecord.create(carContext) carAudioRecord.startRecording() val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) { // Use data array // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech } carAudioRecord.stopRecording()
Java
CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext()); carAudioRecord.startRecording(); byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE]; while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) { // Use data array // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech } carAudioRecord.stopRecording();
Aktywność audio
Podczas nagrywania z mikrofonu w samochodzie musisz najpierw włączyć fokus, aby mieć pewność, że bieżące multimedia zostaną zatrzymane. Jeśli dźwięk nie jest już aktywny, zatrzymaj nagrywanie.
Oto przykład, jak uzyskać koncentrację audio:
Kotlin
val carAudioRecord = CarAudioRecord.create(carContext) // Take audio focus so that user's media is not recorded val audioAttributes = AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) // Use the most appropriate usage type for your use case .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) .build() val audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) .setAudioAttributes(audioAttributes) .setOnAudioFocusChangeListener { state: Int -> if (state == AudioManager.AUDIOFOCUS_LOSS) { // Stop recording if audio focus is lost carAudioRecord.stopRecording() } } .build() if (carContext.getSystemService(AudioManager::class.java) .requestAudioFocus(audioFocusRequest) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED ) { // Don't record if the focus isn't granted return } carAudioRecord.startRecording() // Process the audio and abandon the AudioFocusRequest when done
Java
CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext()); // Take audio focus so that user's media is not recorded AudioAttributes audioAttributes = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) // Use the most appropriate usage type for your use case .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE) .build(); AudioFocusRequest audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) .setAudioAttributes(audioAttributes) .setOnAudioFocusChangeListener(state -> { if (state == AudioManager.AUDIOFOCUS_LOSS) { // Stop recording if audio focus is lost carAudioRecord.stopRecording(); } }) .build(); if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest) != AUDIOFOCUS_REQUEST_GRANTED) { // Don't record if the focus isn't granted return; } carAudioRecord.startRecording(); // Process the audio and abandon the AudioFocusRequest when done
Biblioteka testowania
Biblioteka testowania Androida dla samochodów zawiera klasy pomocnicze, których możesz używać do weryfikowania działania aplikacji w środowisku testowym.
Na przykład SessionController
pozwala symulować połączenie z hostem i sprawdzić, czy utworzone i zwrócone są prawidłowe Screen
oraz Template
.
Przykłady użycia znajdziesz w sekcji Przykłady.
Zgłaszanie problemu z biblioteką aplikacji Android for Cars
Jeśli znajdziesz problem z biblioteką, zgłoś go za pomocą narzędzia Google Issue Tracker. Pamiętaj, aby w szablonie problemu podać wszystkie wymagane informacje.
Zanim zgłosisz nowe wydanie, sprawdź, czy nie ma go w informacjach o wersjach biblioteki lub czy nie ma go na liście problemów. Możesz zasubskrybować problem i głosować na nie, klikając gwiazdkę w narzędziu do śledzenia błędów. Więcej informacji znajdziesz w artykule Subskrybowanie problemu.