From 22b21d9ccc424ce7927d105c4010a31b24bc26a1 Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sun, 12 Mar 2023 16:46:22 -0400 Subject: [PATCH] make multi version android frameworks --- .../com/reandroid/apk/AndroidFrameworks.java | 219 +++++++++++++ .../com/reandroid/apk/FrameworkOptimizer.java | 288 ++++++++++++++++++ .../reandroid/arsc/util/FrameworkTable.java | 199 +++++------- .../java/com/reandroid/common/Frameworks.java | 3 + 4 files changed, 587 insertions(+), 122 deletions(-) create mode 100644 src/main/java/com/reandroid/apk/AndroidFrameworks.java create mode 100644 src/main/java/com/reandroid/apk/FrameworkOptimizer.java diff --git a/src/main/java/com/reandroid/apk/AndroidFrameworks.java b/src/main/java/com/reandroid/apk/AndroidFrameworks.java new file mode 100644 index 0000000..e05cf98 --- /dev/null +++ b/src/main/java/com/reandroid/apk/AndroidFrameworks.java @@ -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 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 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 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 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 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(diffbest)){ + 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 getResourcePaths(){ + if(resource_paths!=null){ + return resource_paths; + } + synchronized (AndroidFrameworks.class){ + resource_paths = scanAvailableResourcePaths(); + return resource_paths; + } + } + private static Map scanAvailableResourcePaths(){ + Map results = new HashMap<>(); + int maxSearch = 50; + for(int version=20; version0){ + 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"; +} diff --git a/src/main/java/com/reandroid/apk/FrameworkOptimizer.java b/src/main/java/com/reandroid/apk/FrameworkOptimizer.java new file mode 100644 index 0000000..143edcc --- /dev/null +++ b/src/main/java/com/reandroid/apk/FrameworkOptimizer.java @@ -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 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 getManifestElementToRemove(ResXmlElement manifest){ + List 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 circularReference = new HashSet<>(); + return getEntryWithValue(tableBlock, resourceId, circularReference); + } + private Entry getEntryWithValue(TableBlock tableBlock, int resourceId, Set 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 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); + } + } +} diff --git a/src/main/java/com/reandroid/arsc/util/FrameworkTable.java b/src/main/java/com/reandroid/arsc/util/FrameworkTable.java index d897920..e4b7118 100755 --- a/src/main/java/com/reandroid/arsc/util/FrameworkTable.java +++ b/src/main/java/com/reandroid/arsc/util/FrameworkTable.java @@ -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 mNameGroupMap; + private boolean mOptimized; + private boolean mOptimizeChecked; public FrameworkTable(){ super(); } + @Override + public void destroy(){ + ResNameMap 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 groupMap=scanAllEntryGroups(); for(EntryGroup group:groupMap.values()){ - List entryList =getEntriesToRemove(group); + List 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 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 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 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; } diff --git a/src/main/java/com/reandroid/common/Frameworks.java b/src/main/java/com/reandroid/common/Frameworks.java index 949d0ad..ea8c5f9 100755 --- a/src/main/java/com/reandroid/common/Frameworks.java +++ b/src/main/java/com/reandroid/common/Frameworks.java @@ -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;