Kod dla konkretnego urządzenia

System przywracania zawiera kilka punktów zaczepienia do wstawiania kodu specyficznego dla urządzenia, dzięki czemu aktualizacje OTA mogą też aktualizować części urządzenia inne niż system Android (np. pasmo podstawowe lub procesor radiowy).

W kolejnych sekcjach i przykładach dostosujemy urządzenie tardis wyprodukowane przez dostawcę yoyodyne.

Mapa partycji

Od Androida 2.3 platforma obsługuje urządzenia flash eMMc i system plików ext4, który działa na tych urządzeniach. Obsługuje też urządzenia flash MTD i system plików yaffs2 ze starszych wersji.

Plik mapy partycji jest określany przez zmienną TARGET_RECOVERY_FSTAB. Jest on używany zarówno przez binarny plik odzyskiwania, jak i przez narzędzia do tworzenia pakietów. Nazwę pliku mapy możesz określić w pliku TARGET_RECOVERY_FSTAB w pliku BoardConfig.mk.

Przykładowy plik mapy partycji może wyglądać tak:

device/yoyodyne/tardis/recovery.fstab
# mount point       fstype  device       [device2]        [options (3.0+ only)]

/sdcard     vfat    /dev/block/mmcblk0p1 /dev/block/mmcblk0
/cache      yaffs2  cache
/misc       mtd misc
/boot       mtd boot
/recovery   emmc    /dev/block/platform/s3c-sdhci.0/by-name/recovery
/system     ext4    /dev/block/platform/s3c-sdhci.0/by-name/system length=-4096
/data       ext4    /dev/block/platform/s3c-sdhci.0/by-name/userdata

Z wyjątkiem /sdcard, który jest opcjonalny, wszystkie punkty montowania w tym przykładzie muszą być zdefiniowane (urządzenia mogą też dodawać dodatkowe partycje). Obsługiwanych jest 5 typów systemów plików:

yaffs2
System plików yaffs2 na urządzeniu flash MTD. „device” musi być nazwą partycji MTD i musi występować w /proc/mtd.
mtd
Surowa partycja MTD używana w przypadku partycji rozruchowych, takich jak boot i recovery. MTD nie jest faktycznie podłączany, ale punkt podłączania jest używany jako klucz do lokalizowania partycji. „device” musi być nazwą partycji MTD w /proc/mtd.
ext4
System plików ext4 na urządzeniu flash eMMc. „device” musi być ścieżką urządzenia blokowego.
emmc
Surowe urządzenie blokowe eMMC, używane w przypadku partycji rozruchowych, takich jak rozruchowa i przywracania. Podobnie jak w przypadku typu mtd, pamięć eMMC nigdy nie jest podłączana, ale ciąg punktu podłączania jest używany do lokalizowania urządzenia w tabeli.
vfat
System plików FAT na urządzeniu blokowym, zwykle w przypadku pamięci zewnętrznej, np. karty SD. Urządzenie to urządzenie blokowe, a urządzenie2 to drugie urządzenie blokowe, które system próbuje zamontować, jeśli nie uda się zamontować urządzenia głównego (dla zgodności z kartami SD, które mogą być sformatowane z tabelą partycji lub bez niej).

Wszystkie partycje muszą być zamontowane w katalogu głównym (czyli wartość punktu montowania musi zaczynać się od ukośnika i nie może zawierać innych ukośników). To ograniczenie dotyczy tylko podłączania systemów plików w trybie odzyskiwania. Główny system może je podłączać w dowolnym miejscu. Katalogi /boot, /recovery/misc powinny być typami surowymi (mtd lub emmc), a katalogi /system, /data, /cache/sdcard (jeśli są dostępne) powinny być typami systemów plików (yaffs2, ext4 lub vfat).

Od Androida 3.0 plik recovery.fstab ma dodatkowe pole opcjonalne: options. Obecnie jedyną zdefiniowaną opcją jest length , która umożliwia jawne określenie długości partycji. Ta długość jest używana podczas ponownego formatowania partycji (np. w przypadku partycji danych użytkownika podczas operacji czyszczenia danych lub przywracania ustawień fabrycznych albo w przypadku partycji systemowej podczas instalacji pełnego pakietu OTA). Jeśli wartość długości jest ujemna, rozmiar do sformatowania jest obliczany przez dodanie wartości długości do rzeczywistego rozmiaru partycji. Na przykład ustawienie „length=-16384” oznacza, że ostatnie 16 tys. B tego obszaru nie zostanie nadpisane podczas ponownego formatowania. Obsługuje to funkcje takie jak szyfrowanie partycji danych użytkownika (gdzie metadane szyfrowania są przechowywane na końcu partycji, która nie powinna być nadpisywana).

Uwaga: pola device2options są opcjonalne, co powoduje niejednoznaczność podczas analizowania. Jeśli wpis w czwartym polu wiersza zaczyna się od znaku „/”, jest to wpis device2. Jeśli wpis nie zaczyna się od znaku „/”, jest to pole options.

Animacja podczas rozruchu

Producenci urządzeń mogą dostosowywać animację wyświetlaną podczas uruchamiania urządzenia z Androidem. Aby to zrobić, utwórz plik ZIP zorganizowany i umieszczony zgodnie ze specyfikacjami w formacie animacji rozruchowej.

W przypadku urządzeń z Androidem Things możesz przesłać spakowany plik w konsoli Androida Things, aby obrazy zostały uwzględnione w wybranym produkcie.

Uwaga: te obrazy muszą być zgodne z wytycznymi dotyczącymi marki Android. Wskazówki dotyczące marki znajdziesz w sekcji Android w Partner Marketing Hub.

Interfejs odzyskiwania

Aby obsługiwać urządzenia z różnymi dostępnymi komponentami (przyciski fizyczne, diody LED, ekrany itp.), możesz dostosować interfejs odzyskiwania, aby wyświetlać stan i uzyskać dostęp do ukrytych funkcji obsługiwanych ręcznie na każdym urządzeniu.

Twoim celem jest utworzenie małej biblioteki statycznej z kilkoma obiektami C++, która będzie udostępniać funkcje specyficzne dla urządzenia. Plik bootable/recovery/default_device.cpp jest używany domyślnie i stanowi dobry punkt początkowy do skopiowania podczas pisania wersji tego pliku na urządzenie.

Uwaga: w tym miejscu może się pojawić komunikat Brak polecenia. Aby przełączać tekst, przytrzymaj przycisk zasilania i naciśnij przycisk zwiększania głośności. Jeśli urządzenie nie ma obu przycisków, naciśnij i przytrzymaj dowolny przycisk, aby przełączyć tekst.

device/yoyodyne/tardis/recovery/recovery_ui.cpp
#include <linux/input.h>

#include "common.h"
#include "device.h"
#include "screen_ui.h"

Funkcje nagłówka i elementu

Klasa Device wymaga funkcji zwracających nagłówki i elementy, które pojawiają się w ukrytym menu odzyskiwania. Nagłówki opisują sposób obsługi menu (np. elementy sterujące do zmiany lub wyboru wyróżnionego elementu).

static const char* HEADERS[] = { "Volume up/down to move highlight;",
                                 "power button to select.",
                                 "",
                                 NULL };

static const char* ITEMS[] =  {"reboot system now",
                               "apply update from ADB",
                               "wipe data/factory reset",
                               "wipe cache partition",
                               NULL };

Uwaga: długie wiersze są obcinane (nie zawijane), więc pamiętaj o szerokości ekranu urządzenia.

Dostosowywanie klucza sprawdzania

Następnie zdefiniuj implementację RecoveryUI na urządzeniu. W tym przykładzie zakłada się, że urządzenie tardis ma ekran, więc możesz dziedziczyć po wbudowanej implementacji ScreenRecoveryUI (instrukcje dotyczące urządzeń bez ekranu). Jedyną funkcją, którą można dostosować w interfejsie ScreenRecoveryUI, jest CheckKey(), która obsługuje początkowe asynchroniczne przetwarzanie kluczy:

class TardisUI : public ScreenRecoveryUI {
  public:
    virtual KeyAction CheckKey(int key) {
        if (key == KEY_HOME) {
            return TOGGLE;
        }
        return ENQUEUE;
    }
};

Stałe KEY

Stałe KEY_* są zdefiniowane w linux/input.h. CheckKey() jest wywoływana niezależnie od tego, co dzieje się w pozostałej części procesu odzyskiwania: gdy menu jest wyłączone, gdy jest włączone, podczas instalacji pakietu, podczas czyszczenia danych użytkownika itp. Może zwracać jedną z 4 stałych:

  • PRZEŁĄCZ Włączanie i wyłączanie wyświetlania menu lub dziennika tekstu
  • REBOOT Natychmiastowe ponowne uruchomienie urządzenia
  • IGNORUJ Zignoruj to naciśnięcie klawisza
  • ENQUEUE Dodaj to naciśnięcie klawisza do kolejki, aby było przetwarzane synchronicznie (tzn.przez system menu odzyskiwania, jeśli wyświetlacz jest włączony).

CheckKey() jest wywoływana za każdym razem, gdy po zdarzeniu naciśnięcia klawisza następuje zdarzenie zwolnienia tego samego klawisza. (Sekwencja zdarzeń A-down B-down B-up A-up powoduje wywołanie tylko funkcji CheckKey(B)). CheckKey() może wywołać IsKeyPressed(), aby sprawdzić, czy inne klawisze są przytrzymywane. (W powyższej sekwencji kluczowych zdarzeń, jeśli CheckKey(B) wywołało IsKeyPressed(A), zwróciłoby wartość „prawda”).

CheckKey() może zachowywać stan w swojej klasie. Może to być przydatne do wykrywania sekwencji klawiszy. Ten przykład przedstawia nieco bardziej złożoną konfigurację: wyświetlacz jest włączany i wyłączany przez przytrzymanie przycisku zasilania i naciśnięcie przycisku zwiększania głośności, a urządzenie można natychmiast ponownie uruchomić, naciskając przycisk zasilania 5 razy z rzędu (bez naciskania innych przycisków):

class TardisUI : public ScreenRecoveryUI {
  private:
    int consecutive_power_keys;

  public:
    TardisUI() : consecutive_power_keys(0) {}

    virtual KeyAction CheckKey(int key) {
        if (IsKeyPressed(KEY_POWER) && key == KEY_VOLUMEUP) {
            return TOGGLE;
        }
        if (key == KEY_POWER) {
            ++consecutive_power_keys;
            if (consecutive_power_keys >= 5) {
                return REBOOT;
            }
        } else {
            consecutive_power_keys = 0;
        }
        return ENQUEUE;
    }
};

ScreenRecoveryUI

Jeśli używasz własnych obrazów (ikona błędu, animacja instalacji, paski postępu) w przypadku ScreenRecoveryUI, możesz ustawić zmienną animation_fps, aby kontrolować szybkość animacji w klatkach na sekundę (FPS).

Uwaga: bieżący skrypt interlace-frames.py umożliwia przechowywanie informacji animation_fps w samym obrazie. W starszych wersjach Androida konieczne było samodzielne ustawienie animation_fps.

Aby ustawić zmienną animation_fps, zastąp funkcję ScreenRecoveryUI::Init() w klasie podrzędnej. Ustaw wartość, a następnie wywołaj funkcję parent Init() , aby zakończyć inicjowanie. Wartość domyślna (20 kl./s) odpowiada domyślnym obrazom przywracania. Jeśli ich używasz, nie musisz podawać funkcji Init(). Więcej informacji o obrazach znajdziesz w sekcji Obrazy interfejsu odzyskiwania.

Klasa urządzenia

Po zaimplementowaniu RecoveryUI zdefiniuj klasę urządzenia (podklasę wbudowanej klasy Device). Powinna utworzyć pojedynczą instancję klasy interfejsu i zwrócić ją z funkcji GetUI():

class TardisDevice : public Device {
  private:
    TardisUI* ui;

  public:
    TardisDevice() :
        ui(new TardisUI) {
    }

    RecoveryUI* GetUI() { return ui; }

StartRecovery

Metoda StartRecovery() jest wywoływana na początku przywracania, po zainicjowaniu interfejsu i przeanalizowaniu argumentów, ale przed podjęciem jakichkolwiek działań. Domyślna implementacja nie wykonuje żadnych działań, więc nie musisz jej podawać w klasie podrzędnej, jeśli nie masz nic do zrobienia:

   void StartRecovery() {
       // ... do something tardis-specific here, if needed ....
    }

Dostarczanie menu regeneracyjnego i zarządzanie nim

System wywołuje 2 metody, aby uzyskać listę wierszy nagłówka i listę elementów. W tej implementacji zwraca statyczne tablice zdefiniowane u góry pliku:

const char* const* GetMenuHeaders() { return HEADERS; }
const char* const* GetMenuItems() { return ITEMS; }

HandleMenuKey

Następnie podaj funkcję HandleMenuKey(), która przyjmuje naciśnięcie klawisza i bieżącą widoczność menu oraz decyduje, jakie działanie należy podjąć:

   int HandleMenuKey(int key, int visible) {
        if (visible) {
            switch (key) {
              case KEY_VOLUMEDOWN: return kHighlightDown;
              case KEY_VOLUMEUP:   return kHighlightUp;
              case KEY_POWER:      return kInvokeItem;
            }
        }
        return kNoAction;
    }

Metoda przyjmuje kod klucza (który został wcześniej przetworzony i umieszczony w kolejce przez metodę CheckKey() obiektu interfejsu) oraz bieżący stan widoczności menu lub dziennika tekstu. Wartość zwracana jest liczbą całkowitą. Jeśli wartość jest równa 0 lub większa, jest traktowana jako pozycja elementu menu, który jest wywoływany natychmiast (patrz metoda InvokeMenuItem() poniżej). W przeciwnym razie może to być jedna z tych predefiniowanych stałych:

  • kHighlightUp. Przesuwanie podświetlenia menu do poprzedniego elementu
  • kHighlightDown. Przesuwanie zaznaczenia menu do następnego elementu
  • kInvokeItem Wywołaj obecnie wyróżniony element
  • kNoAction. Nie wykonuj żadnej czynności po naciśnięciu tego klawisza

Jak wynika z argumentu visible, funkcja HandleMenuKey() jest wywoływana nawet wtedy, gdy menu nie jest widoczne. W przeciwieństwie do CheckKey() nie jest wywoływana, gdy przywracanie wykonuje jakieś działanie, np. czyszczenie danych lub instalowanie pakietu. Jest wywoływana tylko wtedy, gdy przywracanie jest w stanie bezczynności i oczekuje na dane wejściowe.

Mechanizmy trackballa

Jeśli urządzenie ma mechanizm wejściowy podobny do trackballa (generuje zdarzenia wejściowe typu EV_REL i kodu REL_Y), tryb odzyskiwania syntetyzuje naciśnięcia klawiszy KEY_UP i KEY_DOWN, gdy urządzenie wejściowe podobne do trackballa zgłasza ruch w osi Y. Wystarczy, że przypiszesz zdarzenia KEY_UP i KEY_DOWN do działań w menu. To mapowanie nie występuje w przypadku CheckKey(), więc nie możesz używać ruchów trackballa jako wyzwalaczy ponownego uruchamiania ani włączania i wyłączania wyświetlacza.

Klawisze modyfikujące

Aby sprawdzić, czy klawisze są przytrzymywane jako modyfikatory, wywołaj metodę IsKeyPressed() własnego obiektu interfejsu. Na przykład na niektórych urządzeniach naciśnięcie Alt-W w trybie odzyskiwania powodowało wyczyszczenie danych niezależnie od tego, czy menu było widoczne. Możesz to zrobić w ten sposób:

   int HandleMenuKey(int key, int visible) {
        if (ui->IsKeyPressed(KEY_LEFTALT) && key == KEY_W) {
            return 2;  // position of the "wipe data" item in the menu
        }
        ...
    }

Uwaga: jeśli wartość visible to „false”, nie ma sensu zwracać specjalnych wartości, które manipulują menu (przesuwają wyróżnienie, wywołują wyróżniony element), ponieważ użytkownik nie widzi wyróżnienia. W razie potrzeby możesz jednak przywrócić te wartości.

InvokeMenuItem

Następnie podaj InvokeMenuItem() metodę, która mapuje pozycje całkowite w tablicy elementów zwracanych przez GetMenuItems() na działania. W przypadku tablicy elementów w przykładzie dotyczącym TARDIS użyj:

   BuiltinAction InvokeMenuItem(int menu_position) {
        switch (menu_position) {
          case 0: return REBOOT;
          case 1: return APPLY_ADB_SIDELOAD;
          case 2: return WIPE_DATA;
          case 3: return WIPE_CACHE;
          default: return NO_ACTION;
        }
    }

Ta metoda może zwracać dowolny element wyliczenia BuiltinAction, aby poinformować system o wykonaniu danego działania (lub element NO_ACTION, jeśli chcesz, aby system nic nie robił). W tym miejscu możesz udostępnić dodatkowe funkcje odzyskiwania, które nie są dostępne w systemie: dodaj element do menu, wykonaj go tutaj, gdy zostanie wywołany, i zwróć NO_ACTION, aby system nie wykonywał żadnych innych działań.

BuiltinAction zawiera te wartości:

  • NO_ACTION Nic nie rób.
  • REBOOT Wyjdź z trybu odzyskiwania i uruchom urządzenie ponownie w normalny sposób.
  • APPLY_EXT, APPLY_CACHE, APPLY_ADB_SIDELOAD. Instalowanie pakietu aktualizacji z różnych miejsc. Więcej informacji znajdziesz w sekcji Instalowanie aplikacji z innych źródeł.
  • WIPE_CACHE Sformatuj tylko partycję pamięci podręcznej. Potwierdzenie nie jest wymagane, ponieważ jest to stosunkowo nieszkodliwe.
  • WIPE_DATA Ponownie sformatuj partycje userdata i cache, czyli przywróć dane fabryczne. Przed kontynuowaniem użytkownik musi potwierdzić tę czynność.

Ostatnia metoda, WipeData(), jest opcjonalna i jest wywoływana za każdym razem, gdy rozpoczyna się operacja wymazywania danych (z poziomu odzyskiwania za pomocą menu lub gdy użytkownik zdecyduje się przywrócić dane fabryczne z poziomu głównego systemu). Ta metoda jest wywoływana przed wyczyszczeniem danych użytkownika i partycji pamięci podręcznej. Jeśli urządzenie przechowuje dane użytkownika w innych miejscach niż te 2 partycje, usuń je w tym miejscu. W przypadku powodzenia należy zwrócić wartość 0, a w przypadku niepowodzenia – inną wartość, chociaż obecnie wartość zwracana jest ignorowana. Partycje danych użytkownika i pamięci podręcznej są czyszczone niezależnie od tego, czy zwracana jest wartość wskazująca powodzenie czy niepowodzenie.

   int WipeData() {
       // ... do something tardis-specific here, if needed ....
       return 0;
    }

Marka urządzenia

Na koniec dodaj na końcu pliku recovery_ui.cpp kod standardowy dla funkcji make_device(), która tworzy i zwraca instancję klasy Device:

class TardisDevice : public Device {
   // ... all the above methods ...
};

Device* make_device() {
    return new TardisDevice();
}

Po ukończeniu pliku recovery_ui.cpp skompiluj go i połącz z trybem odzyskiwania na urządzeniu. W pliku Android.mk utwórz bibliotekę statyczną, która zawiera tylko ten plik C++:

device/yoyodyne/tardis/recovery/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES += bootable/recovery
LOCAL_SRC_FILES := recovery_ui.cpp

# should match TARGET_RECOVERY_UI_LIB set in BoardConfig.mk
LOCAL_MODULE := librecovery_ui_tardis

include $(BUILD_STATIC_LIBRARY)

Następnie w konfiguracji płyty dla tego urządzenia określ bibliotekę statyczną jako wartość TARGET_RECOVERY_UI_LIB.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# device-specific extensions to the recovery UI
TARGET_RECOVERY_UI_LIB := librecovery_ui_tardis

Obrazy interfejsu odzyskiwania

Interfejs odzyskiwania składa się z obrazów. W idealnej sytuacji użytkownicy nigdy nie wchodzą w interakcję z interfejsem: podczas normalnej aktualizacji telefon uruchamia się w trybie odzyskiwania, wypełnia pasek postępu instalacji i uruchamia się ponownie w nowym systemie bez interwencji użytkownika. W przypadku problemu z aktualizacją systemu jedyną czynnością, jaką może wykonać użytkownik, jest skontaktowanie się z działem obsługi klienta.

Interfejs oparty wyłącznie na obrazach eliminuje potrzebę lokalizacji. Jednak od Androida 5.0 aktualizacja może wyświetlać ciąg tekstu (np. „Instalowanie aktualizacji systemu…”) wraz z obrazem. Więcej informacji znajdziesz w sekcji Zlokalizowany tekst odzyskiwania.

Android 5.0 i nowsze

Interfejs odzyskiwania na Androidzie 5.0 i nowszym korzysta z 2 głównych obrazów: obrazu błędu i animacji instalacji.

obraz wyświetlany podczas błędu aktualizacji OTA

Rysunek 1. icon_error.png

Obraz wyświetlany podczas instalacji OTA

Rysunek 2. icon_installing.png

Animacja instalacji jest przedstawiona jako pojedynczy obraz PNG z różnymi klatkami animacji przeplatanymi wierszami (dlatego Rysunek 2 wygląda na ściśnięty). Na przykład w przypadku animacji 200 x 200 pikseli składającej się z 7 klatek utwórz jeden obraz o wymiarach 200 x 1400 pikseli, w którym pierwsza klatka to wiersze 0, 7, 14, 21 itd., druga klatka to wiersze 1, 8, 15, 22 itd. Połączony obraz zawiera fragment tekstu, który wskazuje liczbę klatek animacji i liczbę klatek na sekundę (FPS). Narzędzie bootable/recovery/interlace-frames.py pobiera zestaw klatek wejściowych i łączy je w niezbędny obraz złożony używany przez funkcję odzyskiwania.

Obrazy domyślne są dostępne w różnych gęstościach i znajdują się w folderze bootable/recovery/res-$DENSITY/images (np. bootable/recovery/res-hdpi/images). Aby użyć obrazu statycznego podczas instalacji, wystarczy podać obraz icon_installing.png i ustawić liczbę klatek w animacji na 0 (ikona błędu nie jest animowana, zawsze jest obrazem statycznym).

Android 4.x i starsze

Interfejs odzyskiwania w Androidzie 4.x i starszych wersjach korzysta z obrazu error (pokazanego powyżej) oraz animacji installing i kilku obrazów nakładkowych:

Obraz wyświetlany podczas instalacji OTA

Rysunek 3. icon_installing.png

obraz wyświetlany jako pierwsza nakładka,

Rysunek 4. icon-installing_overlay01.png

obraz wyświetlany jako siódma nakładka

Rysunek 5. icon_installing_overlay07.png

Podczas instalacji wyświetlacz jest tworzony przez narysowanie obrazu icon_installing.png, a następnie narysowanie na nim jednej z ramek nakładki z odpowiednim przesunięciem. Czerwony kwadrat pokazuje, gdzie nakładka jest umieszczona na obrazie podstawowym:

obraz złożony z instalacji i pierwszej nakładki

Rysunek 6. Instalowanie klatki animacji 1 (icon_installing.png + icon_installing_overlay01.png)

obraz złożony z nakładki instalacji i siódmej nakładki

Rysunek 7. Instalowanie klatki animacji 7 (icon_installing.png + icon_installing_overlay07.png)

Kolejne ramki są wyświetlane przez narysowanie tylko następnego obrazu nakładki na to, co już jest widoczne. Obraz podstawowy nie jest rysowany ponownie.

Liczba klatek w animacji, żądana szybkość oraz przesunięcia nakładki w osiach X i Y względem podstawy są ustawiane przez zmienne składowe klasy ScreenRecoveryUI. Jeśli zamiast obrazów domyślnych używasz obrazów niestandardowych, zastąp metodę Init() w podklasie, aby zmienić te wartości dla obrazów niestandardowych (szczegółowe informacje znajdziesz w artykule ScreenRecoveryUI). Skryptbootable/recovery/make-overlay.py może pomóc w przekształceniu zestawu klatek obrazu na format „obraz podstawowy + obrazy nakładki” wymagany do odzyskiwania, w tym w obliczeniu niezbędnych przesunięć.

Domyślne obrazy znajdują się w bootable/recovery/res/images. Aby podczas instalacji użyć obrazu statycznego, wystarczy podać obraz icon_installing.png i ustawić liczbę klatek w animacji na 0 (ikona błędu nie jest animowana, zawsze jest obrazem statycznym).

Zlokalizowany tekst odzyskiwania

W Androidzie 5.x wyświetlany jest ciąg tekstu (np. „Instalowanie aktualizacji systemu…”) wraz z obrazem. Gdy główny system uruchamia się w trybie odzyskiwania, przekazuje bieżące ustawienia regionalne użytkownika jako opcję wiersza poleceń do trybu odzyskiwania. W przypadku każdej wyświetlanej wiadomości proces odzyskiwania obejmuje drugi obraz złożony z wcześniej wyrenderowanymi ciągami tekstowymi dla tej wiadomości w każdej wersji językowej.

Przykładowy obraz ciągów tekstowych odzyskiwania:

Obraz z tekstem dotyczącym odzyskiwania

Rysunek 8. Zlokalizowany tekst wiadomości dotyczących odzyskiwania przychodów

W SMS-ie w celu odzyskania konta mogą się pojawić te komunikaty:

  • Instaluję aktualizację systemu…
  • Błąd!
  • Wymazuję dane… (podczas czyszczenia danych lub przywracania ustawień fabrycznych)
  • Brak polecenia (gdy użytkownik ręcznie uruchamia tryb odzyskiwania)

Aplikacja na Androida w bootable/recovery/tools/recovery_l10n/ renderuje wersje językowe wiadomości i tworzy obraz złożony. Szczegółowe informacje o korzystaniu z tej aplikacji znajdziesz w komentarzach w bootable/recovery/tools/recovery_l10n/src/com/android/recovery_l10n/Main.java.

Gdy użytkownik ręcznie uruchomi tryb odzyskiwania, język może być niedostępny i nie będzie wyświetlany żaden tekst. Nie sprawiaj, aby SMS-y były kluczowe w procesie odzyskiwania.

Uwaga: ukryty interfejs, który wyświetla komunikaty dziennika i umożliwia użytkownikowi wybieranie działań z menu, jest dostępny tylko w języku angielskim.

Paski postępu

Paski postępu mogą pojawiać się pod głównym obrazem (lub animacją). Pasek postępu powstaje z połączenia dwóch obrazów wejściowych, które muszą mieć ten sam rozmiar:

pusty pasek postępu,

Rysunek 9. progress_empty.png

pełny pasek postępu,

Rysunek 10. progress_fill.png

Lewy koniec obrazu wypełnionego jest wyświetlany obok prawego końca obrazu pustego, aby utworzyć pasek postępu. Położenie granicy między dwoma obrazami zmienia się, aby wskazywać postęp. Na przykład w przypadku powyższych par obrazów wejściowych:

pasek postępu na poziomie 1%

Rysunek 11. Pasek postępu na poziomie 1%>

pasek postępu na poziomie 10%

Rysunek 12. Pasek postępu na poziomie 10%

pasek postępu na poziomie 50%

Rysunek 13. Pasek postępu w 50%

Możesz podać wersje tych obrazów przeznaczone na konkretne urządzenia, umieszczając je w folderze (w tym przykładzie) device/yoyodyne/tardis/recovery/res/images. Nazwy plików muszą być zgodne z nazwami podanymi powyżej. Jeśli w tym katalogu zostanie znaleziony plik, system kompilacji użyje go zamiast odpowiedniego obrazu domyślnego. Obsługiwane są tylko pliki PNG w formacie RGB lub RGBA o 8-bitowej głębi kolorów.

Uwaga: w Androidzie 5.x, jeśli język jest znany w trybie odzyskiwania i jest językiem zapisanym od prawej do lewej (np. arabski, hebrajski), pasek postępu wypełnia się od prawej do lewej.

Urządzenia bez ekranu

Nie wszystkie urządzenia z Androidem mają ekrany. Jeśli urządzenie nie ma ekranu lub ma tylko interfejs audio, może być konieczne bardziej zaawansowane dostosowanie interfejsu odzyskiwania. Zamiast tworzyć podklasę ScreenRecoveryUI, utwórz podklasę bezpośrednio z klasy nadrzędnej RecoveryUI.

RecoveryUI ma metody obsługi operacji interfejsu niższego poziomu, takich jak „przełączanie wyświetlacza”, „aktualizowanie paska postępu”, „wyświetlanie menu”, „zmiana wyboru w menu” itp. Możesz je zastąpić, aby zapewnić odpowiedni interfejs dla swojego urządzenia. Być może urządzenie ma diody LED, które mogą świecić w różnych kolorach lub migać w różnych wzorach, aby wskazywać stan, albo może odtwarzać dźwięk. (Możesz w ogóle nie obsługiwać menu ani trybu „wyświetlanie tekstu”. Możesz uniemożliwić dostęp do nich za pomocą implementacji CheckKey()HandleMenuKey(), które nigdy nie włączają wyświetlania ani nie wybierają elementu menu. W takim przypadku wiele metod RecoveryUI, które musisz udostępnić, może być pustymi elementami zastępczymi).

W deklaracji RecoveryUI znajdziesz bootable/recovery/ui.hmetody, które musisz obsługiwać. RecoveryUI jest klasą abstrakcyjną – niektóre metody są czysto wirtualne i muszą być dostarczane przez podklasy – ale zawiera kod do przetwarzania danych wejściowych kluczy. Możesz też to zmienić, jeśli urządzenie nie ma klawiszy lub chcesz je przetwarzać w inny sposób.

Aktualizator

Podczas instalacji pakietu aktualizacji możesz użyć kodu specyficznego dla urządzenia, udostępniając własne funkcje rozszerzeń, które można wywołać ze skryptu aktualizatora. Oto przykładowa funkcja urządzenia tardis:

device/yoyodyne/tardis/recovery/recovery_updater.c
#include <stdlib.h>
#include <string.h>

#include "edify/expr.h"

Każda funkcja rozszerzenia ma ten sam podpis. Argumentami są nazwa, pod którą wywołano funkcję, State* plik cookie, liczba argumentów przychodzących i tablica wskaźników Expr* reprezentujących argumenty. Wartością zwracaną jest nowo przydzielony obiekt Value*.

Value* ReprogramTardisFn(const char* name, State* state, int argc, Expr* argv[]) {
    if (argc != 2) {
        return ErrorAbort(state, "%s() expects 2 args, got %d", name, argc);
    }

Argumenty nie są oceniane w momencie wywołania funkcji – logika funkcji określa, które z nich zostaną ocenione i ile razy. Dzięki temu możesz używać funkcji rozszerzeń do implementowania własnych struktur sterujących. Call Evaluate(), aby obliczyć wartość argumentu Expr* i zwrócić wartość Value*. Jeśli funkcja Evaluate() zwraca wartość NULL, zwolnij wszystkie używane zasoby i natychmiast zwróć wartość NULL (spowoduje to propagowanie przerwań w górę stosu edify). W przeciwnym razie stajesz się właścicielem zwróconej wartości i ponosisz odpowiedzialność za ostateczne wywołanie funkcji FreeValue().

Załóżmy, że funkcja wymaga 2 argumentów: klucza w postaci ciągu znaków i obrazu w postaci obiektu blob. Możesz przeczytać argumenty takie jak:

   Value* key = EvaluateValue(state, argv[0]);
    if (key == NULL) {
        return NULL;
    }
    if (key->type != VAL_STRING) {
        ErrorAbort(state, "first arg to %s() must be string", name);
        FreeValue(key);
        return NULL;
    }
    Value* image = EvaluateValue(state, argv[1]);
    if (image == NULL) {
        FreeValue(key);    // must always free Value objects
        return NULL;
    }
    if (image->type != VAL_BLOB) {
        ErrorAbort(state, "second arg to %s() must be blob", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

Sprawdzanie wartości NULL i zwalnianie wcześniej obliczonych argumentów może być uciążliwe w przypadku wielu argumentów. Może to ułatwić funkcja ReadValueArgs(). Zamiast kodu powyżej możesz napisać:

   Value* key;
    Value* image;
    if (ReadValueArgs(state, argv, 2, &key, &image) != 0) {
        return NULL;     // ReadValueArgs() will have set the error message
    }
    if (key->type != VAL_STRING || image->type != VAL_BLOB) {
        ErrorAbort(state, "arguments to %s() have wrong type", name);
        FreeValue(key);
        FreeValue(image)
        return NULL;
    }

ReadValueArgs() nie sprawdza typów, więc musisz to zrobić tutaj. Wygodniej jest to zrobić za pomocą jednej instrukcji if, ale kosztem nieco mniej szczegółowego komunikatu o błędzie w przypadku niepowodzenia. Funkcja ReadValueArgs() obsługuje jednak ocenę każdego argumentu i zwalnianie wszystkich wcześniej ocenionych argumentów (a także ustawianie przydatnego komunikatu o błędzie), jeśli któraś z ocen się nie powiedzie. Możesz użyć funkcji ReadValueVarArgs() do oceny zmiennej liczby argumentów (zwraca ona tablicę Value*).

Po obliczeniu argumentów wykonaj działanie funkcji:

   // key->data is a NUL-terminated string
    // image->data and image->size define a block of binary data
    //
    // ... some device-specific magic here to
    // reprogram the tardis using those two values ...

Zwracana wartość musi być obiektem Value*. Właścicielem tego obiektu stanie się wywołujący. Wywołujący przejmuje własność wszystkich danych, na które wskazuje ten wskaźnik Value*, a w szczególności elementu danych.

W tym przypadku chcesz zwrócić wartość prawda lub fałsz, aby wskazać, czy operacja się powiodła. Pamiętaj, że zgodnie z konwencją pusty ciąg znaków ma wartość false, a wszystkie inne ciągi znaków mają wartość true. Musisz przydzielić pamięć dla obiektu Value za pomocą funkcji malloc, aby zwrócić kopię stałego ciągu znaków przydzieloną za pomocą funkcji malloc, ponieważ wywołujący free() obie te wartości. Nie zapomnij wywołać funkcji FreeValue() na obiektach, które uzyskasz w wyniku obliczenia wartości argumentów.

   FreeValue(key);
    FreeValue(image);

    Value* result = malloc(sizeof(Value));
    result->type = VAL_STRING;
    result->data = strdup(successful ? "t" : "");
    result->size = strlen(result->data);
    return result;
}

Funkcja pomocnicza StringValue() opakowuje ciąg znaków w nowy obiekt Value. Użyj tego, aby zapisać powyższy kod w bardziej zwięzły sposób:

   FreeValue(key);
    FreeValue(image);

    return StringValue(strdup(successful ? "t" : ""));
}

Aby podłączyć funkcje do interpretera edify, podaj funkcję Register_foo, gdzie foo to nazwa biblioteki statycznej zawierającej ten kod. Wywołaj funkcję RegisterFunction(), aby zarejestrować każdą funkcję rozszerzenia. Zgodnie z konwencją nazwy funkcji specyficznych dla urządzenia powinny mieć prefiks device.whatever, aby uniknąć konfliktów z przyszłymi funkcjami wbudowanymi.

void Register_librecovery_updater_tardis() {
    RegisterFunction("tardis.reprogram", ReprogramTardisFn);
}

Teraz możesz skonfigurować plik makefile tak, aby tworzył bibliotekę statyczną z Twoim kodem. (Jest to ten sam plik makefile, który został użyty do dostosowania interfejsu odzyskiwania w poprzedniej sekcji. Na urządzeniu mogą być zdefiniowane obie biblioteki statyczne).

device/yoyodyne/tardis/recovery/Android.mk
include $(CLEAR_VARS)
LOCAL_SRC_FILES := recovery_updater.c
LOCAL_C_INCLUDES += bootable/recovery

Nazwa biblioteki statycznej musi być zgodna z nazwą funkcji Register_libname, którą zawiera.

LOCAL_MODULE := librecovery_updater_tardis
include $(BUILD_STATIC_LIBRARY)

Na koniec skonfiguruj kompilację odzyskiwania, aby pobrać bibliotekę. Dodaj bibliotekę do zmiennej TARGET_RECOVERY_UPDATER_LIBS (może ona zawierać wiele bibliotek, które zostaną zarejestrowane). Jeśli Twój kod zależy od innych bibliotek statycznych, które nie są rozszerzeniami edify (np. nie mają funkcji Register_libname), możesz je wymienić w parametrze TARGET_RECOVERY_UPDATER_EXTRA_LIBS, aby połączyć je z programem aktualizującym bez wywoływania ich (nieistniejącej) funkcji rejestracji. Jeśli na przykład kod na urządzeniu ma używać biblioteki zlib do dekompresji danych, musisz ją tu uwzględnić.

device/yoyodyne/tardis/BoardConfig.mk
 [...]

# add device-specific extensions to the updater binary
TARGET_RECOVERY_UPDATER_LIBS += librecovery_updater_tardis
TARGET_RECOVERY_UPDATER_EXTRA_LIBS +=

Skrypty aktualizatora w pakiecie OTA mogą teraz wywoływać Twoją funkcję tak jak każdą inną. Aby przeprogramować urządzenie tardis, skrypt aktualizacji może zawierać:tardis.reprogram("the-key", package_extract_file("tardis-image.dat")) . Używa ona wersji wbudowanej funkcji package_extract_file() z 1 argumentem, która zwraca zawartość pliku wyodrębnionego z pakietu aktualizacji jako obiekt blob, aby utworzyć drugi argument nowej funkcji rozszerzenia.

Generowanie pakietu OTA

Ostatnim elementem jest udostępnienie narzędziom do generowania pakietów OTA informacji o danych specyficznych dla urządzenia i generowanie skryptów aktualizatora, które zawierają wywołania funkcji rozszerzeń.

Najpierw spraw, aby system kompilacji rozpoznawał konkretny blok danych urządzenia. Załóżmy, że plik danych znajduje się w device/yoyodyne/tardis/tardis.dat. W takim przypadku zadeklaruj w pliku AndroidBoard.mk urządzenia:

device/yoyodyne/tardis/AndroidBoard.mk
  [...]

$(call add-radio-file,tardis.dat)

Możesz też umieścić go w pliku Android.mk, ale wtedy musi być chroniony przez sprawdzenie urządzenia, ponieważ wszystkie pliki Android.mk w drzewie są ładowane niezależnie od tego, jakie urządzenie jest budowane. (Jeśli drzewo zawiera wiele urządzeń, plik tardis.dat powinien być dodawany tylko podczas tworzenia urządzenia tardis).

device/yoyodyne/tardis/Android.mk
  [...]

# an alternative to specifying it in AndroidBoard.mk
ifeq (($TARGET_DEVICE),tardis)
  $(call add-radio-file,tardis.dat)
endif

Z przyczyn historycznych nazywa się je plikami radiowymi, ale mogą nie mieć nic wspólnego z radiem urządzenia (jeśli jest dostępne). Są to po prostu nieprzejrzyste bloki danych, które system kompilacji kopiuje do pliku ZIP target-files używanego przez narzędzia do generowania aktualizacji OTA. Podczas kompilacji plik tardis.dat jest zapisywany w pliku target-files.zip jako RADIO/tardis.dat. Możesz wywołać tę funkcjęadd-radio-file wiele razy, aby dodać dowolną liczbę plików.

Moduł Pythona

Aby rozszerzyć narzędzia do wydawania, napisz moduł Pythona (musi mieć nazwę releasetools.py), do którego narzędzia mogą się odwoływać, jeśli jest obecny. Przykład:

device/yoyodyne/tardis/releasetools.py
import common

def FullOTA_InstallEnd(info):
  # copy the data into the package.
  tardis_dat = info.input_zip.read("RADIO/tardis.dat")
  common.ZipWriteStr(info.output_zip, "tardis.dat", tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Osobna funkcja obsługuje generowanie przyrostowego pakietu OTA. Załóżmy, że w tym przykładzie musisz przeprogramować TARDIS tylko wtedy, gdy plik tardis.dat zmienił się między dwiema kompilacjami.

def IncrementalOTA_InstallEnd(info):
  # copy the data into the package.
  source_tardis_dat = info.source_zip.read("RADIO/tardis.dat")
  target_tardis_dat = info.target_zip.read("RADIO/tardis.dat")

  if source_tardis_dat == target_tardis_dat:
      # tardis.dat is unchanged from previous build; no
      # need to reprogram it
      return

  # include the new tardis.dat in the OTA package
  common.ZipWriteStr(info.output_zip, "tardis.dat", target_tardis_dat)

  # emit the script code to install this data on the device
  info.script.AppendExtra(
      """tardis.reprogram("the-key", package_extract_file("tardis.dat"));""")

Funkcje modułu

W module możesz udostępnić te funkcje (wdrażaj tylko te, których potrzebujesz).

FullOTA_Assertions()
Wywoływana na początku generowania pełnej aktualizacji OTA. To dobre miejsce na emitowanie asercji dotyczących bieżącego stanu urządzenia. Nie wysyłaj poleceń skryptu, które wprowadzają zmiany na urządzeniu.
FullOTA_InstallBegin()
Wywoływana po przejściu wszystkich asercji dotyczących stanu urządzenia, ale przed wprowadzeniem jakichkolwiek zmian. Możesz wysyłać polecenia dotyczące aktualizacji specyficznych dla urządzenia, które muszą zostać wykonane, zanim na urządzeniu zostaną wprowadzone jakiekolwiek inne zmiany.
FullOTA_InstallEnd()
Wywoływana na końcu generowania skryptu, po wyemitowaniu poleceń skryptu do aktualizacji partycji rozruchowej i systemowej. Możesz też wysyłać dodatkowe polecenia dotyczące aktualizacji na konkretnych urządzeniach.
IncrementalOTA_Assertions()
Podobne do FullOTA_Assertions(), ale wywoływane podczas generowania pakietu aktualizacji przyrostowej.
IncrementalOTA_VerifyBegin()
Wywoływana po przejściu wszystkich asercji dotyczących stanu urządzenia, ale przed wprowadzeniem jakichkolwiek zmian. Możesz wysyłać polecenia dotyczące aktualizacji specyficznych dla urządzenia, które muszą zostać wykonane, zanim na urządzeniu zostaną wprowadzone jakiekolwiek inne zmiany.
IncrementalOTA_VerifyEnd()
Wywoływana na końcu fazy weryfikacji, gdy skrypt potwierdzi, że pliki, których będzie używać, mają oczekiwaną zawartość początkową. Na tym etapie nic na urządzeniu nie zostało zmienione. Możesz też wygenerować kod do dodatkowych weryfikacji na konkretnych urządzeniach.
IncrementalOTA_InstallBegin()
Wywoływana po sprawdzeniu, czy pliki do poprawienia mają oczekiwany stan przed, ale przed wprowadzeniem jakichkolwiek zmian. Możesz wysyłać polecenia dotyczące aktualizacji specyficznych dla urządzenia, które muszą zostać wykonane, zanim na urządzeniu zostaną wprowadzone jakiekolwiek inne zmiany.
IncrementalOTA_InstallEnd()
Podobnie jak w przypadku pełnego pakietu OTA, to polecenie jest wywoływane na końcu generowania skryptu, po wyemitowaniu poleceń skryptu do aktualizacji partycji rozruchowej i systemowej. Możesz też wysyłać dodatkowe polecenia dotyczące aktualizacji na konkretnych urządzeniach.

Uwaga: jeśli urządzenie straci zasilanie, instalacja OTA może rozpocząć się od początku. Przygotuj się na obsługę urządzeń, na których te polecenia zostały już w całości lub częściowo uruchomione.

Przekazywanie funkcji do obiektów informacji

Przekaż funkcje do jednego obiektu informacji, który zawiera różne przydatne elementy:

  • info.input_zip (Tylko pełne aktualizacje OTA) Obiekt zipfile.ZipFile dla wejściowego pliku ZIP target-files.
  • info.source_zip (Tylko przyrostowe aktualizacje OTA) Obiekt zipfile.ZipFile dla źródłowego pliku ZIP z plikami docelowymi (kompilacja już zainstalowana na urządzeniu w momencie instalacji pakietu przyrostowego).
  • info.target_zip (Tylko przyrostowe aktualizacje OTA) Obiekt zipfile.ZipFile dla docelowego pliku ZIP target-files (kompilacja, którą pakiet przyrostowy umieszcza na urządzeniu).
  • info.output_zip Pakiet jest tworzony; obiekt zipfile.ZipFile został otwarty do zapisu. Aby dodać plik do pakietu, użyj funkcji common.ZipWriteStr(info.output_zip, filename, data).
  • info.script. Obiekt skryptu, do którego możesz dołączać polecenia. Wywołaj info.script.AppendExtra(script_text), aby wygenerować tekst w skrypcie. Upewnij się, że tekst wyjściowy kończy się średnikiem, aby nie kolidował z poleceniami emitowanymi później.

Szczegółowe informacje o obiekcie info znajdziesz w dokumentacji Python Software Foundation dotyczącej archiwów ZIP.

Określanie lokalizacji modułu

W pliku BoardConfig.mk określ lokalizację skryptu releasetools.py na urządzeniu:

device/yoyodyne/tardis/BoardConfig.mk
 [...]

TARGET_RELEASETOOLS_EXTENSIONS := device/yoyodyne/tardis

Jeśli zmienna TARGET_RELEASETOOLS_EXTENSIONS nie jest ustawiona, domyślnie używany jest katalog $(TARGET_DEVICE_DIR)/../common (w tym przykładzie device/yoyodyne/common ). Najlepiej jest wyraźnie określić lokalizację skryptu releasetools.py. Podczas tworzenia urządzenia TARDIS skrypt releasetools.py jest dołączany do pliku target-files.zip (META/releasetools.py ).

Gdy uruchamiasz narzędzia do publikowania (img_from_target_files lub ota_from_target_files), skrypt releasetools.py w pliku target-files .zip, jeśli jest obecny, ma pierwszeństwo przed skryptem z drzewa źródłowego Androida. Możesz też wyraźnie określić ścieżkę do rozszerzeń specyficznych dla urządzenia za pomocą opcji -s (lub --device_specific), która ma najwyższy priorytet. Umożliwia to poprawianie błędów i wprowadzanie zmian w rozszerzeniach releasetools oraz stosowanie tych zmian do starych plików docelowych.

Teraz, gdy uruchomisz ota_from_target_files, automatycznie pobierze on moduł dostosowany do urządzenia z pliku target_files .zip i użyje go podczas generowania pakietów OTA:

./build/make/tools/releasetools/ota_from_target_files \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

Możesz też określić rozszerzenia specyficzne dla urządzenia, gdy uruchamiasz polecenie ota_from_target_files.

./build/make/tools/releasetools/ota_from_target_files \
    -s device/yoyodyne/tardis \
    -i PREVIOUS-tardis-target_files.zip \
    dist_output/tardis-target_files.zip \
    incremental_ota_update.zip

Uwaga: pełną listę opcji znajdziesz w ota_from_target_files komentarzach w  build/make/tools/releasetools/ota_from_target_files.

Mechanizm instalowania z nieoficjalnych źródeł

Tryb odzyskiwania ma mechanizm wczytywania, który umożliwia ręczne instalowanie pakietu aktualizacji bez pobierania go bezprzewodowo przez główny system. Instalowanie aplikacji z nieoficjalnych źródeł jest przydatne do debugowania lub wprowadzania zmian na urządzeniach, na których nie można uruchomić głównego systemu.

W przeszłości instalacja z zewnątrz odbywała się przez wczytywanie pakietów z karty SD urządzenia. W przypadku urządzenia, które się nie uruchamia, pakiet można umieścić na karcie SD za pomocą innego komputera, a następnie włożyć kartę SD do urządzenia. W przypadku urządzeń z Androidem bez wymiennej pamięci zewnętrznej przywracanie obsługuje 2 dodatkowe mechanizmy instalowania z innego urządzenia: wczytywanie pakietów z partycji pamięci podręcznej i wczytywanie ich przez USB za pomocą ADB.

Aby wywołać każdy mechanizm wczytywania z boku, metoda Device::InvokeMenuItem() na urządzeniu może zwracać te wartości BuiltinAction:

  • APPLY_EXT Zainstaluj pakiet aktualizacji z pamięci zewnętrznej (katalog /sdcard). Plik recovery.fstab musi definiować punkt podłączania /sdcard . Nie można go używać na urządzeniach, które emulują kartę SD za pomocą linku symbolicznego do /data (lub podobnego mechanizmu). /data zwykle nie jest dostępny do odzyskania, ponieważ może być zaszyfrowany. W interfejsie odzyskiwania wyświetla się menu plików ZIP w usłudze /sdcard, z którego użytkownik może wybrać jeden plik.
  • APPLY_CACHE Podobnie jak w przypadku wczytywania pakietu z /sdcard, z tą różnicą, że zamiast tego używany jest katalog /cache (który jest zawsze dostępny do odzyskiwania). W normalnym systemie katalog /cache może być zapisywany tylko przez użytkowników z uprawnieniami, a jeśli urządzenie nie może się uruchomić, nie można w ogóle zapisywać katalogu /cache (co sprawia, że ten mechanizm ma ograniczone zastosowanie).
  • APPLY_ADB_SIDELOAD. Umożliwia użytkownikowi wysyłanie pakietu na urządzenie za pomocą kabla USB i narzędzia deweloperskiego adb. Gdy ten mechanizm zostanie wywołany, przywracanie uruchomi własną miniaturową wersję demona adbd, aby umożliwić komunikację z nim za pomocą adb na podłączonym komputerze hosta. Ta wersja mini obsługuje tylko jedno polecenie: adb sideload filename. Nazwany plik jest wysyłany z komputera hosta na urządzenie, które następnie go weryfikuje i instaluje tak, jakby znajdował się w pamięci lokalnej.

Kilka uwag:

  • Obsługiwany jest tylko transport USB.
  • Jeśli tryb odzyskiwania działa normalnie (zwykle w przypadku kompilacji userdebug i eng), zostanie on wyłączony, gdy urządzenie będzie w trybie adb sideload, i ponownie uruchomiony po zakończeniu odbierania pakietu przez adb sideload. W trybie przesyłania bocznego adb działają tylko polecenia adb inne niż sideload ( logcat, reboot, push, pull, shell itp. nie działają).
  • Nie możesz wyjść z trybu adb sideload na urządzeniu. Aby przerwać instalację, możesz wysłać /dev/null (lub dowolny inny nieprawidłowy pakiet). Urządzenie nie będzie w stanie go zweryfikować i przerwie proces instalacji. Metoda CheckKey() implementacji RecoveryUI będzie nadal wywoływana w przypadku naciśnięć klawiszy, więc możesz podać sekwencję klawiszy, która spowoduje ponowne uruchomienie urządzenia i będzie działać w trybie adb sideload.