diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java index 150d944..6ab71a9 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java @@ -26,6 +26,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -106,6 +111,36 @@ private ZipFileInZipEntry findAPKWithLibrary(final Context context, return null; } + // Loop over all APK's again in order to detect which ABI's are actually supported. + // This second loop is more expensive than trying to find a specific ABI, so it should + // only be ran when no matching libraries are found. This should keep the overhead of + // the happy path to a minimum. + private String[] getSupportedABIs(Context context, String mappedLibraryName) { + String p = "lib" + File.separatorChar + "([^\\" + File.separatorChar + "]*)" + File.separatorChar + mappedLibraryName; + Pattern pattern = Pattern.compile(p); + ZipFile zipFile; + Set supportedABIs = new HashSet(); + for (String sourceDir : sourceDirectories(context)) { + try { + zipFile = new ZipFile(new File(sourceDir), ZipFile.OPEN_READ); + } catch (IOException ignored) { + continue; + } + + Enumeration elements = zipFile.entries(); + while (elements.hasMoreElements()) { + ZipEntry el = elements.nextElement(); + Matcher match = pattern.matcher(el.getName()); + if (match.matches()) { + supportedABIs.add(match.group(1)); + } + } + } + + String[] result = new String[supportedABIs.size()]; + return supportedABIs.toArray(result); + } + /** * Attempts to unpack the given library to the given destination. Implements retry logic for * IO operations to ensure they succeed. @@ -124,8 +159,18 @@ public void installLibrary(final Context context, try { found = findAPKWithLibrary(context, abis, mappedLibraryName, instance); if (found == null) { - // Does not exist in any APK - throw new MissingLibraryException(mappedLibraryName); + // Does not exist in any APK. Report exactly what ReLinker is looking for and + // what is actually supported by the APK. + String[] supportedABIs; + try { + supportedABIs = getSupportedABIs(context, mappedLibraryName); + } catch (Exception e) { + // Should never happen as this indicates a bug in ReLinker code, but just to be safe. + // User code should only ever crash with a MissingLibraryException if getting this far. + supportedABIs = new String[1]; + supportedABIs[0] = e.toString(); + } + throw new MissingLibraryException(mappedLibraryName, abis, supportedABIs); } int tries = 0; diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java index e517882..d82fdfd 100755 --- a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java @@ -15,8 +15,12 @@ */ package com.getkeepsafe.relinker; +import java.util.Arrays; + public class MissingLibraryException extends RuntimeException { - public MissingLibraryException(final String library) { - super(library); + public MissingLibraryException(final String library, final String[] wantedABIs, final String[] supportedABIs) { + super("Could not find '" + library + "'. " + + "Looked for: " + Arrays.toString(wantedABIs) + ", " + + "but only found: " + Arrays.toString(supportedABIs) + "."); } -} \ No newline at end of file +} diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java index ab5fa43..0869b41 100644 --- a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java +++ b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java @@ -30,6 +30,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,6 +61,25 @@ public void installsCorrectly() throws IOException { assertThat(fileToString(destination), is("works!")); } + @Test + public void throwsMissingLibraryExceptionWhenABIIsMissing() throws IOException { + final Context context = mock(Context.class); + final ApplicationInfo appInfo = mock(ApplicationInfo.class); + final ReLinkerInstance instance = mock(ReLinkerInstance.class); + final ApkLibraryInstaller installer = new ApkLibraryInstaller(); + final File destination = tempFolder.newFile("test"); + final String[] abis = new String[] {"armeabi-v7a"}; // For unit test running on a developer machine this is normally x86 + + when(context.getApplicationInfo()).thenReturn(appInfo); + appInfo.sourceDir = getClass().getResource("/fake.apk").getFile(); + + try { + installer.installLibrary(context, abis, "libtest.so", destination, instance); + } catch (MissingLibraryException e) { + assertEquals("Could not find 'libtest.so'. Looked for: [armeabi-v7a], but only found: [x86].", e.getMessage()); + } + } + private String fileToString(final File file) throws IOException { final long size = file.length(); if (size > Integer.MAX_VALUE) {