3.3 KiB · text History b868907
defmodule GitGud.Packages.Fdroid.ApkIconTest do
  @moduledoc """
  Pulling the launcher icon and the app's name out of an APK.

  The case that motivated all of this: a release build. R8 rewrites
  `res/mipmap-xxxhdpi/ic_launcher.png` to `res/hi.png`, so the only
  route from `android:icon` to an image runs through `resources.arsc`.
  The convention that came before it still has to work for a debug
  build, which is the other half of these tests.
  """
  use ExUnit.Case, async: true

  alias GitGud.ApkFixtures
  alias GitGud.Packages.Formats.Fdroid.Apk
  alias GitGud.Packages.Formats.Fdroid.Manifest

  @icon_ref 0x7F010000
  @label_ref 0x7F020000
  @png <<0x89, "PNG", 0x0D, 0x0A, 0x1A, 0x0A>> <> :binary.copy(<<7>>, 64)

  defp shrunk, do: ApkFixtures.resource_shrunk_apk(@icon_ref, @label_ref, @png)

  describe "a resource-shrunk APK" do
    test "resolves the icon through the resource table" do
      assert {:ok, @png, "png"} = Apk.icon(shrunk())
    end

    test "never returns the adaptive-icon XML" do
      # `res/ad.xml` is the highest-priority configuration in the table
      # (anydpi) and the one thing an index cannot serve.
      assert {:ok, bytes, _ext} = Apk.icon(shrunk())

      refute bytes == "<adaptive-icon/>, which is not servable"
    end

    test "reads the label the manifest points at" do
      assert {:ok, manifest} = Manifest.parse(ApkFixtures.manifest(@icon_ref, @label_ref))
      assert manifest.icon_ref == @icon_ref
      assert manifest.label == @label_ref
    end

    test "has no icon when the table doesn't define the id the manifest names" do
      # A manifest pointing at 0x7F990000 over a table that defines
      # 0x7F010000: nothing to resolve, and no conventional path to fall
      # back to either.
      {:ok, {_name, apk}} =
        :zip.create(
          ~c"mismatched.apk",
          [
            {~c"AndroidManifest.xml", ApkFixtures.manifest(0x7F990000, @label_ref)},
            {~c"resources.arsc", ApkFixtures.resources_arsc(@icon_ref, @label_ref)},
            {~c"res/hi.png", @png}
          ],
          [:memory]
        )

      assert Apk.icon(apk) == :none
    end
  end

  describe "an APK whose resource table can't be read" do
    test "falls back to the conventional path" do
      {:ok, {_name, apk}} =
        :zip.create(
          ~c"debug.apk",
          [
            {~c"AndroidManifest.xml", ApkFixtures.manifest(@icon_ref, @label_ref)},
            {~c"resources.arsc", "not a resource table"},
            {~c"res/mipmap-xxxhdpi-v4/ic_launcher.png", @png},
            {~c"res/mipmap-mdpi-v4/ic_launcher.png", "low density, should lose"}
          ],
          [:memory]
        )

      assert {:ok, @png, "png"} = Apk.icon(apk)
    end

    test "reports no icon rather than a wrong one" do
      {:ok, {_name, apk}} =
        :zip.create(
          ~c"bare.apk",
          [{~c"AndroidManifest.xml", ApkFixtures.manifest(@icon_ref, @label_ref)}],
          [:memory]
        )

      assert Apk.icon(apk) == :none
    end

    test "skips a candidate that isn't an image" do
      {:ok, {_name, apk}} =
        :zip.create(
          ~c"lying.apk",
          [
            {~c"AndroidManifest.xml", ApkFixtures.manifest(@icon_ref, @label_ref)},
            {~c"res/mipmap-xxxhdpi-v4/ic_launcher.png", "text pretending to be a PNG"}
          ],
          [:memory]
        )

      assert Apk.icon(apk) == :none
    end
  end
end