feat: ASW-90 新增 pcakage 相关接口

This commit is contained in:
shizhendong
2024-09-26 09:23:08 +08:00
parent 00f6bf65a1
commit 89c5a94715
11 changed files with 489 additions and 83 deletions

View File

@@ -1,65 +1,71 @@
package net.geedge.asw.module.app.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import net.geedge.asw.common.util.R;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.ResponseUtil;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.app.entity.PackageEntity;
import net.geedge.asw.module.app.service.IPackageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/package")
@RequestMapping("/api/v1/workspace")
public class PackageController {
@Autowired
private IPackageService packageService;
@GetMapping("/{id}")
public R detail(@PathVariable("id") String id) {
PackageEntity entity = packageService.getById(id);
@GetMapping("/{workspaceId}/package/{id}")
public R detail(@PathVariable("workspaceId") String workspaceId, @PathVariable("id") String id) {
PackageEntity entity = packageService.queryInfo(id);
return R.ok().putData("record", entity);
}
@GetMapping
public R list(@RequestParam Map<String, Object> params) {
T.VerifyUtil.is(params).notNull()
.and(T.MapUtil.getStr(params, "workspaceId")).notEmpty(RCode.WORKSPACE_ID_CANNOT_EMPTY);
@GetMapping("/{workspaceId}/package")
public R list(@PathVariable("workspaceId") String workspaceId, @RequestParam Map<String, Object> params) {
// workspaceId
params = T.MapUtil.defaultIfEmpty(params, new HashMap<>());
params.put("workspaceId", workspaceId);
Page page = packageService.queryList(params);
return R.ok(page);
}
@PostMapping
public R add(@RequestBody PackageEntity entity) {
T.VerifyUtil.is(entity).notNull()
.and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY)
.and(entity.getDescription()).notEmpty(RCode.PACKAGE_DESCRIPTION_CANNOT_EMPTY)
.and(entity.getWorkspaceId()).notEmpty(RCode.WORKSPACE_ID_CANNOT_EMPTY);
@PostMapping("/{workspaceId}/package")
public R add(@PathVariable(value = "workspaceId", required = true) String workspaceId,
@RequestParam(value = "description", required = false) String description,
@RequestParam(value = "file") MultipartFile file) {
PackageEntity pkgEntity = packageService.savePackage(entity);
return R.ok().putData("id", pkgEntity.getId());
PackageEntity entity = packageService.savePackage(workspaceId, description, file.getResource());
return R.ok().putData("record", entity);
}
@PutMapping
public R update(@RequestBody PackageEntity entity) {
T.VerifyUtil.is(entity).notNull()
.and(entity.getId()).notEmpty(RCode.ID_CANNOT_EMPTY)
.and(entity.getName()).notEmpty(RCode.NAME_CANNOT_EMPTY)
.and(entity.getDescription()).notEmpty(RCode.PACKAGE_DESCRIPTION_CANNOT_EMPTY)
.and(entity.getWorkspaceId()).notEmpty(RCode.WORKSPACE_ID_CANNOT_EMPTY);
PackageEntity pkgEntity = packageService.updatePackage(entity);
return R.ok().putData("id", pkgEntity.getId());
}
@DeleteMapping
@DeleteMapping("/{workspaceId}/package")
public R delete(String[] ids) {
T.VerifyUtil.is(ids).notEmpty();
packageService.removePackage(T.ListUtil.of(ids));
return R.ok();
}
@GetMapping("/{workspaceId}/package/{id}/download")
public void download(@PathVariable("workspaceId") String workspaceId,
@PathVariable("id") String id,
HttpServletResponse response) throws IOException {
PackageEntity entity = packageService.getById(id);
T.VerifyUtil.is(entity).notNull(RCode.SYS_RECORD_NOT_FOUND);
File pkgFile = T.FileUtil.file(entity.getPath());
ResponseUtil.downloadFile(response, MediaType.APPLICATION_OCTET_STREAM_VALUE, entity.getName(), T.FileUtil.readBytes(pkgFile));
}
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import net.geedge.asw.module.sys.entity.SysUserEntity;
@Data
@TableName("package")
@@ -13,11 +14,13 @@ public class PackageEntity {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
private String logo;
private String icon;
private String description;
private String platform;
private String version;
private String identifier;
private String path;
private Long size;
private Long createTimestamp;
private Long updateTimestamp;
@@ -27,6 +30,9 @@ public class PackageEntity {
private String workspaceId;
@TableField(exist = false)
private String workbookId;
private SysUserEntity createUser;
@TableField(exist = false)
private SysUserEntity updateUser;
}

View File

@@ -3,17 +3,18 @@ package net.geedge.asw.module.app.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import net.geedge.asw.module.app.entity.PackageEntity;
import org.springframework.core.io.Resource;
import java.util.List;
import java.util.Map;
public interface IPackageService extends IService<PackageEntity>{
PackageEntity queryInfo(String id);
Page queryList(Map<String, Object> params);
PackageEntity savePackage(PackageEntity entity);
PackageEntity updatePackage(PackageEntity entity);
PackageEntity savePackage(String workspaceId, String description, Resource fileResource);
void removePackage(List<String> ids);
}

View File

@@ -1,30 +1,60 @@
package net.geedge.asw.module.app.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import cn.hutool.log.Log;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.geedge.asw.common.util.ASWException;
import net.geedge.asw.common.util.RCode;
import net.geedge.asw.common.util.T;
import net.geedge.asw.module.app.dao.PackageDao;
import net.geedge.asw.module.app.entity.PackageEntity;
import net.geedge.asw.module.app.service.IPackageService;
import net.geedge.asw.module.app.util.ApkInfo;
import net.geedge.asw.module.app.util.ApkUtil;
import net.geedge.asw.module.app.util.PkgConstant;
import net.geedge.asw.module.sys.entity.SysUserEntity;
import net.geedge.asw.module.sys.service.ISysUserService;
import net.geedge.asw.module.workbook.service.IWorkbookResourceService;
import net.geedge.asw.module.workbook.util.WorkbookConstant;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
@Service
public class PackageServiceImpl extends ServiceImpl<PackageDao, PackageEntity> implements IPackageService {
private final static Log log = Log.get();
@Autowired
private ISysUserService sysUserService;
@Autowired
private IWorkbookResourceService workbookResourceService;
@Override
public PackageEntity queryInfo(String id) {
PackageEntity entity = this.getById(id);
T.VerifyUtil.is(entity).notNull(RCode.SYS_RECORD_NOT_FOUND);
// user
SysUserEntity createUser = sysUserService.getById(entity.getCreateUserId());
SysUserEntity updateUser = sysUserService.getById(entity.getUpdateUserId());
createUser.setPwd(null);
updateUser.setPwd(null);
entity.setCreateUser(createUser);
entity.setUpdateUser(updateUser);
return entity;
}
@Override
public Page queryList(Map<String, Object> params) {
Page page = T.PageUtil.getPage(params);
@@ -34,55 +64,63 @@ public class PackageServiceImpl extends ServiceImpl<PackageDao, PackageEntity> i
}
@Override
@Transactional(rollbackFor = Exception.class)
public PackageEntity savePackage(PackageEntity entity) {
PackageEntity one = this.getOne(new LambdaQueryWrapper<PackageEntity>()
.eq(PackageEntity::getWorkspaceId, entity.getWorkspaceId())
.eq(PackageEntity::getName, entity.getName()));
if (T.ObjectUtil.isNotNull(one)) {
throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build();
public PackageEntity savePackage(String workspaceId, String description, Resource fileResource) {
PackageEntity entity = new PackageEntity();
try {
String pkgId = T.StrUtil.uuid();
entity.setId(pkgId);
entity.setName(fileResource.getFilename());
entity.setDescription(T.StrUtil.emptyToDefault(description, ""));
// 默认安卓
entity.setPlatform(PkgConstant.Platform.ANDROID.getValue());
entity.setWorkspaceId(workspaceId);
entity.setCreateUserId(StpUtil.getLoginIdAsString());
entity.setUpdateUserId(StpUtil.getLoginIdAsString());
entity.setCreateTimestamp(System.currentTimeMillis());
entity.setUpdateTimestamp(System.currentTimeMillis());
byte[] bytes = fileResource.getInputStream().readAllBytes();
entity.setSize((long) bytes.length);
// path
String fileExtName = T.StrUtil.emptyToDefault(T.FileUtil.extName(fileResource.getFilename()), "pcap");
String saveFileName = pkgId + "." + fileExtName;
File destination = T.FileUtil.file(PkgConstant.APK_FILES_DIR, saveFileName);
FileUtils.copyInputStreamToFile(fileResource.getInputStream(), destination);
entity.setPath(destination.getPath());
// parse
try {
ApkUtil apkUtil = new ApkUtil();
apkUtil.setAaptToolPath(Path.of(T.WebPathUtil.getRootPath(), "lib", "aapt").toString());
ApkInfo apkInfo = apkUtil.parseApk(entity.getPath());
entity.setVersion(apkInfo.getVersionName());
entity.setIdentifier(apkInfo.getPackageName());
} catch (Exception e) {
log.error(e, "[savePackage] [parse error] [file: {}]", fileResource.getFilename());
}
this.save(entity);
} catch (Exception e) {
log.error(e, "[savePackage] [error] [file: {}]", fileResource.getFilename());
}
entity.setCreateTimestamp(System.currentTimeMillis());
entity.setUpdateTimestamp(System.currentTimeMillis());
entity.setCreateUserId(StpUtil.getLoginIdAsString());
entity.setUpdateUserId(StpUtil.getLoginIdAsString());
// save
this.save(entity);
// workbook resource
workbookResourceService.saveResource(entity.getWorkbookId(), entity.getId(), WorkbookConstant.ResourceType.PACKAGE.getValue());
return entity;
}
@Override
@Transactional(rollbackFor = Exception.class)
public PackageEntity updatePackage(PackageEntity entity) {
PackageEntity one = this.getOne(new LambdaQueryWrapper<PackageEntity>()
.eq(PackageEntity::getWorkspaceId, entity.getWorkspaceId())
.eq(PackageEntity::getName, entity.getName())
.ne(PackageEntity::getId, entity.getId()));
if (T.ObjectUtil.isNotNull(one)) {
throw ASWException.builder().rcode(RCode.SYS_DUPLICATE_RECORD).build();
}
entity.setUpdateTimestamp(System.currentTimeMillis());
entity.setUpdateUserId(StpUtil.getLoginIdAsString());
// update
this.updateById(entity);
// workbook resource
workbookResourceService.saveResource(entity.getWorkbookId(), entity.getId(), WorkbookConstant.ResourceType.PACKAGE.getValue());
return entity;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removePackage(List<String> ids) {
// remove
this.removeBatchByIds(ids);
for (String id : ids) {
PackageEntity entity = this.getById(id);
// remove file
T.FileUtil.del(entity.getPath());
// remove
this.removeById(id);
}
// workbook resource
workbookResourceService.removeResource(ids, WorkbookConstant.ResourceType.PACKAGE.getValue());
}

View File

@@ -0,0 +1,169 @@
package net.geedge.asw.module.app.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ApkInfo {
public static final String APPLICATION_ICON_120 = "application-icon-120";
public static final String APPLICATION_ICON_160 = "application-icon-160";
public static final String APPLICATION_ICON_240 = "application-icon-240";
public static final String APPLICATION_ICON_320 = "application-icon-320";
// 所需设备属性
private List<String> features;
// 图标
private String icon;
// 各分辨率下图标路径
private Map<String, String> icons;
// 应用程序名
private String label;
// 入口Activity
private String launchableActivity;
// 支持的Android平台最低版本号
private String minSdkVersion;
// 主包名
private String packageName;
// 支持的SDK版本
private String sdkVersion;
// Apk文件大小字节
private long size;
// 目标SDK版本
private String targetSdkVersion;
// 所需权限
private List<String> usesPermissions;
// 内部版本号
private String versionCode;
// 外部版本号
private String versionName;
public ApkInfo() {
this.features = new ArrayList<>();
this.icons = new HashMap<>();
this.usesPermissions = new ArrayList<>();
}
public List<String> getFeatures() {
return features;
}
public void setFeatures(List<String> features) {
this.features = features;
}
public void addToFeatures(String feature) {
this.features.add(feature);
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public Map<String, String> getIcons() {
return icons;
}
public void setIcons(Map<String, String> icons) {
this.icons = icons;
}
public void addToIcons(String key, String value) {
this.icons.put(key, value);
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getLaunchableActivity() {
return launchableActivity;
}
public void setLaunchableActivity(String launchableActivity) {
this.launchableActivity = launchableActivity;
}
public String getMinSdkVersion() {
return minSdkVersion;
}
public void setMinSdkVersion(String minSdkVersion) {
this.minSdkVersion = minSdkVersion;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getSdkVersion() {
return sdkVersion;
}
public void setSdkVersion(String sdkVersion) {
this.sdkVersion = sdkVersion;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public String getTargetSdkVersion() {
return targetSdkVersion;
}
public void setTargetSdkVersion(String targetSdkVersion) {
this.targetSdkVersion = targetSdkVersion;
}
public List<String> getUsesPermissions() {
return usesPermissions;
}
public void setUsesPermissions(List<String> usesPermissions) {
this.usesPermissions = usesPermissions;
}
public void addToUsesPermissions(String usesPermission) {
this.usesPermissions.add(usesPermission);
}
public String getVersionCode() {
return versionCode;
}
public void setVersionCode(String versionCode) {
this.versionCode = versionCode;
}
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
@Override
public String toString() {
return "ApkInfo [features=" + features + ", icon=" + icon + ", icons=" + icons + ", label=" + label + ", launchableActivity=" + launchableActivity + ", minSdkVersion=" + minSdkVersion + ", packageName=" + packageName + ", sdkVersion=" + sdkVersion + ", size=" + size + ", targetSdkVersion=" + targetSdkVersion + ", usesPermissions=" + usesPermissions + ", versionCode=" + versionCode + ", versionName=" + versionName + "]";
}
}

View File

@@ -0,0 +1,110 @@
package net.geedge.asw.module.app.util;
import cn.hutool.log.Log;
import net.geedge.asw.common.util.T;
import java.io.*;
public class ApkUtil {
private static final Log log = Log.get();
public static final String APPLICATION = "application:";
public static final String APPLICATION_ICON = "application-icon";
public static final String APPLICATION_LABEL = "application-label";
public static final String APPLICATION_LABEL_N = "application: label";
public static final String DENSITIES = "densities";
public static final String LAUNCHABLE_ACTIVITY = "launchable";
public static final String PACKAGE = "package";
public static final String SDK_VERSION = "sdkVersion";
public static final String SUPPORTS_ANY_DENSITY = "support-any-density";
public static final String SUPPORTS_SCREENS = "support-screens";
public static final String TARGET_SDK_VERSION = "targetSdkVersion";
public static final String VERSION_CODE = "versionCode";
public static final String VERSION_NAME = "versionName";
public static final String USES_FEATURE = "uses-feature";
public static final String USES_IMPLIED_FEATURE = "uses-implied-feature";
public static final String USES_PERMISSION = "uses-permission";
private static final String SPLIT_REGEX = "(: )|(=')|(' )|'";
private ProcessBuilder builder;
// aapt 所在目录
private String aaptToolPath = "aapt";
public ApkUtil() {
builder = new ProcessBuilder();
builder.redirectErrorStream(true);
}
public String getAaptToolPath() {
return aaptToolPath;
}
public void setAaptToolPath(String aaptToolPath) {
this.aaptToolPath = aaptToolPath;
}
public ApkInfo parseApk(String apkPath) {
String aaptTool = aaptToolPath;
Process process = null;
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
process = builder.command(aaptTool, "d", "badging", apkPath).start();
inputStream = process.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
ApkInfo apkInfo = new ApkInfo();
apkInfo.setSize(new File(apkPath).length());
String temp = null;
while ((temp = bufferedReader.readLine()) != null) {
setApkInfoProperty(apkInfo, temp);
}
return apkInfo;
} catch (IOException e) {
log.error(e, "[parseApk] [error] [path: {}]", apkPath);
return null;
} finally {
if (process != null) {
process.destroy();
}
T.IoUtil.close(inputStream);
T.IoUtil.close(bufferedReader);
}
}
private void setApkInfoProperty(ApkInfo apkInfo, String source) {
if (source.startsWith(APPLICATION)) {
String[] rs = source.split("( icon=')|'");
apkInfo.setIcon(rs[rs.length - 1]);
} else if (source.startsWith(APPLICATION_ICON)) {
apkInfo.addToIcons(getKeyBeforeColon(source), getPropertyInQuote(source));
} else if (source.startsWith(APPLICATION_LABEL)) {
apkInfo.setLabel(getPropertyInQuote(source));
} else if (source.startsWith(LAUNCHABLE_ACTIVITY)) {
apkInfo.setLaunchableActivity(getPropertyInQuote(source));
} else if (source.startsWith(PACKAGE)) {
String[] packageInfo = source.split(SPLIT_REGEX);
apkInfo.setPackageName(packageInfo[2]);
apkInfo.setVersionCode(packageInfo[4]);
apkInfo.setVersionName(packageInfo[6]);
} else if (source.startsWith(SDK_VERSION)) {
apkInfo.setSdkVersion(getPropertyInQuote(source));
} else if (source.startsWith(TARGET_SDK_VERSION)) {
apkInfo.setTargetSdkVersion(getPropertyInQuote(source));
} else if (source.startsWith(USES_PERMISSION)) {
apkInfo.addToUsesPermissions(getPropertyInQuote(source));
} else if (source.startsWith(USES_FEATURE)) {
apkInfo.addToFeatures(getPropertyInQuote(source));
}
}
private String getKeyBeforeColon(String source) {
return source.substring(0, source.indexOf(':'));
}
private String getPropertyInQuote(String source) {
int index = source.indexOf("'") + 1;
return source.substring(index, source.indexOf('\'', index));
}
}

View File

@@ -0,0 +1,37 @@
package net.geedge.asw.module.app.util;
import net.geedge.asw.common.util.T;
import java.io.File;
public class PkgConstant {
/**
* android packages file dir
*/
public static File APK_FILES_DIR = T.FileUtil.file(T.WebPathUtil.getRootPath(), "apk_files");
/**
* support platform
*/
public enum Platform {
ANDROID("android"),
IOS("ios"),
WINDOWS("windows"),
LINUX("linux");
private String value;
Platform(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}

View File

@@ -3,12 +3,51 @@
<mapper namespace="net.geedge.asw.module.app.dao.PackageDao">
<resultMap id="pkgResult" type="net.geedge.asw.module.app.entity.PackageEntity">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="icon" column="icon"/>
<result property="description" column="description"/>
<result property="platform" column="platform"/>
<result property="version" column="version"/>
<result property="identifier" column="identifier"/>
<result property="path" column="path"/>
<result property="size" column="size"/>
<result property="createTimestamp" column="create_timestamp"/>
<result property="updateTimestamp" column="update_timestamp"/>
<result property="createUserId" column="create_user_id"/>
<result property="updateUserId" column="update_user_id"/>
<result property="workspaceId" column="workspace_id"/>
<select id="queryList" resultType="net.geedge.asw.module.app.entity.PackageEntity">
<association property="createUser" columnPrefix="cu_" javaType="net.geedge.asw.module.sys.entity.SysUserEntity">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="userName" column="user_name"/>
</association>
<association property="updateUser" columnPrefix="uu_" javaType="net.geedge.asw.module.sys.entity.SysUserEntity">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="userName" column="user_name"/>
</association>
</resultMap>
<select id="queryList" resultMap="pkgResult">
SELECT
pkg.*
pkg.*,
cu.id AS cu_id,
cu.name AS cu_name,
cu.user_name AS cu_user_name,
uu.id AS uu_id,
uu.name AS uu_name,
uu.user_name AS uu_user_name
FROM
package pkg
LEFT JOIN sys_user cu ON pkg.create_user_id = cu.id
LEFT JOIN sys_user uu ON pkg.update_user_id = uu.id
LEFT JOIN workbook_resource wr ON pkg.id = wr.resource_id AND wr.resource_type = 'package'
<where>
<if test="params.ids != null and params.ids != ''">

View File

@@ -28,7 +28,6 @@
<result property="platform" column="platform"/>
<result property="identifier" column="identifier"/>
<result property="version" column="version"/>
<result property="logo" column="logo"/>
</association>
<association property="runner" columnPrefix="run_" javaType="net.geedge.asw.module.runner.entity.RunnerEntity">
@@ -54,7 +53,6 @@
pkg.id AS pkg_id,
pkg.platform AS pkg_platform,
pkg.version AS pkg_version,
pkg.logo AS pkg_logo,
pkg.identifier AS pkg_identifier,
run.id AS run_id,

View File

@@ -361,11 +361,13 @@ DROP TABLE IF EXISTS `package`;
CREATE TABLE `package` (
`id` varchar(64) NOT NULL COMMENT '主键',
`name` varchar(256) NOT NULL DEFAULT '' COMMENT '名称',
`logo` varchar(512) NOT NULL DEFAULT '' COMMENT '图标,图标文件 url 地址',
`icon` text NOT NULL DEFAULT '' COMMENT '图标,图标文件base64内容',
`description` text NOT NULL DEFAULT '' COMMENT '描述信息',
`platform` varchar(256) NOT NULL DEFAULT '' COMMENT '操作系统; 可选值android,ios,windows,linux',
`version` varchar(256) NOT NULL DEFAULT '' COMMENT '安装包版本',
`identifier` varchar(256) NOT NULL DEFAULT '' COMMENT '唯一标识;androidpackage name,iosbundle id',
`path` varchar(512) NOT NULL DEFAULT '' COMMENT '安装包文件路径',
`size` bigint(20) NOT NULL DEFAULT 0 COMMENT '文件大小',
`create_timestamp` bigint(20) NOT NULL COMMENT '创建时间戳',
`update_timestamp` bigint(20) NOT NULL COMMENT '更新时间戳',
`create_user_id` varchar(64) NOT NULL COMMENT '创建人',

BIN
src/main/resources/lib/aapt Normal file

Binary file not shown.