Add files via upload

init
This commit is contained in:
Windy 2019-03-24 23:28:45 +08:00 committed by GitHub
parent d09d3faf4d
commit 2c7fccaefd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 5500 additions and 0 deletions

203
LICENSE.txt Normal file
View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

4
NOTICE.txt Normal file
View File

@ -0,0 +1,4 @@
Xpatch - Tool to work with android apk file, it can insert xposed loader into the apk file, and repackage the apk, then
the repacked apk can load xposed module when started.
Copyright (c) 2019 Wind

6
axmlprinter/build.gradle Normal file
View File

@ -0,0 +1,6 @@
description = 'android manifest content parser'
apply plugin: 'java-library'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -0,0 +1,932 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.content.res;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import wind.v1.XmlPullParserException;
import wind.android.content.res.ChunkUtil;
import wind.android.content.res.IntReader;
import wind.android.content.res.StringBlock;
import wind.android.content.res.XmlResourceParser;
import wind.android.util.TypedValue;
/**
* @author Dmitry Skiba
*
* Binary xml files parser.
*
* Parser has only two states:
* (1) Operational state, which parser obtains after first successful call
* to next() and retains until open(), close(), or failed call to next().
* (2) Closed state, which parser obtains after open(), close(), or failed
* call to next(). In this state methods return invalid values or throw exceptions.
*
* TODO:
* * check all methods in closed state
*
*/
public class AXmlResourceParser implements XmlResourceParser {
public AXmlResourceParser() {
resetEventInfo();
}
public void open(InputStream stream) {
close();
if (stream!=null) {
m_reader=new wind.android.content.res.IntReader(stream,false);
}
}
public void close() {
if (!m_operational) {
return;
}
m_operational=false;
m_reader.close();
m_reader=null;
m_strings=null;
m_resourceIDs=null;
m_namespaces.reset();
resetEventInfo();
}
/////////////////////////////////// iteration
public int next() throws XmlPullParserException,IOException {
if (m_reader==null) {
throw new XmlPullParserException("Parser is not opened.",this,null);
}
try {
doNext();
return m_event;
}
catch (IOException e) {
close();
throw e;
}
}
public int nextToken() throws XmlPullParserException,IOException {
return next();
}
public int nextTag() throws XmlPullParserException,IOException {
int eventType=next();
if (eventType==TEXT && isWhitespace()) {
eventType=next();
}
if (eventType!=START_TAG && eventType!=END_TAG) {
throw new XmlPullParserException("Expected start or end tag.",this,null);
}
return eventType;
}
public String nextText() throws XmlPullParserException,IOException {
if(getEventType()!=START_TAG) {
throw new XmlPullParserException("Parser must be on START_TAG to read next text.",this,null);
}
int eventType=next();
if (eventType==TEXT) {
String result=getText();
eventType=next();
if (eventType!=END_TAG) {
throw new XmlPullParserException("Event TEXT must be immediately followed by END_TAG.",this,null);
}
return result;
} else if (eventType==END_TAG) {
return "";
} else {
throw new XmlPullParserException("Parser must be on START_TAG or TEXT to read text.",this,null);
}
}
public void require(int type,String namespace,String name) throws XmlPullParserException,IOException {
if (type!=getEventType() ||
(namespace!=null && !namespace.equals(getNamespace())) ||
(name!=null && !name.equals(getName())))
{
throw new XmlPullParserException(TYPES[type]+" is expected.",this,null);
}
}
public int getDepth() {
return m_namespaces.getDepth()-1;
}
public int getEventType() throws XmlPullParserException {
return m_event;
}
public int getLineNumber() {
return m_lineNumber;
}
public String getName() {
if (m_name==-1 || (m_event!=START_TAG && m_event!=END_TAG)) {
return null;
}
return m_strings.getString(m_name);
}
public String getText() {
if (m_name==-1 || m_event!=TEXT) {
return null;
}
return m_strings.getString(m_name);
}
public char[] getTextCharacters(int[] holderForStartAndLength) {
String text=getText();
if (text==null) {
return null;
}
holderForStartAndLength[0]=0;
holderForStartAndLength[1]=text.length();
char[] chars=new char[text.length()];
text.getChars(0,text.length(),chars,0);
return chars;
}
public String getNamespace() {
return m_strings.getString(m_namespaceUri);
}
public String getPrefix() {
int prefix=m_namespaces.findPrefix(m_namespaceUri);
return m_strings.getString(prefix);
}
public String getPositionDescription() {
return "XML line #"+getLineNumber();
}
public int getNamespaceCount(int depth) throws XmlPullParserException {
return m_namespaces.getAccumulatedCount(depth);
}
public String getNamespacePrefix(int pos) throws XmlPullParserException {
int prefix=m_namespaces.getPrefix(pos);
return m_strings.getString(prefix);
}
public String getNamespaceUri(int pos) throws XmlPullParserException {
int uri=m_namespaces.getUri(pos);
return m_strings.getString(uri);
}
/////////////////////////////////// attributes
public String getClassAttribute() {
if (m_classAttribute==-1) {
return null;
}
int offset=getAttributeOffset(m_classAttribute);
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public String getIdAttribute() {
if (m_idAttribute==-1) {
return null;
}
int offset=getAttributeOffset(m_idAttribute);
int value=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public int getIdAttributeResourceValue(int defaultValue) {
if (m_idAttribute==-1) {
return defaultValue;
}
int offset=getAttributeOffset(m_idAttribute);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType!=TypedValue.TYPE_REFERENCE) {
return defaultValue;
}
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public int getStyleAttribute() {
if (m_styleAttribute==-1) {
return 0;
}
int offset=getAttributeOffset(m_styleAttribute);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public int getAttributeCount() {
if (m_event!=START_TAG) {
return -1;
}
return m_attributes.length/ATTRIBUTE_LENGHT;
}
public String getAttributeNamespace(int index) {
int offset=getAttributeOffset(index);
int namespace=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
if (namespace==-1) {
return "";
}
return m_strings.getString(namespace);
}
public String getAttributePrefix(int index) {
int offset=getAttributeOffset(index);
int uri=m_attributes[offset+ATTRIBUTE_IX_NAMESPACE_URI];
int prefix=m_namespaces.findPrefix(uri);
if (prefix==-1) {
return "";
}
return m_strings.getString(prefix);
}
public String getAttributeName(int index) {
int offset=getAttributeOffset(index);
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
if (name==-1) {
return "";
}
return m_strings.getString(name);
}
public int getAttributeNameResource(int index) {
int offset=getAttributeOffset(index);
int name=m_attributes[offset+ATTRIBUTE_IX_NAME];
if (m_resourceIDs==null ||
name<0 || name>=m_resourceIDs.length)
{
return 0;
}
return m_resourceIDs[name];
}
public int getAttributeValueType(int index) {
int offset=getAttributeOffset(index);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
}
public int getAttributeValueData(int index) {
int offset=getAttributeOffset(index);
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
public String getAttributeValue(int index) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_STRING) {
int valueString=m_attributes[offset+ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(valueString);
}
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
return "";//TypedValue.coerceToString(valueType,valueData);
}
public boolean getAttributeBooleanValue(int index,boolean defaultValue) {
return getAttributeIntValue(index,defaultValue?1:0)!=0;
}
public float getAttributeFloatValue(int index,float defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_FLOAT) {
int valueData=m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
return Float.intBitsToFloat(valueData);
}
return defaultValue;
}
public int getAttributeIntValue(int index,int defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType>=TypedValue.TYPE_FIRST_INT &&
valueType<=TypedValue.TYPE_LAST_INT)
{
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public int getAttributeUnsignedIntValue(int index,int defaultValue) {
return getAttributeIntValue(index,defaultValue);
}
public int getAttributeResourceValue(int index,int defaultValue) {
int offset=getAttributeOffset(index);
int valueType=m_attributes[offset+ATTRIBUTE_IX_VALUE_TYPE];
if (valueType==TypedValue.TYPE_REFERENCE) {
return m_attributes[offset+ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public String getAttributeValue(String namespace,String attribute) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return null;
}
return getAttributeValue(index);
}
public boolean getAttributeBooleanValue(String namespace,String attribute,boolean defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeBooleanValue(index,defaultValue);
}
public float getAttributeFloatValue(String namespace,String attribute,float defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeFloatValue(index,defaultValue);
}
public int getAttributeIntValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeIntValue(index,defaultValue);
}
public int getAttributeUnsignedIntValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeUnsignedIntValue(index,defaultValue);
}
public int getAttributeResourceValue(String namespace,String attribute,int defaultValue) {
int index=findAttribute(namespace,attribute);
if (index==-1) {
return defaultValue;
}
return getAttributeResourceValue(index,defaultValue);
}
public int getAttributeListValue(int index,String[] options,int defaultValue) {
// TODO implement
return 0;
}
public int getAttributeListValue(String namespace,String attribute,String[] options,int defaultValue) {
// TODO implement
return 0;
}
public String getAttributeType(int index) {
return "CDATA";
}
public boolean isAttributeDefault(int index) {
return false;
}
/////////////////////////////////// dummies
public void setInput(InputStream stream,String inputEncoding) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public void setInput(Reader reader) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getInputEncoding() {
return null;
}
public int getColumnNumber() {
return -1;
}
public boolean isEmptyElementTag() throws XmlPullParserException {
return false;
}
public boolean isWhitespace() throws XmlPullParserException {
return false;
}
public void defineEntityReplacementText(String entityName,String replacementText) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getNamespace(String prefix) {
throw new RuntimeException(E_NOT_SUPPORTED);
}
public Object getProperty(String name) {
return null;
}
public void setProperty(String name,Object value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public boolean getFeature(String feature) {
return false;
}
public void setFeature(String name,boolean value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
///////////////////////////////////////////// implementation
/**
* Namespace stack, holds prefix+uri pairs, as well as
* depth information.
* All information is stored in one int[] array.
* Array consists of depth frames:
* Data=DepthFrame*;
* DepthFrame=Count+[Prefix+Uri]*+Count;
* Count='count of Prefix+Uri pairs';
* Yes, count is stored twice, to enable bottom-up traversal.
* increaseDepth adds depth frame, decreaseDepth removes it.
* push/pop operations operate only in current depth frame.
* decreaseDepth removes any remaining (not pop'ed) namespace pairs.
* findXXX methods search all depth frames starting
* from the last namespace pair of current depth frame.
* All functions that operate with int, use -1 as 'invalid value'.
*
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*
*/
private static final class NamespaceStack {
public NamespaceStack() {
m_data=new int[32];
}
public final void reset() {
m_dataLength=0;
m_count=0;
m_depth=0;
}
public final int getTotalCount() {
return m_count;
}
public final int getCurrentCount() {
if (m_dataLength==0) {
return 0;
}
int offset=m_dataLength-1;
return m_data[offset];
}
public final int getAccumulatedCount(int depth) {
if (m_dataLength==0 || depth<0) {
return 0;
}
if (depth>m_depth) {
depth=m_depth;
}
int accumulatedCount=0;
int offset=0;
for (;depth!=0;--depth) {
int count=m_data[offset];
accumulatedCount+=count;
offset+=(2+count*2);
}
return accumulatedCount;
}
public final void push(int prefix,int uri) {
if (m_depth==0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset=m_dataLength-1;
int count=m_data[offset];
m_data[offset-1-count*2]=count+1;
m_data[offset]=prefix;
m_data[offset+1]=uri;
m_data[offset+2]=count+1;
m_dataLength+=2;
m_count+=1;
}
public final boolean pop(int prefix,int uri) {
if (m_dataLength==0) {
return false;
}
int offset=m_dataLength-1;
int count=m_data[offset];
for (int i=0,o=offset-2;i!=count;++i,o-=2) {
if (m_data[o]!=prefix || m_data[o+1]!=uri) {
continue;
}
count-=1;
if (i==0) {
m_data[o]=count;
o-=(1+count*2);
m_data[o]=count;
} else {
m_data[offset]=count;
offset-=(1+2+count*2);
m_data[offset]=count;
System.arraycopy(
m_data,o+2,
m_data,o,
m_dataLength-o);
}
m_dataLength-=2;
m_count-=1;
return true;
}
return false;
}
public final boolean pop() {
if (m_dataLength==0) {
return false;
}
int offset=m_dataLength-1;
int count=m_data[offset];
if (count==0) {
return false;
}
count-=1;
offset-=2;
m_data[offset]=count;
offset-=(1+count*2);
m_data[offset]=count;
m_dataLength-=2;
m_count-=1;
return true;
}
public final int getPrefix(int index) {
return get(index,true);
}
public final int getUri(int index) {
return get(index,false);
}
public final int findPrefix(int uri) {
return find(uri,false);
}
public final int findUri(int prefix) {
return find(prefix,true);
}
public final int getDepth() {
return m_depth;
}
public final void increaseDepth() {
ensureDataCapacity(2);
int offset=m_dataLength;
m_data[offset]=0;
m_data[offset+1]=0;
m_dataLength+=2;
m_depth+=1;
}
public final void decreaseDepth() {
if (m_dataLength==0) {
return;
}
int offset=m_dataLength-1;
int count=m_data[offset];
if ((offset-1-count*2)==0) {
return;
}
m_dataLength-=2+count*2;
m_count-=count;
m_depth-=1;
}
private void ensureDataCapacity(int capacity) {
int available=(m_data.length-m_dataLength);
if (available>capacity) {
return;
}
int newLength=(m_data.length+available)*2;
int[] newData=new int[newLength];
System.arraycopy(m_data,0,newData,0,m_dataLength);
m_data=newData;
}
private final int find(int prefixOrUri,boolean prefix) {
if (m_dataLength==0) {
return -1;
}
int offset=m_dataLength-1;
for (int i=m_depth;i!=0;--i) {
int count=m_data[offset];
offset-=2;
for (;count!=0;--count) {
if (prefix) {
if (m_data[offset]==prefixOrUri) {
return m_data[offset+1];
}
} else {
if (m_data[offset+1]==prefixOrUri) {
return m_data[offset];
}
}
offset-=2;
}
}
return -1;
}
private final int get(int index,boolean prefix) {
if (m_dataLength==0 || index<0) {
return -1;
}
int offset=0;
for (int i=m_depth;i!=0;--i) {
int count=m_data[offset];
if (index>=count) {
index-=count;
offset+=(2+count*2);
continue;
}
offset+=(1+index*2);
if (!prefix) {
offset+=1;
}
return m_data[offset];
}
return -1;
}
private int[] m_data;
private int m_dataLength;
private int m_count;
private int m_depth;
}
/////////////////////////////////// package-visible
// final void fetchAttributes(int[] styleableIDs,TypedArray result) {
// result.resetIndices();
// if (m_attributes==null || m_resourceIDs==null) {
// return;
// }
// boolean needStrings=false;
// for (int i=0,e=styleableIDs.length;i!=e;++i) {
// int id=styleableIDs[i];
// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) {
// int name=m_attributes[o+ATTRIBUTE_IX_NAME];
// if (name>=m_resourceIDs.length ||
// m_resourceIDs[name]!=id)
// {
// continue;
// }
// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE];
// int valueData;
// int assetCookie;
// if (valueType==TypedValue.TYPE_STRING) {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING];
// assetCookie=-1;
// needStrings=true;
// } else {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA];
// assetCookie=0;
// }
// result.addValue(i,valueType,valueData,assetCookie,id,0);
// }
// }
// if (needStrings) {
// result.setStrings(m_strings);
// }
// }
final wind.android.content.res.StringBlock getStrings() {
return m_strings;
}
///////////////////////////////////
private final int getAttributeOffset(int index) {
if (m_event!=START_TAG) {
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
}
int offset=index*5;
if (offset>=m_attributes.length) {
throw new IndexOutOfBoundsException("Invalid attribute index ("+index+").");
}
return offset;
}
private final int findAttribute(String namespace,String attribute) {
if (m_strings==null || attribute==null) {
return -1;
}
int name=m_strings.find(attribute);
if (name==-1) {
return -1;
}
int uri=(namespace!=null)?
m_strings.find(namespace):
-1;
for (int o=0;o!=m_attributes.length;++o) {
if (name==m_attributes[o+ATTRIBUTE_IX_NAME] &&
(uri==-1 || uri==m_attributes[o+ATTRIBUTE_IX_NAMESPACE_URI]))
{
return o/ATTRIBUTE_LENGHT;
}
}
return -1;
}
private final void resetEventInfo() {
m_event=-1;
m_lineNumber=-1;
m_name=-1;
m_namespaceUri=-1;
m_attributes=null;
m_idAttribute=-1;
m_classAttribute=-1;
m_styleAttribute=-1;
}
private final void doNext() throws IOException {
// Delayed initialization.
if (m_strings==null) {
wind.android.content.res.ChunkUtil.readCheckType(m_reader,CHUNK_AXML_FILE);
/*chunkSize*/m_reader.skipInt();
m_strings= wind.android.content.res.StringBlock.read(m_reader);
m_namespaces.increaseDepth();
m_operational=true;
}
if (m_event==END_DOCUMENT) {
return;
}
int event=m_event;
resetEventInfo();
while (true) {
if (m_decreaseDepth) {
m_decreaseDepth=false;
m_namespaces.decreaseDepth();
}
// Fake END_DOCUMENT event.
if (event==END_TAG &&
m_namespaces.getDepth()==1 &&
m_namespaces.getCurrentCount()==0)
{
m_event=END_DOCUMENT;
break;
}
int chunkType;
if (event==START_DOCUMENT) {
// Fake event, see CHUNK_XML_START_TAG handler.
chunkType=CHUNK_XML_START_TAG;
} else {
chunkType=m_reader.readInt();
}
if (chunkType==CHUNK_RESOURCEIDS) {
int chunkSize=m_reader.readInt();
if (chunkSize<8 || (chunkSize%4)!=0) {
throw new IOException("Invalid resource ids size ("+chunkSize+").");
}
m_resourceIDs=m_reader.readIntArray(chunkSize/4-2);
continue;
}
if (chunkType<CHUNK_XML_FIRST || chunkType>CHUNK_XML_LAST) {
throw new IOException("Invalid chunk type ("+chunkType+").");
}
// Fake START_DOCUMENT event.
if (chunkType==CHUNK_XML_START_TAG && event==-1) {
m_event=START_DOCUMENT;
break;
}
// Common header.
/*chunkSize*/m_reader.skipInt();
int lineNumber=m_reader.readInt();
/*0xFFFFFFFF*/m_reader.skipInt();
if (chunkType==CHUNK_XML_START_NAMESPACE ||
chunkType==CHUNK_XML_END_NAMESPACE)
{
if (chunkType==CHUNK_XML_START_NAMESPACE) {
int prefix=m_reader.readInt();
int uri=m_reader.readInt();
m_namespaces.push(prefix,uri);
} else {
/*prefix*/m_reader.skipInt();
/*uri*/m_reader.skipInt();
m_namespaces.pop();
}
continue;
}
m_lineNumber=lineNumber;
if (chunkType==CHUNK_XML_START_TAG) {
m_namespaceUri=m_reader.readInt();
m_name=m_reader.readInt();
/*flags?*/m_reader.skipInt();
int attributeCount=m_reader.readInt();
m_idAttribute=(attributeCount>>>16)-1;
attributeCount&=0xFFFF;
m_classAttribute=m_reader.readInt();
m_styleAttribute=(m_classAttribute>>>16)-1;
m_classAttribute=(m_classAttribute & 0xFFFF)-1;
m_attributes=m_reader.readIntArray(attributeCount*ATTRIBUTE_LENGHT);
for (int i=ATTRIBUTE_IX_VALUE_TYPE;i<m_attributes.length;) {
m_attributes[i]=(m_attributes[i]>>>24);
i+=ATTRIBUTE_LENGHT;
}
m_namespaces.increaseDepth();
m_event=START_TAG;
break;
}
if (chunkType==CHUNK_XML_END_TAG) {
m_namespaceUri=m_reader.readInt();
m_name=m_reader.readInt();
m_event=END_TAG;
m_decreaseDepth=true;
break;
}
if (chunkType==CHUNK_XML_TEXT) {
m_name=m_reader.readInt();
/*?*/m_reader.skipInt();
/*?*/m_reader.skipInt();
m_event=TEXT;
break;
}
}
}
/////////////////////////////////// data
/*
* All values are essentially indices, e.g. m_name is
* an index of name in m_strings.
*/
private IntReader m_reader;
private boolean m_operational=false;
private StringBlock m_strings;
private int[] m_resourceIDs;
private NamespaceStack m_namespaces=new NamespaceStack();
private boolean m_decreaseDepth;
private int m_event;
private int m_lineNumber;
private int m_name;
private int m_namespaceUri;
private int[] m_attributes;
private int m_idAttribute;
private int m_classAttribute;
private int m_styleAttribute;
private static final String
E_NOT_SUPPORTED ="Method is not supported.";
private static final int
ATTRIBUTE_IX_NAMESPACE_URI =0,
ATTRIBUTE_IX_NAME =1,
ATTRIBUTE_IX_VALUE_STRING =2,
ATTRIBUTE_IX_VALUE_TYPE =3,
ATTRIBUTE_IX_VALUE_DATA =4,
ATTRIBUTE_LENGHT =5;
private static final int
CHUNK_AXML_FILE =0x00080003,
CHUNK_RESOURCEIDS =0x00080180,
CHUNK_XML_FIRST =0x00100100,
CHUNK_XML_START_NAMESPACE =0x00100100,
CHUNK_XML_END_NAMESPACE =0x00100101,
CHUNK_XML_START_TAG =0x00100102,
CHUNK_XML_END_TAG =0x00100103,
CHUNK_XML_TEXT =0x00100104,
CHUNK_XML_LAST =0x00100104;
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.content.res;
import wind.android.content.res.IntReader;
import java.io.IOException;
/**
* @author Dmitry Skiba
*
*/
class ChunkUtil {
public static final void readCheckType(IntReader reader, int expectedType) throws IOException {
int type=reader.readInt();
if (type!=expectedType) {
throw new IOException(
"Expected chunk of type 0x"+Integer.toHexString(expectedType)+
", read 0x"+Integer.toHexString(type)+".");
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.content.res;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Dmitry Skiba
*
* Simple helper class that allows reading of integers.
*
* TODO:
* * implement buffering
*
*/
public final class IntReader {
public IntReader() {
}
public IntReader(InputStream stream,boolean bigEndian) {
reset(stream,bigEndian);
}
public final void reset(InputStream stream,boolean bigEndian) {
m_stream=stream;
m_bigEndian=bigEndian;
m_position=0;
}
public final void close() {
if (m_stream==null) {
return;
}
try {
m_stream.close();
}
catch (IOException e) {
}
reset(null,false);
}
public final InputStream getStream() {
return m_stream;
}
public final boolean isBigEndian() {
return m_bigEndian;
}
public final void setBigEndian(boolean bigEndian) {
m_bigEndian=bigEndian;
}
public final int readByte() throws IOException {
return readInt(1);
}
public final int readShort() throws IOException {
return readInt(2);
}
public final int readInt() throws IOException {
return readInt(4);
}
public final int readInt(int length) throws IOException {
if (length<0 || length>4) {
throw new IllegalArgumentException();
}
int result=0;
if (m_bigEndian) {
for (int i=(length-1)*8;i>=0;i-=8) {
int b=m_stream.read();
if (b==-1) {
throw new EOFException();
}
m_position+=1;
result|=(b<<i);
}
} else {
length*=8;
for (int i=0;i!=length;i+=8) {
int b=m_stream.read();
if (b==-1) {
throw new EOFException();
}
m_position+=1;
result|=(b<<i);
}
}
return result;
}
public final int[] readIntArray(int length) throws IOException {
int[] array=new int[length];
readIntArray(array,0,length);
return array;
}
public final void readIntArray(int[] array,int offset,int length) throws IOException {
for (;length>0;length-=1) {
array[offset++]=readInt();
}
}
public final byte[] readByteArray(int length) throws IOException {
byte[] array=new byte[length];
int read=m_stream.read(array);
m_position+=read;
if (read!=length) {
throw new EOFException();
}
return array;
}
public final void skip(int bytes) throws IOException {
if (bytes<=0) {
return;
}
long skipped=m_stream.skip(bytes);
m_position+=skipped;
if (skipped!=bytes) {
throw new EOFException();
}
}
public final void skipInt() throws IOException {
skip(4);
}
public final int available() throws IOException {
return m_stream.available();
}
public final int getPosition() {
return m_position;
}
/////////////////////////////////// data
private InputStream m_stream;
private boolean m_bigEndian;
private int m_position;
}

View File

@ -0,0 +1,248 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.content.res;
import wind.android.content.res.ChunkUtil;
import wind.android.content.res.IntReader;
import java.io.IOException;
/**
* @author Dmitry Skiba
*
* Block of strings, used in binary xml and arsc.
*
* TODO:
* - implement get()
*
*/
public class StringBlock {
/**
* Reads whole (including chunk type) string block from stream.
* Stream must be at the chunk type.
*/
public static StringBlock read(IntReader reader) throws IOException {
wind.android.content.res.ChunkUtil.readCheckType(reader,CHUNK_TYPE);
int chunkSize=reader.readInt();
int stringCount=reader.readInt();
int styleOffsetCount=reader.readInt();
/*?*/reader.readInt();
int stringsOffset=reader.readInt();
int stylesOffset=reader.readInt();
StringBlock block=new StringBlock();
block.m_stringOffsets=reader.readIntArray(stringCount);
if (styleOffsetCount!=0) {
block.m_styleOffsets=reader.readIntArray(styleOffsetCount);
}
{
int size=((stylesOffset==0)?chunkSize:stylesOffset)-stringsOffset;
if ((size%4)!=0) {
throw new IOException("String data size is not multiple of 4 ("+size+").");
}
block.m_strings=reader.readIntArray(size/4);
}
if (stylesOffset!=0) {
int size=(chunkSize-stylesOffset);
if ((size%4)!=0) {
throw new IOException("Style data size is not multiple of 4 ("+size+").");
}
block.m_styles=reader.readIntArray(size/4);
}
return block;
}
/**
* Returns number of strings in block.
*/
public int getCount() {
return m_stringOffsets!=null?
m_stringOffsets.length:
0;
}
/**
* Returns raw string (without any styling information) at specified index.
*/
public String getString(int index) {
if (index<0 ||
m_stringOffsets==null ||
index>=m_stringOffsets.length)
{
return null;
}
int offset=m_stringOffsets[index];
int length=getShort(m_strings,offset);
StringBuilder result=new StringBuilder(length);
for (;length!=0;length-=1) {
offset+=2;
result.append((char)getShort(m_strings,offset));
}
return result.toString();
}
/**
* Not yet implemented.
*
* Returns string with style information (if any).
*/
public CharSequence get(int index) {
return getString(index);
}
/**
* Returns string with style tags (html-like).
*/
public String getHTML(int index) {
String raw=getString(index);
if (raw==null) {
return raw;
}
int[] style=getStyle(index);
if (style==null) {
return raw;
}
StringBuilder html=new StringBuilder(raw.length()+32);
int offset=0;
while (true) {
int i=-1;
for (int j=0;j!=style.length;j+=3) {
if (style[j+1]==-1) {
continue;
}
if (i==-1 || style[i+1]>style[j+1]) {
i=j;
}
}
int start=((i!=-1)?style[i+1]:raw.length());
for (int j=0;j!=style.length;j+=3) {
int end=style[j+2];
if (end==-1 || end>=start) {
continue;
}
if (offset<=end) {
html.append(raw,offset,end+1);
offset=end+1;
}
style[j+2]=-1;
html.append('<');
html.append('/');
html.append(getString(style[j]));
html.append('>');
}
if (offset<start) {
html.append(raw,offset,start);
offset=start;
}
if (i==-1) {
break;
}
html.append('<');
html.append(getString(style[i]));
html.append('>');
style[i+1]=-1;
}
return html.toString();
}
/**
* Finds index of the string.
* Returns -1 if the string was not found.
*/
public int find(String string) {
if (string==null) {
return -1;
}
for (int i=0;i!=m_stringOffsets.length;++i) {
int offset=m_stringOffsets[i];
int length=getShort(m_strings,offset);
if (length!=string.length()) {
continue;
}
int j=0;
for (;j!=length;++j) {
offset+=2;
if (string.charAt(j)!=getShort(m_strings,offset)) {
break;
}
}
if (j==length) {
return i;
}
}
return -1;
}
///////////////////////////////////////////// implementation
private StringBlock() {
}
/**
* Returns style information - array of int triplets,
* where in each triplet:
* * first int is index of tag name ('b','i', etc.)
* * second int is tag start index in string
* * third int is tag end index in string
*/
private int[] getStyle(int index) {
if (m_styleOffsets==null || m_styles==null ||
index>=m_styleOffsets.length)
{
return null;
}
int offset=m_styleOffsets[index]/4;
int style[];
{
int count=0;
for (int i=offset;i<m_styles.length;++i) {
if (m_styles[i]==-1) {
break;
}
count+=1;
}
if (count==0 || (count%3)!=0) {
return null;
}
style=new int[count];
}
for (int i=offset,j=0;i<m_styles.length;) {
if (m_styles[i]==-1) {
break;
}
style[j++]=m_styles[i++];
}
return style;
}
private static final int getShort(int[] array,int offset) {
int value=array[offset/4];
if ((offset%4)/2==0) {
return (value & 0xFFFF);
} else {
return (value >>> 16);
}
}
private int[] m_stringOffsets;
private int[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private static final int CHUNK_TYPE=0x001C0001;
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.content.res;
import wind.v1.XmlPullParser;
import wind.android.util.AttributeSet;
/**
* @author Dmitry Skiba
*
*/
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
void close();
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.util;
/**
* @author Dmitry Skiba
*
*/
public interface AttributeSet {
int getAttributeCount();
String getAttributeName(int index);
String getAttributeValue(int index);
String getPositionDescription();
int getAttributeNameResource(int index);
int getAttributeListValue(int index, String options[], int defaultValue);
boolean getAttributeBooleanValue(int index, boolean defaultValue);
int getAttributeResourceValue(int index, int defaultValue);
int getAttributeIntValue(int index, int defaultValue);
int getAttributeUnsignedIntValue(int index, int defaultValue);
float getAttributeFloatValue(int index, float defaultValue);
String getIdAttribute();
String getClassAttribute();
int getIdAttributeResourceValue(int index);
int getStyleAttribute();
String getAttributeValue(String namespace, String attribute);
int getAttributeListValue(String namespace, String attribute, String options[], int defaultValue);
boolean getAttributeBooleanValue(String namespace, String attribute, boolean defaultValue);
int getAttributeResourceValue(String namespace, String attribute, int defaultValue);
int getAttributeIntValue(String namespace, String attribute, int defaultValue);
int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue);
float getAttributeFloatValue(String namespace, String attribute, float defaultValue);
//TODO: remove
int getAttributeValueType(int index);
int getAttributeValueData(int index);
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.android.util;
/**
* @author Dmitry Skiba
*
*/
public class TypedValue {
public int type;
public CharSequence string;
public int data;
public int assetCookie;
public int resourceId;
public int changingConfigurations;
public static final int
TYPE_NULL =0,
TYPE_REFERENCE =1,
TYPE_ATTRIBUTE =2,
TYPE_STRING =3,
TYPE_FLOAT =4,
TYPE_DIMENSION =5,
TYPE_FRACTION =6,
TYPE_FIRST_INT =16,
TYPE_INT_DEC =16,
TYPE_INT_HEX =17,
TYPE_INT_BOOLEAN =18,
TYPE_FIRST_COLOR_INT =28,
TYPE_INT_COLOR_ARGB8 =28,
TYPE_INT_COLOR_RGB8 =29,
TYPE_INT_COLOR_ARGB4 =30,
TYPE_INT_COLOR_RGB4 =31,
TYPE_LAST_COLOR_INT =31,
TYPE_LAST_INT =31;
public static final int
COMPLEX_UNIT_PX =0,
COMPLEX_UNIT_DIP =1,
COMPLEX_UNIT_SP =2,
COMPLEX_UNIT_PT =3,
COMPLEX_UNIT_IN =4,
COMPLEX_UNIT_MM =5,
COMPLEX_UNIT_SHIFT =0,
COMPLEX_UNIT_MASK =15,
COMPLEX_UNIT_FRACTION =0,
COMPLEX_UNIT_FRACTION_PARENT=1,
COMPLEX_RADIX_23p0 =0,
COMPLEX_RADIX_16p7 =1,
COMPLEX_RADIX_8p15 =2,
COMPLEX_RADIX_0p23 =3,
COMPLEX_RADIX_SHIFT =4,
COMPLEX_RADIX_MASK =3,
COMPLEX_MANTISSA_SHIFT =8,
COMPLEX_MANTISSA_MASK =0xFFFFFF;
}

View File

@ -0,0 +1,170 @@
/*
* Copyright 2008 Android4ME
*
* 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 wind.test;
import java.io.FileInputStream;
import wind.v1.XmlPullParser;
import wind.android.content.res.AXmlResourceParser;
//import android.util.TypedValue;
/**
* @author Dmitry Skiba
*
* This is example usage of AXMLParser class.
*
* Prints xml document from Android's binary xml file.
*/
public class AXMLPrinter {
public static void main(String[] arguments) {
if (arguments.length<1) {
log("Usage: AXMLPrinter <binary xml file>");
return;
}
try {
AXmlResourceParser parser=new AXmlResourceParser();
parser.open(new FileInputStream(arguments[0]));
StringBuilder indent=new StringBuilder(10);
final String indentStep=" ";
while (true) {
int type=parser.next();
if (type==XmlPullParser.END_DOCUMENT) {
break;
}
switch (type) {
case XmlPullParser.START_DOCUMENT:
{
log("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
break;
}
case XmlPullParser.START_TAG:
{
log("%s<%s%s",indent,
getNamespacePrefix(parser.getPrefix()),parser.getName());
indent.append(indentStep);
int namespaceCountBefore=parser.getNamespaceCount(parser.getDepth()-1);
int namespaceCount=parser.getNamespaceCount(parser.getDepth());
for (int i=namespaceCountBefore;i!=namespaceCount;++i) {
log("%sxmlns:%s=\"%s\"",
indent,
parser.getNamespacePrefix(i),
parser.getNamespaceUri(i));
}
for (int i=0;i!=parser.getAttributeCount();++i) {
log("%s%s%s=\"%s\"",indent,
getNamespacePrefix(parser.getAttributePrefix(i)),
parser.getAttributeName(i),
getAttributeValue(parser,i));
}
log("%s>",indent);
break;
}
case XmlPullParser.END_TAG:
{
indent.setLength(indent.length()-indentStep.length());
log("%s</%s%s>",indent,
getNamespacePrefix(parser.getPrefix()),
parser.getName());
break;
}
case XmlPullParser.TEXT:
{
log("%s%s",indent,parser.getText());
break;
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private static String getNamespacePrefix(String prefix) {
if (prefix==null || prefix.length()==0) {
return "";
}
return prefix+":";
}
private static String getAttributeValue(AXmlResourceParser parser,int index) {
int type=parser.getAttributeValueType(index);
int data=parser.getAttributeValueData(index);
// if (type==TypedValue.TYPE_STRING) {
// return parser.getAttributeValue(index);
// }
// if (type==TypedValue.TYPE_ATTRIBUTE) {
// return String.format("?%s%08X",getPackage(data),data);
// }
// if (type==TypedValue.TYPE_REFERENCE) {
// return String.format("@%s%08X",getPackage(data),data);
// }
// if (type==TypedValue.TYPE_FLOAT) {
// return String.valueOf(Float.intBitsToFloat(data));
// }
// if (type==TypedValue.TYPE_INT_HEX) {
// return String.format("0x%08X",data);
// }
// if (type==TypedValue.TYPE_INT_BOOLEAN) {
// return data!=0?"true":"false";
// }
// if (type==TypedValue.TYPE_DIMENSION) {
// return Float.toString(complexToFloat(data))+
// DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
// }
// if (type==TypedValue.TYPE_FRACTION) {
// return Float.toString(complexToFloat(data))+
// FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
// }
// if (type>=TypedValue.TYPE_FIRST_COLOR_INT && type<=TypedValue.TYPE_LAST_COLOR_INT) {
// return String.format("#%08X",data);
// }
// if (type>=TypedValue.TYPE_FIRST_INT && type<=TypedValue.TYPE_LAST_INT) {
// return String.valueOf(data);
// }
return String.format("<0x%X, type 0x%02X>",data,type);
}
private static String getPackage(int id) {
if (id>>>24==1) {
return "android:";
}
return "";
}
private static void log(String format,Object...arguments) {
System.out.printf(format,arguments);
System.out.println();
}
/////////////////////////////////// ILLEGAL STUFF, DONT LOOK :)
public static float complexToFloat(int complex) {
return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3];
}
private static final float RADIX_MULTS[]={
0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F
};
private static final String DIMENSION_UNITS[]={
"px","dip","sp","pt","in","mm","",""
};
private static final String FRACTION_UNITS[]={
"%","%p","","","","","",""
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
package wind.v1;
import wind.v1.XmlPullParser;
/**
* This exception is thrown to signal XML Pull Parser related faults.
*
* @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
*/
public class XmlPullParserException extends Exception {
protected Throwable detail;
protected int row = -1;
protected int column = -1;
/* public XmlPullParserException() {
}*/
public XmlPullParserException(String s) {
super(s);
}
/*
public XmlPullParserException(String s, Throwable thrwble) {
super(s);
this.detail = thrwble;
}
public XmlPullParserException(String s, int row, int column) {
super(s);
this.row = row;
this.column = column;
}
*/
public XmlPullParserException(String msg, XmlPullParser parser, Throwable chain) {
super ((msg == null ? "" : msg+" ")
+ (parser == null ? "" : "(position:"+parser.getPositionDescription()+") ")
+ (chain == null ? "" : "caused by: "+chain));
if (parser != null) {
this.row = parser.getLineNumber();
this.column = parser.getColumnNumber();
}
this.detail = chain;
}
public Throwable getDetail() { return detail; }
// public void setDetail(Throwable cause) { this.detail = cause; }
public int getLineNumber() { return row; }
public int getColumnNumber() { return column; }
/*
public String getMessage() {
if(detail == null)
return super.getMessage();
else
return super.getMessage() + "; nested exception is: \n\t"
+ detail.getMessage();
}
*/
//NOTE: code that prints this and detail is difficult in J2ME
public void printStackTrace() {
if (detail == null) {
super.printStackTrace();
} else {
synchronized(System.err) {
System.err.println(super.getMessage() + "; nested exception is:");
detail.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,360 @@
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- //------100-columns-wide------>|*/
// for license please see accompanying LICENSE.txt file (available also at http://www.xmlpull.org/)
package wind.v1;
import wind.v1.XmlPullParser;
import wind.v1.XmlPullParserException;
import wind.v1.XmlSerializer;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* This class is used to create implementations of XML Pull Parser defined in XMPULL V1 API.
* The name of actual factory class will be determined based on several parameters.
* It works similar to JAXP but tailored to work in J2ME environments
* (no access to system properties or file system) so name of parser class factory to use
* and its class used for loading (no class loader - on J2ME no access to context class loaders)
* must be passed explicitly. If no name of parser factory was passed (or is null)
* it will try to find name by searching in CLASSPATH for
* META-INF/services/wind.v1.XmlPullParserFactory resource that should contain
* a comma separated list of class names of factories or parsers to try (in order from
* left to the right). If none found, it will throw an exception.
*
* <br /><strong>NOTE:</strong>In J2SE or J2EE environments, you may want to use
* <code>newInstance(property, classLoaderCtx)</code>
* where first argument is
* <code>System.getProperty(XmlPullParserFactory.PROPERTY_NAME)</code>
* and second is <code>Thread.getContextClassLoader().getClass()</code> .
*
* @see wind.v1.XmlPullParser
*
* @author <a href="http://www.extreme.indiana.edu/~aslom/">Aleksander Slominski</a>
* @author Stefan Haustein
*/
public class XmlPullParserFactory {
/** used as default class to server as context class in newInstance() */
final static Class referenceContextClass;
static {
XmlPullParserFactory f = new XmlPullParserFactory();
referenceContextClass = f.getClass();
}
/** Name of the system or midlet property that should be used for
a system property containing a comma separated list of factory
or parser class names (value:
wind.v1.XmlPullParserFactory). */
public static final String PROPERTY_NAME =
"org.xmlpull.v1.XmlPullParserFactory";
private static final String RESOURCE_NAME =
"/META-INF/services/" + PROPERTY_NAME;
// public static final String DEFAULT_PROPERTY =
// "wind.xpp3.XmlPullParser,org.kxml2.io.KXmlParser";
protected Vector parserClasses;
protected String classNamesLocation;
protected Vector serializerClasses;
// features are kept there
protected Hashtable features = new Hashtable();
/**
* Protected constructor to be called by factory implementations.
*/
protected XmlPullParserFactory() {
}
/**
* Set the features to be set when XML Pull Parser is created by this factory.
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
*
* @param name string with URI identifying feature
* @param state if true feature will be set; if false will be ignored
*/
public void setFeature(String name,
boolean state) throws XmlPullParserException {
features.put(name, new Boolean(state));
}
/**
* Return the current value of the feature with given name.
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
*
* @param name The name of feature to be retrieved.
* @return The value of named feature.
* Unknown features are <string>always</strong> returned as false
*/
public boolean getFeature (String name) {
Boolean value = (Boolean) features.get(name);
return value != null ? value.booleanValue() : false;
}
/**
* Specifies that the parser produced by this factory will provide
* support for XML namespaces.
* By default the value of this is set to false.
*
* @param awareness true if the parser produced by this code
* will provide support for XML namespaces; false otherwise.
*/
public void setNamespaceAware(boolean awareness) {
features.put (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES, new Boolean (awareness));
}
/**
* Indicates whether or not the factory is configured to produce
* parsers which are namespace aware
* (it simply set feature XmlPullParser.FEATURE_PROCESS_NAMESPACES to true or false).
*
* @return true if the factory is configured to produce parsers
* which are namespace aware; false otherwise.
*/
public boolean isNamespaceAware() {
return getFeature (wind.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES);
}
/**
* Specifies that the parser produced by this factory will be validating
* (it simply set feature XmlPullParser.FEATURE_VALIDATION to true or false).
*
* By default the value of this is set to false.
*
* @param validating - if true the parsers created by this factory must be validating.
*/
public void setValidating(boolean validating) {
features.put (wind.v1.XmlPullParser.FEATURE_VALIDATION, new Boolean (validating));
}
/**
* Indicates whether or not the factory is configured to produce parsers
* which validate the XML content during parse.
*
* @return true if the factory is configured to produce parsers
* which validate the XML content during parse; false otherwise.
*/
public boolean isValidating() {
return getFeature (wind.v1.XmlPullParser.FEATURE_VALIDATION);
}
/**
* Creates a new instance of a XML Pull Parser
* using the currently configured factory features.
*
* @return A new instance of a XML Pull Parser.
* @throws XmlPullParserException if a parser cannot be created which satisfies the
* requested configuration.
*/
public wind.v1.XmlPullParser newPullParser() throws XmlPullParserException {
if (parserClasses == null) throw new XmlPullParserException
("Factory initialization was incomplete - has not tried "+classNamesLocation);
if (parserClasses.size() == 0) throw new XmlPullParserException
("No valid parser classes found in "+classNamesLocation);
final StringBuffer issues = new StringBuffer ();
for (int i = 0; i < parserClasses.size (); i++) {
final Class ppClass = (Class) parserClasses.elementAt (i);
try {
final wind.v1.XmlPullParser pp = (wind.v1.XmlPullParser) ppClass.newInstance();
// if( ! features.isEmpty() ) {
//Enumeration keys = features.keys();
// while(keys.hasMoreElements()) {
for (Enumeration e = features.keys (); e.hasMoreElements ();) {
final String key = (String) e.nextElement();
final Boolean value = (Boolean) features.get(key);
if(value != null && value.booleanValue()) {
pp.setFeature(key, true);
}
}
return pp;
} catch(Exception ex) {
issues.append (ppClass.getName () + ": "+ ex.toString ()+"; ");
}
}
throw new XmlPullParserException ("could not create parser: "+issues);
}
/**
* Creates a new instance of a XML Serializer.
*
* <p><b>NOTE:</b> factory features are not used for XML Serializer.
*
* @return A new instance of a XML Serializer.
* @throws XmlPullParserException if a parser cannot be created which satisfies the
* requested configuration.
*/
public wind.v1.XmlSerializer newSerializer() throws XmlPullParserException {
if (serializerClasses == null) {
throw new XmlPullParserException
("Factory initialization incomplete - has not tried "+classNamesLocation);
}
if(serializerClasses.size() == 0) {
throw new XmlPullParserException
("No valid serializer classes found in "+classNamesLocation);
}
final StringBuffer issues = new StringBuffer ();
for (int i = 0; i < serializerClasses.size (); i++) {
final Class ppClass = (Class) serializerClasses.elementAt (i);
try {
final wind.v1.XmlSerializer ser = (wind.v1.XmlSerializer) ppClass.newInstance();
// for (Enumeration e = features.keys (); e.hasMoreElements ();) {
// String key = (String) e.nextElement();
// Boolean value = (Boolean) features.get(key);
// if(value != null && value.booleanValue()) {
// ser.setFeature(key, true);
// }
// }
return ser;
} catch(Exception ex) {
issues.append (ppClass.getName () + ": "+ ex.toString ()+"; ");
}
}
throw new XmlPullParserException ("could not create serializer: "+issues);
}
/**
* Create a new instance of a PullParserFactory that can be used
* to create XML pull parsers (see class description for more
* details).
*
* @return a new instance of a PullParserFactory, as returned by newInstance (null, null);
*/
public static XmlPullParserFactory newInstance () throws XmlPullParserException {
return newInstance(null, null);
}
public static XmlPullParserFactory newInstance (String classNames, Class context)
throws XmlPullParserException {
if (context == null) {
//NOTE: make sure context uses the same class loader as API classes
// this is the best we can do without having access to context classloader in J2ME
// if API is in the same classloader as implementation then this will work
context = referenceContextClass;
}
String classNamesLocation = null;
if (classNames == null || classNames.length() == 0 || "DEFAULT".equals(classNames)) {
try {
InputStream is = context.getResourceAsStream (RESOURCE_NAME);
if (is == null) throw new XmlPullParserException
("resource not found: "+RESOURCE_NAME
+" make sure that parser implementing XmlPull API is available");
final StringBuffer sb = new StringBuffer();
while (true) {
final int ch = is.read();
if (ch < 0) break;
else if (ch > ' ')
sb.append((char) ch);
}
is.close ();
classNames = sb.toString ();
}
catch (Exception e) {
throw new XmlPullParserException (null, null, e);
}
classNamesLocation = "resource "+RESOURCE_NAME+" that contained '"+classNames+"'";
} else {
classNamesLocation =
"parameter classNames to newInstance() that contained '"+classNames+"'";
}
XmlPullParserFactory factory = null;
final Vector parserClasses = new Vector ();
final Vector serializerClasses = new Vector ();
int pos = 0;
while (pos < classNames.length ()) {
int cut = classNames.indexOf (',', pos);
if (cut == -1) cut = classNames.length ();
final String name = classNames.substring (pos, cut);
Class candidate = null;
Object instance = null;
try {
candidate = Class.forName (name);
// necessary because of J2ME .class issue
instance = candidate.newInstance ();
}
catch (Exception e) {}
if (candidate != null) {
boolean recognized = false;
if (instance instanceof XmlPullParser) {
parserClasses.addElement (candidate);
recognized = true;
}
if (instance instanceof XmlSerializer) {
serializerClasses.addElement (candidate);
recognized = true;
}
if (instance instanceof XmlPullParserFactory) {
if (factory == null) {
factory = (XmlPullParserFactory) instance;
}
recognized = true;
}
if (!recognized) {
throw new XmlPullParserException ("incompatible class: "+name);
}
}
pos = cut + 1;
}
if (factory == null) {
factory = new XmlPullParserFactory ();
}
factory.parserClasses = parserClasses;
factory.serializerClasses = serializerClasses;
factory.classNamesLocation = classNamesLocation;
return factory;
}
}

View File

@ -0,0 +1,326 @@
package wind.v1;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
/**
* Define an interface to serialziation of XML Infoset.
* This interface abstracts away if serialized XML is XML 1.0 comaptible text or
* other formats of XML 1.0 serializations (such as binary XML for example with WBXML).
*
* <p><b>PLEASE NOTE:</b> This interface will be part of XmlPull 1.2 API.
* It is included as basis for discussion. It may change in any way.
*
* <p>Exceptions that may be thrown are: IOException or runtime exception
* (more runtime exceptions can be thrown but are not declared and as such
* have no semantics defined for this interface):
* <ul>
* <li><em>IllegalArgumentException</em> - for almost all methods to signal that
* argument is illegal
* <li><em>IllegalStateException</em> - to signal that call has good arguments but
* is not expected here (violation of contract) and for features/properties
* when requesting setting unimplemented feature/property
* (UnsupportedOperationException would be better but it is not in MIDP)
* </ul>
*
* <p><b>NOTE:</b> writing CDSECT, ENTITY_REF, IGNORABLE_WHITESPACE,
* PROCESSING_INSTRUCTION, COMMENT, and DOCDECL in some implementations
* may not be supported (for example when serializing to WBXML).
* In such case IllegalStateException will be thrown and it is recommened
* to use an optional feature to signal that implementation is not
* supporting this kind of output.
*/
public interface XmlSerializer {
/**
* Set feature identified by name (recommended to be URI for uniqueness).
* Some well known optional features are defined in
* <a href="http://www.xmlpull.org/v1/doc/features.html">
* http://www.xmlpull.org/v1/doc/features.html</a>.
*
* If feature is not recocgnized or can not be set
* then IllegalStateException MUST be thrown.
*
* @exception IllegalStateException If the feature is not supported or can not be set
*/
void setFeature(String name,
boolean state)
throws IllegalArgumentException, IllegalStateException;
/**
* Return the current value of the feature with given name.
* <p><strong>NOTE:</strong> unknown properties are <strong>always</strong> returned as null
*
* @param name The name of feature to be retrieved.
* @return The value of named feature.
* @exception IllegalArgumentException if feature string is null
*/
boolean getFeature(String name);
/**
* Set the value of a property.
* (the property name is recommened to be URI for uniqueness).
* Some well known optional properties are defined in
* <a href="http://www.xmlpull.org/v1/doc/properties.html">
* http://www.xmlpull.org/v1/doc/properties.html</a>.
*
* If property is not recocgnized or can not be set
* then IllegalStateException MUST be thrown.
*
* @exception IllegalStateException if the property is not supported or can not be set
*/
void setProperty(String name,
Object value)
throws IllegalArgumentException, IllegalStateException;
/**
* Look up the value of a property.
*
* The property name is any fully-qualified URI. I
* <p><strong>NOTE:</strong> unknown properties are <string>always</strong> returned as null
*
* @param name The name of property to be retrieved.
* @return The value of named property.
*/
Object getProperty(String name);
/**
* Set to use binary output stream with given encoding.
*/
void setOutput(OutputStream os, String encoding)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Set the output to the given writer.
* <p><b>WARNING</b> no information about encoding is available!
*/
void setOutput(Writer writer)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Write &lt;&#63;xml declaration with encoding (if encoding not null)
* and standalone flag (if standalone not null)
* This method can only be called just after setOutput.
*/
void startDocument(String encoding, Boolean standalone)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Finish writing. All unclosed start tags will be closed and output
* will be flushed. After calling this method no more output can be
* serialized until next call to setOutput()
*/
void endDocument()
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Binds the given prefix to the given namespace.
* This call is valid for the next element including child elements.
* The prefix and namespace MUST be always declared even if prefix
* is not used in element (startTag() or attribute()) - for XML 1.0
* it must result in declaring <code>xmlns:prefix='namespace'</code>
* (or <code>xmlns:prefix="namespace"</code> depending what character is used
* to quote attribute value).
*
* <p><b>NOTE:</b> this method MUST be called directly before startTag()
* and if anything but startTag() or setPrefix() is called next there will be exception.
* <p><b>NOTE:</b> prefixes "xml" and "xmlns" are already bound
* and can not be redefined see:
* <a href="http://www.w3.org/XML/xml-names-19990114-errata#NE05">Namespaces in XML Errata</a>.
* <p><b>NOTE:</b> to set default namespace use as prefix empty string.
*
* @param prefix must be not null (or IllegalArgumentException is thrown)
* @param namespace must be not null
*/
void setPrefix(String prefix, String namespace)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Return namespace that corresponds to given prefix
* If there is no prefix bound to this namespace return null
* but if generatePrefix is false then return generated prefix.
*
* <p><b>NOTE:</b> if the prefix is empty string "" and defualt namespace is bound
* to this prefix then empty string ("") is returned.
*
* <p><b>NOTE:</b> prefixes "xml" and "xmlns" are already bound
* will have values as defined
* <a href="http://www.w3.org/TR/REC-xml-names/">Namespaces in XML specification</a>
*/
String getPrefix(String namespace, boolean generatePrefix)
throws IllegalArgumentException;
/**
* Returns the current depth of the element.
* Outside the root element, the depth is 0. The
* depth is incremented by 1 when startTag() is called.
* The depth is decremented after the call to endTag()
* event was observed.
*
* <pre>
* &lt;!-- outside --&gt; 0
* &lt;root&gt; 1
* sometext 1
* &lt;foobar&gt; 2
* &lt;/foobar&gt; 2
* &lt;/root&gt; 1
* &lt;!-- outside --&gt; 0
* </pre>
*/
int getDepth();
/**
* Returns the namespace URI of the current element as set by startTag().
*
* <p><b>NOTE:</b> that measn in particaulr that: <ul>
* <li>if there was startTag("", ...) then getNamespace() returns ""
* <li>if there was startTag(null, ...) then getNamespace() returns null
* </ul>
*
* @return namespace set by startTag() that is currently in scope
*/
String getNamespace();
/**
* Returns the name of the current element as set by startTag().
* It can only be null before first call to startTag()
* or when last endTag() is called to close first startTag().
*
* @return namespace set by startTag() that is currently in scope
*/
String getName();
/**
* Writes a start tag with the given namespace and name.
* If there is no prefix defined for the given namespace,
* a prefix will be defined automatically.
* The explicit prefixes for namespaces can be established by calling setPrefix()
* immediately before this method.
* If namespace is null no namespace prefix is printed but just name.
* If namespace is empty string then serialzier will make sure that
* default empty namespace is declared (in XML 1.0 xmlns='')
* or throw IllegalStateException if default namespace is already bound
* to non-empty string.
*/
XmlSerializer startTag(String namespace, String name)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Write an attribute. Calls to attribute() MUST follow a call to
* startTag() immediately. If there is no prefix defined for the
* given namespace, a prefix will be defined automatically.
* If namespace is null or empty string
* no namespace prefix is printed but just name.
*/
XmlSerializer attribute(String namespace, String name, String value)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Write end tag. Repetition of namespace and name is just for avoiding errors.
* <p><b>Background:</b> in kXML endTag had no arguments, and non matching tags were
* very difficult to find...
* If namespace is null no namespace prefix is printed but just name.
* If namespace is empty string then serialzier will make sure that
* default empty namespace is declared (in XML 1.0 xmlns='').
*/
XmlSerializer endTag(String namespace, String name)
throws IOException, IllegalArgumentException, IllegalStateException;
// /**
// * Writes a start tag with the given namespace and name.
// * <br />If there is no prefix defined (prefix == null) for the given namespace,
// * a prefix will be defined automatically.
// * <br />If explicit prefixes is passed (prefix != null) then it will be used
// *and namespace declared if not already declared or
// * throw IllegalStateException the same prefix was already set on this
// * element (setPrefix()) and was bound to different namespace.
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
// * <br />If namespace is null then no namespace prefix is printed but just name.
// * <br />If namespace is empty string then serializer will make sure that
// * default empty namespace is declared (in XML 1.0 xmlns='')
// * or throw IllegalStateException if default namespace is already bound
// * to non-empty string.
// */
// XmlSerializer startTag (String prefix, String namespace, String name)
// throws IOException, IllegalArgumentException, IllegalStateException;
//
// /**
// * Write an attribute. Calls to attribute() MUST follow a call to
// * startTag() immediately.
// * <br />If there is no prefix defined (prefix == null) for the given namespace,
// * a prefix will be defined automatically.
// * <br />If explicit prefixes is passed (prefix != null) then it will be used
// * and namespace declared if not already declared or
// * throw IllegalStateException the same prefix was already set on this
// * element (setPrefix()) and was bound to different namespace.
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
// * <br />If namespace is null then no namespace prefix is printed but just name.
// * <br />If namespace is empty string then serializer will make sure that
// * default empty namespace is declared (in XML 1.0 xmlns='')
// * or throw IllegalStateException if default namespace is already bound
// * to non-empty string.
// */
// XmlSerializer attribute (String prefix, String namespace, String name, String value)
// throws IOException, IllegalArgumentException, IllegalStateException;
//
// /**
// * Write end tag. Repetition of namespace, prefix, and name is just for avoiding errors.
// * <br />If namespace or name arguments are different from corresponding startTag call
// * then IllegalArgumentException is thrown, if prefix argument is not null and is different
// * from corresponding starTag then IllegalArgumentException is thrown.
// * <br />If namespace is null then prefix must be null too or IllegalStateException is thrown.
// * <br />If namespace is null then no namespace prefix is printed but just name.
// * <br />If namespace is empty string then serializer will make sure that
// * default empty namespace is declared (in XML 1.0 xmlns='').
// * <p><b>Background:</b> in kXML endTag had no arguments, and non matching tags were
// * very difficult to find...</p>
// */
// ALEK: This is really optional as prefix in end tag MUST correspond to start tag but good for error checking
// XmlSerializer endTag (String prefix, String namespace, String name)
// throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Writes text, where special XML chars are escaped automatically
*/
XmlSerializer text(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Writes text, where special XML chars are escaped automatically
*/
XmlSerializer text(char[] buf, int start, int len)
throws IOException, IllegalArgumentException, IllegalStateException;
void cdsect(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
void entityRef(String text) throws IOException,
IllegalArgumentException, IllegalStateException;
void processingInstruction(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
void comment(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
void docdecl(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
void ignorableWhitespace(String text)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* Write all pending output to the stream.
* If method startTag() or attribute() was called then start tag is closed (final &gt;)
* before flush() is called on underlying output stream.
*
* <p><b>NOTE:</b> if there is need to close start tag
* (so no more attribute() calls are allowed) but without flushinging output
* call method text() with empty string (text("")).
*
*/
void flush()
throws IOException;
}

49
build.gradle Normal file
View File

@ -0,0 +1,49 @@
// 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'
}
defaultTasks('clean','distZip')
subprojects {
apply plugin: 'java'
apply plugin: 'maven'
sourceCompatibility = 1.7
targetCompatibility = 1.7
// task packageSources(type: Jar) {
// classifier = 'sources'
// from sourceSets.main.allSource
// }
// artifacts.archives packageSources
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')
}
}
}

19
gradle.properties Normal file
View File

@ -0,0 +1,19 @@
# 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=false

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Mar 06 01:11:44 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip

172
gradlew vendored Normal file
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 Normal file
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

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'Xpatch'
include ':axmlprinter', ':xpatch'

33
xpatch/build.gradle Normal file
View File

@ -0,0 +1,33 @@
apply plugin: 'java-library'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':axmlprinter')
}
jar{
baseName = "xpatch"
// version = '1.0'
manifest {
attributes 'Main-Class': 'com.storm.wind.xpatch.MainCommand'
}
//jar的源码打入最终的jar
from {
(configurations.runtime).collect {
it.isDirectory() ? it : zipTree(it)
}
}
from fileTree(dir:'src/main', includes: ['assets/**'])
//jar中的签名信息
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/*.MF'
}
//
sourceSets.main.resources {
srcDirs = [
"src/main/java",
];
include "**/*.*"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,188 @@
package com.storm.wind.xpatch;
import com.storm.wind.xpatch.base.BaseCommand;
import com.storm.wind.xpatch.task.ApkModifyTask;
import com.storm.wind.xpatch.task.BuildAndSignApkTask;
import com.storm.wind.xpatch.task.SoAndDexCopyTask;
import com.storm.wind.xpatch.util.FileUtils;
import com.storm.wind.xpatch.util.ManifestParser;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
public class MainCommand extends BaseCommand {
private String apkPath;
private String unzipApkFilePath;
@Opt(opt = "o", longOpt = "output", description = "output .apk file, default is " +
"$source_apk_dir/[file-name]-xposed-signed.apk", argName = "out-apk-file")
private String output; // 输出的apk文件的目录以及名称
@Opt(opt = "f", longOpt = "force", hasArg = false, description = "force overwrite")
private boolean forceOverwrite = false;
@Opt(opt = "k", longOpt = "keep", hasArg = false, description = "not delete the jar file " +
"that is changed by dex2jar and the apk zip files")
private boolean keepBuildFiles = false;
@Opt(opt = "l", longOpt = "log", hasArg = false, description = "show all the debug logs")
private boolean showAllLogs = false;
// 原来apk中dex文件的数量
private int dexFileCount = 0;
private static final String UNZIP_APK_FILE_NAME = "apk-unzip-files";
private static final String DEFAULT_APPLICATION_NAME = "android.app.Application";
private List<Runnable> mXpatchTasks = new ArrayList<>();
public static void main(String... args) {
new MainCommand().doMain(args);
}
@Override
protected void doCommandLine() {
if (remainingArgs.length != 1) {
if (remainingArgs.length == 0) {
System.out.println("Please choose one apk file you want to process. ");
}
if (remainingArgs.length > 1) {
System.out.println("This tool can only used with one apk file.");
}
usage();
return;
}
apkPath = remainingArgs[0];
File srcApkFile = new File(apkPath);
if (!srcApkFile.exists()) {
System.out.println(" The source apk file not exsit, please choose another one. " +
"current apk file is = " + apkPath);
return;
}
String srcApkFileParentPath = srcApkFile.getParent();
if (srcApkFileParentPath == null) {
String absPath = srcApkFile.getAbsolutePath();
int index = absPath.lastIndexOf(File.separatorChar);
srcApkFileParentPath = absPath.substring(0, index);
}
String currentDir = new File(".").getAbsolutePath(); // 当前命令行所在的目录
if (showAllLogs) {
System.out.println(" currentDir = " + currentDir + " \n apkPath = " + apkPath);
}
if (output == null || output.length() == 0) {
output = getBaseName(apkPath) + "-xposed-signed.apk";
}
File outputFile = new File(output);
if (outputFile.exists() && !forceOverwrite) {
System.err.println(output + " exists, use --force to overwrite");
usage();
return;
}
System.out.println(" !!!!! output apk path --> " + output);
String apkFileName = getBaseName(srcApkFile);
// 中间文件临时存储的位置
String tempFilePath = srcApkFileParentPath + File.separator + currentTimeStr() + "-tmp" + File.separator;
// apk文件解压的目录
unzipApkFilePath = tempFilePath + apkFileName + "-" + UNZIP_APK_FILE_NAME + File.separator;
if (showAllLogs) {
System.out.println(" !!!!! srcApkFileParentPath = " + srcApkFileParentPath +
"\n unzipApkFilePath = " + unzipApkFilePath);
}
// 先解压apk到指定目录下
FileUtils.decompressZip(apkPath, unzipApkFilePath);
// Get the dex count in the apk zip file
dexFileCount = findDexFileCount(unzipApkFilePath);
if (showAllLogs) {
System.out.println(" --- dexFileCount = " + dexFileCount);
}
String manifestFilePath = unzipApkFilePath + "AndroidManifest.xml";
// parse the app main application full name from the manifest file
ManifestParser.Pair pair = ManifestParser.parseManidestFile(manifestFilePath);
String applicationName;
if (pair != null && pair.applictionName != null) {
applicationName = pair.applictionName;
} else {
System.out.println(" Application name not found error !!!!!! ");
applicationName = DEFAULT_APPLICATION_NAME;
}
if (showAllLogs) {
System.out.println(" Get the application name --> " + applicationName);
}
// 1. modify the apk dex file to make xposed can run in it
mXpatchTasks.add(new ApkModifyTask(showAllLogs, keepBuildFiles, unzipApkFilePath, applicationName,
dexFileCount));
// 2. copy xposed so and dex files into the unzipped apk
mXpatchTasks.add(new SoAndDexCopyTask(dexFileCount, unzipApkFilePath));
// 3. compress all files into an apk and then sign it.
mXpatchTasks.add(new BuildAndSignApkTask(keepBuildFiles, unzipApkFilePath, output));
// 4. excute these tasks
for (Runnable executor : mXpatchTasks) {
executor.run();
}
// 5. delete all the build files that is useless now
File unzipApkFile = new File(unzipApkFilePath);
if (!keepBuildFiles && unzipApkFile.exists()) {
FileUtils.deleteDir(unzipApkFile);
}
File tempFile = new File(tempFilePath);
if (!keepBuildFiles && tempFile.exists()) {
tempFile.delete();
}
}
private int findDexFileCount(String unzipApkFilePath) {
File zipfileRoot = new File(unzipApkFilePath);
if (!zipfileRoot.exists()) {
return 0;
}
File[] childFiles = zipfileRoot.listFiles();
if (childFiles == null || childFiles.length == 0) {
return 0;
}
int count = 0;
for (File file : childFiles) {
String fileName = file.getName();
if (Pattern.matches("classes.*\\.dex", fileName)) {
count++;
}
}
return count;
}
// Use the current timestamp as the name of the build file
private String currentTimeStr() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");//设置日期格式
return df.format(new Date());
}
}

View File

@ -0,0 +1,460 @@
package com.storm.wind.xpatch.base;
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.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;
/**
* Created by Wind
*/
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) {
needArgOpt.field.set(this, convert(s, needArgOpt.field.getType()));
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,119 @@
package com.storm.wind.xpatch.task;
import com.googlecode.dex2jar.tools.Dex2jarCmd;
import com.googlecode.dex2jar.tools.Jar2Dex;
import java.io.File;
import java.util.ArrayList;
/**
* Created by Wind
*/
public class ApkModifyTask implements Runnable {
private static final String JAR_FILE_NAME = "output-jar.jar";
private String unzipApkFilePath;
private boolean keepJarFile;
private boolean showAllLogs;
private String applicationName;
private int dexFileCount;
public ApkModifyTask(boolean showAllLogs, boolean keepJarFile, String unzipApkFilePath, String applicationName, int
dexFileCount) {
this.showAllLogs = showAllLogs;
this.unzipApkFilePath = unzipApkFilePath;
this.keepJarFile = keepJarFile;
this.applicationName = applicationName;
this.dexFileCount = dexFileCount;
}
@Override
public void run() {
File unzipApkFile = new File(unzipApkFilePath);
String jarOutputPath = unzipApkFile.getParent() + File.separator + JAR_FILE_NAME;
// classes.dex
String targetDexFileName = dumpJarFile(dexFileCount, unzipApkFilePath, jarOutputPath, applicationName);
if (showAllLogs) {
System.out.println(" the application class is in this dex file = " + targetDexFileName);
}
String dexOutputPath = unzipApkFilePath + targetDexFileName;
File dexFile = new File(dexOutputPath);
if (dexFile.exists()) {
dexFile.delete();
}
// 将jar转换为dex文件
jar2DexCmd(jarOutputPath, dexOutputPath);
// 删除掉jar文件
File jarFile = new File(jarOutputPath);
if (!keepJarFile && jarFile.exists()) {
jarFile.delete();
}
}
private String dumpJarFile(int dexFileCount, String dexFilePath, String jarOutputPath, String applicationName) {
ArrayList<String> dexFileList = createClassesDotDexFileList(dexFileCount);
// String jarOutputPath = dexFilePath + JAR_FILE_NAME;
for (String dexFileName : dexFileList) {
String filePath = dexFilePath + dexFileName;
// 执行dex2jar命令修改源代码
boolean isApplicationClassFound = dex2JarCmd(filePath, jarOutputPath, applicationName);
// 找到了目标应用主application的包名说明代码注入成功则返回当前dex文件
if (isApplicationClassFound) {
return dexFileName;
}
}
return "";
}
private boolean dex2JarCmd(String dexPath, String jarOutputPath, String applicationName) {
Dex2jarCmd cmd = new Dex2jarCmd();
String[] args = new String[]{
dexPath,
"-o",
jarOutputPath,
"-app",
applicationName,
"--force"
};
cmd.doMain(args);
boolean isApplicationClassFounded = cmd.isApplicationClassFounded();
if (showAllLogs) {
System.out.println("isApplicationClassFounded -> " + isApplicationClassFounded + "the dexPath is " +
dexPath);
}
return isApplicationClassFounded;
}
private void jar2DexCmd(String jarFilePath, String dexOutPath) {
Jar2Dex cmd = new Jar2Dex();
String[] args = new String[]{
jarFilePath,
"-o",
dexOutPath
};
cmd.doMain(args);
}
// 列出目录下所有dex文件classes.dexclasses2.dexclasses3.dex .....
private ArrayList<String> createClassesDotDexFileList(int dexFileCount) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < dexFileCount; i++) {
if (i == 0) {
list.add("classes.dex");
} else {
list.add("classes" + (i + 1) + ".dex");
}
}
return list;
}
}

View File

@ -0,0 +1,107 @@
package com.storm.wind.xpatch.task;
import com.storm.wind.xpatch.util.FileUtils;
import com.storm.wind.xpatch.util.ShellCmdUtil;
import java.io.File;
/**
* Created by Wind
*/
public class BuildAndSignApkTask implements Runnable {
private boolean keepUnsignedApkFile;
private String signedApkPath;
private String unzipApkFilePath;
public BuildAndSignApkTask(boolean keepUnsignedApkFile, String unzipApkFilePath, String signedApkPath) {
this.keepUnsignedApkFile = keepUnsignedApkFile;
this.unzipApkFilePath = unzipApkFilePath;
this.signedApkPath = signedApkPath;
}
@Override
public void run() {
File unzipApkFile = new File(unzipApkFilePath);
// 将文件压缩到当前apk文件的上一级目录上
String unsignedApkPath = unzipApkFile.getParent() + File.separator + "unsigned.apk";
FileUtils.compressToZip(unzipApkFilePath, unsignedApkPath);
// 将签名文件复制从assets目录下复制出来
String keyStoreFilePath = unzipApkFile.getParent() + File.separator + "keystore";
File keyStoreFile = new File(keyStoreFilePath);
// assets/keystore分隔符不能使用File.separator否则在windows上抛出IOException !!!
FileUtils.copyFileFromJar("assets/keystore", keyStoreFilePath);
signApk(unsignedApkPath, keyStoreFilePath, signedApkPath, false);
File unsignedApkFile = new File(unsignedApkPath);
File signedApkFile = new File(signedApkPath);
// delete unsigned apk file
if (!keepUnsignedApkFile && unsignedApkFile.exists() && signedApkFile.exists()) {
unsignedApkFile.delete();
}
// delete the keystore file
if (keyStoreFile.exists()) {
keyStoreFile.delete();
}
}
private boolean signApk(String apkPath, String keyStorePath, String signedApkPath, boolean useLocalJarsigner) {
File localJarsignerFile = null;
try {
long time = System.currentTimeMillis();
File keystoreFile = new File(keyStorePath);
if (keystoreFile.exists()) {
StringBuilder signCmd;
if (!useLocalJarsigner) {
signCmd = new StringBuilder("jarsigner ");
} else {
String localJarsignerPath = (new File(apkPath)).getParent() + File.separator + "jarsigner-081688";
localJarsignerFile = new File(localJarsignerPath);
FileUtils.copyFileFromJar("assets/jarsigner", localJarsignerPath);
ShellCmdUtil.execCmd("chmod -R 777 " + localJarsignerPath, null);
signCmd = new StringBuilder(localJarsignerPath + " ");
}
signCmd.append(" -keystore ")
.append(keyStorePath)
.append(" -storepass ")
.append("123456")
.append(" -signedjar ")
.append(" " + signedApkPath + " ")
.append(" " + apkPath + " ")
.append(" -digestalg SHA1 -sigalg SHA1withRSA ")
.append(" key0 ");
// System.out.println("\n" + signCmd + "\n");
String result = ShellCmdUtil.execCmd(signCmd.toString(), null);
System.out.println(" sign apk time is :" + ((System.currentTimeMillis() - time) / 1000) +
"s\n\n" + " result=" + result);
return true;
}
System.out.println(" keystore not exist :" + keystoreFile.getAbsolutePath() +
" please sign the apk by hand. \n");
return false;
} catch (Throwable e) {
if (!useLocalJarsigner) {
System.out.println("use default jarsigner to sign apk failedand try again, fail msg is :" +
e.toString());
signApk(apkPath, keyStorePath, signedApkPath, true);
} else {
System.out.println("use inner jarsigner to sign apk failed, sign it yourself fail msg is :" +
e.toString());
}
return false;
} finally {
if (localJarsignerFile != null && localJarsignerFile.exists()) {
localJarsignerFile.delete();
}
}
}
}

View File

@ -0,0 +1,107 @@
package com.storm.wind.xpatch.task;
import com.storm.wind.xpatch.util.FileUtils;
import java.io.File;
import java.util.HashMap;
/**
* Created by Wind
*/
public class SoAndDexCopyTask implements Runnable {
private static final String SO_FILE_NAME = "libxpatch_wl.so";
private final String[] APK_LIB_PATH_ARRAY = {
"lib/armeabi-v7a/",
"lib/armeabi/",
"lib/arm64-v8a/"
};
private final HashMap<String, String> SO_FILE_PATH_MAP = new HashMap<String, String>() {
{
put(APK_LIB_PATH_ARRAY[0], "assets/" + APK_LIB_PATH_ARRAY[0] + SO_FILE_NAME);
put(APK_LIB_PATH_ARRAY[1], "assets/" + APK_LIB_PATH_ARRAY[0] + SO_FILE_NAME);
put(APK_LIB_PATH_ARRAY[2], "assets/" + APK_LIB_PATH_ARRAY[2] + SO_FILE_NAME);
}
};
private int dexFileCount;
private String unzipApkFilePath;
public SoAndDexCopyTask(int dexFileCount, String unzipApkFilePath) {
this.dexFileCount = dexFileCount;
this.unzipApkFilePath = unzipApkFilePath;
}
@Override
public void run() {
// 复制xposed兼容层的dex文件以及so文件到当前目录下
copySoFile();
copyDexFile(dexFileCount);
// 删除签名信息
deleteMetaInfo();
}
private void copySoFile() {
// Try to find so file path in the apk, then copy so into it
boolean copySuccess = false;
for (String libPath : APK_LIB_PATH_ARRAY) {
copySuccess = copyLibFile(unzipApkFilePath + libPath.replace("/", File.separator),
SO_FILE_PATH_MAP.get(libPath), false);
if (copySuccess) {
break;
}
}
// Iif apk do not contain so file path, then create lib/armeabi-v7a, and copy libwhale.so into it
if (!copySuccess) {
String path = APK_LIB_PATH_ARRAY[0];
copySuccess = copyLibFile(unzipApkFilePath + path.replace("/", File.separator),
SO_FILE_PATH_MAP.get(path), true);
}
if (!copySuccess) {
throw new IllegalArgumentException(" copy so file failed ");
}
}
private void copyDexFile(int dexFileCount) {
// copy dex file to root dir, rename it first
String copiedDexFileName = "classes" + (dexFileCount + 1) + ".dex";
// assets/classes.dex分隔符不能使用File.seperater,否则在windows上无法读取到文件IOException
FileUtils.copyFileFromJar("assets/classes.dex", unzipApkFilePath + copiedDexFileName);
}
private boolean copyLibFile(String libFilePath, String srcSoPath, boolean forceCopy) {
File apkSoParentFile = new File(libFilePath);
if (forceCopy && !apkSoParentFile.exists()) {
apkSoParentFile.mkdirs();
}
File[] childs = apkSoParentFile.listFiles();
if (apkSoParentFile.exists() && (forceCopy || (childs != null && childs.length > 0))) {
FileUtils.copyFileFromJar(srcSoPath, new File(apkSoParentFile, SO_FILE_NAME).getAbsolutePath());
return true;
}
return false;
}
private void deleteMetaInfo() {
String metaInfoFilePath = "META-INF";
File metaInfoFileRoot = new File(unzipApkFilePath + metaInfoFilePath);
if (!metaInfoFileRoot.exists()) {
return;
}
File[] childFileList = metaInfoFileRoot.listFiles();
if (childFileList == null || childFileList.length == 0) {
return;
}
for (File file : childFileList) {
String fileName = file.getName().toUpperCase();
if (fileName.endsWith(".MF") || fileName.endsWith(".RAS") || fileName.endsWith(".SF")) {
file.delete();
}
}
}
}

View File

@ -0,0 +1,218 @@
package com.storm.wind.xpatch.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
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 java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
/**
* Created by Wind
*/
public class FileUtils {
static final int BUFFER = 8192;
/**
* 解压文件
*
* @param zipPath 要解压的目标文件
* @param descDir 指定解压目录
* @return 解压结果成功失败
*/
@SuppressWarnings("rawtypes")
public static boolean decompressZip(String zipPath, String descDir) {
File zipFile = new File(zipPath);
boolean flag = false;
if (!descDir.endsWith(File.separator)) {
descDir = descDir + File.separator;
}
File pathFile = new File(descDir);
if (!pathFile.exists()) {
pathFile.mkdirs();
}
ZipFile zip = null;
try {
zip = new ZipFile(zipFile, Charset.forName("gbk"));//防止中文目录乱码
for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String zipEntryName = entry.getName();
InputStream in = zip.getInputStream(entry);
//指定解压后的文件夹+当前zip文件的名称
String outPath = (descDir + zipEntryName).replace("/", File.separator);
//判断路径是否存在,不存在则创建文件路径
File file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator)));
if (!file.exists()) {
file.mkdirs();
}
//判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
if (new File(outPath).isDirectory()) {
continue;
}
//保存文件路径信息可利用md5.zip名称的唯一性来判断是否已经解压
// System.err.println("当前zip解压之后的路径为" + outPath);
OutputStream out = new FileOutputStream(outPath);
byte[] buf1 = new byte[2048];
int len;
while ((len = in.read(buf1)) > 0) {
out.write(buf1, 0, len);
}
close(in);
close(out);
}
flag = true;
close(zip);
} catch (IOException e) {
e.printStackTrace();
}
return flag;
}
private static InputStream getInputStreamFromFile(String filePath) {
return FileUtils.class.getClassLoader().getResourceAsStream(filePath);
}
// copy an asset file into a path
public static void copyFileFromJar(String inJarPath, String distPath) {
// System.out.println("start copyFile inJarPath =" + inJarPath + " 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);
}
}
public static void deleteDir(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
deleteDir(f);
}
}
}
file.delete();
}
public static void compressToZip(String srcPath, String dstPath) {
File srcFile = new File(srcPath);
File dstFile = new File(dstPath);
if (!srcFile.exists()) {
System.out.println(srcPath + " does not exist ");
return;
}
FileOutputStream out = null;
ZipOutputStream zipOut = null;
try {
out = new FileOutputStream(dstFile);
CheckedOutputStream cos = new CheckedOutputStream(out, new CRC32());
zipOut = new ZipOutputStream(cos);
String baseDir = "";
compress(srcFile, zipOut, baseDir, true);
} catch (IOException e) {
System.out.println(" compress exception = " + e.getMessage());
} finally {
try {
if (zipOut != null) {
zipOut.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
close(zipOut);
close(out);
}
}
private static void compress(File file, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
if (file.isDirectory()) {
compressDirectory(file, zipOut, baseDir, isRootDir);
} else {
compressFile(file, zipOut, baseDir);
}
}
/**
* 压缩一个目录
*/
private static void compressDirectory(File dir, ZipOutputStream zipOut, String baseDir, boolean isRootDir) throws IOException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (int i = 0; i < files.length; i++) {
String compressBaseDir = "";
if (!isRootDir) {
compressBaseDir = baseDir + dir.getName() + "/";
}
compress(files[i], zipOut, compressBaseDir, false);
}
}
/**
* 压缩一个文件
*/
private static void compressFile(File file, ZipOutputStream zipOut, String baseDir) throws IOException {
if (!file.exists()) {
return;
}
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(file));
ZipEntry entry = new ZipEntry(baseDir + file.getName());
zipOut.putNextEntry(entry);
int count;
byte data[] = new byte[BUFFER];
while ((count = bis.read(data, 0, BUFFER)) != -1) {
zipOut.write(data, 0, count);
}
} finally {
if (null != bis) {
bis.close();
}
}
}
private static void close(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (IOException io) {
io.printStackTrace();
}
}
}

View File

@ -0,0 +1,82 @@
package com.storm.wind.xpatch.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import wind.android.content.res.AXmlResourceParser;
import wind.v1.XmlPullParser;
import wind.v1.XmlPullParserException;
/**
* Created by Wind
*/
public class ManifestParser {
/**
* Get the package name and the main application name from the manifest file
* */
public static Pair parseManidestFile(String filePath) {
AXmlResourceParser parser = new AXmlResourceParser();
File file = new File(filePath);
String packageName = null;
String applictionName = null;
if (!file.exists()) {
System.out.println(" manifest file not exsit!!! filePath -> " + filePath);
return null;
}
try {
FileInputStream inputStream = new FileInputStream(file);
parser.open(inputStream);
while (true) {
int type = parser.next();
if (type == XmlPullParser.END_DOCUMENT) {
break;
}
if (type == XmlPullParser.START_TAG) {
int attrCount = parser.getAttributeCount();
for (int i = 0; i < attrCount; i++) {
String attrName = parser.getAttributeName(i);
String name = parser.getName();
if ("manifest".equals(name)) {
if ("package".equals(attrName)) {
packageName = parser.getAttributeValue(i);
}
}
if ("application".equals(name)) {
if ("name".equals(attrName)) {
applictionName = parser.getAttributeValue(i);
}
}
if (packageName != null && packageName.length() > 0 && applictionName != null && applictionName.length() > 0) {
return new Pair(packageName, applictionName);
}
}
} else if (type == XmlPullParser.END_TAG) {
// ignored
}
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
System.out.println("parseManidestFile failed, reason --> " + e.getMessage());
}
return new Pair(packageName, applictionName);
}
public static class Pair {
public String packageName;
public String applictionName;
public Pair(String packageName, String applictionName) {
this.packageName = packageName;
this.applictionName = applictionName;
}
}
}

View File

@ -0,0 +1,69 @@
package com.storm.wind.xpatch.util;
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
}
}
}
}