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;
|
||||
|
||||
import com.reandroid.arsc.BuildInfo;
|
||||
import com.reandroid.arsc.array.SpecTypePairArray;
|
||||
import com.reandroid.arsc.array.TypeBlockArray;
|
||||
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.group.EntryGroup;
|
||||
import com.reandroid.arsc.header.HeaderBlock;
|
||||
import com.reandroid.arsc.io.BlockReader;
|
||||
import com.reandroid.arsc.item.ReferenceItem;
|
||||
import com.reandroid.arsc.item.TableString;
|
||||
import com.reandroid.arsc.pool.TableStringPool;
|
||||
import com.reandroid.arsc.value.Entry;
|
||||
import com.reandroid.arsc.value.ResConfig;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class FrameworkTable extends TableBlock {
|
||||
|
||||
private String mFrameworkTitle;
|
||||
private String mFrameworkName;
|
||||
private String mFrameworkVersion;
|
||||
private String frameworkName;
|
||||
private int versionCode;
|
||||
private ResNameMap<EntryGroup> mNameGroupMap;
|
||||
private boolean mOptimized;
|
||||
private boolean mOptimizeChecked;
|
||||
public FrameworkTable(){
|
||||
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){
|
||||
Entry entry = searchEntry(typeName, entryName);
|
||||
if(entry !=null){
|
||||
@ -109,127 +119,89 @@ public class FrameworkTable extends TableBlock {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String getFrameworkTitle(){
|
||||
if(mFrameworkTitle==null){
|
||||
mFrameworkTitle=loadProperty(PROP_TITLE);
|
||||
public int getVersionCode(){
|
||||
if(versionCode == 0 && isOptimized()){
|
||||
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(){
|
||||
if(mFrameworkName==null){
|
||||
mFrameworkName=loadProperty(PROP_NAME);
|
||||
if(frameworkName == null){
|
||||
frameworkName = loadProperty(PROP_NAME);
|
||||
}
|
||||
return mFrameworkName;
|
||||
}
|
||||
public String getFrameworkVersion(){
|
||||
if(mFrameworkVersion==null){
|
||||
mFrameworkVersion=loadProperty(PROP_VERSION);
|
||||
}
|
||||
return mFrameworkVersion;
|
||||
}
|
||||
private void setFrameworkTitle(String value){
|
||||
mFrameworkTitle=null;
|
||||
writeProperty(PROP_TITLE, value);
|
||||
return frameworkName;
|
||||
}
|
||||
public void setFrameworkName(String value){
|
||||
mFrameworkName=null;
|
||||
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();
|
||||
frameworkName = value;
|
||||
if(isOptimized()){
|
||||
writeProperty(PROP_NAME, value);
|
||||
}
|
||||
FileOutputStream outputStream=new FileOutputStream(resourcesArscFile, false);
|
||||
return writeTable(outputStream);
|
||||
}
|
||||
public int writeTable(OutputStream outputStream) throws IOException{
|
||||
return writeBytes(outputStream);
|
||||
}
|
||||
public void readTable(File resourcesArscFile) throws IOException{
|
||||
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){
|
||||
|
||||
public void optimize(String name, int version){
|
||||
mOptimizeChecked = true;
|
||||
mOptimized = false;
|
||||
Map<Integer, EntryGroup> groupMap=scanAllEntryGroups();
|
||||
for(EntryGroup group:groupMap.values()){
|
||||
List<Entry> entryList =getEntriesToRemove(group);
|
||||
List<Entry> entryList = getEntriesToRemove(group);
|
||||
removeEntries(entryList);
|
||||
}
|
||||
for(PackageBlock pkg:listPackages()){
|
||||
clearNonDefaultConfigs(pkg);
|
||||
removeEmptyBlocks(pkg);
|
||||
}
|
||||
for(PackageBlock pkg:listPackages()){
|
||||
pkg.removeEmpty();
|
||||
pkg.refresh();
|
||||
}
|
||||
optimizeTableString();
|
||||
setFrameworkTitle(TITLE_STRING);
|
||||
setFrameworkName(frameworkName);
|
||||
setFrameworkVersion(frameworkVersion);
|
||||
writeVersionCode(version);
|
||||
mOptimizeChecked = false;
|
||||
setFrameworkName(name);
|
||||
refresh();
|
||||
}
|
||||
private void clearNonDefaultConfigs(PackageBlock pkg){
|
||||
private void removeEmptyBlocks(PackageBlock pkg){
|
||||
SpecTypePairArray specTypePairArray = pkg.getSpecTypePairArray();
|
||||
specTypePairArray.sort();
|
||||
List<SpecTypePair> specTypePairList=new ArrayList<>(specTypePairArray.listItems());
|
||||
for(SpecTypePair specTypePair:specTypePairList){
|
||||
clearNonDefaultConfigs(specTypePair);
|
||||
removeEmptyBlocks(specTypePair);
|
||||
}
|
||||
}
|
||||
private void clearNonDefaultConfigs(SpecTypePair specTypePair){
|
||||
private void removeEmptyBlocks(SpecTypePair specTypePair){
|
||||
TypeBlockArray typeBlockArray = specTypePair.getTypeBlockArray();
|
||||
if(typeBlockArray.childesCount()<2){
|
||||
return;
|
||||
}
|
||||
List<TypeBlock> typeBlockList=new ArrayList<>(typeBlockArray.listItems());
|
||||
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);
|
||||
}
|
||||
typeBlockArray.removeEmptyBlocks();
|
||||
}
|
||||
private void optimizeTableString(){
|
||||
removeUnusedTableString();
|
||||
shrinkTableString();
|
||||
getStringPool().getStyleArray().clearChildes();
|
||||
removeUnusedTableString();
|
||||
}
|
||||
private void removeUnusedTableString(){
|
||||
TableStringPool tableStringPool=getTableStringPool();
|
||||
tableStringPool.getStyleArray().clearChildes();
|
||||
TableStringPool tableStringPool=getStringPool();
|
||||
tableStringPool.removeUnusedStrings();
|
||||
tableStringPool.refresh();
|
||||
}
|
||||
private void shrinkTableString(){
|
||||
TableStringPool tableStringPool=getTableStringPool();
|
||||
TableStringPool tableStringPool=getStringPool();
|
||||
tableStringPool.getStringsArray().ensureSize(1);
|
||||
TableString title=tableStringPool.get(0);
|
||||
title.set(PROP_TITLE+":"+TITLE_STRING);
|
||||
title.set(BuildInfo.getRepo());
|
||||
for(TableString tableString:tableStringPool.getStringsArray().listItems()){
|
||||
if(tableString==title){
|
||||
continue;
|
||||
@ -308,7 +280,7 @@ public class FrameworkTable extends TableBlock {
|
||||
if(tableString!=null){
|
||||
tableString.set(value);
|
||||
}else {
|
||||
TableStringPool tableStringPool=getTableStringPool();
|
||||
TableStringPool tableStringPool=getStringPool();
|
||||
tableString=tableStringPool.getOrCreate(value);
|
||||
}
|
||||
return tableString;
|
||||
@ -325,7 +297,7 @@ public class FrameworkTable extends TableBlock {
|
||||
return null;
|
||||
}
|
||||
String str=tableString.get().trim();
|
||||
return str.substring(name.length());
|
||||
return str.substring(name.length()).trim();
|
||||
}
|
||||
private TableString loadPropertyString(String name){
|
||||
if(name==null){
|
||||
@ -334,7 +306,7 @@ public class FrameworkTable extends TableBlock {
|
||||
if(!name.endsWith(":")){
|
||||
name=name+":";
|
||||
}
|
||||
TableStringPool tableStringPool=getTableStringPool();
|
||||
TableStringPool tableStringPool=getStringPool();
|
||||
int max=PROP_COUNT;
|
||||
for(int i=0;i<max;i++){
|
||||
TableString tableString=tableStringPool.get(i);
|
||||
@ -353,7 +325,21 @@ public class FrameworkTable extends TableBlock {
|
||||
return null;
|
||||
}
|
||||
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
|
||||
public String toString(){
|
||||
@ -364,38 +350,7 @@ public class FrameworkTable extends TableBlock {
|
||||
if(!isOptimized()){
|
||||
return "Unoptimized: "+super.toString();
|
||||
}
|
||||
StringBuilder builder=new StringBuilder();
|
||||
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();
|
||||
return getFrameworkName()+'-'+getVersionCode();
|
||||
}
|
||||
public static FrameworkTable load(File file) throws IOException{
|
||||
return load(new FileInputStream(file));
|
||||
@ -405,9 +360,9 @@ public class FrameworkTable extends TableBlock {
|
||||
frameworkTable.readBytes(inputStream);
|
||||
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_VERSION="VERSION";
|
||||
|
||||
private static final String PROP_NAME = "NAME";
|
||||
private static final String PROP_VERSION_CODE = "VERSION_CODE";
|
||||
private static final String PROP_VERSION_NAME = "VERSION_NAME";
|
||||
private static final int PROP_COUNT=10;
|
||||
}
|
||||
|
@ -15,11 +15,14 @@
|
||||
*/
|
||||
package com.reandroid.common;
|
||||
|
||||
import com.reandroid.apk.AndroidFrameworks;
|
||||
import com.reandroid.arsc.util.FrameworkTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**Use {@link AndroidFrameworks} */
|
||||
@Deprecated
|
||||
public class Frameworks {
|
||||
private static FrameworkTable android_table;
|
||||
private static boolean load_once;
|
||||
|
Loading…
x
Reference in New Issue
Block a user