Initial commit
commit
631582fbab
|
@ -0,0 +1,15 @@
|
|||
*.iml
|
||||
.gradle
|
||||
.idea
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
|
@ -0,0 +1,41 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
allprojects {
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'eclipse'
|
||||
version = '1.0.1'
|
||||
}
|
||||
|
||||
defaultTasks('clean','distZip')
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'maven'
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
[compileJava, compileTestJava]*.options.collect {options ->options.encoding = 'UTF-8'}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes("Implementation-Title": project.name,
|
||||
"Implementation-Version": project.version,
|
||||
"Build-Time": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
"Build-Number": System.env.BUILD_NUMBER?System.env.BUILD_NUMBER:"-1",
|
||||
)
|
||||
}
|
||||
from (project.parent.projectDir) {
|
||||
include 'NOTICE.txt'
|
||||
include 'LICENSE.txt'
|
||||
into('META-INF')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
|
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
#Mon Jan 27 13:23:47 CST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4-rc-1-all.zip
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,31 @@
|
|||
apply plugin: 'java-library'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
jar{
|
||||
baseName = "ManifestEditor"
|
||||
manifest {
|
||||
attributes 'Main-Class': 'com.wind.meditor.ManifestEditorMain'
|
||||
}
|
||||
//添加将引用的jar的源码打入最终的jar
|
||||
from {
|
||||
(configurations.runtime).collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
}
|
||||
}
|
||||
|
||||
from fileTree(dir:'src/main', includes: ['assets/**'])
|
||||
|
||||
//排除引用的jar中的签名信息
|
||||
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
|
||||
}
|
||||
|
||||
//添加源码中引入的非代码文件,例如资源等
|
||||
sourceSets.main.resources {
|
||||
srcDirs = [
|
||||
"src/main/java",
|
||||
];
|
||||
include "**/*.*"
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,243 @@
|
|||
package com.wind.meditor;
|
||||
|
||||
import com.wind.meditor.base.BaseCommand;
|
||||
import com.wind.meditor.core.ApkSigner;
|
||||
import com.wind.meditor.core.FileProcesser;
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
import com.wind.meditor.utils.FileTypeUtils;
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.NodeValue;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class ManifestEditorMain extends BaseCommand {
|
||||
|
||||
private static final String MULTI_NAME_SEPERATER = ":";
|
||||
private static final String ANDROID_NAMESPACE = "android-";
|
||||
|
||||
@Opt(opt = "o", longOpt = "output", description = "output modified xml or apk file, default is " +
|
||||
"$source_apk_dir/[file-name]-new.xml or [file-name]-new-unsigned.apk", argName = "output-file")
|
||||
private String output; // the output file path
|
||||
|
||||
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
|
||||
private boolean forceOverwrite = false;
|
||||
|
||||
@Opt(opt = "s", longOpt = "signApk", hasArg = false, description = "use jarsigner to sign the output apk file")
|
||||
private boolean needSignApk = false;
|
||||
|
||||
@Opt(opt = "pkg", longOpt = "packageName", description = "set the android manifest package name",
|
||||
argName = "new-package-name")
|
||||
private String packageName;
|
||||
|
||||
@Opt(opt = "vc", longOpt = "versionCode", description = "set the app version code",
|
||||
argName = "new-version-code")
|
||||
private int versionCode;
|
||||
|
||||
@Opt(opt = "vn", longOpt = "versionName", description = "set the app version name",
|
||||
argName = "new-version-name")
|
||||
private String versionName;
|
||||
|
||||
@Opt(opt = "d", longOpt = "debuggable", description = "set 1 to make the app debuggable = true, " +
|
||||
"set 0 to make the app debuggable = false", argName = "0 or 1")
|
||||
private int debuggable = -1;
|
||||
|
||||
@Opt(opt = "an", longOpt = "applicationName", description = "set the app entry application name",
|
||||
argName = "new-application-name")
|
||||
private String applicationName;
|
||||
|
||||
@Opt(opt = "up", longOpt = "usesPermission", description = "add the app uses permission " +
|
||||
"name to the manifest file, multi option is supported", argName = "uses-permission-name")
|
||||
private List<String> usesPermissionList = new ArrayList<>();
|
||||
|
||||
@Opt(opt = "ma", longOpt = "manifestAttribute", description = "set the app manifest attribute, " +
|
||||
" name and value should be separated by " + MULTI_NAME_SEPERATER +
|
||||
" , if name is in android namespace, prefix \"" + ANDROID_NAMESPACE + "\" should be set" +
|
||||
", multi option is supported", argName = "manifest-attribute-name-value")
|
||||
private List<String> manifestAttributeList = new ArrayList<>();
|
||||
|
||||
@Opt(opt = "aa", longOpt = "applicationAttribute", description = "set the application attribute, " +
|
||||
" name and value should be separated by " + MULTI_NAME_SEPERATER +
|
||||
" , if name is in android namespace, prefix \"" + ANDROID_NAMESPACE + "\" should be set" +
|
||||
", multi option is supported", argName = "application-attribute-name-value")
|
||||
private List<String> applicationAttributeList = new ArrayList<>();
|
||||
|
||||
public static void main(String... args) {
|
||||
new ManifestEditorMain().doMain(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCommandLine() throws Exception {
|
||||
if (remainingArgs.length != 1) {
|
||||
if (remainingArgs.length == 0) {
|
||||
Log.e("Please choose one xml or apk file you want to process. ");
|
||||
}
|
||||
if (remainingArgs.length > 1) {
|
||||
Log.e("This tool can only used with one xml or apk file.");
|
||||
}
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
String srcFilePath = remainingArgs[0];
|
||||
File srcFile = new File(srcFilePath);
|
||||
|
||||
if (!srcFile.exists()) {
|
||||
Log.e(String.format("input file %s do not exist, please check it!", srcFilePath));
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isMainfestFile = FileTypeUtils.isAndroidManifestFile(srcFilePath);
|
||||
boolean isApkFile = false;
|
||||
if (!isMainfestFile) {
|
||||
isApkFile = FileTypeUtils.isApkFile(srcFilePath);
|
||||
}
|
||||
|
||||
if (!isMainfestFile && !isApkFile) {
|
||||
Log.e("input file should be manifest file or apk file !!!");
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
String signedApkPath = "";
|
||||
|
||||
if (output == null || output.length() == 0) {
|
||||
if (isMainfestFile) {
|
||||
output = getBaseName(srcFilePath) + "-new.xml";
|
||||
}
|
||||
if (isApkFile) {
|
||||
output = getBaseName(srcFilePath) + "-unsigned.apk";
|
||||
if (needSignApk) {
|
||||
signedApkPath = getBaseName(srcFilePath) + "-signed.apk";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isApkFile && needSignApk) {
|
||||
signedApkPath = getBaseName(output) + "-signed.apk";
|
||||
}
|
||||
}
|
||||
|
||||
File outputFile = new File(output);
|
||||
if (outputFile.exists() && !forceOverwrite) {
|
||||
Log.e(output + " exists, use --force to overwrite the output file");
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i("output file path --> " + output);
|
||||
|
||||
ModificationProperty modificationProperty = composeProperty();
|
||||
|
||||
if (isMainfestFile) {
|
||||
Log.i("Start to process manifest file ");
|
||||
FileProcesser.processManifestFile(srcFilePath, output, modificationProperty);
|
||||
} else if (isApkFile) {
|
||||
Log.i("Start to process apk.");
|
||||
FileProcesser.processApkFile(srcFilePath, output, modificationProperty);
|
||||
|
||||
if (needSignApk) {
|
||||
Log.i("Start to sign the apk.");
|
||||
|
||||
String parentPath = null;
|
||||
String keyStoreFilePath = null;
|
||||
File parentFile = new File(output).getParentFile();
|
||||
|
||||
if (parentFile != null) {
|
||||
parentPath = parentFile.getAbsolutePath();
|
||||
keyStoreFilePath = parentPath + File.separator + "keystore";
|
||||
} else {
|
||||
// 当前命令行所在的目录
|
||||
keyStoreFilePath = "keystore";
|
||||
}
|
||||
Log.d(" parentPath = " + parentPath + " keyStoreFilePath = " + keyStoreFilePath);
|
||||
|
||||
Log.i(" output unsigned apk path = " + output);
|
||||
Log.i(" output signed apk path = " + signedApkPath);
|
||||
|
||||
|
||||
// cannot use File.separator to seperate assets/new_keystore,or IOException is thrown on window os
|
||||
Utils.copyFileFromJar("assets/new_keystore", keyStoreFilePath);
|
||||
|
||||
ApkSigner.signApk(output, keyStoreFilePath, signedApkPath);
|
||||
|
||||
// delete the keystore file finally
|
||||
File keyStoreFile = new File(keyStoreFilePath);
|
||||
if (keyStoreFile.exists()) {
|
||||
keyStoreFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModificationProperty composeProperty() {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
if (!Utils.isNullOrEmpty(packageName)) {
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, packageName).setNamespace(null));
|
||||
}
|
||||
|
||||
if (versionCode > 0) {
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, versionCode));
|
||||
}
|
||||
|
||||
if (!Utils.isNullOrEmpty(versionName)) {
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, versionName));
|
||||
}
|
||||
|
||||
if (debuggable >= 0) {
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggable != 0));
|
||||
}
|
||||
|
||||
if (!Utils.isNullOrEmpty(applicationName)) {
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, applicationName));
|
||||
}
|
||||
|
||||
for (String permission : usesPermissionList) {
|
||||
property.addUsesPermission(permission);
|
||||
}
|
||||
|
||||
for (String manfestAttr : manifestAttributeList) {
|
||||
String[] nameValue = manfestAttr.split(MULTI_NAME_SEPERATER);
|
||||
if (nameValue.length == 2) {
|
||||
if (nameValue[0].trim().startsWith(ANDROID_NAMESPACE)) {
|
||||
property.addManifestAttribute(new AttributeItem(
|
||||
nameValue[0].trim().substring(ANDROID_NAMESPACE.length()), nameValue[1].trim()));
|
||||
} else {
|
||||
property.addManifestAttribute(new AttributeItem(nameValue[0].trim(), nameValue[1].trim()).setNamespace(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String applicationAttr : applicationAttributeList) {
|
||||
String[] nameValue = applicationAttr.split(MULTI_NAME_SEPERATER);
|
||||
if (nameValue.length == 2) {
|
||||
if (nameValue[0].trim().startsWith(ANDROID_NAMESPACE)) {
|
||||
property.addApplicationAttribute(new AttributeItem(
|
||||
nameValue[0].trim().substring(ANDROID_NAMESPACE.length()), nameValue[1].trim()));
|
||||
} else {
|
||||
property.addApplicationAttribute(new AttributeItem(nameValue[0].trim(), nameValue[1].trim()).setNamespace(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, "wind.new.pkg.name111").setNamespace(null))
|
||||
// .addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1))
|
||||
// .addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_NAME, "1123"))
|
||||
// .addUsesPermission("android.permission.READ_EXTERNAL_STORAGE")
|
||||
// .addUsesPermission("android.permission.WRITE_EXTERNAL_STORAGE")
|
||||
// .addMetaData(new ModificationProperty.MetaData("aa", "11"))
|
||||
// .addMetaData(new ModificationProperty.MetaData("aa", "22"))
|
||||
// .addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, false))
|
||||
// .addApplicationAttribute(new AttributeItem(NodeValue.Application.NAME, "my.app.name.MyTestApplication"))
|
||||
// .addApplicationAttribute(new AttributeItem("appComponentFactory", "my.app.name.MyTestApplication111"));
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,477 @@
|
|||
package com.wind.meditor.base;
|
||||
|
||||
import com.wind.meditor.utils.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public abstract class BaseCommand {
|
||||
|
||||
private String onlineHelp;
|
||||
|
||||
protected Map<String, Option> optMap = new HashMap<String, Option>();
|
||||
|
||||
@Opt(opt = "h", longOpt = "help", hasArg = false, description = "Print this help message")
|
||||
private boolean printHelp = false;
|
||||
|
||||
protected String remainingArgs[];
|
||||
protected String orginalArgs[];
|
||||
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.FIELD })
|
||||
static public @interface Opt {
|
||||
String argName() default "";
|
||||
|
||||
String description() default "";
|
||||
|
||||
boolean hasArg() default true;
|
||||
|
||||
String longOpt() default "";
|
||||
|
||||
String opt() default "";
|
||||
|
||||
boolean required() default false;
|
||||
}
|
||||
|
||||
static protected class Option implements Comparable<Option> {
|
||||
public String argName = "arg";
|
||||
public String description;
|
||||
public Field field;
|
||||
public boolean hasArg = true;
|
||||
public String longOpt;
|
||||
public String opt;
|
||||
public boolean required = false;
|
||||
|
||||
@Override
|
||||
public int compareTo(Option o) {
|
||||
int result = s(this.opt, o.opt);
|
||||
if (result == 0) {
|
||||
result = s(this.longOpt, o.longOpt);
|
||||
if (result == 0) {
|
||||
result = s(this.argName, o.argName);
|
||||
if (result == 0) {
|
||||
result = s(this.description, o.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int s(String a, String b) {
|
||||
if (a != null && b != null) {
|
||||
return a.compareTo(b);
|
||||
} else if (a != null) {
|
||||
return 1;
|
||||
} else if (b != null) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String getOptAndLongOpt() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean havePrev = false;
|
||||
if (opt != null && opt.length() > 0) {
|
||||
sb.append("-").append(opt);
|
||||
havePrev = true;
|
||||
}
|
||||
if (longOpt != null && longOpt.length() > 0) {
|
||||
if (havePrev) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append("--").append(longOpt);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Retention(value = RetentionPolicy.RUNTIME)
|
||||
@Target(value = { ElementType.TYPE })
|
||||
static public @interface Syntax {
|
||||
|
||||
String cmd();
|
||||
|
||||
String desc() default "";
|
||||
|
||||
String onlineHelp() default "";
|
||||
|
||||
String syntax() default "";
|
||||
}
|
||||
|
||||
public void doMain(String... args) {
|
||||
try {
|
||||
initOptions();
|
||||
parseSetArgs(args);
|
||||
doCommandLine();
|
||||
} catch (HelpException e) {
|
||||
String msg = e.getMessage();
|
||||
if (msg != null && msg.length() > 0) {
|
||||
System.err.println("ERROR: " + msg);
|
||||
}
|
||||
usage();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void doCommandLine() throws Exception;
|
||||
|
||||
protected String getVersionString() {
|
||||
return getClass().getPackage().getImplementationVersion();
|
||||
}
|
||||
|
||||
protected void initOptions() {
|
||||
initOptionFromClass(this.getClass());
|
||||
}
|
||||
|
||||
protected void initOptionFromClass(Class<?> clz) {
|
||||
if (clz == null) {
|
||||
return;
|
||||
} else {
|
||||
initOptionFromClass(clz.getSuperclass());
|
||||
}
|
||||
|
||||
Syntax syntax = clz.getAnnotation(Syntax.class);
|
||||
if (syntax != null) {
|
||||
this.onlineHelp = syntax.onlineHelp();
|
||||
}
|
||||
|
||||
Field[] fs = clz.getDeclaredFields();
|
||||
for (Field f : fs) {
|
||||
Opt opt = f.getAnnotation(Opt.class);
|
||||
if (opt != null) {
|
||||
f.setAccessible(true);
|
||||
Option option = new Option();
|
||||
option.field = f;
|
||||
option.description = opt.description();
|
||||
option.hasArg = opt.hasArg();
|
||||
option.required = opt.required();
|
||||
if ("".equals(opt.longOpt()) && "".equals(opt.opt())) { // into automode
|
||||
option.longOpt = fromCamel(f.getName());
|
||||
if (f.getType().equals(boolean.class)) {
|
||||
option.hasArg=false;
|
||||
try {
|
||||
if (f.getBoolean(this)) {
|
||||
throw new RuntimeException("the value of " + f +
|
||||
" must be false, as it is declared as no args");
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
checkConflict(option, "--" + option.longOpt);
|
||||
continue;
|
||||
}
|
||||
if (!opt.hasArg()) {
|
||||
if (!f.getType().equals(boolean.class)) {
|
||||
throw new RuntimeException("the type of " + f
|
||||
+ " must be boolean, as it is declared as no args");
|
||||
}
|
||||
|
||||
try {
|
||||
if (f.getBoolean(this)) {
|
||||
throw new RuntimeException("the value of " + f +
|
||||
" must be false, as it is declared as no args");
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
boolean haveLongOpt = false;
|
||||
if (!"".equals(opt.longOpt())) {
|
||||
option.longOpt = opt.longOpt();
|
||||
checkConflict(option, "--" + option.longOpt);
|
||||
haveLongOpt = true;
|
||||
}
|
||||
if (!"".equals(opt.argName())) {
|
||||
option.argName = opt.argName();
|
||||
}
|
||||
if (!"".equals(opt.opt())) {
|
||||
option.opt = opt.opt();
|
||||
checkConflict(option, "-" + option.opt);
|
||||
} else {
|
||||
if (!haveLongOpt) {
|
||||
throw new RuntimeException("opt or longOpt is not set in @Opt(...) " + f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConflict(Option option, String key) {
|
||||
if (optMap.containsKey(key)) {
|
||||
Option preOption = optMap.get(key);
|
||||
throw new RuntimeException(String.format("[@Opt(...) %s] conflict with [@Opt(...) %s]",
|
||||
preOption.field.toString(), option.field
|
||||
));
|
||||
}
|
||||
optMap.put(key, option);
|
||||
}
|
||||
|
||||
private static String fromCamel(String name) {
|
||||
if (name.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] charArray = name.toCharArray();
|
||||
sb.append(Character.toLowerCase(charArray[0]));
|
||||
for (int i = 1; i < charArray.length; i++) {
|
||||
char c = charArray[i];
|
||||
if (Character.isUpperCase(c)) {
|
||||
sb.append("-").append(Character.toLowerCase(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected void parseSetArgs(String... args) throws IllegalArgumentException, IllegalAccessException {
|
||||
this.orginalArgs = args;
|
||||
List<String> remainsOptions = new ArrayList<String>();
|
||||
Set<Option> requiredOpts = collectRequriedOptions(optMap);
|
||||
Option needArgOpt = null;
|
||||
for (String s : args) {
|
||||
if (needArgOpt != null) {
|
||||
Field field = needArgOpt.field;
|
||||
Class clazz = field.getType();
|
||||
if (clazz.equals(List.class)) {
|
||||
try {
|
||||
List<Object> object = ((List<Object>) field.get(this));
|
||||
|
||||
// 获取List对象的泛型类型
|
||||
ParameterizedType listGenericType = (ParameterizedType) field.getGenericType();
|
||||
Type[] listActualTypeArguments = listGenericType.getActualTypeArguments();
|
||||
Class typeClazz = (Class) listActualTypeArguments[0];
|
||||
object.add(convert(s, typeClazz));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
field.set(this, convert(s, clazz));
|
||||
}
|
||||
needArgOpt = null;
|
||||
} else if (s.startsWith("-")) {// its a short or long option
|
||||
Option opt = optMap.get(s);
|
||||
requiredOpts.remove(opt);
|
||||
if (opt == null) {
|
||||
System.err.println("ERROR: Unrecognized option: " + s);
|
||||
throw new HelpException();
|
||||
} else {
|
||||
if (opt.hasArg) {
|
||||
needArgOpt = opt;
|
||||
} else {
|
||||
opt.field.set(this, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remainsOptions.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (needArgOpt != null) {
|
||||
System.err.println("ERROR: Option " + needArgOpt.getOptAndLongOpt() + " need an argument value");
|
||||
throw new HelpException();
|
||||
}
|
||||
this.remainingArgs = remainsOptions.toArray(new String[remainsOptions.size()]);
|
||||
if (this.printHelp) {
|
||||
throw new HelpException();
|
||||
}
|
||||
if (!requiredOpts.isEmpty()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ERROR: Options: ");
|
||||
boolean first = true;
|
||||
for (Option option : requiredOpts) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(option.getOptAndLongOpt());
|
||||
}
|
||||
sb.append(" is required");
|
||||
System.err.println(sb.toString());
|
||||
throw new HelpException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected Object convert(String value, Class type) {
|
||||
if (type.equals(String.class)) {
|
||||
return value;
|
||||
}
|
||||
if (type.equals(int.class) || type.equals(Integer.class)) {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
if (type.equals(long.class) || type.equals(Long.class)) {
|
||||
return Long.parseLong(value);
|
||||
}
|
||||
if (type.equals(float.class) || type.equals(Float.class)) {
|
||||
return Float.parseFloat(value);
|
||||
}
|
||||
if (type.equals(double.class) || type.equals(Double.class)) {
|
||||
return Double.parseDouble(value);
|
||||
}
|
||||
if (type.equals(boolean.class) || type.equals(Boolean.class)) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
if (type.equals(File.class)) {
|
||||
return new File(value);
|
||||
}
|
||||
if (type.equals(Path.class)) {
|
||||
return new File(value).toPath();
|
||||
}
|
||||
try {
|
||||
type.asSubclass(Enum.class);
|
||||
return Enum.valueOf(type, value);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
throw new RuntimeException("can't convert [" + value + "] to type " + type);
|
||||
}
|
||||
|
||||
private Set<Option> collectRequriedOptions(Map<String, Option> optMap) {
|
||||
Set<Option> options = new HashSet<Option>();
|
||||
for (Map.Entry<String, Option> e : optMap.entrySet()) {
|
||||
Option option = e.getValue();
|
||||
if (option.required) {
|
||||
options.add(option);
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
protected static class HelpException extends RuntimeException {
|
||||
|
||||
public HelpException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HelpException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void usage() {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err, StandardCharsets.UTF_8), true);
|
||||
|
||||
final int maxLength = 80;
|
||||
final int maxPaLength = 40;
|
||||
// out.println(this.cmdName + " -- " + desc);
|
||||
// out.println("usage: " + this.cmdName + " " + cmdLineSyntax);
|
||||
if (this.optMap.size() > 0) {
|
||||
out.println("options:");
|
||||
}
|
||||
// [PART.A.........][Part.B
|
||||
// .-a,--aa.<arg>...desc1
|
||||
// .................desc2
|
||||
// .-b,--bb
|
||||
TreeSet<Option> options = new TreeSet<Option>(this.optMap.values());
|
||||
int palength = -1;
|
||||
for (Option option : options) {
|
||||
int pa = 4 + option.getOptAndLongOpt().length();
|
||||
if (option.hasArg) {
|
||||
pa += 3 + option.argName.length();
|
||||
}
|
||||
if (pa < maxPaLength) {
|
||||
if (pa > palength) {
|
||||
palength = pa;
|
||||
}
|
||||
}
|
||||
}
|
||||
int pblength = maxLength - palength;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Option option : options) {
|
||||
sb.setLength(0);
|
||||
sb.append(" ").append(option.getOptAndLongOpt());
|
||||
if (option.hasArg) {
|
||||
sb.append(" <").append(option.argName).append(">");
|
||||
}
|
||||
String desc = option.description;
|
||||
if (desc == null || desc.length() == 0) {// no description
|
||||
out.println(sb);
|
||||
} else {
|
||||
for (int i = palength - sb.length(); i > 0; i--) {
|
||||
sb.append(' ');
|
||||
}
|
||||
if (sb.length() > maxPaLength) {// to huge part A
|
||||
out.println(sb);
|
||||
sb.setLength(0);
|
||||
for (int i = 0; i < palength; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
int nextStart = 0;
|
||||
while (nextStart < desc.length()) {
|
||||
if (desc.length() - nextStart < pblength) {// can put in one line
|
||||
sb.append(desc.substring(nextStart));
|
||||
out.println(sb);
|
||||
nextStart = desc.length();
|
||||
sb.setLength(0);
|
||||
} else {
|
||||
sb.append(desc.substring(nextStart, nextStart + pblength));
|
||||
out.println(sb);
|
||||
nextStart += pblength;
|
||||
sb.setLength(0);
|
||||
if (nextStart < desc.length()) {
|
||||
for (int i = 0; i < palength; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
out.println(sb);
|
||||
sb.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
String ver = getVersionString();
|
||||
if (ver != null && !"".equals(ver)) {
|
||||
out.println("version: " + ver);
|
||||
}
|
||||
if (onlineHelp != null && !"".equals(onlineHelp)) {
|
||||
if (onlineHelp.length() + "online help: ".length() > maxLength) {
|
||||
out.println("online help: ");
|
||||
out.println(onlineHelp);
|
||||
} else {
|
||||
out.println("online help: " + onlineHelp);
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public static String getBaseName(String fn) {
|
||||
int x = fn.lastIndexOf('.');
|
||||
return x >= 0 ? fn.substring(0, x) : fn;
|
||||
}
|
||||
|
||||
// 获取文件不包含后缀的名称
|
||||
public static String getBaseName(File fn) {
|
||||
return getBaseName(fn.getName());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.wind.meditor.core;
|
||||
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.ShellCmdUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class ApkSigner {
|
||||
|
||||
public static void signApk(String apkPath, String keyStorePath, String signedApkPath) {
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
File keystoreFile = new File(keyStorePath);
|
||||
if (keystoreFile.exists()) {
|
||||
StringBuilder signCmd;
|
||||
signCmd = new StringBuilder("jarsigner ");
|
||||
signCmd.append(" -keystore ")
|
||||
.append(keyStorePath)
|
||||
.append(" -storepass ")
|
||||
.append("manifest_editor")
|
||||
.append(" -signedjar ")
|
||||
.append(" " + signedApkPath + " ")
|
||||
.append(" " + apkPath + " ")
|
||||
.append(" -digestalg SHA1 -sigalg SHA1withRSA ")
|
||||
.append(" key0 ");
|
||||
String result = ShellCmdUtil.execCmd(signCmd.toString(), null);
|
||||
Log.i(" sign apk time is : " + ((System.currentTimeMillis() - time)) +
|
||||
"ms\n\n" + " result=" + result);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.i("sign apk failed, result ==> " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package com.wind.meditor.core;
|
||||
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class FileProcesser {
|
||||
|
||||
public static void processApkFile(String srcApkPath, String dstApkPath, ModificationProperty property) {
|
||||
FileOutputStream outputStream = null;
|
||||
ZipOutputStream zipOutputStream = null;
|
||||
ZipFile zipFile = null;
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
outputStream = new FileOutputStream(dstApkPath);
|
||||
zipOutputStream = new ZipOutputStream(outputStream);
|
||||
|
||||
try {
|
||||
zipFile = new ZipFile(srcApkPath, Charset.forName("gbk"));
|
||||
} catch (Throwable e) {
|
||||
zipFile = new ZipFile(srcApkPath);
|
||||
}
|
||||
|
||||
for (Enumeration entries = zipFile.entries(); entries.hasMoreElements(); ) {
|
||||
ZipEntry entry = (ZipEntry) entries.nextElement();
|
||||
String zipEntryName = entry.getName();
|
||||
|
||||
// Log.d(" zipEntryName = " + zipEntryName);
|
||||
|
||||
// ignore signature files, we will resign it.
|
||||
if (zipEntryName.startsWith("META-INF")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputStream zipInputStream = null;
|
||||
try {
|
||||
zipInputStream = zipFile.getInputStream(entry);
|
||||
|
||||
ZipEntry zosEntry = new ZipEntry(entry.getName());
|
||||
zosEntry.setComment(entry.getComment());
|
||||
zosEntry.setExtra(entry.getExtra());
|
||||
|
||||
zipOutputStream.putNextEntry(zosEntry);
|
||||
if ("AndroidManifest.xml".equals(zipEntryName)) {
|
||||
// if it is manifest file, modify it.
|
||||
new ManifestEditor(zipInputStream, zipOutputStream, property).processManifest();
|
||||
} else if (!entry.isDirectory()) {
|
||||
Utils.copyStream(zipInputStream, zipOutputStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(zipInputStream);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.close(zipOutputStream);
|
||||
Utils.close(outputStream);
|
||||
Utils.close(zipFile);
|
||||
|
||||
Log.i(" processApkFile time --> " + (System.currentTimeMillis() - time) + " ms");
|
||||
}
|
||||
}
|
||||
|
||||
public static void processManifestFile(String srcManifestPath, String dstManifestPath, ModificationProperty property) {
|
||||
new ManifestEditor(srcManifestPath, dstManifestPath, property).processManifest();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package com.wind.meditor.core;
|
||||
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
import com.wind.meditor.visitor.ManifestTagVisitor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import pxb.android.axml.AxmlReader;
|
||||
import pxb.android.axml.AxmlVisitor;
|
||||
import pxb.android.axml.AxmlWriter;
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
public class ManifestEditor {
|
||||
|
||||
private InputStream inputStream;
|
||||
private OutputStream outputStream;
|
||||
|
||||
// can not be null
|
||||
private ModificationProperty properties;
|
||||
|
||||
private boolean needClosedStream = false;
|
||||
|
||||
public ManifestEditor(String srcManifestFilePath, String dstManifestFilePath,
|
||||
ModificationProperty properties) {
|
||||
this.properties = properties;
|
||||
try {
|
||||
inputStream = new FileInputStream(srcManifestFilePath);
|
||||
outputStream = new FileOutputStream(dstManifestFilePath);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
needClosedStream = true;
|
||||
}
|
||||
|
||||
public ManifestEditor(InputStream inputStream, OutputStream outputStream,
|
||||
ModificationProperty properties) {
|
||||
this.properties = properties;
|
||||
this.inputStream = inputStream;
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
public void processManifest() {
|
||||
if (inputStream == null || outputStream == null || properties == null) {
|
||||
Log.i(" processManifest failed , inputStream = " + inputStream
|
||||
+ " outputStream=" + outputStream + " properties = " + properties);
|
||||
return;
|
||||
}
|
||||
// byte[] bytes = Utils.getBytesFromFile(srcManifestFilePath);
|
||||
byte[] bytes = Utils.getBytesFromInputStream(inputStream);
|
||||
|
||||
AxmlReader reader = new AxmlReader(bytes);
|
||||
AxmlWriter writer = new AxmlWriter();
|
||||
|
||||
try {
|
||||
reader.accept(new AxmlVisitor(writer) {
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
return new ManifestTagVisitor(child, properties);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] modified = writer.toByteArray();
|
||||
outputStream.write(modified);
|
||||
// Utils.writeBytesToFile(modified, dstManifestFilePath);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (needClosedStream) {
|
||||
Utils.close(inputStream);
|
||||
Utils.close(outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.wind.meditor.property;
|
||||
|
||||
import com.wind.meditor.utils.TypedValue;
|
||||
import com.wind.meditor.xml.ResourceIdXmlReader;
|
||||
|
||||
import static com.wind.meditor.utils.NodeValue.MANIFEST_NAMESPACE;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class AttributeItem {
|
||||
private String namespace = MANIFEST_NAMESPACE; // if no namespace, set it null.
|
||||
private String name;
|
||||
private int resourceId = -1;
|
||||
private int type = TypedValue.TYPE_NULL;
|
||||
private Object value;
|
||||
|
||||
// if only change the attrbute value, resourceId, type is not needed to be setted
|
||||
public AttributeItem(String name, Object value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getResourceId() {
|
||||
if (resourceId > 0) {
|
||||
return resourceId;
|
||||
}
|
||||
if (MANIFEST_NAMESPACE.equals(namespace)) {
|
||||
resourceId = ResourceIdXmlReader.parseIdFromXml(getName());
|
||||
}
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
if (type == TypedValue.TYPE_NULL) {
|
||||
if (value instanceof String) {
|
||||
type = TypedValue.TYPE_STRING;
|
||||
} else if (value instanceof Boolean) {
|
||||
type = TypedValue.TYPE_INT_BOOLEAN;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public AttributeItem setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AttributeItem setResourceId(int resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AttributeItem setType(int type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.wind.meditor.property;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 修改的参数
|
||||
*
|
||||
* @author windysha
|
||||
*/
|
||||
public class ModificationProperty {
|
||||
|
||||
private List<String> usesPermissionList = new ArrayList<>();
|
||||
private List<MetaData> metaDataList = new ArrayList<>();
|
||||
|
||||
private List<AttributeItem> applicationAttributeList = new ArrayList<>();
|
||||
private List<AttributeItem> manifestAttributeList = new ArrayList<>();
|
||||
|
||||
public List<String> getUsesPermissionList() {
|
||||
return usesPermissionList;
|
||||
}
|
||||
|
||||
public ModificationProperty addUsesPermission(String permissionName) {
|
||||
usesPermissionList.add(permissionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<AttributeItem> getApplicationAttributeList() {
|
||||
return applicationAttributeList;
|
||||
}
|
||||
|
||||
public ModificationProperty addApplicationAttribute(AttributeItem item) {
|
||||
applicationAttributeList.add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<MetaData> getMetaDataList() {
|
||||
return metaDataList;
|
||||
}
|
||||
|
||||
public ModificationProperty addMetaData(MetaData data) {
|
||||
metaDataList.add(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<AttributeItem> getManifestAttributeList() {
|
||||
return manifestAttributeList;
|
||||
}
|
||||
|
||||
public ModificationProperty addManifestAttribute(AttributeItem item) {
|
||||
manifestAttributeList.add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class MetaData {
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public MetaData(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class FileTypeUtils {
|
||||
|
||||
private static final HashMap<String, String> fileHeaderCache = new HashMap<>();
|
||||
|
||||
public static boolean isAndroidManifestFile(String filePath) {
|
||||
String sufix = filePath.substring(filePath.lastIndexOf(".") + 1);
|
||||
if ("xml".equalsIgnoreCase(sufix)) {
|
||||
return true;
|
||||
}
|
||||
String fileHeader = getFileHeader(filePath);
|
||||
if ("03000800".equalsIgnoreCase(fileHeader)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isApkFile(String filePath) {
|
||||
String sufix = filePath.substring(filePath.lastIndexOf(".") + 1);
|
||||
if ("apk".equalsIgnoreCase(sufix)) {
|
||||
return true;
|
||||
}
|
||||
String fileHeader = getFileHeader(filePath);
|
||||
if ("504B0304".equalsIgnoreCase(fileHeader)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getFileHeader(String filePath) {
|
||||
String cachedHeader = fileHeaderCache.get(filePath);
|
||||
if (cachedHeader != null && !cachedHeader.isEmpty()) {
|
||||
return cachedHeader;
|
||||
}
|
||||
String header = getFileHeaderInternal(filePath);
|
||||
fileHeaderCache.put(filePath, header);
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 方法描述:根据文件路径获取文件头信息
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @return 文件头信息
|
||||
*/
|
||||
private static String getFileHeaderInternal(String filePath) {
|
||||
FileInputStream is = null;
|
||||
String value = null;
|
||||
try {
|
||||
is = new FileInputStream(filePath);
|
||||
byte[] b = new byte[4];
|
||||
is.read(b, 0, b.length);
|
||||
value = bytesToHexString(b);
|
||||
} catch (Exception e) {
|
||||
} finally {
|
||||
Utils.close(is);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法描述:将要读取文件头信息的文件的byte数组转换成string类型表示
|
||||
*
|
||||
* @param src 要读取文件头信息的文件的byte数组
|
||||
* @return 文件头信息
|
||||
*/
|
||||
private static String bytesToHexString(byte[] src) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (src == null || src.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
String hv;
|
||||
for (int i = 0; i < src.length; i++) {
|
||||
// 以十六进制(基数 16)无符号整数形式返回一个整数参数的字符串表示形式,并转换为大写
|
||||
hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
|
||||
if (hv.length() < 2) {
|
||||
builder.append(0);
|
||||
}
|
||||
builder.append(hv);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
public static void i(String msg) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
|
||||
public static void e(String msg) {
|
||||
System.err.println(msg);
|
||||
}
|
||||
|
||||
public static void d(String msg) {
|
||||
if (DEBUG) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
public final class NodeValue {
|
||||
private NodeValue(){}
|
||||
|
||||
public static final String NAME="name";
|
||||
public static final String VALUE="value";
|
||||
public static final String LABEL="label";
|
||||
|
||||
public static final String MANIFEST_NAMESPACE = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
public static final class Manifest{
|
||||
public static final String TAG_NAME = "manifest";
|
||||
|
||||
public static final String VERSION_CODE="versionCode";
|
||||
public static final String VERSION_NAME="versionName";
|
||||
public static final String INSTALL_LOCATION="installLocation";
|
||||
public static final String SHARDE_USER_ID="sharedUserId";
|
||||
public static final String SHARED_USER_LABEL="sharedUserLabel";
|
||||
public static final String PACKAGE="package";
|
||||
}
|
||||
|
||||
public static final class UsesSDK{
|
||||
public static final String TAG_NAME = "uses-sdk";
|
||||
|
||||
public static final String MAX_SDK_VERSION="maxSdkVersion";
|
||||
public static final String MIN_SDK_VERSION="minSdkVersion";
|
||||
public static final String TARGET_SDK_VERSION="targetSdkVersion";
|
||||
}
|
||||
|
||||
public static final class UsesPermission{
|
||||
public static final String TAG_NAME = "uses-permission";
|
||||
|
||||
public static final String NAME="name";
|
||||
public static final String MAX_SDK_VERSION="maxSdkVersion";
|
||||
}
|
||||
|
||||
public static final class MetaData{
|
||||
public static final String TAG_NAME = "meta-data";
|
||||
|
||||
public static final String NAME="name";
|
||||
public static final String VALUE="value";
|
||||
public static final String RESOURCE="resource";
|
||||
}
|
||||
|
||||
|
||||
public static final class Application{
|
||||
|
||||
public static final String TAG_NAME = "application";
|
||||
|
||||
public static final String NAME="name";
|
||||
public static final String VALUE="value";
|
||||
public static final String LABEL="label";
|
||||
public static final String THEME="theme";
|
||||
public static final String ICON="icon";
|
||||
public static final String PERSISTENT="persistent";
|
||||
public static final String ALLOWBACKUP="allowBackup";
|
||||
public static final String LARGEHEAP="largeHeap";
|
||||
public static final String DEBUGGABLE="debuggable";
|
||||
public static final String HARDWAREACCELERATED="hardwareAccelerated";
|
||||
public static final String FULLBACKUPONLY="fullBackupOnly";
|
||||
public static final String VMSAFEMODE="vmSafeMode";
|
||||
public static final String ENABLED="enabled";
|
||||
public static final String DESCRIPTION="description";
|
||||
public static final String PROCESS="process";
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Created by Wind
|
||||
*/
|
||||
public class ShellCmdUtil {
|
||||
|
||||
/**
|
||||
* 执行系统命令, 返回执行结果
|
||||
*
|
||||
* @param cmd 需要执行的命令
|
||||
* @param dir 执行命令的子进程的工作目录, null 表示和当前主进程工作目录相同
|
||||
*/
|
||||
public static String execCmd(String cmd, File dir) throws Exception {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
Process process = null;
|
||||
BufferedReader bufrIn = null;
|
||||
BufferedReader bufrError = null;
|
||||
|
||||
try {
|
||||
// 执行命令, 返回一个子进程对象(命令在子进程中执行)
|
||||
process = Runtime.getRuntime().exec(cmd, null, dir);
|
||||
|
||||
// 方法阻塞, 等待命令执行完成(成功会返回0)
|
||||
process.waitFor();
|
||||
|
||||
// 获取命令执行结果, 有两个结果: 正常的输出 和 错误的输出(PS: 子进程的输出就是主进程的输入)
|
||||
bufrIn = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
|
||||
bufrError = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
|
||||
|
||||
// 读取输出
|
||||
String line = null;
|
||||
while ((line = bufrIn.readLine()) != null) {
|
||||
result.append(line).append('\n');
|
||||
}
|
||||
while ((line = bufrError.readLine()) != null) {
|
||||
result.append(line).append('\n');
|
||||
}
|
||||
|
||||
} finally {
|
||||
close(bufrIn);
|
||||
close(bufrError);
|
||||
|
||||
// 销毁子进程
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 返回执行结果
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static void close(Closeable stream) {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (Exception e) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,520 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Container for a dynamically typed data value. Primarily used with
|
||||
* {@link1 android.content.res.Resources} for holding resource values.
|
||||
*/
|
||||
public class TypedValue {
|
||||
/** The value contains no data. */
|
||||
public static final int TYPE_NULL = 0x00;
|
||||
|
||||
/** The <var>data</var> field holds a resource identifier. */
|
||||
public static final int TYPE_REFERENCE = 0x01;
|
||||
/** The <var>data</var> field holds an attribute resource
|
||||
* identifier (referencing an attribute in the current theme
|
||||
* style, not a resource entry). */
|
||||
public static final int TYPE_ATTRIBUTE = 0x02;
|
||||
/** The <var>string</var> field holds string data. In addition, if
|
||||
* <var>data</var> is non-zero then it is the string block
|
||||
* index of the string and <var>assetCookie</var> is the set of
|
||||
* assets the string came from. */
|
||||
public static final int TYPE_STRING = 0x03;
|
||||
/** The <var>data</var> field holds an IEEE 754 floating point number. */
|
||||
public static final int TYPE_FLOAT = 0x04;
|
||||
/** The <var>data</var> field holds a complex number encoding a
|
||||
* dimension value. */
|
||||
public static final int TYPE_DIMENSION = 0x05;
|
||||
/** The <var>data</var> field holds a complex number encoding a fraction
|
||||
* of a container. */
|
||||
public static final int TYPE_FRACTION = 0x06;
|
||||
|
||||
/** Identifies the start of plain integer values. Any type value
|
||||
* from this to {@link #TYPE_LAST_INT} means the
|
||||
* <var>data</var> field holds a generic integer value. */
|
||||
public static final int TYPE_FIRST_INT = 0x10;
|
||||
|
||||
/** The <var>data</var> field holds a number that was
|
||||
* originally specified in decimal. */
|
||||
public static final int TYPE_INT_DEC = 0x10;
|
||||
/** The <var>data</var> field holds a number that was
|
||||
* originally specified in hexadecimal (0xn). */
|
||||
public static final int TYPE_INT_HEX = 0x11;
|
||||
/** The <var>data</var> field holds 0 or 1 that was originally
|
||||
* specified as "false" or "true". */
|
||||
public static final int TYPE_INT_BOOLEAN = 0x12;
|
||||
|
||||
/** Identifies the start of integer values that were specified as
|
||||
* color constants (starting with '#'). */
|
||||
public static final int TYPE_FIRST_COLOR_INT = 0x1c;
|
||||
|
||||
/** The <var>data</var> field holds a color that was originally
|
||||
* specified as #aarrggbb. */
|
||||
public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
|
||||
/** The <var>data</var> field holds a color that was originally
|
||||
* specified as #rrggbb. */
|
||||
public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
|
||||
/** The <var>data</var> field holds a color that was originally
|
||||
* specified as #argb. */
|
||||
public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
|
||||
/** The <var>data</var> field holds a color that was originally
|
||||
* specified as #rgb. */
|
||||
public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
|
||||
|
||||
/** Identifies the end of integer values that were specified as color
|
||||
* constants. */
|
||||
public static final int TYPE_LAST_COLOR_INT = 0x1f;
|
||||
|
||||
/** Identifies the end of plain integer values. */
|
||||
public static final int TYPE_LAST_INT = 0x1f;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/** Complex data: bit location of unit information. */
|
||||
public static final int COMPLEX_UNIT_SHIFT = 0;
|
||||
/** Complex data: mask to extract unit information (after shifting by
|
||||
* {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as
|
||||
* defined below. */
|
||||
public static final int COMPLEX_UNIT_MASK = 0xf;
|
||||
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
|
||||
public static final int COMPLEX_UNIT_PX = 0;
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
|
||||
* Pixels. */
|
||||
public static final int COMPLEX_UNIT_DIP = 1;
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */
|
||||
public static final int COMPLEX_UNIT_SP = 2;
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is in points. */
|
||||
public static final int COMPLEX_UNIT_PT = 3;
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */
|
||||
public static final int COMPLEX_UNIT_IN = 4;
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */
|
||||
public static final int COMPLEX_UNIT_MM = 5;
|
||||
|
||||
/** {@link #TYPE_FRACTION} complex unit: A basic fraction of the overall
|
||||
* size. */
|
||||
public static final int COMPLEX_UNIT_FRACTION = 0;
|
||||
/** {@link #TYPE_FRACTION} complex unit: A fraction of the parent size. */
|
||||
public static final int COMPLEX_UNIT_FRACTION_PARENT = 1;
|
||||
|
||||
/** Complex data: where the radix information is, telling where the decimal
|
||||
* place appears in the mantissa. */
|
||||
public static final int COMPLEX_RADIX_SHIFT = 4;
|
||||
/** Complex data: mask to extract radix information (after shifting by
|
||||
* {@link #COMPLEX_RADIX_SHIFT}). This give us 4 possible fixed point
|
||||
* representations as defined below. */
|
||||
public static final int COMPLEX_RADIX_MASK = 0x3;
|
||||
|
||||
/** Complex data: the mantissa is an integral number -- i.e., 0xnnnnnn.0 */
|
||||
public static final int COMPLEX_RADIX_23p0 = 0;
|
||||
/** Complex data: the mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn */
|
||||
public static final int COMPLEX_RADIX_16p7 = 1;
|
||||
/** Complex data: the mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn */
|
||||
public static final int COMPLEX_RADIX_8p15 = 2;
|
||||
/** Complex data: the mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn */
|
||||
public static final int COMPLEX_RADIX_0p23 = 3;
|
||||
|
||||
/** Complex data: bit location of mantissa information. */
|
||||
public static final int COMPLEX_MANTISSA_SHIFT = 8;
|
||||
/** Complex data: mask to extract mantissa information (after shifting by
|
||||
* {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision;
|
||||
* the top bit is the sign. */
|
||||
public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* {@link #TYPE_NULL} data indicating the value was not specified.
|
||||
*/
|
||||
public static final int DATA_NULL_UNDEFINED = 0;
|
||||
/**
|
||||
* {@link #TYPE_NULL} data indicating the value was explicitly set to null.
|
||||
*/
|
||||
public static final int DATA_NULL_EMPTY = 1;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* If {@link #density} is equal to this value, then the density should be
|
||||
* treated as the system's default density value: {@link1 DisplayMetrics#DENSITY_DEFAULT}.
|
||||
*/
|
||||
public static final int DENSITY_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* If {@link #density} is equal to this value, then there is no density
|
||||
* associated with the resource and it should not be scaled.
|
||||
*/
|
||||
public static final int DENSITY_NONE = 0xffff;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/** The type held by this value, as defined by the constants here.
|
||||
* This tells you how to interpret the other fields in the object. */
|
||||
public int type;
|
||||
|
||||
/** If the value holds a string, this is it. */
|
||||
public CharSequence string;
|
||||
|
||||
/** Basic data in the value, interpreted according to {@link #type} */
|
||||
public int data;
|
||||
|
||||
/** Additional information about where the value came from; only
|
||||
* set for strings. */
|
||||
public int assetCookie;
|
||||
|
||||
/** If Value came from a resource, this holds the corresponding resource id. */
|
||||
// @AnyRes
|
||||
public int resourceId;
|
||||
|
||||
/**
|
||||
* If the value came from a resource, these are the configurations for
|
||||
* which its contents can change.
|
||||
*
|
||||
* <p>For example, if a resource has a value defined for the -land resource qualifier,
|
||||
* this field will have the {@link1 android.content.pm.ActivityInfo#CONFIG_ORIENTATION} bit set.
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
// public @Config int changingConfigurations = -1;
|
||||
|
||||
/**
|
||||
* If the Value came from a resource, this holds the corresponding pixel density.
|
||||
* */
|
||||
public int density;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/** Return the data for this value as a float. Only use for values
|
||||
* whose type is {@link #TYPE_FLOAT}. */
|
||||
public final float getFloat() {
|
||||
return Float.intBitsToFloat(data);
|
||||
}
|
||||
|
||||
private static final float MANTISSA_MULT =
|
||||
1.0f / (1<<TypedValue.COMPLEX_MANTISSA_SHIFT);
|
||||
private static final float[] RADIX_MULTS = new float[] {
|
||||
1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
|
||||
1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the base value from a complex data integer. This uses the
|
||||
* {@link #COMPLEX_MANTISSA_MASK} and {@link #COMPLEX_RADIX_MASK} fields of
|
||||
* the data to compute a floating point representation of the number they
|
||||
* describe. The units are ignored.
|
||||
*
|
||||
* @param complex A complex data value.
|
||||
*
|
||||
* @return A floating point value corresponding to the complex data.
|
||||
*/
|
||||
public static float complexToFloat(int complex)
|
||||
{
|
||||
return (complex&(TypedValue.COMPLEX_MANTISSA_MASK
|
||||
<<TypedValue.COMPLEX_MANTISSA_SHIFT))
|
||||
* RADIX_MULTS[(complex>>TypedValue.COMPLEX_RADIX_SHIFT)
|
||||
& TypedValue.COMPLEX_RADIX_MASK];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a complex data value holding a dimension to its final floating
|
||||
* point value. The given <var>data</var> must be structured as a
|
||||
* {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @param data A complex data value holding a unit, magnitude, and
|
||||
* mantissa.
|
||||
* @param metrics Current display metrics to use in the conversion --
|
||||
* supplies display density and scaling information.
|
||||
*
|
||||
* @return The complex floating point value multiplied by the appropriate
|
||||
* metrics depending on its unit.
|
||||
*/
|
||||
// public static float complexToDimension(int data, DisplayMetrics metrics)
|
||||
// {
|
||||
// return applyDimension(
|
||||
// (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
|
||||
// complexToFloat(data),
|
||||
// metrics);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts a complex data value holding a dimension to its final value
|
||||
* as an integer pixel offset. This is the same as
|
||||
* {@link #complexToDimension}, except the raw floating point value is
|
||||
* truncated to an integer (pixel) value.
|
||||
* The given <var>data</var> must be structured as a
|
||||
* {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @param data A complex data value holding a unit, magnitude, and
|
||||
* mantissa.
|
||||
* @param metrics Current display metrics to use in the conversion --
|
||||
* supplies display density and scaling information.
|
||||
*
|
||||
* @return The number of pixels specified by the data and its desired
|
||||
* multiplier and units.
|
||||
*/
|
||||
// public static int complexToDimensionPixelOffset(int data,
|
||||
// DisplayMetrics metrics)
|
||||
// {
|
||||
// return (int)applyDimension(
|
||||
// (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
|
||||
// complexToFloat(data),
|
||||
// metrics);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts a complex data value holding a dimension to its final value
|
||||
* as an integer pixel size. This is the same as
|
||||
* {@link1 #complexToDimension}, except the raw floating point value is
|
||||
* converted to an integer (pixel) value for use as a size. A size
|
||||
* conversion involves rounding the base value, and ensuring that a
|
||||
* non-zero base value is at least one pixel in size.
|
||||
* The given <var>data</var> must be structured as a
|
||||
* {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @param data A complex data value holding a unit, magnitude, and
|
||||
* mantissa.
|
||||
* @param metrics Current display metrics to use in the conversion --
|
||||
* supplies display density and scaling information.
|
||||
*
|
||||
* @return The number of pixels specified by the data and its desired
|
||||
* multiplier and units.
|
||||
*/
|
||||
// public static int complexToDimensionPixelSize(int data,
|
||||
// DisplayMetrics metrics)
|
||||
// {
|
||||
// final float value = complexToFloat(data);
|
||||
// final float f = applyDimension(
|
||||
// (data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
|
||||
// value,
|
||||
// metrics);
|
||||
// final int res = (int) ((f >= 0) ? (f + 0.5f) : (f - 0.5f));
|
||||
// if (res != 0) return res;
|
||||
// if (value == 0) return 0;
|
||||
// if (value > 0) return 1;
|
||||
// return -1;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @hide Was accidentally exposed in API level 1 for debugging purposes.
|
||||
* Kept for compatibility just in case although the debugging code has been removed.
|
||||
*/
|
||||
@Deprecated
|
||||
// public static float complexToDimensionNoisy(int data, DisplayMetrics metrics)
|
||||
// {
|
||||
// return complexToDimension(data, metrics);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Return the complex unit type for this value. For example, a dimen type
|
||||
* with value 12sp will return {@link #COMPLEX_UNIT_SP}. Only use for values
|
||||
* whose type is {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @return The complex unit type.
|
||||
*/
|
||||
public int getComplexUnit()
|
||||
{
|
||||
return COMPLEX_UNIT_MASK & (data>>TypedValue.COMPLEX_UNIT_SHIFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an unpacked complex data value holding a dimension to its final floating
|
||||
* point value. The two parameters <var>unit</var> and <var>value</var>
|
||||
* are as in {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @param unit The unit to convert from.
|
||||
* @param value The value to apply the unit to.
|
||||
* @param metrics Current display metrics to use in the conversion --
|
||||
* supplies display density and scaling information.
|
||||
*
|
||||
* @return The complex floating point value multiplied by the appropriate
|
||||
* metrics depending on its unit.
|
||||
*/
|
||||
// public static float applyDimension(int unit, float value,
|
||||
// DisplayMetrics metrics)
|
||||
// {
|
||||
// switch (unit) {
|
||||
// case COMPLEX_UNIT_PX:
|
||||
// return value;
|
||||
// case COMPLEX_UNIT_DIP:
|
||||
// return value * metrics.density;
|
||||
// case COMPLEX_UNIT_SP:
|
||||
// return value * metrics.scaledDensity;
|
||||
// case COMPLEX_UNIT_PT:
|
||||
// return value * metrics.xdpi * (1.0f/72);
|
||||
// case COMPLEX_UNIT_IN:
|
||||
// return value * metrics.xdpi;
|
||||
// case COMPLEX_UNIT_MM:
|
||||
// return value * metrics.xdpi * (1.0f/25.4f);
|
||||
// }
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Return the data for this value as a dimension. Only use for values
|
||||
* whose type is {@link #TYPE_DIMENSION}.
|
||||
*
|
||||
* @param metrics Current display metrics to use in the conversion --
|
||||
* supplies display density and scaling information.
|
||||
*
|
||||
* @return The complex floating point value multiplied by the appropriate
|
||||
* metrics depending on its unit.
|
||||
*/
|
||||
// public float getDimension(DisplayMetrics metrics)
|
||||
// {
|
||||
// return complexToDimension(data, metrics);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts a complex data value holding a fraction to its final floating
|
||||
* point value. The given <var>data</var> must be structured as a
|
||||
* {@link #TYPE_FRACTION}.
|
||||
*
|
||||
* @param data A complex data value holding a unit, magnitude, and
|
||||
* mantissa.
|
||||
* @param base The base value of this fraction. In other words, a
|
||||
* standard fraction is multiplied by this value.
|
||||
* @param pbase The parent base value of this fraction. In other
|
||||
* words, a parent fraction (nn%p) is multiplied by this
|
||||
* value.
|
||||
*
|
||||
* @return The complex floating point value multiplied by the appropriate
|
||||
* base value depending on its unit.
|
||||
*/
|
||||
public static float complexToFraction(int data, float base, float pbase)
|
||||
{
|
||||
switch ((data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK) {
|
||||
case COMPLEX_UNIT_FRACTION:
|
||||
return complexToFloat(data) * base;
|
||||
case COMPLEX_UNIT_FRACTION_PARENT:
|
||||
return complexToFloat(data) * pbase;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data for this value as a fraction. Only use for values whose
|
||||
* type is {@link #TYPE_FRACTION}.
|
||||
*
|
||||
* @param base The base value of this fraction. In other words, a
|
||||
* standard fraction is multiplied by this value.
|
||||
* @param pbase The parent base value of this fraction. In other
|
||||
* words, a parent fraction (nn%p) is multiplied by this
|
||||
* value.
|
||||
*
|
||||
* @return The complex floating point value multiplied by the appropriate
|
||||
* base value depending on its unit.
|
||||
*/
|
||||
public float getFraction(float base, float pbase)
|
||||
{
|
||||
return complexToFraction(data, base, pbase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regardless of the actual type of the value, try to convert it to a
|
||||
* string value. For example, a color type will be converted to a
|
||||
* string of the form #aarrggbb.
|
||||
*
|
||||
* @return CharSequence The coerced string value. If the value is
|
||||
* null or the type is not known, null is returned.
|
||||
*/
|
||||
public final CharSequence coerceToString()
|
||||
{
|
||||
int t = type;
|
||||
if (t == TYPE_STRING) {
|
||||
return string;
|
||||
}
|
||||
return coerceToString(t, data);
|
||||
}
|
||||
|
||||
private static final String[] DIMENSION_UNIT_STRS = new String[] {
|
||||
"px", "dip", "sp", "pt", "in", "mm"
|
||||
};
|
||||
private static final String[] FRACTION_UNIT_STRS = new String[] {
|
||||
"%", "%p"
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform type conversion as per {@link #coerceToString()} on an
|
||||
* explicitly supplied type and data.
|
||||
*
|
||||
* @param type The data type identifier.
|
||||
* @param data The data value.
|
||||
*
|
||||
* @return String The coerced string value. If the value is
|
||||
* null or the type is not known, null is returned.
|
||||
*/
|
||||
public static final String coerceToString(int type, int data)
|
||||
{
|
||||
switch (type) {
|
||||
case TYPE_NULL:
|
||||
return null;
|
||||
case TYPE_REFERENCE:
|
||||
return "@" + data;
|
||||
case TYPE_ATTRIBUTE:
|
||||
return "?" + data;
|
||||
case TYPE_FLOAT:
|
||||
return Float.toString(Float.intBitsToFloat(data));
|
||||
case TYPE_DIMENSION:
|
||||
return Float.toString(complexToFloat(data)) + DIMENSION_UNIT_STRS[
|
||||
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
|
||||
case TYPE_FRACTION:
|
||||
return Float.toString(complexToFloat(data)*100) + FRACTION_UNIT_STRS[
|
||||
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK];
|
||||
case TYPE_INT_HEX:
|
||||
return "0x" + Integer.toHexString(data);
|
||||
case TYPE_INT_BOOLEAN:
|
||||
return data != 0 ? "true" : "false";
|
||||
}
|
||||
|
||||
if (type >= TYPE_FIRST_COLOR_INT && type <= TYPE_LAST_COLOR_INT) {
|
||||
return "#" + Integer.toHexString(data);
|
||||
} else if (type >= TYPE_FIRST_INT && type <= TYPE_LAST_INT) {
|
||||
return Integer.toString(data);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setTo(TypedValue other)
|
||||
{
|
||||
type = other.type;
|
||||
string = other.string;
|
||||
data = other.data;
|
||||
assetCookie = other.assetCookie;
|
||||
resourceId = other.resourceId;
|
||||
density = other.density;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("TypedValue{t=0x").append(Integer.toHexString(type));
|
||||
sb.append("/d=0x").append(Integer.toHexString(data));
|
||||
if (type == TYPE_STRING) {
|
||||
sb.append(" \"").append(string != null ? string : "<null>").append("\"");
|
||||
}
|
||||
if (assetCookie != 0) {
|
||||
sb.append(" a=").append(assetCookie);
|
||||
}
|
||||
if (resourceId != 0) {
|
||||
sb.append(" r=0x").append(Integer.toHexString(resourceId));
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
package com.wind.meditor.utils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static com.wind.meditor.utils.NodeValue.MANIFEST_NAMESPACE;
|
||||
|
||||
public class Utils {
|
||||
|
||||
private static final byte[] BUFFER = new byte[4096 * 1024];
|
||||
|
||||
//将文件转换成Byte数组
|
||||
public static byte[] getBytesFromFile(String pathStr) {
|
||||
File file = new File(pathStr);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(file);
|
||||
return getBytesFromInputStream(fis);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
close(fis);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] getBytesFromInputStream(InputStream inputStream) {
|
||||
ByteArrayOutputStream bos = null;
|
||||
try {
|
||||
bos = new ByteArrayOutputStream();
|
||||
byte[] b = new byte[1024];
|
||||
int n;
|
||||
while ((n = inputStream.read(b)) != -1) {
|
||||
bos.write(b, 0, n);
|
||||
}
|
||||
byte[] data = bos.toByteArray();
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
close(bos);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//将Byte数组写入到文件
|
||||
public static void writeBytesToFile(byte[] bytes, String filePath) {
|
||||
FileOutputStream fileOuputStream = null;
|
||||
try {
|
||||
fileOuputStream = new FileOutputStream(filePath);
|
||||
fileOuputStream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
close(fileOuputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void close(Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
} catch (IOException io) {
|
||||
io.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
return str == null || str.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isEqual(String str1, String str2) {
|
||||
if (str1 == null) {
|
||||
return str2 == null;
|
||||
} else {
|
||||
return str1.equals(str2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAndroidNamespace(String str) {
|
||||
return MANIFEST_NAMESPACE.equals(str);
|
||||
}
|
||||
|
||||
public static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
int bytesRead;
|
||||
while ((bytesRead = input.read(BUFFER)) != -1) {
|
||||
output.write(BUFFER, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
public static InputStream getInputStreamFromFile(String filePath) {
|
||||
return Utils.class.getClassLoader().getResourceAsStream(filePath);
|
||||
}
|
||||
|
||||
// copy an asset file into a path
|
||||
public static void copyFileFromJar(String inJarPath, String distPath) {
|
||||
Log.d("start copyFile inJarPath =" + inJarPath + "\n distPath = " + distPath);
|
||||
InputStream inputStream = getInputStreamFromFile(inJarPath);
|
||||
|
||||
BufferedInputStream in = null;
|
||||
BufferedOutputStream out = null;
|
||||
try {
|
||||
in = new BufferedInputStream(inputStream);
|
||||
out = new BufferedOutputStream(new FileOutputStream(distPath));
|
||||
|
||||
int len = -1;
|
||||
byte[] b = new byte[1024];
|
||||
while ((len = in.read(b)) != -1) {
|
||||
out.write(b, 0, len);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
close(out);
|
||||
close(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.wind.meditor.visitor;
|
||||
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
import com.wind.meditor.utils.NodeValue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class ApplicationTagVisitor extends ModifyAttributeVisitor {
|
||||
|
||||
private List<ModificationProperty.MetaData> metaDataList;
|
||||
private ModificationProperty.MetaData curMetaData;
|
||||
|
||||
private static final String META_DATA_FLAG = "meta_data_flag";
|
||||
|
||||
ApplicationTagVisitor(NodeVisitor nv, List<AttributeItem> modifyAttributeList,
|
||||
List<ModificationProperty.MetaData> metaDataList) {
|
||||
super(nv, modifyAttributeList);
|
||||
this.metaDataList = metaDataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
if (META_DATA_FLAG.equals(ns)) {
|
||||
NodeVisitor nv = super.child(null, name);
|
||||
if (curMetaData != null) {
|
||||
return new MetaDataVisitor(nv, new ModificationProperty.MetaData(
|
||||
curMetaData.getName(), curMetaData.getValue()));
|
||||
}
|
||||
}
|
||||
return super.child(ns, name);
|
||||
}
|
||||
|
||||
private void addChild(ModificationProperty.MetaData data) {
|
||||
curMetaData = data;
|
||||
child(META_DATA_FLAG, NodeValue.MetaData.TAG_NAME);
|
||||
curMetaData = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (metaDataList != null) {
|
||||
for (ModificationProperty.MetaData data : metaDataList) {
|
||||
addChild(data);
|
||||
}
|
||||
}
|
||||
super.end();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package com.wind.meditor.visitor;
|
||||
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.NodeValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
public class ManifestTagVisitor extends ModifyAttributeVisitor {
|
||||
|
||||
private ModificationProperty properties;
|
||||
|
||||
private List<String> hasIncludedUsesPermissionList = new ArrayList<>();
|
||||
|
||||
private UserPermissionTagVisitor.IUsesPermissionGetter addedPermissionGetter;
|
||||
|
||||
public ManifestTagVisitor(NodeVisitor nv, ModificationProperty properties) {
|
||||
super(nv, properties.getManifestAttributeList());
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
Log.d(" ManifestTagVisitor child --> ns = " + ns + " name = " + name);
|
||||
|
||||
if (ns != null && (NodeValue.UsesPermission.TAG_NAME).equals(name)) {
|
||||
NodeVisitor child = super.child(null, NodeValue.UsesPermission.TAG_NAME);
|
||||
return new UserPermissionTagVisitor(child, null, ns);
|
||||
}
|
||||
|
||||
NodeVisitor child = super.child(ns, name);
|
||||
if (NodeValue.Application.TAG_NAME.equals(name)) {
|
||||
return new ApplicationTagVisitor(child, properties.getApplicationAttributeList(),
|
||||
properties.getMetaDataList());
|
||||
}
|
||||
|
||||
if (NodeValue.UsesPermission.TAG_NAME.equals(name)) {
|
||||
return new UserPermissionTagVisitor(child, getUsesPermissionGetter(), null);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
Log.d(" ManifestTagVisitor attr --> ns = " + ns + " name = " +
|
||||
name + " resourceId=" + resourceId + " obj = " + obj);
|
||||
super.attr(ns, name, resourceId, type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
List<String> list = properties.getUsesPermissionList();
|
||||
if (list != null && list.size() > 0) {
|
||||
for (String permissionName : list) {
|
||||
// permission is not added.
|
||||
if (!hasIncludedUsesPermissionList.contains(permissionName)) {
|
||||
// pass permission name to child by name space
|
||||
child(permissionName, NodeValue.UsesPermission.TAG_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.end();
|
||||
}
|
||||
|
||||
private UserPermissionTagVisitor.IUsesPermissionGetter getUsesPermissionGetter() {
|
||||
if (addedPermissionGetter == null) {
|
||||
addedPermissionGetter = permissionName -> {
|
||||
if (!hasIncludedUsesPermissionList.contains(permissionName)) {
|
||||
hasIncludedUsesPermissionList.add(permissionName);
|
||||
}
|
||||
};
|
||||
}
|
||||
return addedPermissionGetter;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.wind.meditor.visitor;
|
||||
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class MetaDataVisitor extends ModifyAttributeVisitor {
|
||||
|
||||
MetaDataVisitor(NodeVisitor nv, ModificationProperty.MetaData metaData) {
|
||||
super(nv, convertToAttr(metaData), true);
|
||||
}
|
||||
|
||||
private static List<AttributeItem> convertToAttr(ModificationProperty.MetaData metaData) {
|
||||
if (metaData == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<AttributeItem> list = new ArrayList<>();
|
||||
list.add(new AttributeItem("name", metaData.getName()));
|
||||
list.add(new AttributeItem("value", metaData.getValue()));
|
||||
return list;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package com.wind.meditor.visitor;
|
||||
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
public class ModifyAttributeVisitor extends NodeVisitor {
|
||||
private List<AttributeItem> hasBeenAddedAttributeList = new ArrayList<>();
|
||||
private List<AttributeItem> mModifyAttributeList;
|
||||
|
||||
// whether the tag where the attribute attached is newly added
|
||||
private boolean isNewAddedTag;
|
||||
|
||||
ModifyAttributeVisitor(NodeVisitor nv, List<AttributeItem> modifyAttributeList, boolean isNewAddedTag) {
|
||||
super(nv);
|
||||
mModifyAttributeList = modifyAttributeList;
|
||||
this.isNewAddedTag = isNewAddedTag;
|
||||
|
||||
if (isNewAddedTag) {
|
||||
modifyAttr();
|
||||
}
|
||||
}
|
||||
|
||||
ModifyAttributeVisitor(NodeVisitor nv, List<AttributeItem> modifyAttributeList) {
|
||||
this(nv, modifyAttributeList, false);
|
||||
}
|
||||
|
||||
public void addModifyAttributeItem(AttributeItem item) {
|
||||
if (mModifyAttributeList == null) {
|
||||
mModifyAttributeList = new ArrayList<>();
|
||||
}
|
||||
mModifyAttributeList.add(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
Object newObj = null;
|
||||
if (mModifyAttributeList != null) {
|
||||
for (AttributeItem attributeItem : mModifyAttributeList) {
|
||||
if (attributeItem == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Utils.isEqual(ns, attributeItem.getNamespace())
|
||||
&& Utils.isEqual(name, attributeItem.getName())) {
|
||||
hasBeenAddedAttributeList.add(attributeItem);
|
||||
newObj = attributeItem.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newObj == null) {
|
||||
newObj = obj;
|
||||
}
|
||||
|
||||
super.attr(ns, name, resourceId, type, newObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (!isNewAddedTag) {
|
||||
modifyAttr();
|
||||
}
|
||||
super.end();
|
||||
}
|
||||
|
||||
private void modifyAttr() {
|
||||
if (mModifyAttributeList != null) {
|
||||
for (AttributeItem attributeItem : mModifyAttributeList) {
|
||||
if (!hasBeenAddedAttributeList.contains(attributeItem)) {
|
||||
super.attr(attributeItem.getNamespace(),
|
||||
attributeItem.getName(),
|
||||
attributeItem.getResourceId(),
|
||||
attributeItem.getType(),
|
||||
attributeItem.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.wind.meditor.visitor;
|
||||
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.utils.NodeValue;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
|
||||
import pxb.android.axml.NodeVisitor;
|
||||
|
||||
class UserPermissionTagVisitor extends NodeVisitor {
|
||||
|
||||
private IUsesPermissionGetter permissionGetter;
|
||||
|
||||
UserPermissionTagVisitor(NodeVisitor nv, IUsesPermissionGetter permissionGetter, String permissionTobeAdded) {
|
||||
super(nv);
|
||||
this.permissionGetter = permissionGetter;
|
||||
|
||||
if (!Utils.isNullOrEmpty(permissionTobeAdded)) {
|
||||
AttributeItem attributeItem = new AttributeItem(NodeValue.UsesPermission.NAME, permissionTobeAdded);
|
||||
super.attr(attributeItem.getNamespace(),
|
||||
attributeItem.getName(),
|
||||
attributeItem.getResourceId(),
|
||||
attributeItem.getType(),
|
||||
attributeItem.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
if (obj instanceof String && permissionGetter != null) {
|
||||
permissionGetter.onPermissionGetted((String) obj);
|
||||
}
|
||||
super.attr(ns, name, resourceId, type, obj);
|
||||
}
|
||||
|
||||
public interface IUsesPermissionGetter {
|
||||
void onPermissionGetted(String permissionName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package com.wind.meditor.xml;
|
||||
|
||||
import com.wind.meditor.utils.Log;
|
||||
import com.wind.meditor.utils.Utils;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
/**
|
||||
* @author Windysha
|
||||
*/
|
||||
public class ResourceIdXmlReader {
|
||||
|
||||
private static final Map<String, Integer> attrCachedMap = new HashMap<>();
|
||||
|
||||
public static int parseIdFromXml(String name) {
|
||||
String filePath = "assets/public.xml";
|
||||
InputStream inputStream = Utils.getInputStreamFromFile(filePath);
|
||||
try {
|
||||
Integer cacherId = attrCachedMap.get(name);
|
||||
if (cacherId != null && cacherId > 0) {
|
||||
return cacherId;
|
||||
} else {
|
||||
// String id = findIdFromXmlFile(new FileInputStream(filePath2), "attr", name);
|
||||
String id = findIdFromXmlFile(inputStream, "attr", name);
|
||||
Log.d(String.format("name = %s, id = %s", name, id));
|
||||
if (id != null) {
|
||||
int idInt = Integer.parseInt(id.substring(2), 16);
|
||||
attrCachedMap.put(name, idInt);
|
||||
return idInt;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String findIdFromXmlFile(InputStream inputStream, String type, String name) {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = null;
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
} catch (ParserConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (builder == null) {
|
||||
System.out.println("parse xml failed, DocumentBuilder is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
Document doc = null;
|
||||
try {
|
||||
doc = builder.parse(inputStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (org.xml.sax.SAXException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (doc == null) {
|
||||
System.out.println("parse xml failed, Document is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
NodeList nodeList = doc.getElementsByTagName("public");
|
||||
|
||||
int length = nodeList.getLength();
|
||||
for (int i = 0; i < length; i++) {
|
||||
Node node = nodeList.item(i);
|
||||
NamedNodeMap nnm = node.getAttributes();
|
||||
String id = findIdByNameAndType(nnm, type, name);
|
||||
if (id != null) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String findIdByNameAndType(NamedNodeMap map, String type, String name) {
|
||||
int length = map.getLength();
|
||||
String nodeName = null;
|
||||
String nodeType = null;
|
||||
String nodeId = null;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Node node = map.item(i);
|
||||
if (node != null) {
|
||||
String attrName = node.getNodeName();
|
||||
String attrValue = node.getNodeValue();
|
||||
|
||||
if ("type".equals(attrName)) {
|
||||
nodeType = attrValue;
|
||||
} else if ("name".equals(attrName)) {
|
||||
nodeName = attrValue;
|
||||
} else if ("id".equals(attrName)) {
|
||||
nodeId = attrValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeName != null && nodeName.equals(name)
|
||||
&& nodeType != null && nodeType.equals(type)) {
|
||||
try {
|
||||
return nodeId;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android;
|
||||
|
||||
public interface ResConst {
|
||||
int RES_STRING_POOL_TYPE = 0x0001;
|
||||
int RES_TABLE_TYPE = 0x0002;
|
||||
int RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||
int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||
int RES_TABLE_TYPE_TYPE = 0x0201;
|
||||
|
||||
int RES_XML_TYPE = 0x0003;
|
||||
int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
int RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
||||
int RES_XML_END_ELEMENT_TYPE = 0x0103;
|
||||
int RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
||||
int RES_XML_START_ELEMENT_TYPE = 0x0102;
|
||||
int RES_XML_CDATA_TYPE = 0x0104;
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android;
|
||||
|
||||
public class StringItem {
|
||||
public String data;
|
||||
public int dataOffset;
|
||||
public int index;
|
||||
|
||||
public StringItem() {
|
||||
super();
|
||||
}
|
||||
|
||||
public StringItem(String data) {
|
||||
super();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
StringItem other = (StringItem) obj;
|
||||
if (data == null) {
|
||||
if (other.data != null)
|
||||
return false;
|
||||
} else if (!data.equals(other.data))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("S%04d %s", index, data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class StringItems extends ArrayList<StringItem> {
|
||||
private static final int UTF8_FLAG = 0x00000100;
|
||||
|
||||
|
||||
public static String[] read(ByteBuffer in) throws IOException {
|
||||
int trunkOffset = in.position() - 8;
|
||||
int stringCount = in.getInt();
|
||||
int styleOffsetCount = in.getInt();
|
||||
int flags = in.getInt();
|
||||
int stringDataOffset = in.getInt();
|
||||
int stylesOffset = in.getInt();
|
||||
int offsets[] = new int[stringCount];
|
||||
String strings[] = new String[stringCount];
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
offsets[i] = in.getInt();
|
||||
}
|
||||
|
||||
int base = trunkOffset + stringDataOffset;
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
in.position(base + offsets[i]);
|
||||
String s;
|
||||
|
||||
if (0 != (flags & UTF8_FLAG)) {
|
||||
u8length(in); // ignored
|
||||
int u8len = u8length(in);
|
||||
int start = in.position();
|
||||
int blength = u8len;
|
||||
while (in.get(start + blength) != 0) {
|
||||
blength++;
|
||||
}
|
||||
s = new String(in.array(), start, blength, "UTF-8");
|
||||
} else {
|
||||
int length = u16length(in);
|
||||
s = new String(in.array(), in.position(), length * 2, "UTF-16LE");
|
||||
}
|
||||
strings[i] = s;
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
static int u16length(ByteBuffer in) {
|
||||
int length = in.getShort() & 0xFFFF;
|
||||
if (length > 0x7FFF) {
|
||||
length = ((length & 0x7FFF) << 8) | (in.getShort() & 0xFFFF);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static int u8length(ByteBuffer in) {
|
||||
int len = in.get() & 0xFF;
|
||||
if ((len & 0x80) != 0) {
|
||||
len = ((len & 0x7F) << 8) | (in.get() & 0xFF);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
byte[] stringData;
|
||||
|
||||
public int getSize() {
|
||||
return 5 * 4 + this.size() * 4 + stringData.length + 0;// TODO
|
||||
}
|
||||
|
||||
public void prepare() throws IOException {
|
||||
for (StringItem s : this) {
|
||||
if (s.data.length() > 0x7FFF) {
|
||||
useUTF8 = false;
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int i = 0;
|
||||
int offset = 0;
|
||||
baos.reset();
|
||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||
for (StringItem item : this) {
|
||||
item.index = i++;
|
||||
String stringData = item.data;
|
||||
Integer of = map.get(stringData);
|
||||
if (of != null) {
|
||||
item.dataOffset = of;
|
||||
} else {
|
||||
item.dataOffset = offset;
|
||||
map.put(stringData, offset);
|
||||
if (useUTF8) {
|
||||
int length = stringData.length();
|
||||
byte[] data = stringData.getBytes("UTF-8");
|
||||
int u8lenght = data.length;
|
||||
|
||||
if (length > 0x7F) {
|
||||
offset++;
|
||||
baos.write((length >> 8) | 0x80);
|
||||
}
|
||||
baos.write(length);
|
||||
|
||||
if (u8lenght > 0x7F) {
|
||||
offset++;
|
||||
baos.write((u8lenght >> 8) | 0x80);
|
||||
}
|
||||
baos.write(u8lenght);
|
||||
baos.write(data);
|
||||
baos.write(0);
|
||||
offset += 3 + u8lenght;
|
||||
} else {
|
||||
int length = stringData.length();
|
||||
byte[] data = stringData.getBytes("UTF-16LE");
|
||||
if (length > 0x7FFF) {
|
||||
int x = (length >> 16) | 0x8000;
|
||||
baos.write(x);
|
||||
baos.write(x >> 8);
|
||||
offset += 2;
|
||||
}
|
||||
baos.write(length);
|
||||
baos.write(length >> 8);
|
||||
baos.write(data);
|
||||
baos.write(0);
|
||||
baos.write(0);
|
||||
offset += 4 + data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
stringData = baos.toByteArray();
|
||||
}
|
||||
|
||||
private boolean useUTF8 = true;
|
||||
|
||||
public void write(ByteBuffer out) throws IOException {
|
||||
out.putInt(this.size());
|
||||
out.putInt(0);// TODO style count
|
||||
out.putInt(useUTF8 ? UTF8_FLAG : 0);
|
||||
out.putInt(7 * 4 + this.size() * 4);
|
||||
out.putInt(0);
|
||||
for (StringItem item : this) {
|
||||
out.putInt(item.dataOffset);
|
||||
}
|
||||
out.put(stringData);
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Axml extends AxmlVisitor {
|
||||
|
||||
public static class Node extends NodeVisitor {
|
||||
public static class Attr {
|
||||
public String ns, name;
|
||||
public int resourceId, type;
|
||||
public Object value;
|
||||
|
||||
public void accept(NodeVisitor nodeVisitor) {
|
||||
nodeVisitor.attr(ns, name, resourceId, type, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Text {
|
||||
public int ln;
|
||||
public String text;
|
||||
|
||||
public void accept(NodeVisitor nodeVisitor) {
|
||||
nodeVisitor.text(ln, text);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Attr> attrs = new ArrayList<Attr>();
|
||||
public List<Node> children = new ArrayList<Node>();
|
||||
public Integer ln;
|
||||
public String ns, name;
|
||||
public Text text;
|
||||
|
||||
public void accept(NodeVisitor nodeVisitor) {
|
||||
NodeVisitor nodeVisitor2 = nodeVisitor.child(ns, name);
|
||||
acceptB(nodeVisitor2);
|
||||
nodeVisitor2.end();
|
||||
}
|
||||
|
||||
public void acceptB(NodeVisitor nodeVisitor) {
|
||||
if (text != null) {
|
||||
text.accept(nodeVisitor);
|
||||
}
|
||||
for (Attr a : attrs) {
|
||||
a.accept(nodeVisitor);
|
||||
}
|
||||
if (ln != null) {
|
||||
nodeVisitor.line(ln);
|
||||
}
|
||||
for (Node c : children) {
|
||||
c.accept(nodeVisitor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
Attr attr = new Attr();
|
||||
attr.name = name;
|
||||
attr.ns = ns;
|
||||
attr.resourceId = resourceId;
|
||||
attr.type = type;
|
||||
attr.value = obj;
|
||||
attrs.add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
Node node = new Node();
|
||||
node.name = name;
|
||||
node.ns = ns;
|
||||
children.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void line(int ln) {
|
||||
this.ln = ln;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void text(int lineNumber, String value) {
|
||||
Text text = new Text();
|
||||
text.ln = lineNumber;
|
||||
text.text = value;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Ns {
|
||||
public int ln;
|
||||
public String prefix, uri;
|
||||
|
||||
public void accept(AxmlVisitor visitor) {
|
||||
visitor.ns(prefix, uri, ln);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Node> firsts = new ArrayList<Node>();
|
||||
public List<Ns> nses = new ArrayList<Ns>();
|
||||
|
||||
public void accept(final AxmlVisitor visitor) {
|
||||
for (Ns ns : nses) {
|
||||
ns.accept(visitor);
|
||||
}
|
||||
for (Node first : firsts) {
|
||||
first.accept(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
Node node = new Node();
|
||||
node.name = name;
|
||||
node.ns = ns;
|
||||
firsts.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ns(String prefix, String uri, int ln) {
|
||||
Ns ns = new Ns();
|
||||
ns.prefix = prefix;
|
||||
ns.uri = uri;
|
||||
ns.ln = ln;
|
||||
nses.add(ns);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import static pxb.android.axml.NodeVisitor.TYPE_INT_BOOLEAN;
|
||||
import static pxb.android.axml.NodeVisitor.TYPE_STRING;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import pxb.android.ResConst;
|
||||
import pxb.android.StringItems;
|
||||
|
||||
/**
|
||||
* a class to read android axml
|
||||
*
|
||||
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
|
||||
*/
|
||||
public class AxmlParser implements ResConst {
|
||||
|
||||
public static final int END_FILE = 7;
|
||||
public static final int END_NS = 5;
|
||||
public static final int END_TAG = 3;
|
||||
public static final int START_FILE = 1;
|
||||
public static final int START_NS = 4;
|
||||
public static final int START_TAG = 2;
|
||||
public static final int TEXT = 6;
|
||||
// private int attrName[];
|
||||
// private int attrNs[];
|
||||
// private int attrResId[];
|
||||
// private int attrType[];
|
||||
// private Object attrValue[];
|
||||
|
||||
private int attributeCount;
|
||||
|
||||
private IntBuffer attrs;
|
||||
|
||||
private int classAttribute;
|
||||
private int fileSize = -1;
|
||||
private int idAttribute;
|
||||
private ByteBuffer in;
|
||||
private int lineNumber;
|
||||
private int nameIdx;
|
||||
private int nsIdx;
|
||||
|
||||
private int prefixIdx;
|
||||
|
||||
private int[] resourceIds;
|
||||
|
||||
private String[] strings;
|
||||
|
||||
private int styleAttribute;
|
||||
|
||||
private int textIdx;
|
||||
|
||||
public AxmlParser(byte[] data) {
|
||||
this(ByteBuffer.wrap(data));
|
||||
}
|
||||
|
||||
public AxmlParser(ByteBuffer in) {
|
||||
super();
|
||||
this.in = in.order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
public int getAttrCount() {
|
||||
return attributeCount;
|
||||
}
|
||||
|
||||
public int getAttributeCount() {
|
||||
return attributeCount;
|
||||
}
|
||||
|
||||
public String getAttrName(int i) {
|
||||
int idx = attrs.get(i * 5 + 1);
|
||||
return strings[idx];
|
||||
|
||||
}
|
||||
|
||||
public String getAttrNs(int i) {
|
||||
int idx = attrs.get(i * 5 + 0);
|
||||
return idx >= 0 ? strings[idx] : null;
|
||||
}
|
||||
|
||||
String getAttrRawString(int i) {
|
||||
int idx = attrs.get(i * 5 + 2);
|
||||
if (idx >= 0) {
|
||||
return strings[idx];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getAttrResId(int i) {
|
||||
if (resourceIds != null) {
|
||||
int idx = attrs.get(i * 5 + 1);
|
||||
if (idx >= 0 && idx < resourceIds.length) {
|
||||
return resourceIds[idx];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getAttrType(int i) {
|
||||
return attrs.get(i * 5 + 3) >> 24;
|
||||
}
|
||||
|
||||
public Object getAttrValue(int i) {
|
||||
int v = attrs.get(i * 5 + 4);
|
||||
|
||||
if (i == idAttribute) {
|
||||
return ValueWrapper.wrapId(v, getAttrRawString(i));
|
||||
} else if (i == styleAttribute) {
|
||||
return ValueWrapper.wrapStyle(v, getAttrRawString(i));
|
||||
} else if (i == classAttribute) {
|
||||
return ValueWrapper.wrapClass(v, getAttrRawString(i));
|
||||
}
|
||||
|
||||
switch (getAttrType(i)) {
|
||||
case TYPE_STRING:
|
||||
return strings[v];
|
||||
case TYPE_INT_BOOLEAN:
|
||||
return v != 0;
|
||||
default:
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return strings[nameIdx];
|
||||
}
|
||||
|
||||
public String getNamespacePrefix() {
|
||||
return strings[prefixIdx];
|
||||
}
|
||||
|
||||
public String getNamespaceUri() {
|
||||
return nsIdx >= 0 ? strings[nsIdx] : null;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return strings[textIdx];
|
||||
}
|
||||
|
||||
public int next() throws IOException {
|
||||
if (fileSize < 0) {
|
||||
int type = in.getInt() & 0xFFFF;
|
||||
if (type != RES_XML_TYPE) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
fileSize = in.getInt();
|
||||
return START_FILE;
|
||||
}
|
||||
int event = -1;
|
||||
for (int p = in.position(); p < fileSize; p = in.position()) {
|
||||
int type = in.getInt() & 0xFFFF;
|
||||
int size = in.getInt();
|
||||
switch (type) {
|
||||
case RES_XML_START_ELEMENT_TYPE: {
|
||||
{
|
||||
lineNumber = in.getInt();
|
||||
in.getInt();/* skip, 0xFFFFFFFF */
|
||||
nsIdx = in.getInt();
|
||||
nameIdx = in.getInt();
|
||||
int flag = in.getInt();// 0x00140014 ?
|
||||
if (flag != 0x00140014) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
attributeCount = in.getShort() & 0xFFFF;
|
||||
idAttribute = (in.getShort() & 0xFFFF) - 1;
|
||||
classAttribute = (in.getShort() & 0xFFFF) - 1;
|
||||
styleAttribute = (in.getShort() & 0xFFFF) - 1;
|
||||
|
||||
attrs = in.asIntBuffer();
|
||||
|
||||
// attrResId = new int[attributeCount];
|
||||
// attrName = new int[attributeCount];
|
||||
// attrNs = new int[attributeCount];
|
||||
// attrType = new int[attributeCount];
|
||||
// attrValue = new Object[attributeCount];
|
||||
// for (int i = 0; i < attributeCount; i++) {
|
||||
// int attrNsIdx = in.getInt();
|
||||
// int attrNameIdx = in.getInt();
|
||||
// int raw = in.getInt();
|
||||
// int aValueType = in.getInt() >>> 24;
|
||||
// int aValue = in.getInt();
|
||||
// Object value = null;
|
||||
// switch (aValueType) {
|
||||
// case TYPE_STRING:
|
||||
// value = strings[aValue];
|
||||
// break;
|
||||
// case TYPE_INT_BOOLEAN:
|
||||
// value = aValue != 0;
|
||||
// break;
|
||||
// default:
|
||||
// value = aValue;
|
||||
// }
|
||||
// int resourceId = attrNameIdx < this.resourceIds.length ?
|
||||
// resourceIds[attrNameIdx] : -1;
|
||||
// attrNs[i] = attrNsIdx;
|
||||
// attrName[i] = attrNameIdx;
|
||||
// attrType[i] = aValueType;
|
||||
// attrResId[i] = resourceId;
|
||||
// attrValue[i] = value;
|
||||
// }
|
||||
event = START_TAG;
|
||||
}
|
||||
break;
|
||||
case RES_XML_END_ELEMENT_TYPE: {
|
||||
in.position(p + size);
|
||||
event = END_TAG;
|
||||
}
|
||||
break;
|
||||
case RES_XML_START_NAMESPACE_TYPE:
|
||||
lineNumber = in.getInt();
|
||||
in.getInt();/* 0xFFFFFFFF */
|
||||
prefixIdx = in.getInt();
|
||||
nsIdx = in.getInt();
|
||||
event = START_NS;
|
||||
break;
|
||||
case RES_XML_END_NAMESPACE_TYPE:
|
||||
in.position(p + size);
|
||||
event = END_NS;
|
||||
break;
|
||||
case RES_STRING_POOL_TYPE:
|
||||
strings = StringItems.read(in);
|
||||
in.position(p + size);
|
||||
continue;
|
||||
case RES_XML_RESOURCE_MAP_TYPE:
|
||||
int count = size / 4 - 2;
|
||||
resourceIds = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
resourceIds[i] = in.getInt();
|
||||
}
|
||||
in.position(p + size);
|
||||
continue;
|
||||
case RES_XML_CDATA_TYPE:
|
||||
lineNumber = in.getInt();
|
||||
in.getInt();/* 0xFFFFFFFF */
|
||||
textIdx = in.getInt();
|
||||
|
||||
in.getInt();/* 00000008 00000000 */
|
||||
in.getInt();
|
||||
|
||||
event = TEXT;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
}
|
||||
in.position(p + size);
|
||||
return event;
|
||||
}
|
||||
return END_FILE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import static pxb.android.axml.AxmlParser.END_FILE;
|
||||
import static pxb.android.axml.AxmlParser.END_NS;
|
||||
import static pxb.android.axml.AxmlParser.END_TAG;
|
||||
import static pxb.android.axml.AxmlParser.START_FILE;
|
||||
import static pxb.android.axml.AxmlParser.START_NS;
|
||||
import static pxb.android.axml.AxmlParser.START_TAG;
|
||||
import static pxb.android.axml.AxmlParser.TEXT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* a class to read android axml
|
||||
*
|
||||
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
|
||||
*/
|
||||
public class AxmlReader {
|
||||
public static final NodeVisitor EMPTY_VISITOR = new NodeVisitor() {
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
return this;
|
||||
}
|
||||
|
||||
};
|
||||
final AxmlParser parser;
|
||||
|
||||
public AxmlReader(byte[] data) {
|
||||
super();
|
||||
this.parser = new AxmlParser(data);
|
||||
}
|
||||
|
||||
public void accept(final AxmlVisitor av) throws IOException {
|
||||
Stack<NodeVisitor> nvs = new Stack<NodeVisitor>();
|
||||
NodeVisitor tos = av;
|
||||
while (true) {
|
||||
int type = parser.next();
|
||||
switch (type) {
|
||||
case START_FILE:
|
||||
break;
|
||||
case START_TAG:
|
||||
nvs.push(tos);
|
||||
tos = tos.child(parser.getNamespaceUri(), parser.getName());
|
||||
if (tos != null) {
|
||||
if (tos != EMPTY_VISITOR) {
|
||||
tos.line(parser.getLineNumber());
|
||||
for (int i = 0; i < parser.getAttrCount(); i++) {
|
||||
tos.attr(parser.getAttrNs(i), parser.getAttrName(i), parser.getAttrResId(i),
|
||||
parser.getAttrType(i), parser.getAttrValue(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tos = EMPTY_VISITOR;
|
||||
}
|
||||
break;
|
||||
case END_TAG:
|
||||
tos.end();
|
||||
tos = nvs.pop();
|
||||
break;
|
||||
case START_NS:
|
||||
av.ns(parser.getNamespacePrefix(), parser.getNamespaceUri(), parser.getLineNumber());
|
||||
break;
|
||||
case END_NS:
|
||||
break;
|
||||
case TEXT:
|
||||
tos.text(parser.getLineNumber(), parser.getText());
|
||||
break;
|
||||
case END_FILE:
|
||||
return;
|
||||
default:
|
||||
System.err.println("AxmlReader: Unsupported tag: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
/**
|
||||
* visitor to visit an axml
|
||||
*
|
||||
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
|
||||
*/
|
||||
public class AxmlVisitor extends NodeVisitor {
|
||||
|
||||
public AxmlVisitor() {
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
public AxmlVisitor(NodeVisitor av) {
|
||||
super(av);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a ns
|
||||
*
|
||||
* @param prefix
|
||||
* @param uri
|
||||
* @param ln
|
||||
*/
|
||||
public void ns(String prefix, String uri, int ln) {
|
||||
if (nv != null && nv instanceof AxmlVisitor) {
|
||||
((AxmlVisitor) nv).ns(prefix, uri, ln);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,454 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import pxb.android.StringItem;
|
||||
import pxb.android.StringItems;
|
||||
|
||||
import static pxb.android.ResConst.RES_STRING_POOL_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_CDATA_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_END_ELEMENT_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_END_NAMESPACE_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_RESOURCE_MAP_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_START_ELEMENT_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_START_NAMESPACE_TYPE;
|
||||
import static pxb.android.ResConst.RES_XML_TYPE;
|
||||
|
||||
/**
|
||||
* a class to write android axml
|
||||
*
|
||||
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
|
||||
*/
|
||||
public class AxmlWriter extends AxmlVisitor {
|
||||
static final Comparator<Attr> ATTR_CMP = new Comparator<Attr>() {
|
||||
|
||||
@Override
|
||||
public int compare(Attr a, Attr b) {
|
||||
int x = a.resourceId - b.resourceId;
|
||||
if (x == 0) {
|
||||
x = a.name.data.compareTo(b.name.data);
|
||||
if (x == 0) {
|
||||
boolean aNsIsnull = a.ns == null;
|
||||
boolean bNsIsnull = b.ns == null;
|
||||
if (aNsIsnull) {
|
||||
if (bNsIsnull) {
|
||||
x = 0;
|
||||
} else {
|
||||
x = -1;
|
||||
}
|
||||
} else {
|
||||
if (bNsIsnull) {
|
||||
x = 1;
|
||||
} else {
|
||||
x = a.ns.data.compareTo(b.ns.data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
static class Attr {
|
||||
|
||||
public int index;
|
||||
public StringItem name;
|
||||
public StringItem ns;
|
||||
public int resourceId;
|
||||
public int type;
|
||||
public Object value;
|
||||
public StringItem raw;
|
||||
|
||||
public Attr(StringItem ns, StringItem name, int resourceId) {
|
||||
super();
|
||||
this.ns = ns;
|
||||
this.name = name;
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public void prepare(AxmlWriter axmlWriter) {
|
||||
ns = axmlWriter.updateNs(ns);
|
||||
if (this.name != null) {
|
||||
if (resourceId != -1) {
|
||||
this.name = axmlWriter.updateWithResourceId(this.name, this.resourceId);
|
||||
} else {
|
||||
this.name = axmlWriter.update(this.name);
|
||||
}
|
||||
}
|
||||
if (value instanceof StringItem) {
|
||||
value = axmlWriter.update((StringItem) value);
|
||||
}
|
||||
if (raw != null) {
|
||||
raw = axmlWriter.update(raw);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NodeImpl extends NodeVisitor {
|
||||
private Set<Attr> attrs = new TreeSet<Attr>(ATTR_CMP);
|
||||
private List<NodeImpl> children = new ArrayList<NodeImpl>();
|
||||
private int line;
|
||||
private StringItem name;
|
||||
private StringItem ns;
|
||||
private StringItem text;
|
||||
private int textLineNumber;
|
||||
Attr id;
|
||||
Attr style;
|
||||
Attr clz;
|
||||
|
||||
public NodeImpl(String ns, String name) {
|
||||
super(null);
|
||||
this.ns = ns == null ? null : new StringItem(ns);
|
||||
this.name = name == null ? null : new StringItem(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object value) {
|
||||
if (name == null) {
|
||||
throw new RuntimeException("name can't be null");
|
||||
}
|
||||
Attr a = new Attr(ns == null ? null : new StringItem(ns), new StringItem(name), resourceId);
|
||||
a.type = type;
|
||||
|
||||
if (value instanceof ValueWrapper) {
|
||||
ValueWrapper valueWrapper = (ValueWrapper) value;
|
||||
if (valueWrapper.raw != null) {
|
||||
a.raw = new StringItem(valueWrapper.raw);
|
||||
}
|
||||
a.value = valueWrapper.ref;
|
||||
switch (valueWrapper.type) {
|
||||
case ValueWrapper.CLASS:
|
||||
clz = a;
|
||||
break;
|
||||
case ValueWrapper.ID:
|
||||
id = a;
|
||||
break;
|
||||
case ValueWrapper.STYLE:
|
||||
style = a;
|
||||
break;
|
||||
}
|
||||
} else if (type == TYPE_STRING) {
|
||||
StringItem raw = new StringItem((String) value);
|
||||
a.raw = raw;
|
||||
a.value = raw;
|
||||
|
||||
} else {
|
||||
a.raw = null;
|
||||
a.value = value;
|
||||
}
|
||||
|
||||
attrs.add(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeImpl child = new NodeImpl(ns, name);
|
||||
this.children.add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void line(int ln) {
|
||||
this.line = ln;
|
||||
}
|
||||
|
||||
public int prepare(AxmlWriter axmlWriter) {
|
||||
ns = axmlWriter.updateNs(ns);
|
||||
name = axmlWriter.update(name);
|
||||
|
||||
int attrIndex = 0;
|
||||
for (Attr attr : attrs) {
|
||||
attr.index = attrIndex++;
|
||||
attr.prepare(axmlWriter);
|
||||
}
|
||||
|
||||
text = axmlWriter.update(text);
|
||||
int size = 24 + 36 + attrs.size() * 20;// 24 for end tag,36+x*20 for
|
||||
// start tag
|
||||
for (NodeImpl child : children) {
|
||||
size += child.prepare(axmlWriter);
|
||||
}
|
||||
if (text != null) {
|
||||
size += 28;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void text(int ln, String value) {
|
||||
this.text = new StringItem(value);
|
||||
this.textLineNumber = ln;
|
||||
}
|
||||
|
||||
void write(ByteBuffer out) throws IOException {
|
||||
// start tag
|
||||
out.putInt(RES_XML_START_ELEMENT_TYPE | (0x0010 << 16));
|
||||
out.putInt(36 + attrs.size() * 20);
|
||||
out.putInt(line);
|
||||
out.putInt(0xFFFFFFFF);
|
||||
out.putInt(ns != null ? this.ns.index : -1);
|
||||
out.putInt(name.index);
|
||||
out.putInt(0x00140014);// TODO
|
||||
out.putShort((short) this.attrs.size());
|
||||
out.putShort((short) (id == null ? 0 : id.index + 1));
|
||||
out.putShort((short) (clz == null ? 0 : clz.index + 1));
|
||||
out.putShort((short) (style == null ? 0 : style.index + 1));
|
||||
for (Attr attr : attrs) {
|
||||
out.putInt(attr.ns == null ? -1 : attr.ns.index);
|
||||
out.putInt(attr.name.index);
|
||||
out.putInt(attr.raw != null ? attr.raw.index : -1);
|
||||
out.putInt((attr.type << 24) | 0x000008);
|
||||
Object v = attr.value;
|
||||
if (v instanceof StringItem) {
|
||||
out.putInt(((StringItem) attr.value).index);
|
||||
} else if (v instanceof Boolean) {
|
||||
out.putInt(Boolean.TRUE.equals(v) ? -1 : 0);
|
||||
} else {
|
||||
if (attr.value instanceof Integer) {
|
||||
out.putInt((Integer) attr.value);
|
||||
} else if (attr.value instanceof String) {
|
||||
if ("true".equalsIgnoreCase((String) attr.value)) {
|
||||
out.putInt(-1);
|
||||
} else if ("false".equalsIgnoreCase((String) attr.value)) {
|
||||
out.putInt(0);
|
||||
} else {
|
||||
try {
|
||||
out.putInt(Integer.valueOf((String) attr.value));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.text != null) {
|
||||
out.putInt(RES_XML_CDATA_TYPE | (0x0010 << 16));
|
||||
out.putInt(28);
|
||||
out.putInt(textLineNumber);
|
||||
out.putInt(0xFFFFFFFF);
|
||||
out.putInt(text.index);
|
||||
out.putInt(0x00000008);
|
||||
out.putInt(0x00000000);
|
||||
}
|
||||
|
||||
// children
|
||||
for (NodeImpl child : children) {
|
||||
child.write(out);
|
||||
}
|
||||
|
||||
// end tag
|
||||
out.putInt(RES_XML_END_ELEMENT_TYPE | (0x0010 << 16));
|
||||
out.putInt(24);
|
||||
out.putInt(-1);
|
||||
out.putInt(0xFFFFFFFF);
|
||||
out.putInt(ns != null ? this.ns.index : -1);
|
||||
out.putInt(name.index);
|
||||
}
|
||||
}
|
||||
|
||||
static class Ns {
|
||||
int ln;
|
||||
StringItem prefix;
|
||||
StringItem uri;
|
||||
|
||||
public Ns(StringItem prefix, StringItem uri, int ln) {
|
||||
super();
|
||||
this.prefix = prefix;
|
||||
this.uri = uri;
|
||||
this.ln = ln;
|
||||
}
|
||||
}
|
||||
|
||||
private List<NodeImpl> firsts = new ArrayList<NodeImpl>(3);
|
||||
|
||||
private Map<String, Ns> nses = new HashMap<String, Ns>();
|
||||
|
||||
private List<StringItem> otherString = new ArrayList<StringItem>();
|
||||
|
||||
private Map<String, StringItem> resourceId2Str = new HashMap<String, StringItem>();
|
||||
|
||||
private List<Integer> resourceIds = new ArrayList<Integer>();
|
||||
|
||||
private List<StringItem> resourceString = new ArrayList<StringItem>();
|
||||
|
||||
private StringItems stringItems = new StringItems();
|
||||
|
||||
// TODO add style support
|
||||
// private List<StringItem> styleItems = new ArrayList();
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
NodeImpl first = new NodeImpl(ns, name);
|
||||
this.firsts.add(first);
|
||||
return first;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ns(String prefix, String uri, int ln) {
|
||||
nses.put(uri, new Ns(prefix == null ? null : new StringItem(prefix), new StringItem(uri), ln));
|
||||
}
|
||||
|
||||
private int prepare() throws IOException {
|
||||
int size = 0;
|
||||
|
||||
for (NodeImpl first : firsts) {
|
||||
size += first.prepare(this);
|
||||
}
|
||||
{
|
||||
int a = 0;
|
||||
for (Map.Entry<String, Ns> e : nses.entrySet()) {
|
||||
Ns ns = e.getValue();
|
||||
if (ns == null) {
|
||||
ns = new Ns(null, new StringItem(e.getKey()), 0);
|
||||
e.setValue(ns);
|
||||
}
|
||||
if (ns.prefix == null) {
|
||||
ns.prefix = new StringItem(String.format("axml_auto_%02d", a++));
|
||||
}
|
||||
ns.prefix = update(ns.prefix);
|
||||
ns.uri = update(ns.uri);
|
||||
}
|
||||
}
|
||||
|
||||
size += nses.size() * 24 * 2;
|
||||
|
||||
this.stringItems.addAll(resourceString);
|
||||
resourceString = null;
|
||||
this.stringItems.addAll(otherString);
|
||||
otherString = null;
|
||||
this.stringItems.prepare();
|
||||
int stringSize = this.stringItems.getSize();
|
||||
if (stringSize % 4 != 0) {
|
||||
stringSize += 4 - stringSize % 4;
|
||||
}
|
||||
size += 8 + stringSize;
|
||||
size += 8 + resourceIds.size() * 4;
|
||||
return size;
|
||||
}
|
||||
|
||||
public byte[] toByteArray() throws IOException {
|
||||
|
||||
int size = 8 + prepare();
|
||||
ByteBuffer out = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
out.putInt(RES_XML_TYPE | (0x0008 << 16));
|
||||
out.putInt(size);
|
||||
|
||||
int stringSize = this.stringItems.getSize();
|
||||
int padding = 0;
|
||||
if (stringSize % 4 != 0) {
|
||||
padding = 4 - stringSize % 4;
|
||||
}
|
||||
out.putInt(RES_STRING_POOL_TYPE | (0x001C << 16));
|
||||
out.putInt(stringSize + padding + 8);
|
||||
this.stringItems.write(out);
|
||||
out.put(new byte[padding]);
|
||||
|
||||
out.putInt(RES_XML_RESOURCE_MAP_TYPE | (0x0008 << 16));
|
||||
out.putInt(8 + this.resourceIds.size() * 4);
|
||||
for (Integer i : resourceIds) {
|
||||
out.putInt(i);
|
||||
}
|
||||
|
||||
Stack<Ns> stack = new Stack<Ns>();
|
||||
for (Map.Entry<String, Ns> e : this.nses.entrySet()) {
|
||||
Ns ns = e.getValue();
|
||||
stack.push(ns);
|
||||
out.putInt(RES_XML_START_NAMESPACE_TYPE | (0x0010 << 16));
|
||||
out.putInt(24);
|
||||
out.putInt(-1);
|
||||
out.putInt(0xFFFFFFFF);
|
||||
out.putInt(ns.prefix.index);
|
||||
out.putInt(ns.uri.index);
|
||||
}
|
||||
|
||||
for (NodeImpl first : firsts) {
|
||||
first.write(out);
|
||||
}
|
||||
|
||||
while (stack.size() > 0) {
|
||||
Ns ns = stack.pop();
|
||||
out.putInt(RES_XML_END_NAMESPACE_TYPE | (0x0010 << 16));
|
||||
out.putInt(24);
|
||||
out.putInt(ns.ln);
|
||||
out.putInt(0xFFFFFFFF);
|
||||
out.putInt(ns.prefix.index);
|
||||
out.putInt(ns.uri.index);
|
||||
}
|
||||
return out.array();
|
||||
}
|
||||
|
||||
StringItem update(StringItem item) {
|
||||
if (item == null)
|
||||
return null;
|
||||
int i = this.otherString.indexOf(item);
|
||||
if (i < 0) {
|
||||
StringItem copy = new StringItem(item.data);
|
||||
this.otherString.add(copy);
|
||||
return copy;
|
||||
} else {
|
||||
return this.otherString.get(i);
|
||||
}
|
||||
}
|
||||
|
||||
StringItem updateNs(StringItem item) {
|
||||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
String ns = item.data;
|
||||
if (!this.nses.containsKey(ns)) {
|
||||
this.nses.put(ns, null);
|
||||
}
|
||||
return update(item);
|
||||
}
|
||||
|
||||
StringItem updateWithResourceId(StringItem name, int resourceId) {
|
||||
String key = name.data + resourceId;
|
||||
StringItem item = this.resourceId2Str.get(key);
|
||||
if (item != null) {
|
||||
return item;
|
||||
} else {
|
||||
StringItem copy = new StringItem(name.data);
|
||||
resourceIds.add(resourceId);
|
||||
resourceString.add(copy);
|
||||
resourceId2Str.put(key, copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* dump axml to stdout
|
||||
*
|
||||
* @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
|
||||
*/
|
||||
public class DumpAdapter extends AxmlVisitor {
|
||||
protected int deep;
|
||||
protected Map<String, String> nses;
|
||||
|
||||
public DumpAdapter() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public DumpAdapter(NodeVisitor nv) {
|
||||
this(nv, 0, new HashMap<String, String>());
|
||||
}
|
||||
|
||||
public DumpAdapter(NodeVisitor nv, int x, Map<String, String> nses) {
|
||||
super(nv);
|
||||
this.deep = x;
|
||||
this.nses = nses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
for (int i = 0; i < deep; i++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
if (ns != null) {
|
||||
System.out.print(String.format("%s:", getPrefix(ns)));
|
||||
}
|
||||
System.out.print(name);
|
||||
if (resourceId != -1) {
|
||||
System.out.print(String.format("(%08x)", resourceId));
|
||||
}
|
||||
if (obj instanceof String) {
|
||||
System.out.print(String.format("=[%08x]\"%s\"", type, obj));
|
||||
} else if (obj instanceof Boolean) {
|
||||
System.out.print(String.format("=[%08x]\"%b\"", type, obj));
|
||||
} else if (obj instanceof ValueWrapper) {
|
||||
ValueWrapper w = (ValueWrapper) obj;
|
||||
System.out.print(String.format("=[%08x]@%08x, raw: \"%s\"", type, w.ref, w.raw));
|
||||
} else if (type == TYPE_REFERENCE) {
|
||||
System.out.print(String.format("=[%08x]@%08x", type, obj));
|
||||
} else {
|
||||
System.out.print(String.format("=[%08x]%08x", type, obj));
|
||||
}
|
||||
System.out.println();
|
||||
super.attr(ns, name, resourceId, type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
for (int i = 0; i < deep; i++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.print("<");
|
||||
if (ns != null) {
|
||||
System.out.print(getPrefix(ns) + ":");
|
||||
}
|
||||
System.out.println(name);
|
||||
NodeVisitor nv = super.child(ns, name);
|
||||
if (nv != null) {
|
||||
return new DumpAdapter(nv, deep + 1, nses);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getPrefix(String uri) {
|
||||
if (nses != null) {
|
||||
String prefix = nses.get(uri);
|
||||
if (prefix != null) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ns(String prefix, String uri, int ln) {
|
||||
System.out.println(prefix + "=" + uri);
|
||||
this.nses.put(uri, prefix);
|
||||
super.ns(prefix, uri, ln);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void text(int ln, String value) {
|
||||
for (int i = 0; i < deep + 1; i++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.print("T: ");
|
||||
System.out.println(value);
|
||||
super.text(ln, value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
public abstract class NodeVisitor {
|
||||
|
||||
public static final int TYPE_FIRST_INT = 0x10;
|
||||
public static final int TYPE_INT_BOOLEAN = 0x12;
|
||||
public static final int TYPE_INT_HEX = 0x11;
|
||||
public static final int TYPE_REFERENCE = 0x01;
|
||||
public static final int TYPE_STRING = 0x03;
|
||||
protected NodeVisitor nv;
|
||||
|
||||
public NodeVisitor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NodeVisitor(NodeVisitor nv) {
|
||||
super();
|
||||
this.nv = nv;
|
||||
}
|
||||
|
||||
/**
|
||||
* add attribute to the node
|
||||
*
|
||||
* @param ns
|
||||
* @param name
|
||||
* @param resourceId
|
||||
* @param type
|
||||
* {@link #TYPE_STRING} or others
|
||||
* @param obj
|
||||
* a string for {@link #TYPE_STRING} ,and Integer for others
|
||||
*/
|
||||
public void attr(String ns, String name, int resourceId, int type, Object obj) {
|
||||
if (nv != null) {
|
||||
nv.attr(ns, name, resourceId, type, obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a child node
|
||||
*
|
||||
* @param ns
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public NodeVisitor child(String ns, String name) {
|
||||
if (nv != null) {
|
||||
return nv.child(ns, name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* end the visit
|
||||
*/
|
||||
public void end() {
|
||||
if (nv != null) {
|
||||
nv.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* line number in the .xml
|
||||
*
|
||||
* @param ln
|
||||
*/
|
||||
public void line(int ln) {
|
||||
if (nv != null) {
|
||||
nv.line(ln);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the node text
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void text(int lineNumber, String value) {
|
||||
if (nv != null) {
|
||||
nv.text(lineNumber, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2009-2013 Panxiaobo
|
||||
*
|
||||
* 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 pxb.android.axml;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Util {
|
||||
public static byte[] readFile(File in) throws IOException {
|
||||
InputStream is = new FileInputStream(in);
|
||||
byte[] xml = new byte[is.available()];
|
||||
is.read(xml);
|
||||
is.close();
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static byte[] readIs(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
copy(is, os);
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
public static void writeFile(byte[] data, File out) throws IOException {
|
||||
FileOutputStream fos = new FileOutputStream(out);
|
||||
fos.write(data);
|
||||
fos.close();
|
||||
}
|
||||
|
||||
public static Map<String, String> readProguardConfig(File config) throws IOException {
|
||||
Map<String, String> clzMap = new HashMap<String, String>();
|
||||
BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(config), "utf8"));
|
||||
try {
|
||||
for (String ln = r.readLine(); ln != null; ln = r.readLine()) {
|
||||
if (ln.startsWith("#") || ln.startsWith(" ")) {
|
||||
continue;
|
||||
}
|
||||
// format a.pt.Main -> a.a.a:
|
||||
int i = ln.indexOf("->");
|
||||
if (i > 0) {
|
||||
clzMap.put(ln.substring(0, i).trim(), ln.substring(i + 2, ln.length() - 1).trim());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
r.close();
|
||||
}
|
||||
return clzMap;
|
||||
}
|
||||
|
||||
public static void copy(InputStream is, OutputStream os) throws IOException {
|
||||
byte[] xml = new byte[10 * 1024];
|
||||
for (int c = is.read(xml); c > 0; c = is.read(xml)) {
|
||||
os.write(xml, 0, c);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package pxb.android.axml;
|
||||
|
||||
public class ValueWrapper {
|
||||
|
||||
public static final int ID = 1;
|
||||
public static final int STYLE = 2;
|
||||
public static final int CLASS = 3;
|
||||
public final int type;
|
||||
public final String raw;
|
||||
public final int ref;
|
||||
|
||||
private ValueWrapper(int type, int ref, String raw) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.raw = raw;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public ValueWrapper replaceRaw(String raw) {
|
||||
return new ValueWrapper(type, ref, raw);
|
||||
}
|
||||
|
||||
public static ValueWrapper wrapId(int ref, String raw) {
|
||||
return new ValueWrapper(ID, ref, raw);
|
||||
}
|
||||
|
||||
public static ValueWrapper wrapStyle(int ref, String raw) {
|
||||
return new ValueWrapper(STYLE, ref, raw);
|
||||
}
|
||||
|
||||
public static ValueWrapper wrapClass(int ref, String raw) {
|
||||
return new ValueWrapper(CLASS, ref, raw);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
include ':lib'
|
||||
rootProject.name='ManifestEditor'
|
Loading…
Reference in New Issue