mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 14:24:25 +02:00
make multi version android frameworks
This commit is contained in:
parent
ce53ffd072
commit
22b21d9ccc
219
src/main/java/com/reandroid/apk/AndroidFrameworks.java
Normal file
219
src/main/java/com/reandroid/apk/AndroidFrameworks.java
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AndroidFrameworks {
|
||||||
|
private static Map<Integer, String> resource_paths;
|
||||||
|
private static FrameworkApk mCurrent;
|
||||||
|
|
||||||
|
public static void setCurrent(FrameworkApk current){
|
||||||
|
synchronized (AndroidFrameworks.class){
|
||||||
|
mCurrent = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static FrameworkApk getCurrent(){
|
||||||
|
FrameworkApk current = mCurrent;
|
||||||
|
if(current==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(current.isDestroyed()){
|
||||||
|
mCurrent = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
public static FrameworkApk getLatest() throws IOException {
|
||||||
|
Map<Integer, String> pathMap = getResourcePaths();
|
||||||
|
synchronized (AndroidFrameworks.class){
|
||||||
|
int latest = getHighestVersion();
|
||||||
|
FrameworkApk current = getCurrent();
|
||||||
|
if(current!=null && latest==current.getVersionCode()){
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
String path = pathMap.get(latest);
|
||||||
|
if(path == null){
|
||||||
|
throw new IOException("Could not get latest framework");
|
||||||
|
}
|
||||||
|
return loadResource(latest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static FrameworkApk getBestMatch(int version) throws IOException {
|
||||||
|
Map<Integer, String> pathMap = getResourcePaths();
|
||||||
|
synchronized (AndroidFrameworks.class){
|
||||||
|
int best = getBestMatchVersion(version);
|
||||||
|
FrameworkApk current = getCurrent();
|
||||||
|
if(current!=null && best==current.getVersionCode()){
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
String path = pathMap.get(best);
|
||||||
|
if(path == null){
|
||||||
|
throw new IOException("Could not get framework for version = "+version);
|
||||||
|
}
|
||||||
|
return loadResource(best);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void destroyCurrent(){
|
||||||
|
synchronized (AndroidFrameworks.class){
|
||||||
|
FrameworkApk current = mCurrent;
|
||||||
|
if(current==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static int getHighestVersion() {
|
||||||
|
Map<Integer, String> pathMap = getResourcePaths();
|
||||||
|
int highest = 0;
|
||||||
|
for(int id:pathMap.keySet()){
|
||||||
|
if(highest==0){
|
||||||
|
highest = id;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(id>highest){
|
||||||
|
highest = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return highest;
|
||||||
|
}
|
||||||
|
private static int getBestMatchVersion(int version) {
|
||||||
|
Map<Integer, String> pathMap = getResourcePaths();
|
||||||
|
if(pathMap.containsKey(version)){
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
int highest = 0;
|
||||||
|
int best = 0;
|
||||||
|
int prevDifference = 0;
|
||||||
|
for(int id:pathMap.keySet()){
|
||||||
|
if(highest==0){
|
||||||
|
highest = id;
|
||||||
|
best = id;
|
||||||
|
prevDifference = version*2 + 1000;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(id>highest){
|
||||||
|
highest = id;
|
||||||
|
}
|
||||||
|
int diff = id-version;
|
||||||
|
if(diff<0){
|
||||||
|
diff=-diff;
|
||||||
|
}
|
||||||
|
if(diff<prevDifference || (diff==prevDifference && id>best)){
|
||||||
|
best = id;
|
||||||
|
prevDifference = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
public static FrameworkApk loadResource(int version) throws IOException {
|
||||||
|
String path = getResourcePath(version);
|
||||||
|
if(path == null){
|
||||||
|
throw new IOException("No resource found for version: "+version);
|
||||||
|
}
|
||||||
|
String simpleName = toSimpleName(path);
|
||||||
|
return FrameworkApk.loadApkBuffer(simpleName, AndroidFrameworks.class.getResourceAsStream(path));
|
||||||
|
}
|
||||||
|
private static String getResourcePath(int version){
|
||||||
|
return getResourcePaths().get(version);
|
||||||
|
}
|
||||||
|
private static Map<Integer, String> getResourcePaths(){
|
||||||
|
if(resource_paths!=null){
|
||||||
|
return resource_paths;
|
||||||
|
}
|
||||||
|
synchronized (AndroidFrameworks.class){
|
||||||
|
resource_paths = scanAvailableResourcePaths();
|
||||||
|
return resource_paths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static Map<Integer, String> scanAvailableResourcePaths(){
|
||||||
|
Map<Integer, String> results = new HashMap<>();
|
||||||
|
int maxSearch = 50;
|
||||||
|
for(int version=20; version<maxSearch; version++){
|
||||||
|
String path = toResourcePath(version);
|
||||||
|
if(!isAvailable(path)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.put(version, path);
|
||||||
|
maxSearch = version + 20;
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private static String toSimpleName(String path){
|
||||||
|
int i = path.lastIndexOf('/');
|
||||||
|
if(i<0){
|
||||||
|
i = path.lastIndexOf(File.separatorChar);
|
||||||
|
}
|
||||||
|
if(i>0){
|
||||||
|
i++;
|
||||||
|
path = path.substring(i);
|
||||||
|
}
|
||||||
|
i = path.lastIndexOf('.');
|
||||||
|
if(i>=0){
|
||||||
|
path = path.substring(0, i);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
private static int parseVersion(String name){
|
||||||
|
int i = name.lastIndexOf('/');
|
||||||
|
if(i<0){
|
||||||
|
i = name.lastIndexOf(File.separatorChar);
|
||||||
|
}
|
||||||
|
if(i>0){
|
||||||
|
i++;
|
||||||
|
name = name.substring(i);
|
||||||
|
}
|
||||||
|
i = name.lastIndexOf('-');
|
||||||
|
if(i>=0){
|
||||||
|
i++;
|
||||||
|
name = name.substring(i);
|
||||||
|
}
|
||||||
|
i = name.indexOf('.');
|
||||||
|
if(i>=0){
|
||||||
|
name = name.substring(0, i);
|
||||||
|
}
|
||||||
|
return Integer.parseInt(name);
|
||||||
|
}
|
||||||
|
private static boolean isAvailable(String path){
|
||||||
|
InputStream inputStream = AndroidFrameworks.class.getResourceAsStream(path);
|
||||||
|
if(inputStream==null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
closeQuietly(inputStream);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private static void closeQuietly(InputStream stream){
|
||||||
|
if(stream == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static String toResourcePath(int version){
|
||||||
|
return ANDROID_RESOURCE_DIRECTORY + ANDROID_PACKAGE
|
||||||
|
+ '-' + version
|
||||||
|
+FRAMEWORK_EXTENSION;
|
||||||
|
}
|
||||||
|
private static final String ANDROID_RESOURCE_DIRECTORY = "/frameworks/android/";
|
||||||
|
private static final String ANDROID_PACKAGE = "android";
|
||||||
|
private static final String FRAMEWORK_EXTENSION = ".apk";
|
||||||
|
}
|
288
src/main/java/com/reandroid/apk/FrameworkOptimizer.java
Normal file
288
src/main/java/com/reandroid/apk/FrameworkOptimizer.java
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* 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.APKArchive;
|
||||||
|
import com.reandroid.archive.InputSource;
|
||||||
|
import com.reandroid.arsc.chunk.TableBlock;
|
||||||
|
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlAttribute;
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlElement;
|
||||||
|
import com.reandroid.arsc.chunk.xml.ResXmlNode;
|
||||||
|
import com.reandroid.arsc.group.EntryGroup;
|
||||||
|
import com.reandroid.arsc.io.BlockReader;
|
||||||
|
import com.reandroid.arsc.pool.ResXmlStringPool;
|
||||||
|
import com.reandroid.arsc.util.FrameworkTable;
|
||||||
|
import com.reandroid.arsc.value.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class FrameworkOptimizer {
|
||||||
|
private final ApkModule frameworkApk;
|
||||||
|
private APKLogger apkLogger;
|
||||||
|
private boolean mOptimizing;
|
||||||
|
public FrameworkOptimizer(ApkModule frameworkApk){
|
||||||
|
this.frameworkApk = frameworkApk;
|
||||||
|
this.apkLogger = frameworkApk.getApkLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void optimize(){
|
||||||
|
if(mOptimizing){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mOptimizing = true;
|
||||||
|
if(!frameworkApk.hasTableBlock()){
|
||||||
|
logMessage("Don't have: "+TableBlock.FILE_NAME);
|
||||||
|
mOptimizing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FrameworkTable frameworkTable = getFrameworkTable();
|
||||||
|
AndroidManifestBlock manifestBlock = null;
|
||||||
|
if(frameworkApk.hasAndroidManifestBlock()){
|
||||||
|
manifestBlock = frameworkApk.getAndroidManifestBlock();
|
||||||
|
}
|
||||||
|
optimizeTable(frameworkTable, manifestBlock);
|
||||||
|
clearFiles(frameworkApk.getApkArchive());
|
||||||
|
logMessage("Optimized");
|
||||||
|
}
|
||||||
|
private void clearFiles(APKArchive archive){
|
||||||
|
int count = archive.entriesCount();
|
||||||
|
if(count==2){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage("Removing files from: "+count);
|
||||||
|
InputSource tableSource = archive.getInputSource(TableBlock.FILE_NAME);
|
||||||
|
InputSource manifestSource = archive.getInputSource(AndroidManifestBlock.FILE_NAME);
|
||||||
|
archive.clear();
|
||||||
|
archive.add(tableSource);
|
||||||
|
archive.add(manifestSource);
|
||||||
|
count = count - archive.entriesCount();
|
||||||
|
logMessage("Removed files: "+count);
|
||||||
|
}
|
||||||
|
private void optimizeTable(FrameworkTable table, AndroidManifestBlock manifestBlock){
|
||||||
|
if(table.isOptimized()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logMessage("Optimizing ...");
|
||||||
|
int prev = table.countBytes();
|
||||||
|
int version = 0;
|
||||||
|
String name = "framework";
|
||||||
|
if(manifestBlock !=null){
|
||||||
|
Integer code = manifestBlock.getVersionCode();
|
||||||
|
if(code!=null){
|
||||||
|
version = code;
|
||||||
|
}
|
||||||
|
name = manifestBlock.getPackageName();
|
||||||
|
compressManifest(manifestBlock);
|
||||||
|
backupManifestValue(manifestBlock, table);
|
||||||
|
}
|
||||||
|
logMessage("Optimizing table ...");
|
||||||
|
table.optimize(name, version);
|
||||||
|
long diff=prev - table.countBytes();
|
||||||
|
long percent=(diff*100L)/prev;
|
||||||
|
logMessage("Table size reduced by: "+percent+" %");
|
||||||
|
mOptimizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrameworkTable getFrameworkTable(){
|
||||||
|
TableBlock tableBlock = frameworkApk.getTableBlock();
|
||||||
|
if(tableBlock instanceof FrameworkTable){
|
||||||
|
return (FrameworkTable) tableBlock;
|
||||||
|
}
|
||||||
|
FrameworkTable frameworkTable = toFramework(tableBlock);
|
||||||
|
frameworkApk.setTableBlock(frameworkTable);
|
||||||
|
return frameworkTable;
|
||||||
|
}
|
||||||
|
private FrameworkTable toFramework(TableBlock tableBlock){
|
||||||
|
logMessage("Converting to framework ...");
|
||||||
|
BlockReader reader = new BlockReader(tableBlock.getBytes());
|
||||||
|
FrameworkTable frameworkTable = new FrameworkTable();
|
||||||
|
try {
|
||||||
|
frameworkTable.readBytes(reader);
|
||||||
|
} catch (IOException exception) {
|
||||||
|
logError("Error re-loading framework: ", exception);
|
||||||
|
}
|
||||||
|
return frameworkTable;
|
||||||
|
}
|
||||||
|
private void compressManifest(AndroidManifestBlock manifestBlock){
|
||||||
|
logMessage("Compressing manifest ...");
|
||||||
|
int prev = manifestBlock.countBytes();
|
||||||
|
ResXmlElement manifest = manifestBlock.getResXmlElement();
|
||||||
|
List<ResXmlNode> removeList = getManifestElementToRemove(manifest);
|
||||||
|
for(ResXmlNode node:removeList){
|
||||||
|
manifest.removeNode(node);
|
||||||
|
}
|
||||||
|
ResXmlElement application = manifestBlock.getApplicationElement();
|
||||||
|
if(application!=null){
|
||||||
|
removeList = application.listXmlNodes();
|
||||||
|
for(ResXmlNode node:removeList){
|
||||||
|
application.removeNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResXmlStringPool stringPool = manifestBlock.getStringPool();
|
||||||
|
stringPool.removeUnusedStrings();
|
||||||
|
manifestBlock.refresh();
|
||||||
|
long diff=prev - manifestBlock.countBytes();
|
||||||
|
long percent=(diff*100L)/prev;
|
||||||
|
logMessage("Manifest size reduced by: "+percent+" %");
|
||||||
|
}
|
||||||
|
private List<ResXmlNode> getManifestElementToRemove(ResXmlElement manifest){
|
||||||
|
List<ResXmlNode> results = new ArrayList<>();
|
||||||
|
for(ResXmlNode node:manifest.listXmlNodes()){
|
||||||
|
if(!(node instanceof ResXmlElement)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ResXmlElement element = (ResXmlElement)node;
|
||||||
|
if(AndroidManifestBlock.TAG_application.equals(element.getTag())){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.add(element);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
private void backupManifestValue(AndroidManifestBlock manifestBlock, TableBlock tableBlock){
|
||||||
|
logMessage("Backup manifest values ...");
|
||||||
|
ResXmlElement application = manifestBlock.getApplicationElement();
|
||||||
|
ResXmlAttribute iconAttribute = null;
|
||||||
|
int iconReference = 0;
|
||||||
|
if(application!=null){
|
||||||
|
ResXmlAttribute attribute = application
|
||||||
|
.searchAttributeByResourceId(AndroidManifestBlock.ID_icon);
|
||||||
|
if(attribute!=null && attribute.getValueType()==ValueType.REFERENCE){
|
||||||
|
iconAttribute = attribute;
|
||||||
|
iconReference = attribute.getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResXmlElement element = manifestBlock.getResXmlElement();
|
||||||
|
backupAttributeValues(tableBlock, element);
|
||||||
|
|
||||||
|
if(iconAttribute!=null){
|
||||||
|
iconAttribute.setTypeAndData(ValueType.REFERENCE, iconReference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void backupAttributeValues(TableBlock tableBlock, ResXmlElement element){
|
||||||
|
if(element==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(ResXmlAttribute attribute: element.listAttributes()){
|
||||||
|
backupAttributeValues(tableBlock, attribute);
|
||||||
|
}
|
||||||
|
for(ResXmlElement child: element.listElements()){
|
||||||
|
backupAttributeValues(tableBlock, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void backupAttributeValues(TableBlock tableBlock, ResXmlAttribute attribute){
|
||||||
|
if(attribute==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ValueType valueType = attribute.getValueType();
|
||||||
|
if(valueType!=ValueType.REFERENCE && valueType!=ValueType.ATTRIBUTE){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int reference = attribute.getData();
|
||||||
|
Entry entry = getEntryWithValue(tableBlock, reference);
|
||||||
|
if(entry == null || isReferenceEntry(entry) || entry.isComplex()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResTableEntry resTableEntry = (ResTableEntry) entry.getTableEntry();
|
||||||
|
ResValue resValue = resTableEntry.getValue();
|
||||||
|
valueType = resValue.getValueType();
|
||||||
|
if(valueType==ValueType.STRING){
|
||||||
|
String value = resValue.getValueAsString();
|
||||||
|
attribute.setValueAsString(value);
|
||||||
|
}else {
|
||||||
|
int data = resValue.getData();
|
||||||
|
attribute.setTypeAndData(valueType, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Entry getEntryWithValue(TableBlock tableBlock, int resourceId){
|
||||||
|
Set<Integer> circularReference = new HashSet<>();
|
||||||
|
return getEntryWithValue(tableBlock, resourceId, circularReference);
|
||||||
|
}
|
||||||
|
private Entry getEntryWithValue(TableBlock tableBlock, int resourceId, Set<Integer> circularReference){
|
||||||
|
if(circularReference.contains(resourceId)){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
circularReference.add(resourceId);
|
||||||
|
EntryGroup entryGroup = tableBlock.getEntryGroup(resourceId);
|
||||||
|
Entry entry = entryGroup.pickOne();
|
||||||
|
if(entry==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if(isReferenceEntry(entry)){
|
||||||
|
return getEntryWithValue(
|
||||||
|
tableBlock,
|
||||||
|
((ResValue)entry.getTableEntry().getValue()).getData(),
|
||||||
|
circularReference);
|
||||||
|
}
|
||||||
|
if(!entry.isNull()){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
Iterator<Entry> itr = entryGroup.iterator(true);
|
||||||
|
while (itr.hasNext()){
|
||||||
|
entry = itr.next();
|
||||||
|
if(!isReferenceEntry(entry)){
|
||||||
|
if(!entry.isNull()){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
private boolean isReferenceEntry(Entry entry){
|
||||||
|
if(entry==null || entry.isNull()){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TableEntry<?, ?> tableEntry = entry.getTableEntry();
|
||||||
|
if(tableEntry instanceof ResTableMapEntry){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!(tableEntry instanceof ResTableEntry)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ResTableEntry resTableEntry = (ResTableEntry) tableEntry;
|
||||||
|
ResValue resValue = resTableEntry.getValue();
|
||||||
|
|
||||||
|
ValueType valueType = resValue.getValueType();
|
||||||
|
|
||||||
|
return valueType == ValueType.REFERENCE
|
||||||
|
|| valueType == ValueType.ATTRIBUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.arsc.util;
|
package com.reandroid.arsc.util;
|
||||||
|
|
||||||
|
import com.reandroid.arsc.BuildInfo;
|
||||||
import com.reandroid.arsc.array.SpecTypePairArray;
|
import com.reandroid.arsc.array.SpecTypePairArray;
|
||||||
import com.reandroid.arsc.array.TypeBlockArray;
|
import com.reandroid.arsc.array.TypeBlockArray;
|
||||||
import com.reandroid.arsc.chunk.ChunkType;
|
import com.reandroid.arsc.chunk.ChunkType;
|
||||||
@ -24,26 +25,35 @@ import com.reandroid.arsc.chunk.TypeBlock;
|
|||||||
import com.reandroid.arsc.container.SpecTypePair;
|
import com.reandroid.arsc.container.SpecTypePair;
|
||||||
import com.reandroid.arsc.group.EntryGroup;
|
import com.reandroid.arsc.group.EntryGroup;
|
||||||
import com.reandroid.arsc.header.HeaderBlock;
|
import com.reandroid.arsc.header.HeaderBlock;
|
||||||
import com.reandroid.arsc.io.BlockReader;
|
|
||||||
import com.reandroid.arsc.item.ReferenceItem;
|
import com.reandroid.arsc.item.ReferenceItem;
|
||||||
import com.reandroid.arsc.item.TableString;
|
import com.reandroid.arsc.item.TableString;
|
||||||
import com.reandroid.arsc.pool.TableStringPool;
|
import com.reandroid.arsc.pool.TableStringPool;
|
||||||
import com.reandroid.arsc.value.Entry;
|
import com.reandroid.arsc.value.Entry;
|
||||||
import com.reandroid.arsc.value.ResConfig;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class FrameworkTable extends TableBlock {
|
public class FrameworkTable extends TableBlock {
|
||||||
|
|
||||||
private String mFrameworkTitle;
|
private String frameworkName;
|
||||||
private String mFrameworkName;
|
private int versionCode;
|
||||||
private String mFrameworkVersion;
|
|
||||||
private ResNameMap<EntryGroup> mNameGroupMap;
|
private ResNameMap<EntryGroup> mNameGroupMap;
|
||||||
|
private boolean mOptimized;
|
||||||
|
private boolean mOptimizeChecked;
|
||||||
public FrameworkTable(){
|
public FrameworkTable(){
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy(){
|
||||||
|
ResNameMap<EntryGroup> nameGroupMap = this.mNameGroupMap;
|
||||||
|
if(nameGroupMap!=null){
|
||||||
|
nameGroupMap.clear();
|
||||||
|
}
|
||||||
|
this.frameworkName = null;
|
||||||
|
this.versionCode = 0;
|
||||||
|
super.destroy();
|
||||||
|
}
|
||||||
public int resolveResourceId(String typeName, String entryName){
|
public int resolveResourceId(String typeName, String entryName){
|
||||||
Entry entry = searchEntry(typeName, entryName);
|
Entry entry = searchEntry(typeName, entryName);
|
||||||
if(entry !=null){
|
if(entry !=null){
|
||||||
@ -109,127 +119,89 @@ public class FrameworkTable extends TableBlock {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public String getFrameworkTitle(){
|
public int getVersionCode(){
|
||||||
if(mFrameworkTitle==null){
|
if(versionCode == 0 && isOptimized()){
|
||||||
mFrameworkTitle=loadProperty(PROP_TITLE);
|
String version = loadProperty(PROP_VERSION_CODE);
|
||||||
|
if(version!=null){
|
||||||
|
try{
|
||||||
|
versionCode = Integer.parseInt(version);
|
||||||
|
}catch (NumberFormatException ignored){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return versionCode;
|
||||||
|
}
|
||||||
|
public void setVersionCode(int value){
|
||||||
|
versionCode = value;
|
||||||
|
if(isOptimized()){
|
||||||
|
writeVersionCode(value);
|
||||||
}
|
}
|
||||||
return mFrameworkTitle;
|
|
||||||
}
|
}
|
||||||
public String getFrameworkName(){
|
public String getFrameworkName(){
|
||||||
if(mFrameworkName==null){
|
if(frameworkName == null){
|
||||||
mFrameworkName=loadProperty(PROP_NAME);
|
frameworkName = loadProperty(PROP_NAME);
|
||||||
}
|
}
|
||||||
return mFrameworkName;
|
return frameworkName;
|
||||||
}
|
|
||||||
public String getFrameworkVersion(){
|
|
||||||
if(mFrameworkVersion==null){
|
|
||||||
mFrameworkVersion=loadProperty(PROP_VERSION);
|
|
||||||
}
|
|
||||||
return mFrameworkVersion;
|
|
||||||
}
|
|
||||||
private void setFrameworkTitle(String value){
|
|
||||||
mFrameworkTitle=null;
|
|
||||||
writeProperty(PROP_TITLE, value);
|
|
||||||
}
|
}
|
||||||
public void setFrameworkName(String value){
|
public void setFrameworkName(String value){
|
||||||
mFrameworkName=null;
|
frameworkName = value;
|
||||||
writeProperty(PROP_NAME, value);
|
if(isOptimized()){
|
||||||
}
|
writeProperty(PROP_NAME, value);
|
||||||
public void setFrameworkVersion(String value){
|
|
||||||
mFrameworkVersion=null;
|
|
||||||
writeProperty(PROP_VERSION, value);
|
|
||||||
}
|
|
||||||
public int writeTable(File resourcesArscFile) throws IOException{
|
|
||||||
File dir=resourcesArscFile.getParentFile();
|
|
||||||
if(dir!=null && !dir.exists()){
|
|
||||||
dir.mkdirs();
|
|
||||||
}
|
}
|
||||||
FileOutputStream outputStream=new FileOutputStream(resourcesArscFile, false);
|
|
||||||
return writeTable(outputStream);
|
|
||||||
}
|
}
|
||||||
public int writeTable(OutputStream outputStream) throws IOException{
|
|
||||||
return writeBytes(outputStream);
|
public void optimize(String name, int version){
|
||||||
}
|
mOptimizeChecked = true;
|
||||||
public void readTable(File resourcesArscFile) throws IOException{
|
mOptimized = false;
|
||||||
FileInputStream inputStream=new FileInputStream(resourcesArscFile);
|
|
||||||
readTable(inputStream);
|
|
||||||
}
|
|
||||||
public void readTable(InputStream inputStream) throws IOException{
|
|
||||||
BlockReader reader=new BlockReader(inputStream);
|
|
||||||
super.readBytes(reader);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onReadBytes(BlockReader reader) throws IOException {
|
|
||||||
super.onReadBytes(reader);
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
public void optimize(String frameworkName, String frameworkVersion){
|
|
||||||
Map<Integer, EntryGroup> groupMap=scanAllEntryGroups();
|
Map<Integer, EntryGroup> groupMap=scanAllEntryGroups();
|
||||||
for(EntryGroup group:groupMap.values()){
|
for(EntryGroup group:groupMap.values()){
|
||||||
List<Entry> entryList =getEntriesToRemove(group);
|
List<Entry> entryList = getEntriesToRemove(group);
|
||||||
removeEntries(entryList);
|
removeEntries(entryList);
|
||||||
}
|
}
|
||||||
for(PackageBlock pkg:listPackages()){
|
for(PackageBlock pkg:listPackages()){
|
||||||
clearNonDefaultConfigs(pkg);
|
removeEmptyBlocks(pkg);
|
||||||
}
|
}
|
||||||
for(PackageBlock pkg:listPackages()){
|
for(PackageBlock pkg:listPackages()){
|
||||||
pkg.removeEmpty();
|
pkg.removeEmpty();
|
||||||
pkg.refresh();
|
pkg.refresh();
|
||||||
}
|
}
|
||||||
optimizeTableString();
|
optimizeTableString();
|
||||||
setFrameworkTitle(TITLE_STRING);
|
writeVersionCode(version);
|
||||||
setFrameworkName(frameworkName);
|
mOptimizeChecked = false;
|
||||||
setFrameworkVersion(frameworkVersion);
|
setFrameworkName(name);
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
private void clearNonDefaultConfigs(PackageBlock pkg){
|
private void removeEmptyBlocks(PackageBlock pkg){
|
||||||
SpecTypePairArray specTypePairArray = pkg.getSpecTypePairArray();
|
SpecTypePairArray specTypePairArray = pkg.getSpecTypePairArray();
|
||||||
specTypePairArray.sort();
|
specTypePairArray.sort();
|
||||||
List<SpecTypePair> specTypePairList=new ArrayList<>(specTypePairArray.listItems());
|
List<SpecTypePair> specTypePairList=new ArrayList<>(specTypePairArray.listItems());
|
||||||
for(SpecTypePair specTypePair:specTypePairList){
|
for(SpecTypePair specTypePair:specTypePairList){
|
||||||
clearNonDefaultConfigs(specTypePair);
|
removeEmptyBlocks(specTypePair);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void clearNonDefaultConfigs(SpecTypePair specTypePair){
|
private void removeEmptyBlocks(SpecTypePair specTypePair){
|
||||||
TypeBlockArray typeBlockArray = specTypePair.getTypeBlockArray();
|
TypeBlockArray typeBlockArray = specTypePair.getTypeBlockArray();
|
||||||
if(typeBlockArray.childesCount()<2){
|
if(typeBlockArray.childesCount()<2){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<TypeBlock> typeBlockList=new ArrayList<>(typeBlockArray.listItems());
|
typeBlockArray.removeEmptyBlocks();
|
||||||
TypeBlock defTypeBlock=null;
|
|
||||||
for(TypeBlock typeBlock:typeBlockList){
|
|
||||||
if(defTypeBlock==null){
|
|
||||||
defTypeBlock=typeBlock;
|
|
||||||
}
|
|
||||||
ResConfig config = typeBlock.getResConfig();
|
|
||||||
if(config.isDefault()){
|
|
||||||
defTypeBlock=typeBlock;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(TypeBlock typeBlock:typeBlockList){
|
|
||||||
if(typeBlock==defTypeBlock){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
typeBlockArray.remove(typeBlock);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
private void optimizeTableString(){
|
private void optimizeTableString(){
|
||||||
removeUnusedTableString();
|
removeUnusedTableString();
|
||||||
shrinkTableString();
|
shrinkTableString();
|
||||||
|
getStringPool().getStyleArray().clearChildes();
|
||||||
removeUnusedTableString();
|
removeUnusedTableString();
|
||||||
}
|
}
|
||||||
private void removeUnusedTableString(){
|
private void removeUnusedTableString(){
|
||||||
TableStringPool tableStringPool=getTableStringPool();
|
TableStringPool tableStringPool=getStringPool();
|
||||||
tableStringPool.getStyleArray().clearChildes();
|
|
||||||
tableStringPool.removeUnusedStrings();
|
tableStringPool.removeUnusedStrings();
|
||||||
tableStringPool.refresh();
|
tableStringPool.refresh();
|
||||||
}
|
}
|
||||||
private void shrinkTableString(){
|
private void shrinkTableString(){
|
||||||
TableStringPool tableStringPool=getTableStringPool();
|
TableStringPool tableStringPool=getStringPool();
|
||||||
tableStringPool.getStringsArray().ensureSize(1);
|
tableStringPool.getStringsArray().ensureSize(1);
|
||||||
TableString title=tableStringPool.get(0);
|
TableString title=tableStringPool.get(0);
|
||||||
title.set(PROP_TITLE+":"+TITLE_STRING);
|
title.set(BuildInfo.getRepo());
|
||||||
for(TableString tableString:tableStringPool.getStringsArray().listItems()){
|
for(TableString tableString:tableStringPool.getStringsArray().listItems()){
|
||||||
if(tableString==title){
|
if(tableString==title){
|
||||||
continue;
|
continue;
|
||||||
@ -308,7 +280,7 @@ public class FrameworkTable extends TableBlock {
|
|||||||
if(tableString!=null){
|
if(tableString!=null){
|
||||||
tableString.set(value);
|
tableString.set(value);
|
||||||
}else {
|
}else {
|
||||||
TableStringPool tableStringPool=getTableStringPool();
|
TableStringPool tableStringPool=getStringPool();
|
||||||
tableString=tableStringPool.getOrCreate(value);
|
tableString=tableStringPool.getOrCreate(value);
|
||||||
}
|
}
|
||||||
return tableString;
|
return tableString;
|
||||||
@ -325,7 +297,7 @@ public class FrameworkTable extends TableBlock {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String str=tableString.get().trim();
|
String str=tableString.get().trim();
|
||||||
return str.substring(name.length());
|
return str.substring(name.length()).trim();
|
||||||
}
|
}
|
||||||
private TableString loadPropertyString(String name){
|
private TableString loadPropertyString(String name){
|
||||||
if(name==null){
|
if(name==null){
|
||||||
@ -334,7 +306,7 @@ public class FrameworkTable extends TableBlock {
|
|||||||
if(!name.endsWith(":")){
|
if(!name.endsWith(":")){
|
||||||
name=name+":";
|
name=name+":";
|
||||||
}
|
}
|
||||||
TableStringPool tableStringPool=getTableStringPool();
|
TableStringPool tableStringPool=getStringPool();
|
||||||
int max=PROP_COUNT;
|
int max=PROP_COUNT;
|
||||||
for(int i=0;i<max;i++){
|
for(int i=0;i<max;i++){
|
||||||
TableString tableString=tableStringPool.get(i);
|
TableString tableString=tableStringPool.get(i);
|
||||||
@ -353,7 +325,21 @@ public class FrameworkTable extends TableBlock {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public boolean isOptimized(){
|
public boolean isOptimized(){
|
||||||
return getFrameworkVersion()!=null;
|
if(!mOptimizeChecked){
|
||||||
|
mOptimizeChecked = true;
|
||||||
|
String version = loadProperty(PROP_VERSION_CODE);
|
||||||
|
if(version!=null){
|
||||||
|
try{
|
||||||
|
int v = Integer.parseInt(version);
|
||||||
|
mOptimized = (v!=0);
|
||||||
|
}catch (NumberFormatException ignored){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mOptimized;
|
||||||
|
}
|
||||||
|
private void writeVersionCode(int value){
|
||||||
|
writeProperty(PROP_VERSION_CODE, String.valueOf(value));
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
@ -364,38 +350,7 @@ public class FrameworkTable extends TableBlock {
|
|||||||
if(!isOptimized()){
|
if(!isOptimized()){
|
||||||
return "Unoptimized: "+super.toString();
|
return "Unoptimized: "+super.toString();
|
||||||
}
|
}
|
||||||
StringBuilder builder=new StringBuilder();
|
return getFrameworkName()+'-'+getVersionCode();
|
||||||
builder.append(getClass().getSimpleName());
|
|
||||||
builder.append(": SIZE=").append(headerBlock.getChunkSize());
|
|
||||||
String str=getFrameworkTitle();
|
|
||||||
builder.append("\n");
|
|
||||||
if(str==null){
|
|
||||||
builder.append(PROP_TITLE).append(":null");
|
|
||||||
}else {
|
|
||||||
builder.append(str);
|
|
||||||
}
|
|
||||||
str=getFrameworkName();
|
|
||||||
builder.append("\n ").append(PROP_NAME).append(":");
|
|
||||||
if(str==null){
|
|
||||||
builder.append("null");
|
|
||||||
}else {
|
|
||||||
builder.append(str);
|
|
||||||
}
|
|
||||||
str=getFrameworkVersion();
|
|
||||||
builder.append("\n ").append(PROP_VERSION).append(":");
|
|
||||||
if(str==null){
|
|
||||||
builder.append("null");
|
|
||||||
}else {
|
|
||||||
builder.append(str);
|
|
||||||
}
|
|
||||||
Collection<PackageBlock> allPkg = listPackages();
|
|
||||||
builder.append("\n PACKAGES=").append(allPkg.size());
|
|
||||||
for(PackageBlock packageBlock:allPkg){
|
|
||||||
builder.append("\n ");
|
|
||||||
builder.append(String.format("0x%02x", packageBlock.getId()));
|
|
||||||
builder.append(":").append(packageBlock.getName());
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
public static FrameworkTable load(File file) throws IOException{
|
public static FrameworkTable load(File file) throws IOException{
|
||||||
return load(new FileInputStream(file));
|
return load(new FileInputStream(file));
|
||||||
@ -405,9 +360,9 @@ public class FrameworkTable extends TableBlock {
|
|||||||
frameworkTable.readBytes(inputStream);
|
frameworkTable.readBytes(inputStream);
|
||||||
return frameworkTable;
|
return frameworkTable;
|
||||||
}
|
}
|
||||||
private static final String TITLE_STRING="Framework table";
|
|
||||||
private static final String PROP_TITLE="TITLE";
|
private static final String PROP_NAME = "NAME";
|
||||||
private static final String PROP_NAME="NAME";
|
private static final String PROP_VERSION_CODE = "VERSION_CODE";
|
||||||
private static final String PROP_VERSION="VERSION";
|
private static final String PROP_VERSION_NAME = "VERSION_NAME";
|
||||||
private static final int PROP_COUNT=10;
|
private static final int PROP_COUNT=10;
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.reandroid.common;
|
package com.reandroid.common;
|
||||||
|
|
||||||
|
import com.reandroid.apk.AndroidFrameworks;
|
||||||
import com.reandroid.arsc.util.FrameworkTable;
|
import com.reandroid.arsc.util.FrameworkTable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**Use {@link AndroidFrameworks} */
|
||||||
|
@Deprecated
|
||||||
public class Frameworks {
|
public class Frameworks {
|
||||||
private static FrameworkTable android_table;
|
private static FrameworkTable android_table;
|
||||||
private static boolean load_once;
|
private static boolean load_once;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user