Initial commit

master
xiawanli 2020-02-27 11:46:54 +08:00
commit 631582fbab
43 changed files with 7275 additions and 0 deletions

15
.gitignore vendored 100644
View File

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

41
build.gradle 100644
View File

@ -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')
}
}
}

20
gradle.properties 100644
View File

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

BIN
gradle/wrapper/gradle-wrapper.jar vendored 100644

Binary file not shown.

View File

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

172
gradlew vendored 100755
View File

@ -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" "$@"

84
gradlew.bat vendored 100644
View File

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

1
lib/.gitignore vendored 100644
View File

@ -0,0 +1 @@
/build

31
lib/build.gradle 100644
View File

@ -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'
}
//jarjar
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
/**
* bytestring
*
* @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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
settings.gradle 100644
View File

@ -0,0 +1,2 @@
include ':lib'
rootProject.name='ManifestEditor'