/* * Tencent is pleased to support the open source community by making Tinker available. * * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved. * * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language governing permissions and * limitations under the License. */ package com.tencent.tinker.build.apkparser; import com.tencent.tinker.build.patch.Configuration; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import tinker.net.dongliu.apk.parser.ApkParser; import tinker.net.dongliu.apk.parser.bean.ApkMeta; import tinker.net.dongliu.apk.parser.exception.ParserException; import tinker.net.dongliu.apk.parser.struct.StringPool; import tinker.net.dongliu.apk.parser.struct.resource.ResourceTable; import tinker.net.dongliu.apk.parser.utils.ParseUtils; /** * Created by zhangshaowen on 16/5/5. */ public class AndroidParser { public static final int TYPE_SERVICE = 1; public static final int TYPE_ACTIVITY = 2; public static final int TYPE_BROADCAST_RECEIVER = 3; public static final int TYPE_CONTENT_PROVIDER = 4; public final List<String> activities = new ArrayList<>(); public final List<String> receivers = new ArrayList<>(); public final List<String> services = new ArrayList<>(); public final List<String> providers = new ArrayList<>(); public final ApkMeta apkMeta; public final String xml; public final HashMap<String, String> metaDatas = new HashMap<>(); public AndroidParser(ApkMeta apkMeta, String xml) throws ParserException { this.apkMeta = apkMeta; this.xml = xml; parse(); } public static boolean resourceTableLogicalChange(Configuration config) throws IOException { ApkParser parser = new ApkParser(config.mOldApkFile); ApkParser newParser = new ApkParser(config.mNewApkFile); parser.parseResourceTable(); newParser.parseResourceTable(); return parser.getResourceTable().equals(newParser.getResourceTable()); } public static void editResourceTableString(String from, String to, File originFile, File destFile) throws IOException { if (from == null || to == null) { return; } if (!originFile.exists()) { throw new RuntimeException("origin resources.arsc is not exist, path:" + originFile.getPath()); } if (from.length() != to.length()) { throw new RuntimeException("only support the same string length now!"); } ApkParser parser = new ApkParser(); parser.parseResourceTable(originFile); ResourceTable resourceTable = parser.getResourceTable(); StringPool stringPool = resourceTable.getStringPool(); ByteBuffer buffer = resourceTable.getBuffers(); byte[] array = buffer.array(); int length = stringPool.getPool().length; boolean found = false; for (int i = 0; i < length; i++) { String value = stringPool.get(i); if (value.equals(from)) { found = true; long offset = stringPool.getPoolOffsets().get(i); //length offset += 2; byte[] tempByte; if (stringPool.isUtf8()) { tempByte = to.getBytes(ParseUtils.charsetUTF8); if (to.length() != tempByte.length) { throw new RuntimeException(String.format( "editResourceTableString length is different, name %d, tempByte %d\n", to.length(), tempByte.length)); } } else { tempByte = to.getBytes(ParseUtils.charsetUTF16); if ((to.length() * 2) != tempByte.length) { throw new RuntimeException(String.format( "editResourceTableString length is different, name %d, tempByte %d\n", to.length(), tempByte.length)); } } System.arraycopy(tempByte, 0, array, (int) offset, tempByte.length); } } if (!found) { throw new RuntimeException("can't found string:" + from + " in the resources.arsc file's string pool!"); } //write array to file FileOutputStream fileOutputStream = new FileOutputStream(destFile); try { fileOutputStream.write(array); } finally { fileOutputStream.close(); } } public static AndroidParser getAndroidManifest(File file) throws IOException, ParseException { ApkParser apkParser = new ApkParser(file); AndroidParser androidManifest = new AndroidParser(apkParser.getApkMeta(), apkParser.getManifestXml()); return androidManifest; } private static String getAttribute(NamedNodeMap namedNodeMap, String name) { Node node = namedNodeMap.getNamedItem(name); if (node == null) { if (name.startsWith("android:")) { name = name.substring("android:".length()); } node = namedNodeMap.getNamedItem(name); if (node == null) { return null; } } return node.getNodeValue(); } /** * @return a list of all components */ public List<String> getComponents() { List<String> components = new ArrayList<>(); components.addAll(activities); components.addAll(services); components.addAll(receivers); components.addAll(providers); return components; } private void parse() throws ParserException { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); Document document; try { DocumentBuilder builder = builderFactory.newDocumentBuilder(); document = builder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))); Node manifestNode = document.getElementsByTagName("manifest").item(0); NodeList nodes = manifestNode.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); String nodeName = node.getNodeName(); if (nodeName.equals("application")) { NodeList children = node.getChildNodes(); for (int j = 0; j < children.getLength(); j++) { Node child = children.item(j); String childName = child.getNodeName(); switch (childName) { case "service": services.add(getAndroidComponent(child, TYPE_SERVICE)); break; case "activity": activities.add(getAndroidComponent(child, TYPE_ACTIVITY)); break; case "receiver": receivers.add(getAndroidComponent(child, TYPE_BROADCAST_RECEIVER)); break; case "provider": providers.add(getAndroidComponent(child, TYPE_CONTENT_PROVIDER)); break; case "meta-data": NamedNodeMap attributes = child.getAttributes(); metaDatas.put(getAttribute(attributes, "android:name"), getAttribute(attributes, "android:value")); break; } } } } } catch (Exception e) { throw new ParserException("Error parsing AndroidManifest.xml", e); } } private String getAndroidComponent(Node node, int type) { NamedNodeMap attributes = node.getAttributes(); return getAttribute(attributes, "android:name"); } }