
com.reandroid.apk.ApkModule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ARSCLib Show documentation
Show all versions of ARSCLib Show documentation
Android binary resources read/write library
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.reandroid.apk;
import com.reandroid.archive.*;
import com.reandroid.archive.block.ApkSignatureBlock;
import com.reandroid.archive.io.ArchiveFileEntrySource;
import com.reandroid.archive.writer.ApkWriter;
import com.reandroid.arsc.ApkFile;
import com.reandroid.arsc.array.PackageArray;
import com.reandroid.arsc.chunk.Chunk;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
import com.reandroid.arsc.container.SpecTypePair;
import com.reandroid.arsc.group.StringGroup;
import com.reandroid.arsc.item.TableString;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.utils.collection.CollectionUtil;
import com.reandroid.utils.collection.EmptyList;
import com.reandroid.arsc.model.FrameworkTable;
import com.reandroid.arsc.value.Entry;
import com.reandroid.arsc.value.ResConfig;
import com.reandroid.identifiers.PackageIdentifier;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement;
import java.io.*;
import java.util.*;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
public class ApkModule implements ApkFile, Closeable {
private final String moduleName;
private final ZipEntryMap zipEntryMap;
private boolean loadDefaultFramework = true;
private boolean mDisableLoadFramework = false;
private TableBlock mTableBlock;
private InputSource mTableOriginalSource;
private AndroidManifestBlock mManifestBlock;
private InputSource mManifestOriginalSource;
private final UncompressedFiles mUncompressedFiles;
private APKLogger apkLogger;
private ApkType mApkType;
private ApkSignatureBlock apkSignatureBlock;
private Integer preferredFramework;
private Closeable mCloseable;
private final List mExternalFrameworks;
public ApkModule(String moduleName, ZipEntryMap zipEntryMap){
this.moduleName=moduleName;
this.zipEntryMap = zipEntryMap;
this.mUncompressedFiles=new UncompressedFiles();
this.mUncompressedFiles.addPath(zipEntryMap);
this.mExternalFrameworks = new ArrayList<>();
}
public ApkModule(ZipEntryMap zipEntryMap){
this("base", zipEntryMap);
}
public ApkModule(){
this("base", new ZipEntryMap());
}
public void addExternalFramework(File frameworkFile) throws IOException {
if(frameworkFile == null){
return;
}
logMessage("Loading external framework: " + frameworkFile);
FrameworkApk framework = FrameworkApk.loadTableBlock(frameworkFile);
framework.setAPKLogger(getApkLogger());
addExternalFramework(framework);
}
public void addExternalFramework(ApkModule apkModule){
if(apkModule == null || apkModule == this || !apkModule.hasTableBlock()){
return;
}
addExternalFramework(apkModule.getTableBlock());
}
public void addExternalFramework(TableBlock tableBlock){
if(tableBlock == null
|| tableBlock.getApkFile() == this
|| mExternalFrameworks.contains(tableBlock)){
return;
}
mExternalFrameworks.add(tableBlock);
updateExternalFramework();
}
public String refreshTable(){
TableBlock tableBlock = this.mTableBlock;
if(tableBlock != null){
return tableBlock.refreshFull();
}
return null;
}
public String refreshManifest(){
AndroidManifestBlock manifestBlock = this.mManifestBlock;
if(manifestBlock != null){
return manifestBlock.refreshFull();
}
return null;
}
public void validateResourceNames(){
if(!hasTableBlock()){
return;
}
logMessage("Validating resource names ...");
TableBlock tableBlock = getTableBlock();
for(PackageBlock packageBlock : tableBlock.listPackages()){
validateResourceNames(packageBlock);
}
}
public void validateResourceNames(PackageBlock packageBlock){
PackageIdentifier packageIdentifier = new PackageIdentifier();
packageIdentifier.load(packageBlock);
if(!packageIdentifier.hasDuplicateResources()){
return;
}
logMessage("Renaming duplicate resources ... ");
packageIdentifier.ensureUniqueResourceNames();
packageIdentifier.setResourceNamesToPackage(packageBlock);
}
public ApkSignatureBlock getApkSignatureBlock() {
return apkSignatureBlock;
}
public void setApkSignatureBlock(ApkSignatureBlock apkSignatureBlock) {
this.apkSignatureBlock = apkSignatureBlock;
}
public boolean hasSignatureBlock(){
return getApkSignatureBlock() != null;
}
public void dumpSignatureInfoFiles(File directory) throws IOException{
ApkSignatureBlock apkSignatureBlock = getApkSignatureBlock();
if(apkSignatureBlock == null){
throw new IOException("Don't have signature block");
}
apkSignatureBlock.writeSplitRawToDirectory(directory);
}
public void dumpSignatureBlock(File file) throws IOException{
ApkSignatureBlock apkSignatureBlock = getApkSignatureBlock();
if(apkSignatureBlock == null){
throw new IOException("Don't have signature block");
}
apkSignatureBlock.writeRaw(file);
}
public void scanSignatureInfoFiles(File directory) throws IOException{
if(!directory.isDirectory()){
throw new IOException("No such directory: " + directory);
}
ApkSignatureBlock apkSignatureBlock = this.apkSignatureBlock;
if(apkSignatureBlock == null){
apkSignatureBlock = new ApkSignatureBlock();
}
apkSignatureBlock.scanSplitFiles(directory);
setApkSignatureBlock(apkSignatureBlock);
}
public void loadSignatureBlock(File file) throws IOException{
if(!file.isFile()){
throw new IOException("No such file: " + file);
}
ApkSignatureBlock apkSignatureBlock = this.apkSignatureBlock;
if(apkSignatureBlock == null){
apkSignatureBlock = new ApkSignatureBlock();
}
apkSignatureBlock.read(file);
setApkSignatureBlock(apkSignatureBlock);
}
public String getSplit(){
if(!hasAndroidManifestBlock()){
return null;
}
return getAndroidManifestBlock().getSplit();
}
public List getLoadedFrameworks(){
List results = new ArrayList<>();
if(!hasTableBlock()){
return results;
}
TableBlock tableBlock = getTableBlock(false);
results.addAll(tableBlock.getFrameWorks());
return results;
}
public boolean isFrameworkVersionLoaded(Integer version){
if(version == null){
return false;
}
for(TableBlock tableBlock : getLoadedFrameworks()){
if(!(tableBlock instanceof FrameworkTable)){
continue;
}
FrameworkTable frame = (FrameworkTable) tableBlock;
if(version.equals(frame.getVersionCode())){
return true;
}
}
return false;
}
public FrameworkApk getLoadedFramework(Integer version, boolean onlyAndroid){
for(TableBlock tableBlock : getLoadedFrameworks()){
if(!(tableBlock instanceof FrameworkTable)){
continue;
}
FrameworkTable frame = (FrameworkTable) tableBlock;
if(onlyAndroid && !isAndroid(frame)){
continue;
}
if(version == null || version.equals(frame.getVersionCode())){
return (FrameworkApk) frame.getApkFile();
}
}
return null;
}
public FrameworkApk initializeAndroidFramework(Integer version) throws IOException {
TableBlock tableBlock = getTableBlock(false);
return initializeAndroidFramework(tableBlock, version);
}
public FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
if(mDisableLoadFramework || tableBlock == null || isAndroid(tableBlock)){
return null;
}
FrameworkApk exist = getLoadedFramework(version, true);
if(exist != null){
return exist;
}
logMessage("Initializing android framework ...");
FrameworkApk frameworkApk;
if(version == null){
logMessage("Can not read framework version, loading latest");
frameworkApk = AndroidFrameworks.getLatest();
}else {
logMessage("Loading android framework for version: " + version);
frameworkApk = AndroidFrameworks.getBestMatch(version);
}
FrameworkTable frameworkTable = frameworkApk.getTableBlock();
tableBlock.addFramework(frameworkTable);
logMessage("Initialized framework: " + frameworkApk.getName()
+ " (" + frameworkApk.getVersionName() + ")");
return frameworkApk;
}
public FrameworkApk initializeAndroidFramework(XMLDocument xmlDocument) throws IOException {
if(this.preferredFramework != null){
return initializeAndroidFramework(preferredFramework);
}
if(isAndroidCoreApp(xmlDocument)){
logMessage("Looks framework itself, skip loading frameworks");
return null;
}
Integer version = readVersionCode(xmlDocument);
return initializeAndroidFramework(version);
}
private boolean isAndroid(TableBlock tableBlock){
if(tableBlock instanceof FrameworkTable){
FrameworkTable frameworkTable = (FrameworkTable) tableBlock;
return frameworkTable.isAndroid();
}
return false;
}
private boolean isAndroidCoreApp(XMLDocument manifestDocument){
XMLElement root = manifestDocument.getDocumentElement();
if(root == null){
return false;
}
if(!"android".equals(root.getAttributeValue("package"))){
return false;
}
String coreApp = root.getAttributeValue("coreApp");
return "true".equals(coreApp);
}
private Integer readVersionCode(XMLDocument xmlDocument){
if(xmlDocument == null){
return null;
}
XMLElement manifestRoot = xmlDocument.getDocumentElement();
if(manifestRoot == null){
logMessage("WARN: Manifest root not found");
return null;
}
String versionString = manifestRoot.getAttributeValue("android:compileSdkVersion");
Integer version = null;
if(versionString!=null){
version = safeParseInteger(versionString);
}
if(version == null){
versionString = manifestRoot.getAttributeValue("platformBuildVersionCode");
if(versionString!=null){
version = safeParseInteger(versionString);
}
}
Integer target = null;
Iterator iterator = manifestRoot
.getElements(AndroidManifestBlock.TAG_uses_sdk);
while (iterator.hasNext()){
XMLElement element = iterator.next();
versionString = element.getAttributeValue("android:targetSdkVersion");
if(versionString != null){
target = safeParseInteger(versionString);
}
}
if(version == null){
version = target;
}else if(target != null && target > version){
version = target;
}
return version;
}
private Integer safeParseInteger(String versionString) {
try{
return Integer.parseInt(versionString);
}catch (NumberFormatException exception){
logMessage("NumberFormatException on manifest version reading: '"
+versionString+"': "+exception.getMessage());
return null;
}
}
public void setPreferredFramework(Integer version) throws IOException {
if(version != null && version.equals(preferredFramework)){
return;
}
this.preferredFramework = version;
if(version == null || mTableBlock == null){
return;
}
if(isFrameworkVersionLoaded(version)){
return;
}
logMessage("Initializing preferred framework: " + version);
mTableBlock.clearFrameworks();
FrameworkApk frameworkApk = AndroidFrameworks.getBestMatch(version);
AndroidFrameworks.setCurrent(frameworkApk);
mTableBlock.addFramework(frameworkApk.getTableBlock());
logMessage("Initialized framework: " + frameworkApk.getVersionCode());
}
public Integer getAndroidFrameworkVersion(){
if(preferredFramework != null){
return preferredFramework;
}
if(!hasAndroidManifestBlock()){
return null;
}
AndroidManifestBlock manifestBlock = getAndroidManifestBlock();
Integer version = manifestBlock.getCompileSdkVersion();
if(version == null){
version = manifestBlock.getPlatformBuildVersionCode();
}
Integer target = manifestBlock.getTargetSdkVersion();
if(version == null){
version = target;
}else if(target != null && target > version){
version = target;
}
return version;
}
public void removeResFilesWithEntry(int resourceId) {
removeResFilesWithEntry(resourceId, null, true);
}
public void removeResFilesWithEntry(int resourceId, ResConfig resConfig, boolean trimEntryArray) {
List removedList = removeResFiles(resourceId, resConfig);
SpecTypePair specTypePair = null;
for(Entry entry:removedList){
if(entry == null || entry.isNull()){
continue;
}
if(trimEntryArray && specTypePair==null){
specTypePair = entry.getTypeBlock().getParentSpecTypePair();
}
entry.setNull(true);
}
if(specTypePair!=null){
specTypePair.removeNullEntries(resourceId);
}
}
public List removeResFiles(int resourceId) {
return removeResFiles(resourceId, null);
}
public List removeResFiles(int resourceId, ResConfig resConfig) {
List results = new ArrayList<>();
if(resourceId == 0 && resConfig==null){
return results;
}
List resFileList = listResFiles(resourceId, resConfig);
ZipEntryMap zipEntryMap = getZipEntryMap();
for(ResFile resFile:resFileList){
results.addAll(resFile.getEntryList());
String path = resFile.getFilePath();
zipEntryMap.remove(path);
}
return results;
}
public XMLDocument decodeXMLFile(String path) throws IOException {
ResXmlDocument resXmlDocument = loadResXmlDocument(path);
AndroidManifestBlock manifestBlock = getAndroidManifestBlock();
int pkgId = manifestBlock.guessCurrentPackageId();
if(pkgId != 0 && hasTableBlock()){
PackageBlock packageBlock = getTableBlock().pickOne(pkgId);
if(packageBlock != null){
resXmlDocument.setPackageBlock(packageBlock);
}
}
return resXmlDocument.decodeToXml();
}
public List listDexFiles(){
List results = new ArrayList<>();
for(InputSource source: getInputSources()){
if(DexFileInputSource.isDexName(source.getAlias())){
results.add(new DexFileInputSource(source.getAlias(), source));
}
}
DexFileInputSource.sort(results);
return results;
}
public boolean isBaseModule(){
if(!hasAndroidManifestBlock()){
return false;
}
AndroidManifestBlock manifestBlock;
try {
manifestBlock=getAndroidManifestBlock();
return manifestBlock.getMainActivity()!=null;
} catch (Exception ignored) {
return false;
}
}
public String getModuleName(){
return moduleName;
}
public void writeApk(File file) throws IOException {
writeApk(file, null);
}
public void writeApk(File file, WriteProgress progress) throws IOException {
ZipEntryMap archive = getZipEntryMap();
UncompressedFiles uf = getUncompressedFiles();
uf.apply(archive);
ApkWriter apkWriter = new ApkWriter(file, archive.toArray());
apkWriter.setAPKLogger(getApkLogger());
apkWriter.setWriteProgress(progress);
apkWriter.setApkSignatureBlock(getApkSignatureBlock());
apkWriter.write();
apkWriter.close();
}
public void uncompressNonXmlResFiles() {
for(ResFile resFile:listResFiles()){
if(resFile.isBinaryXml()){
continue;
}
resFile.getInputSource().setMethod(ZipEntry.STORED);
}
}
public UncompressedFiles getUncompressedFiles(){
return mUncompressedFiles;
}
public void removeDir(String dirName){
getZipEntryMap().removeDir(dirName);
}
public void validateResourcesDir() {
List resFileList = listResFiles();
Set existPaths=new HashSet<>();
InputSource[] sourceList = getInputSources();
for(InputSource inputSource:sourceList){
existPaths.add(inputSource.getAlias());
}
for(ResFile resFile:resFileList){
String path=resFile.getFilePath();
String pathNew=resFile.validateTypeDirectoryName();
if(pathNew==null || pathNew.equals(path)){
continue;
}
if(existPaths.contains(pathNew)){
continue;
}
existPaths.remove(path);
existPaths.add(pathNew);
resFile.setFilePath(pathNew);
if(resFile.getInputSource().getMethod() == ZipEntry.STORED){
getUncompressedFiles().replacePath(path, pathNew);
}
logVerbose("Dir validated: '"+path+"' -> '"+pathNew+"'");
}
TableStringPool stringPool= getTableBlock().getStringPool();
stringPool.refreshUniqueIdMap();
getTableBlock().refresh();
}
public void setResourcesRootDir(String dirName) {
List resFileList = listResFiles();
Set existPaths=new HashSet<>();
InputSource[] sourceList = getInputSources();
for(InputSource inputSource:sourceList){
existPaths.add(inputSource.getAlias());
}
for(ResFile resFile:resFileList){
String path=resFile.getFilePath();
String pathNew=ApkUtil.replaceRootDir(path, dirName);
if(existPaths.contains(pathNew)){
continue;
}
existPaths.remove(path);
existPaths.add(pathNew);
resFile.setFilePath(pathNew);
if(resFile.getInputSource().getMethod() == ZipEntry.STORED){
getUncompressedFiles().replacePath(path, pathNew);
}
logVerbose("Root changed: '"+path+"' -> '"+pathNew+"'");
}
TableStringPool stringPool= getTableBlock().getStringPool();
stringPool.refreshUniqueIdMap();
getTableBlock().refresh();
}
public List listResFiles() {
return listResFiles(0, null);
}
public List listResFiles(int resourceId, ResConfig resConfig) {
List results=new ArrayList<>();
TableBlock tableBlock=getTableBlock();
if (tableBlock==null){
return results;
}
TableStringPool stringPool= tableBlock.getStringPool();
for(InputSource inputSource : getInputSources()){
String name=inputSource.getAlias();
StringGroup groupTableString = stringPool.get(name);
if(groupTableString==null){
continue;
}
for(TableString tableString:groupTableString.listItems()){
List entryList = filterResFileEntries(
tableString, resourceId, resConfig);
if(entryList.size()==0){
continue;
}
ResFile resFile = new ResFile(inputSource, entryList);
results.add(resFile);
}
}
return results;
}
public List listReferencedEntries(String path) {
TableBlock tableBlock = getTableBlock();
if (tableBlock == null){
return new ArrayList<>();
}
TableStringPool stringPool = tableBlock.getStringPool();
StringGroup stringGroup = stringPool.get(path);
if(stringGroup == null){
return EmptyList.of();
}
TableString tableString = stringPool.get(0);
return tableString.listReferencedResValueEntries();
}
private List filterResFileEntries(TableString tableString, int resourceId, ResConfig resConfig){
Iterator itr = tableString.getEntries(new Predicate() {
@Override
public boolean test(Entry item) {
if(!item.isScalar()){
return false;
}
if(resourceId != 0 && resourceId != item.getResourceId()){
return false;
}
return resConfig == null || resConfig.equals(item.getResConfig());
}
});
return CollectionUtil.toList(itr);
}
public String getPackageName(){
if(hasAndroidManifestBlock()){
return getAndroidManifestBlock().getPackageName();
}
if(!hasTableBlock()){
return null;
}
TableBlock tableBlock=getTableBlock();
PackageArray pkgArray = tableBlock.getPackageArray();
PackageBlock pkg = pkgArray.get(0);
if(pkg==null){
return null;
}
return pkg.getName();
}
public void setPackageName(String name) {
String old=getPackageName();
if(hasAndroidManifestBlock()){
getAndroidManifestBlock().setPackageName(name);
}
if(!hasTableBlock()){
return;
}
TableBlock tableBlock=getTableBlock();
PackageArray pkgArray = tableBlock.getPackageArray();
for(PackageBlock pkg:pkgArray.listItems()){
if(pkgArray.childesCount()==1){
pkg.setName(name);
continue;
}
String pkgName=pkg.getName();
if(pkgName.startsWith(old)){
pkgName=pkgName.replace(old, name);
pkg.setName(pkgName);
}
}
}
public boolean hasAndroidManifestBlock(){
return mManifestBlock!=null
|| getZipEntryMap().getInputSource(AndroidManifestBlock.FILE_NAME)!=null;
}
public boolean hasTableBlock(){
return mTableBlock!=null
|| getZipEntryMap().getInputSource(TableBlock.FILE_NAME)!=null;
}
public void destroy(){
getZipEntryMap().clear();
AndroidManifestBlock manifestBlock = this.mManifestBlock;
if(manifestBlock!=null){
manifestBlock.destroy();
this.mManifestBlock = null;
}
TableBlock tableBlock = this.mTableBlock;
if(tableBlock!=null){
mExternalFrameworks.clear();
tableBlock.destroy();
this.mTableBlock = null;
}
try {
close();
} catch (IOException ignored) {
}
}
public void setManifest(AndroidManifestBlock manifestBlock){
ZipEntryMap archive = getZipEntryMap();
if(manifestBlock==null){
mManifestBlock = null;
archive.remove(AndroidManifestBlock.FILE_NAME);
return;
}
manifestBlock.setApkFile(this);
BlockInputSource source =
new BlockInputSource<>(AndroidManifestBlock.FILE_NAME, manifestBlock);
source.setMethod(ZipEntry.STORED);
archive.add(source);
mManifestBlock = manifestBlock;
}
public void setTableBlock(TableBlock tableBlock){
ZipEntryMap archive = getZipEntryMap();
if(tableBlock == null){
mTableBlock = null;
archive.remove(TableBlock.FILE_NAME);
return;
}
tableBlock.setApkFile(this);
BlockInputSource source =
new BlockInputSource<>(TableBlock.FILE_NAME, tableBlock);
archive.add(source);
source.setMethod(ZipEntry.STORED);
getUncompressedFiles().addPath(source);
mTableBlock = tableBlock;
updateExternalFramework();
}
@Override
public AndroidManifestBlock getAndroidManifestBlock() {
if(mManifestBlock!=null){
return mManifestBlock;
}
InputSource inputSource = getInputSource(AndroidManifestBlock.FILE_NAME);
if(inputSource == null){
return null;
}
setManifestOriginalSource(inputSource);
InputStream inputStream = null;
try {
inputStream = inputSource.openStream();
AndroidManifestBlock manifestBlock=AndroidManifestBlock.load(inputStream);
inputStream.close();
BlockInputSource blockInputSource=new BlockInputSource<>(inputSource.getName(),manifestBlock);
blockInputSource.setSort(inputSource.getSort());
blockInputSource.setMethod(inputSource.getMethod());
addInputSource(blockInputSource);
manifestBlock.setApkFile(this);
TableBlock tableBlock = this.mTableBlock;
if(tableBlock != null){
int packageId = manifestBlock.guessCurrentPackageId();
if(packageId != 0){
manifestBlock.setPackageBlock(tableBlock.pickOne(packageId));
}else {
manifestBlock.setPackageBlock(tableBlock.pickOne());
}
}
mManifestBlock = manifestBlock;
onManifestBlockLoaded(manifestBlock);
} catch (IOException exception) {
throw new IllegalArgumentException(exception);
}
return mManifestBlock;
}
private void onManifestBlockLoaded(AndroidManifestBlock manifestBlock){
initializeApkType(manifestBlock);
}
public TableBlock getTableBlock(boolean initFramework) {
if(mTableBlock==null){
if(!hasTableBlock()){
return null;
}
try {
mTableBlock = loadTableBlock();
if(initFramework && loadDefaultFramework){
Integer version = getAndroidFrameworkVersion();
initializeAndroidFramework(mTableBlock, version);
}
updateExternalFramework();
} catch (IOException exception) {
throw new IllegalArgumentException(exception);
}
}
return mTableBlock;
}
private void updateExternalFramework(){
TableBlock tableBlock = mTableBlock;
if(tableBlock == null){
return;
}
for(TableBlock framework : mExternalFrameworks){
tableBlock.addFramework(framework);
}
}
public InputSource getManifestOriginalSource(){
InputSource inputSource = this.mManifestOriginalSource;
if(inputSource == null){
inputSource = getInputSource(AndroidManifestBlock.FILE_NAME);
mManifestOriginalSource = inputSource;
}
return inputSource;
}
private void setManifestOriginalSource(InputSource inputSource){
if(mManifestOriginalSource == null
&& !(inputSource instanceof BlockInputSource)){
mManifestOriginalSource = inputSource;
}
}
public InputSource getTableOriginalSource(){
InputSource inputSource = this.mTableOriginalSource;
if(inputSource == null){
inputSource = getInputSource(TableBlock.FILE_NAME);
mTableOriginalSource = inputSource;
}
return inputSource;
}
private void setTableOriginalSource(InputSource inputSource){
if(mTableOriginalSource == null
&& !(inputSource instanceof BlockInputSource)){
mTableOriginalSource = inputSource;
}
}
@Override
public TableBlock getTableBlock() {
if(mTableBlock != null){
return mTableBlock;
}
checkExternalFramework();
checkSelfFramework();
return getTableBlock(!mDisableLoadFramework);
}
@Override
public TableBlock getLoadedTableBlock(){
return mTableBlock;
}
private void checkExternalFramework(){
if(mDisableLoadFramework || preferredFramework != null){
return;
}
if(mExternalFrameworks.size() == 0){
return;
}
mDisableLoadFramework = true;
}
private void checkSelfFramework(){
if(mDisableLoadFramework || preferredFramework != null){
return;
}
AndroidManifestBlock manifestBlock = getAndroidManifestBlock();
if(manifestBlock == null){
return;
}
if(manifestBlock.isCoreApp() == null
|| !"android".equals(manifestBlock.getPackageName())){
return;
}
if(manifestBlock.guessCurrentPackageId() != 0x01){
return;
}
logMessage("Looks like framework apk, skip loading framework");
mDisableLoadFramework = true;
}
@Override
public ResXmlDocument loadResXmlDocument(String path) throws IOException{
InputSource inputSource = getInputSource(path);
if(inputSource==null){
throw new FileNotFoundException("No such file in apk: " + path);
}
return loadResXmlDocument(inputSource);
}
public ResXmlDocument loadResXmlDocument(InputSource inputSource) throws IOException{
ResXmlDocument resXmlDocument = new ResXmlDocument();
resXmlDocument.setApkFile(this);
resXmlDocument.readBytes(inputSource.openStream());
TableBlock tableBlock = getTableBlock();
if(tableBlock != null){
resXmlDocument.setPackageBlock(tableBlock.pickOne());
}
return resXmlDocument;
}
public ApkType getApkType(){
if(mApkType!=null){
return mApkType;
}
return initializeApkType(mManifestBlock);
}
public void setApkType(ApkType apkType){
this.mApkType = apkType;
}
private ApkType initializeApkType(AndroidManifestBlock manifestBlock){
if(mApkType!=null){
return mApkType;
}
ApkType apkType = null;
if(manifestBlock!=null){
apkType = manifestBlock.guessApkType();
}
if(apkType != null){
mApkType = apkType;
}else {
apkType = ApkType.UNKNOWN;
}
return apkType;
}
// If we need TableStringPool only, this loads pool without
// loading packages and other chunk blocks for faster and less memory usage
public TableStringPool getVolatileTableStringPool() throws IOException{
if(mTableBlock!=null){
return mTableBlock.getStringPool();
}
InputSource inputSource = getInputSource(TableBlock.FILE_NAME);
if(inputSource==null){
throw new IOException("Module don't have: "+TableBlock.FILE_NAME);
}
if((inputSource instanceof ArchiveFileEntrySource)
||(inputSource instanceof FileInputSource)){
InputStream inputStream = inputSource.openStream();
TableStringPool stringPool = TableStringPool.readFromTable(inputStream);
inputStream.close();
return stringPool;
}
return getTableBlock().getStringPool();
}
TableBlock loadTableBlock() throws IOException {
InputSource inputSource = getInputSource(TableBlock.FILE_NAME);
if(inputSource==null){
throw new IOException("Entry not found: "+TableBlock.FILE_NAME);
}
TableBlock tableBlock;
if(inputSource instanceof SplitJsonTableInputSource){
tableBlock=((SplitJsonTableInputSource)inputSource).getTableBlock();
}else if(inputSource instanceof SingleJsonTableInputSource){
tableBlock=((SingleJsonTableInputSource)inputSource).getTableBlock();
}else if(inputSource instanceof BlockInputSource){
Chunk> block = ((BlockInputSource>) inputSource).getBlock();
tableBlock = (TableBlock) block;
}else {
setTableOriginalSource(inputSource);
InputStream inputStream = inputSource.openStream();
tableBlock = TableBlock.load(inputStream);
inputStream.close();
}
BlockInputSource blockInputSource=new BlockInputSource<>(inputSource.getName(), tableBlock);
blockInputSource.setMethod(inputSource.getMethod());
blockInputSource.setSort(inputSource.getSort());
zipEntryMap.add(blockInputSource);
tableBlock.setApkFile(this);
return tableBlock;
}
public void addAll(Collection extends InputSource> inputSources){
if(inputSources == null){
return;
}
for(InputSource inputSource : inputSources){
add(inputSource);
}
}
public void add(InputSource inputSource){
if(inputSource == null){
return;
}
String path = inputSource.getAlias();
if(AndroidManifestBlock.FILE_NAME.equals(path)){
InputSource manifestSource = getManifestOriginalSource();
if(manifestSource != inputSource){
mManifestBlock = null;
}
setManifestOriginalSource(inputSource);
}else if(TableBlock.FILE_NAME.equals(path)){
InputSource table = getTableOriginalSource();
if(inputSource != table){
mTableBlock = null;
}
setTableOriginalSource(inputSource);
}
addInputSource(inputSource);
}
public InputSource getInputSource(String path){
return getZipEntryMap().getInputSource(path);
}
private void addInputSource(InputSource inputSource){
getZipEntryMap().add(inputSource);
}
public List listInputSources(){
return getZipEntryMap().listInputSources();
}
public InputSource[] getInputSources(){
return getZipEntryMap().toArray();
}
public ZipEntryMap getZipEntryMap() {
return zipEntryMap;
}
public void setLoadDefaultFramework(boolean loadDefaultFramework) {
this.loadDefaultFramework = loadDefaultFramework;
this.mDisableLoadFramework = !loadDefaultFramework;
}
public void merge(ApkModule module) throws IOException {
if(module==null||module==this){
return;
}
logMessage("Merging: "+module.getModuleName());
mergeDexFiles(module);
mergeTable(module);
mergeFiles(module);
getUncompressedFiles().merge(module.getUncompressedFiles());
}
private void mergeTable(ApkModule module) {
if(!module.hasTableBlock()){
return;
}
logMessage("Merging resource table: "+module.getModuleName());
TableBlock exist;
if(!hasTableBlock()){
exist=new TableBlock();
BlockInputSource inputSource=new BlockInputSource<>(TableBlock.FILE_NAME, exist);
addInputSource(inputSource);
}else{
exist=getTableBlock();
}
TableBlock coming=module.getTableBlock();
exist.merge(coming);
}
private void mergeFiles(ApkModule module) {
ZipEntryMap entryMapExist = getZipEntryMap();
ZipEntryMap entryMapComing = module.getZipEntryMap();
Map comingAlias = entryMapComing.toAliasMap();
Map existAlias = entryMapExist.toAliasMap();
UncompressedFiles uncompressedFiles = module.getUncompressedFiles();
for(InputSource inputSource:comingAlias.values()){
if(existAlias.containsKey(inputSource.getAlias())
|| existAlias.containsKey(inputSource.getName())){
continue;
}
if(DexFileInputSource.isDexName(inputSource.getName())){
continue;
}
if(inputSource.getAlias().startsWith("lib/")){
uncompressedFiles.removePath(inputSource.getAlias());
}
logVerbose("Added: " + inputSource.getAlias());
entryMapExist.add(inputSource);
}
}
private void mergeDexFiles(ApkModule module){
UncompressedFiles uncompressedFiles = module.getUncompressedFiles();
List existList = listDexFiles();
List comingList = module.listDexFiles();
ZipEntryMap zipEntryMap = getZipEntryMap();
int index=0;
if(existList.size()>0){
index=existList.get(existList.size()-1).getDexNumber();
if(index==0){
index=2;
}else {
index++;
}
}
for(DexFileInputSource source : comingList){
uncompressedFiles.removePath(source.getAlias());
String name = DexFileInputSource.getDexName(index);
DexFileInputSource add = new DexFileInputSource(name, source.getInputSource());
zipEntryMap.add(add);
logMessage("Added [" + module.getModuleName() +"] "
+ source.getAlias() + " -> " + name);
index++;
if(index==1){
index=2;
}
}
}
public APKLogger getApkLogger(){
return apkLogger;
}
public void setAPKLogger(APKLogger logger) {
this.apkLogger = logger;
}
void logMessage(String msg) {
if(apkLogger!=null){
apkLogger.logMessage(msg);
}
}
private void logError(String msg, Throwable tr) {
if(apkLogger!=null){
apkLogger.logError(msg, tr);
}
}
private void logVerbose(String msg) {
if(apkLogger!=null){
apkLogger.logVerbose(msg);
}
}
public void setCloseable(Closeable closeable){
this.mCloseable = closeable;
}
@Override
public void close() throws IOException {
Closeable closeable = this.mCloseable;
if(closeable != null){
closeable.close();
}
}
@Override
public String toString(){
return getModuleName();
}
public static ApkModule loadApkFile(File apkFile) throws IOException {
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
}
public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException {
ArchiveFile archive = new ArchiveFile(apkFile);
ApkModule apkModule = new ApkModule(moduleName, archive.createZipEntryMap());
apkModule.setApkSignatureBlock(archive.getApkSignatureBlock());
apkModule.setCloseable(archive);
return apkModule;
}
public static ApkModule loadApkFile(File apkFile, File ... externalFrameworks) throws IOException {
return loadApkFile(null, apkFile, externalFrameworks);
}
public static ApkModule loadApkFile(APKLogger logger, File apkFile, File ... externalFrameworks) throws IOException {
ArchiveFile archive = new ArchiveFile(apkFile);
ApkModule apkModule = new ApkModule(ApkUtil.DEF_MODULE_NAME, archive.createZipEntryMap());
apkModule.setAPKLogger(logger);
apkModule.setApkSignatureBlock(archive.getApkSignatureBlock());
apkModule.setCloseable(archive);
if(externalFrameworks == null || externalFrameworks.length == 0){
return apkModule;
}
for(File frameworkFile : externalFrameworks){
if(frameworkFile == null){
continue;
}
if(apkFile.equals(frameworkFile)){
throw new IOException("External framework should be different: " + apkFile);
}
apkModule.addExternalFramework(frameworkFile);
}
return apkModule;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy