Dynamically load device-specific recovery UI lib.

We used to statically link the device-specific recovery UI extension
(`TARGET_RECOVERY_UI_LIB`) into `recovery`. Such a logic can't be easily
migrated to Soong, as modules specified by `TARGET_RECOVERY_UI_LIB` may
not be built with Soong.

Instead of porting all the device-specific codes over, this CL builds
and installs the UI lib as a shared library with Android.mk. `recovery`
dlopen(3)'s and dlsym(3)'s `make_device` to invoke the device-specific
UI lib on start.

Note that in order to make dlopen(3) actually working, we have to switch
`recovery` to be dynamically linked (we will make the move later
anyway).

Bug: 110380063
Test: Build and boot into marlin recovery image. Check that
      device-specific recovery UI is successfully loaded.
Change-Id: Ia9861c7559a95f3f50676534540c0cb87cae4574
diff --git a/Android.mk b/Android.mk
index 0540bdb..9542080 100644
--- a/Android.mk
+++ b/Android.mk
@@ -28,6 +28,64 @@
     -Werror \
     -DRECOVERY_API_VERSION=$(RECOVERY_API_VERSION)
 
+# librecovery_ui_ext (shared library)
+# ===================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := librecovery_ui_ext
+
+# LOCAL_MODULE_PATH for shared libraries is unsupported in multiarch builds.
+LOCAL_MULTILIB := first
+
+ifeq ($(TARGET_IS_64_BIT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64
+else
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib
+endif
+
+LOCAL_WHOLE_STATIC_LIBRARIES := \
+    $(TARGET_RECOVERY_UI_LIB)
+
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    liblog \
+    librecovery_ui
+
+include $(BUILD_SHARED_LIBRARY)
+
+# librecovery_ui (shared library)
+# ===============================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+    device.cpp \
+    screen_ui.cpp \
+    ui.cpp \
+    vr_ui.cpp \
+    wear_ui.cpp
+
+LOCAL_MODULE := librecovery_ui
+
+LOCAL_CFLAGS := $(recovery_common_cflags)
+
+LOCAL_MULTILIB := first
+
+ifeq ($(TARGET_IS_64_BIT),true)
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib64
+else
+LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/lib
+endif
+
+LOCAL_STATIC_LIBRARIES := \
+    libminui \
+    libotautil \
+
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libpng \
+    libz \
+
+include $(BUILD_SHARED_LIBRARY)
+
 # librecovery_ui (static library)
 # ===============================
 include $(CLEAR_VARS)
@@ -40,21 +98,23 @@
 
 LOCAL_MODULE := librecovery_ui
 
+LOCAL_CFLAGS := $(recovery_common_cflags)
+
 LOCAL_STATIC_LIBRARIES := \
     libminui \
     libotautil \
-    libbase
 
-LOCAL_CFLAGS := $(recovery_common_cflags)
+LOCAL_SHARED_LIBRARIES := \
+    libbase \
+    libpng \
+    libz \
 
 include $(BUILD_STATIC_LIBRARY)
 
 librecovery_static_libraries := \
-    $(TARGET_RECOVERY_UI_LIB) \
     libbootloader_message \
     libfusesideload \
     libminadbd \
-    librecovery_ui \
     libminui \
     libverifier \
     libotautil \
@@ -112,8 +172,6 @@
 
 LOCAL_MODULE := recovery
 
-LOCAL_FORCE_STATIC_EXECUTABLE := true
-
 LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/system/bin
 
 # Cannot link with LLD: undefined symbol: UsbNoPermissionsLongHelpText
@@ -124,8 +182,12 @@
 
 LOCAL_STATIC_LIBRARIES := \
     librecovery \
+    librecovery_ui_default \
     $(librecovery_static_libraries)
 
+LOCAL_SHARED_LIBRARIES := \
+    librecovery_ui \
+
 LOCAL_HAL_STATIC_LIBRARIES := libhealthd
 
 LOCAL_REQUIRED_MODULES := \
@@ -154,6 +216,17 @@
     recovery-refresh
 endif
 
+LOCAL_REQUIRED_MODULES += \
+    librecovery_ui_ext
+
+# TODO(b/110380063): Explicitly install the following shared libraries to recovery, until `recovery`
+# module is built with Soong (with `recovery: true` flag).
+LOCAL_REQUIRED_MODULES += \
+    libbase.recovery \
+    liblog.recovery \
+    libpng.recovery \
+    libz.recovery \
+
 include $(BUILD_EXECUTABLE)
 
 include \
diff --git a/device.h b/device.h
index cbecc43..a6ad627 100644
--- a/device.h
+++ b/device.h
@@ -119,8 +119,12 @@
   std::unique_ptr<RecoveryUI> ui_;
 };
 
+// Disable name mangling, as this function will be loaded via dlsym(3).
+extern "C" {
+
 // The device-specific library must define this function (or the default one will be used, if there
 // is no device-specific library). It returns the Device object that recovery should use.
 Device* make_device();
+}
 
 #endif  // _DEVICE_H
diff --git a/recovery_main.cpp b/recovery_main.cpp
index c79d7d8..9a9890d 100644
--- a/recovery_main.cpp
+++ b/recovery_main.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <dlfcn.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
@@ -329,7 +330,32 @@
 
   printf("locale is [%s]\n", locale.c_str());
 
-  Device* device = make_device();
+  static constexpr const char* kDefaultLibRecoveryUIExt = "librecovery_ui_ext.so";
+  // Intentionally not calling dlclose(3) to avoid potential gotchas (e.g. `make_device` may have
+  // handed out pointers to code or static [or thread-local] data and doesn't collect them all back
+  // in on dlclose).
+  void* librecovery_ui_ext = dlopen(kDefaultLibRecoveryUIExt, RTLD_NOW);
+
+  using MakeDeviceType = decltype(&make_device);
+  MakeDeviceType make_device_func = nullptr;
+  if (librecovery_ui_ext == nullptr) {
+    printf("Failed to dlopen %s: %s\n", kDefaultLibRecoveryUIExt, dlerror());
+  } else {
+    reinterpret_cast<void*&>(make_device_func) = dlsym(librecovery_ui_ext, "make_device");
+    if (make_device_func == nullptr) {
+      printf("Failed to dlsym make_device: %s\n", dlerror());
+    }
+  }
+
+  Device* device;
+  if (make_device_func == nullptr) {
+    printf("Falling back to the default make_device() instead\n");
+    device = make_device();
+  } else {
+    printf("Loading make_device from %s\n", kDefaultLibRecoveryUIExt);
+    device = (*make_device_func)();
+  }
+
   if (android::base::GetBoolProperty("ro.boot.quiescent", false)) {
     printf("Quiescent recovery mode.\n");
     device->ResetUI(new StubRecoveryUI());
diff --git a/tests/Android.mk b/tests/Android.mk
index e68e77e..58ef3a2 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -155,10 +155,10 @@
 
 librecovery_static_libraries := \
     librecovery \
-    $(TARGET_RECOVERY_UI_LIB) \
     libbootloader_message \
     libfusesideload \
     libminadbd \
+    librecovery_ui_default \
     librecovery_ui \
     libminui \
     libverifier \