make multi version android frameworks

This commit is contained in:
REAndroid 2023-03-12 16:46:22 -04:00
parent ce53ffd072
commit 22b21d9ccc
4 changed files with 587 additions and 122 deletions

View 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";
}

View 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);
}
}
}

View File

@ -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;
}

View File

@ -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;