/*
* Copyright 2014-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.android.support.exopackage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.os.Build;
import android.util.Log;
/**
* Loads native library files installed by the exopackage installer. This class requires
* initialization before use; {@link ExopackageApplication#attachBaseContext} is responsible
* for calling {@link #init} so that application-specific code doesn't need to.
*/
public class ExopackageSoLoader {
private static final String TAG = "ExopackageSoLoader";
private static boolean initialized = false;
private static String nativeLibsDir = null;
private static File privateNativeLibsDir = null;
private static Map<String, String> abi1Libraries = new HashMap<String, String>();
private static Map<String, String> abi2Libraries = new HashMap<String, String>();
private ExopackageSoLoader() {}
public static void init(Context context) {
if (initialized) {
Log.d(TAG, "init() already called, so nothing to do.");
return;
}
nativeLibsDir = "/data/local/tmp/exopackage/" + context.getPackageName() + "/native-libs/";
verifyMetadataFile();
preparePrivateDirectory(context);
parseMetadata();
initialized = true;
}
private static void verifyMetadataFile() {
File abiMetadata = getAbi1Metadata();
if (abiMetadata.exists()) {
return;
}
abiMetadata = getAbi2Metadata();
if (abiMetadata == null || abiMetadata.exists()) {
return;
}
throw new RuntimeException("Either 'native' exopackage is not turned on for this build, " +
"or the installation did not complete successfully.");
}
private static void preparePrivateDirectory(Context context) {
privateNativeLibsDir = context.getDir("exo-libs", Context.MODE_PRIVATE);
for (File file : privateNativeLibsDir.listFiles()) {
file.delete();
}
}
private static void parseMetadata() {
doParseMetadata(getAbi1Metadata(), abi1Libraries);
doParseMetadata(getAbi2Metadata(), abi2Libraries);
}
private static void doParseMetadata(File metadata, Map<String, String> libraries) {
if (metadata == null || !metadata.exists()) {
return;
}
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(metadata));
String line;
try {
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
int spaceIndex = line.indexOf(' ');
if (spaceIndex == -1) {
throw new RuntimeException("Error parsing metadata.txt; invalid line: " + line);
}
String libname = line.substring(0, spaceIndex);
String filename = line.substring(spaceIndex + 1);
libraries.put(libname, filename);
}
} finally {
br.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void loadLibrary(String shortName) throws UnsatisfiedLinkError {
if (!initialized) {
Log.d(TAG, "ExopackageSoLoader not initialized, falling back to System.loadLibrary()");
System.loadLibrary(shortName);
return;
}
String libname = shortName.startsWith("lib") ? shortName : "lib" + shortName;
File libraryFile = copySoFileIfRequired(libname);
if (libraryFile == null) {
throw new UnsatisfiedLinkError("Could not find library file for either ABIs.");
}
String path = libraryFile.getAbsolutePath();
Log.d(TAG, "Attempting to load library: " + path);
System.load(path);
Log.d(TAG, "Successfully loaded library: " + path);
}
private static File copySoFileIfRequired(String libname) {
File libraryFile = new File(privateNativeLibsDir, libname + ".so");
if (libraryFile.exists()) {
return libraryFile;
}
if (!abi1Libraries.containsKey(libname) && !abi2Libraries.containsKey(libname)) {
return null;
}
String abiDir;
String sourceFilename;
if (abi1Libraries.containsKey(libname)) {
sourceFilename = abi1Libraries.get(libname);
abiDir = Build.CPU_ABI;
} else {
sourceFilename = abi2Libraries.get(libname);
abiDir = Build.CPU_ABI2;
}
String sourcePath = nativeLibsDir + abiDir + "/" + sourceFilename;
try {
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(sourcePath));
out = new BufferedOutputStream(new FileOutputStream(libraryFile));
byte[] buffer = new byte[4 * 1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return libraryFile;
}
private static File getAbi1Metadata() {
return new File(nativeLibsDir + Build.CPU_ABI + "/metadata.txt");
}
private static File getAbi2Metadata() {
if (Build.CPU_ABI2.equals("unknown")) {
return null;
}
return new File(nativeLibsDir + Build.CPU_ABI2 + "/metadata.txt");
}
}