diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java new file mode 100644 index 0000000..fae6560 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/AbiSupportInfo.java @@ -0,0 +1,35 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public class AbiSupportInfo { + private final Set supportedAbis; + private final Map unScannableFileAndReasons; + + public AbiSupportInfo(Set supportedAbis) { + this(supportedAbis, Collections.emptyMap()); + } + + public AbiSupportInfo(Set supportedAbis, Map unscannableFilesAndReasons) { + this.supportedAbis = supportedAbis; + this.unScannableFileAndReasons = unscannableFilesAndReasons; + } + + public String[] getSupportedAbis() { + String[] arr = new String[supportedAbis.size()]; + supportedAbis.toArray(arr); + return arr; + } + + public String[] getUnscannableFileNameAndReasons() { + String[] arr = new String[unScannableFileAndReasons.size()]; + int i = 0; + for (Map.Entry entry : unScannableFileAndReasons.entrySet()) { + arr[i++] = entry.getKey().getName() + " => " + entry.getValue(); + } + return arr; + } +} diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java b/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java new file mode 100644 index 0000000..5146360 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ActualZipFileFactory.java @@ -0,0 +1,12 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public class ActualZipFileFactory implements ZipFileFactory { + @Override + public ZipFile create(File file, int openMode) throws IOException { + return new ZipFile(file, openMode); + } +} diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java index 396f849..bb938cd 100644 --- a/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ApkLibraryInstaller.java @@ -27,8 +27,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,6 +42,16 @@ public class ApkLibraryInstaller implements ReLinker.LibraryInstaller { private static final int MAX_TRIES = 5; private static final int COPY_BUFFER_SIZE = 4096; + private final ZipFileFactory zipFileFactory; + + ApkLibraryInstaller() { + this(new ActualZipFileFactory()); + } + + ApkLibraryInstaller(ZipFileFactory zipFileFactory) { + this.zipFileFactory = zipFileFactory; + } + private String[] sourceDirectories(final Context context) { final ApplicationInfo appInfo = context.getApplicationInfo(); @@ -74,7 +87,7 @@ private ZipFileInZipEntry findAPKWithLibrary(final Context context, int tries = 0; while (tries++ < MAX_TRIES) { try { - zipFile = new ZipFile(new File(sourceDir), ZipFile.OPEN_READ); + zipFile = zipFileFactory.create(new File(sourceDir), ZipFile.OPEN_READ); break; } catch (IOException ignored) { } @@ -116,15 +129,18 @@ private ZipFileInZipEntry findAPKWithLibrary(final Context context, // 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) { + private AbiSupportInfo 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(); + Map unscannableFiles = new HashMap(); for (String sourceDir : sourceDirectories(context)) { + File source = new File(sourceDir); try { - zipFile = new ZipFile(new File(sourceDir), ZipFile.OPEN_READ); - } catch (IOException ignored) { + zipFile = zipFileFactory.create(source, ZipFile.OPEN_READ); + } catch (IOException e) { + unscannableFiles.put(source, e); continue; } @@ -138,8 +154,7 @@ private String[] getSupportedABIs(Context context, String mappedLibraryName) { } } - String[] result = new String[supportedABIs.size()]; - return supportedABIs.toArray(result); + return new AbiSupportInfo(supportedABIs, unscannableFiles); } /** @@ -163,14 +178,13 @@ public void installLibrary(final Context context, if (found == null) { // Does not exist in any APK. Report exactly what ReLinker is looking for and // what is actually supported by the APK. - String[] supportedABIs; + AbiSupportInfo 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(); + supportedABIs = new AbiSupportInfo(Collections.singleton(e.toString())); } throw new MissingLibraryException(mappedLibraryName, abis, supportedABIs); } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java index 739d8d1..36a95a0 100755 --- a/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java +++ b/relinker/src/main/java/com/getkeepsafe/relinker/MissingLibraryException.java @@ -18,9 +18,28 @@ import java.util.Arrays; public class MissingLibraryException extends RuntimeException { - 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) + "."); + private final String library; + private final String[] wantedABIs; + private final AbiSupportInfo abiSupportInfo; + + public MissingLibraryException(String library, String[] wantedABIs, AbiSupportInfo abiSupportInfo) { + this.library = library; + this.wantedABIs = wantedABIs; + this.abiSupportInfo = abiSupportInfo; + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(); + sb.append("Could not find '").append(library).append("'. "); + sb.append("Looked for: ").append(Arrays.toString(wantedABIs)).append(", "); + sb.append("but only found: ").append(Arrays.toString(abiSupportInfo.getSupportedAbis())).append("."); + + String[] unscannableFileNames = abiSupportInfo.getUnscannableFileNameAndReasons(); + if (unscannableFileNames.length != 0) { + sb.append(" Additionally, encountered errors while scanning: ").append(Arrays.toString(unscannableFileNames)).append("."); + } + + return sb.toString(); } } diff --git a/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java b/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java new file mode 100644 index 0000000..4542451 --- /dev/null +++ b/relinker/src/main/java/com/getkeepsafe/relinker/ZipFileFactory.java @@ -0,0 +1,9 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public interface ZipFileFactory { + ZipFile create(File file, int openMode) throws IOException; +} diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java index 0869b41..2c563fc 100644 --- a/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java +++ b/relinker/src/test/java/com/getkeepsafe/relinker/ApkLibraryInstallerTest.java @@ -80,6 +80,25 @@ public void throwsMissingLibraryExceptionWhenABIIsMissing() throws IOException { } } + @Test + public void throwsMissingLibraryExceptionWhenABIIsMissingAndThereWereUnscannableDirs() 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(new FaultyZipFileFactory()); + 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: []. Additionally, encountered errors while scanning: [fake.apk => java.io.IOException: Could not create zip file.].", e.getMessage()); + } + } + private String fileToString(final File file) throws IOException { final long size = file.length(); if (size > Integer.MAX_VALUE) { diff --git a/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java b/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java new file mode 100644 index 0000000..b7768f5 --- /dev/null +++ b/relinker/src/test/java/com/getkeepsafe/relinker/FaultyZipFileFactory.java @@ -0,0 +1,12 @@ +package com.getkeepsafe.relinker; + +import java.io.File; +import java.io.IOException; +import java.util.zip.ZipFile; + +public class FaultyZipFileFactory implements ZipFileFactory { + @Override + public ZipFile create(File file, int openMode) throws IOException { + throw new IOException("Could not create zip file."); + } +}