Commit 95c20178 authored by Administrator's avatar Administrator

剥离RatelApi代码

parents
Pipeline #1337 canceled with stages
/build
.idea/
.gradle/
*.iml
local.properties
# RatelApi
这是Ratel(平头哥)的api代码模块,剥离自Ratel整体工程,以git父子module进行关联。
剥离RatelApi工程的目的是使得用户可以通过代码使用RatelEngine已经支持,但是RatelApi还没有发布到jar包的功能。另一方面方便RatelEngine调试
\ No newline at end of file
repositories {
repositories {
maven {
name "aliyunmaven"
url "https://maven.aliyun.com/repository/public"
}
maven {
name "aliyunGoogle"
url "https://maven.aliyun.com/repository/google"
}
maven {
name "contralSnapshot"
url "https://oss.sonatype.org/content/repositories/snapshots/"
}
}
}
apply plugin: 'java-library'
apply plugin: 'maven-publish'
apply plugin: 'signing'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.google.android:android:4.1.1.4'
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
compileOnly 'com.android.support:support-annotations:28.0.0'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
group 'com.virjar'
version '1.3.3-SNAPSHOT'
compileJava {
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileJava]*.options*.encoding = 'UTF-8'
}
compileTestJava {
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileTestJava]*.options*.encoding = 'UTF-8'
}
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allJava
}
// 生成 javadoc jar
task javadocJar(type: Jar) {
classifier = 'javadoc'
from javadoc.destinationDir
}
// javadoc 配置,这里是自定义了 java doc 的一些配置
javadoc {
description = "Generates project-level javadoc for use in -javadoc jar"
options.memberLevel = JavadocMemberLevel.PROTECTED
options.author = true
options.version = true
options.header = project.name
options.addStringOption('Xdoclint:none', '-quiet')
// suppress warnings due to cross-module @see and @link references;
// note that global 'api' task does display all warnings.
logging.captureStandardError LogLevel.INFO
logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
options.encoding = "UTF-8" //编码一定要配置否则直接出错
options.charSet = 'UTF-8'
}
def getRepositoryUsername() {
return hasProperty('MAVEN_USERNAME') ? MAVEN_USERNAME : ""
}
def getRepositoryPassword() {
return hasProperty('MAVEN_PASSWORD') ? MAVEN_PASSWORD : ""
}
publishing {
publications {
// 这一个推送项目名称,mavenJava 相当于是一个 task name
mavenJava(MavenPublication) {
groupId project.group
artifactId 'ratel-api'
version "${version}"
from components.java
artifact sourcesJar
artifact javadocJar
// 添加 pom 相关信息
// https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html
pom {
name = "Ratel API"
description = "ratel api,used for developer on ratel system,an extension for xposed framewrok,ratel api compatable with original xposed framework"
url = "https://git.virjar.com:ratel/ratel"
licenses {
license {
name = "The Apache License, Version 2.0"
url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
}
}
developers {
// 添加开发者描述,这个id不知道是什么
developer {
id = "weijia.deng"
name = "dengweijia"
email = "virjar@virjar.com"
}
}
// 添加你的 git 仓库 信息
scm {
connection = "scm:git@git.virjar.com:ratel/ratel.git"
developerConnection = "scm:git@git.virjar.com:ratel/ratel.git"
url = "http://git.virjar.com:ratel/ratel"
}
}
}
}
repositories {
// 添加一个远程仓库地址
// releases 仓库
maven {
// 在对 task 中会生成对应的名称 publishMavenJavaPublicationToxxx
// 后面的 xxx 就是你这里的名称,表示你要把jar 上传到这个仓库中
name 'sonatypeRepository' // 为你这个仓库起名
url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
credentials {
username = getRepositoryUsername() // 之前在 sonatype 注册的账户名
password = getRepositoryPassword()
}
}
// snapshots 仓库
maven {
name = 'sonatypeSnapshotRepository'
url = 'https://oss.sonatype.org/content/repositories/snapshots/'
credentials {
username = getRepositoryUsername()
password = getRepositoryPassword()
}
}
}
}
// 签名配置,注意这里的顺序,今天第一次知道 gradle 中的 task 等配置也是有顺序的
// 必须在 publishing 配置之后
signing {
sign publishing.publications.mavenJava
}
\ No newline at end of file
#Sat Feb 08 16:14:09 CST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
#!/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" "$@"
@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
package com.virjar.ratel.api;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public interface DexMakerProxyBuilder<T> {
DexMakerProxyBuilder<T> parentClassLoader(ClassLoader parent);
DexMakerProxyBuilder<T> handler(InvocationHandler handler);
DexMakerProxyBuilder<T> implementing(Class<?>... interfaces);
DexMakerProxyBuilder<T> constructorArgValues(Object... constructorArgValues);
DexMakerProxyBuilder<T> constructorArgTypes(Class<?>... constructorArgTypes);
DexMakerProxyBuilder<T> onlyMethods(Method[] methods);
DexMakerProxyBuilder<T> withSharedClassLoader();
DexMakerProxyBuilder<T> markTrusted();
T build() throws IOException;
Class<? extends T> buildProxyClass() throws IOException;
}
package com.virjar.ratel.api;
import com.virjar.ratel.api.hint.RatelEngineHistory;
import com.virjar.ratel.api.hint.RatelEngineVersion;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@RatelEngineVersion(RatelEngineHistory.V_1_2_8)
public interface DexMakerProxyBuilderHelper {
/**
* 创建一个aop代理构造器
*
* @param clazz 对应class,如代理InputStream
* @param <T> 任意class类型
* @return builder实例
*/
<T> DexMakerProxyBuilder<T> forClass(Class<T> clazz);
/**
* 判断一个class是不是aop代理产生的class
*
* @param c 待测试的class
* @return 测试结果,是否为代理class
*/
boolean isProxyClass(Class<?> c);
/**
* 调用supper方法,由于我们继承了方法,在AOP场景下,我们还需要call origin。我们可能只是修改参数或者拦截返回值
*
* @param proxy 当前的代理对象
* @param method 当前调用方法
* @param args 参数
* @return 方法调用结果
* @throws Throwable 可能抛出异常
*/
Object callSuper(Object proxy, Method method, Object... args) throws Throwable;
/**
* InvocationHandler是动态代理机制里面很重要的一个概念,通过他处理AOP的实际业务逻辑,一般很少使用
*
* @param instance 代理对象
* @return 获取对应的 InvocationHandler
*/
InvocationHandler getInvocationHandler(Object instance);
/**
* 给代理对象替换handler,一般很少使用
*
* @param instance 代理对象
* @param handler 新的handler
*/
void setInvocationHandler(Object instance, InvocationHandler handler);
}
This diff is collapsed.
package com.virjar.ratel.api;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import java.lang.reflect.Member;
public interface HookProvider {
void hookMethod(Member method, RC_MethodHook callback);
void unhookMethod(Member method, RC_MethodHook callback);
Object invokeOriginalMethod(Member method,
Object thisObject, Object[] args) throws Throwable;
}
package com.virjar.ratel.api;
public interface IORelocator {
String getRedirectedPath(String origPath);
void redirectDirectory(String origPath, String newPath);
void redirectFile(String origPath, String newPath);
void readOnlyFile(String path);
void readOnly(String path);
void whitelistFile(String path);
void whitelist(String path);
void forbid(String path, boolean file);
boolean addMockSystemProperty(String key, String value);
String queryMockSystemProperty(String key);
}
package com.virjar.ratel.api;
public interface ProcessUtils {
void killMe();
}
package com.virjar.ratel.api;
public interface RatalStartUpCallback {
/**
* ratel 框架启动完成钩子函数
*/
void onRatelStartCompletedEvent();
}
package com.virjar.ratel.api;
public interface RatelConfig {
String getConfig(String key);
}
package com.virjar.ratel.api;
public interface RatelEngineUpgradeEvent {
void onEngineUpgrade();
}
package com.virjar.ratel.api;
import android.annotation.SuppressLint;
import android.content.Context;
import com.virjar.ratel.api.hint.PostInited;
import com.virjar.ratel.api.providers.ContentProviderFakeRegister;
import com.virjar.ratel.api.scheduler.SchedulerTaskBeanHandler;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class RatelToolKit {
/**
* 1。 以下对象,是暴露给调用方的额外API,可以通过他们操作ratel提供的额外功能(除开xposed本身功能之外)
*/
//全局的一个context,context是调用Android系统功能的重要对象。有这个对象之后,无需手动通过拦截attach的方式获取context
@SuppressLint("StaticFieldLeak")
public static Context sContext = null;
/**
* 宿主apk的classLoader,可以替代llparma
*/
public static ClassLoader hostClassLoader = null;
/**
* ratel框架的配置信息,代表了ratel编码、打包、运行过程产生的一些特定flag
*/
public static RatelConfig ratelConfig = null;
/**
* ratel支持对文件进行重定向
*/
public static IORelocator ioRelocator = null;
/**
* 当前进程名称
*/
public static String processName = null;
/**
* 当成packageName
*/
public static String packageName = null;
public static String ratelVersionName = null;
public static int ratelVersionCode = 0;
/**
* 用户ID,可以作为一个设备ID,MULTI模式下和nowUser相等。其他模式可能为随机值,但是除非设备被删除,该ID不会改变
* 请注意他是PostInited的
*/
@PostInited
public static String userIdentifier;
/**
* 也是用户ID,不过转化为一个long类型,方便在ID空间震荡算法中作为随机数起点种子
* 请注意他是PostInited的
*/
@PostInited
public static long userIdentifierSeed;
/**
* 虚拟化环境功能支持
*/
public static VirtualEnv virtualEnv = null;
/**
* 指纹fake接口
*/
public static FingerPrintModel fingerPrintModel = null;
/**
* contentProvider模拟接口
*/
public static ContentProviderFakeRegister contentProviderFakeRegister = null;
/**
* 基于dexmaker的动态代理支持
*/
public static DexMakerProxyBuilderHelper dexMakerProxyBuilderHelper = null;
/**
* 给调度任务使用的,用户操作调度任务状态
*/
public static SchedulerTaskBeanHandler schedulerTaskBeanHandler = null;
/**
* 设置为true之后,框架将会自动检测apk处于ANR态,并且ANR态将会自杀程序,避免apk卡屏
*/
public static boolean killAppIfDetectANR = false;
/**
* 虚拟化环境下,sdcard将会被隔离,导致无法往sdcard写入数据。但是如果ratel模块期望通过sdcard和其他app交换数据,那么需要通过一个sdcard白名单进行放行<br>
* 该路径规则为: /sdcard/ratel_white_dir/packageName/ <br>
* 如: /sdcard/ratel_white_dir/com.kanxue.container.demoapp <br>
* 比如一般来说,通过文件实现多账户身份切换,指定文件为: /sdcard/ratel_white_dir/com.kanxue.container.demoapp/userId.txt
*/
public static String whiteSdcardDirPath = null;
/**
* @hidden
*/
public static Set<RatalStartUpCallback> ratalStartUpCallbackSet = new CopyOnWriteArraySet<>();
public static Set<RatelEngineUpgradeEvent> ratelEngineUpgradeEventSet = new CopyOnWriteArraySet<>();
@Deprecated
public static void setOnRatelStartUpCallback(RatalStartUpCallback ratelStartUpCallback) {
addOnRatelStartUpCallback(ratelStartUpCallback);
}
public static void addOnRatelStartUpCallback(RatalStartUpCallback ratalStartUpCallback) {
ratalStartUpCallbackSet.add(ratalStartUpCallback);
}
public static void addOnEngineUpgradeListener(RatelEngineUpgradeEvent ratelEngineUpgradeEvent) {
ratelEngineUpgradeEventSet.add(ratelEngineUpgradeEvent);
}
public static ProcessUtils processUtils = null;
/**
* 2。 以下以下对象,是用户层不需要关心的。我也不会做解释
*/
public static HookProvider usedHookProvider;
public static String TAG = null;
}
This diff is collapsed.
package com.virjar.ratel.api;
import java.util.Random;
public class SuffixTrimUtils {
private static Random random = new Random();
public interface ValueUpdater {
String modify(String value);
}
public static class SuffixModifier implements ValueUpdater {
private int with;
public SuffixModifier(int with) {
this.with = with;
}
public String modify(String input) {
return mockSuffix(input, with);
}
}
public static SuffixModifier SuffixModifier5 = new SuffixModifier(5);
public static SuffixModifier SuffixModifier4 = new SuffixModifier(4);
public static ValueUpdater replaceToEmpty = value -> "";
public static String mockSuffix(String input, int width) {
int fakeWith = Math.min(width, input.length() - 1);
if (fakeWith < 1) {
return input;
}
char[] needModify = input.substring(input.length() - fakeWith).toCharArray();
for (int i = 0; i < needModify.length; i++) {
if (Character.isDigit(needModify[i])) {
needModify[i] = nextDigit();
} else if (isHex(needModify[i])) {
needModify[i] = nextHex();
} else if (Character.isLetter(needModify[i])) {
needModify[i] = nextChar();
}
//其他字符串,不是数字或者字母。下划线,连接线,其他特殊字符保留原来的模样
}
return input.substring(0, input.length() - fakeWith) + new String(needModify);
}
private static boolean isHex(char ch) {
// if (ch >= '0' && ch <= '9') {
// return true;
// }
if (ch >= 'a' && ch <= 'f') {
return true;
}
return false;
}
private static char nextHex() {
return (char) ('a' + random.nextInt('f' - 'a'));
}
public static char nextChar() {
int aAStart = random.nextInt(2) % 2 == 0 ? 65 : 97; //取得大写字母还是小写字母
return (char) (aAStart + random.nextInt(26));
}
public static int randomMockDiff(int size) {
return random.nextInt(size * 2) - size;
}
private static char nextDigit() {
return (char) ('0' + random.nextInt(10));
}
public static Random getRandom() {
return random;
}
public static void main(String[] args) {
System.out.println(mockSuffix("ac:37:43:a1:0b:88", 7));
System.out.println(mockSuffix("hello-world123a", 3));
System.out.println(mockSuffix("hello-world", 11));
System.out.println(mockSuffix("hello-world", 12));
System.out.println(mockSuffix("abc", 1));
System.out.println(mockSuffix("abc", 2));
System.out.println(mockSuffix("abc", 3));
}
}
package com.virjar.ratel.api;
import java.util.Set;
public interface VirtualEnv {
enum VirtualEnvModel {
/**
* 未启用虚拟环境
*/
DISABLE,
/**
* 每次启动app切换设备数据,适合未登录数据抓取使用
*/
START_UP,
/**
* app重新安装的时候切换设备数据,适合登录态+不会编程控制
*/
INSTALL,
/**
* 多用户模式,这个模式比较特殊,将会放大用户。通过时间分割的方式实现多用户
*/
MULTI,
/**
* 通过ratel api来控制设备信息切换,灵活的根据业务逻辑控制。比如发现设备被拉黑,主动控制设备切换<br>
* 目前MULTI支持通过apk手动控制,所以不再需要支持MANUAL模式了
*/
@Deprecated
MANUAL
}
class RawFingerData {
public RawFingerData(String imei, String serial, double latitude, double longitude) {
this.imei = imei;
this.serial = serial;
this.latitude = latitude;
this.longitude = longitude;
}
public RawFingerData() {
}
public String imei = "";
public String serial = "";
public Double latitude;
public Double longitude;
}
/**
* 查询当前设备虚拟环境,结果为枚举。包含系统ratel定义的几种模式
*
* @return 虚拟化环境模式
* @see VirtualEnvModel
*/
VirtualEnvModel getVirtualEnvModel();
/**
* 切换设备,本API在不同的 {@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel}。如下:<br>
* <ul>
* <li>{@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel#DISABLE}: 不会有任何作用,调用直接被忽略</li>
* <li>{@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel#START_UP} | {@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel#INSTALL} </li>
* <li>{@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel#MULTI}: 切换到userId对应的账户下,如果userId不存在,那么重新创建。如果userId为控,那么ratel自动创建一个</li>
* </ul>
* 本API只有在main进程中执行有效,所有环境操作只能由主进程执行<br>
* 如果设备数据切换成功,那么将会导致app主动停止。需要被其他框架重新守护
*
* @param userId 目标用户id,仅在 {@link com.virjar.ratel.api.VirtualEnv.VirtualEnvModel#MULTI}下有效 ,请注意,userId需要满足JavaIdentify规则(数字、字母、下划线),不满足规则的字符将会被抹除
*/
void switchEnv(String userId);
/**
* 系统可用的user集合,仅在multi模式下有效
*
* @return userList集合
*/
Set<String> availableUserSet();
/**
* 删除某个用户。用户需要存在,并且不能删除nowUser
*
* @param userId 用户id
* @return 是否删除成功。一般来说,除非用户不存在,否则不会删除失败
*/
boolean removeUser(String userId);
/**
* manual 模式下,需要手动控制是否切换设备,如果设置设备切换状态为true。那么设备信息永远不会切换。除非将标记从新设置为false。 keep标记默认为false
* multi模式支持API控制,不需要存在manual了
*
* @param keep 是否需要保持设备不被切换
*/
@Deprecated
void keepEnvForManualModel(boolean keep);
/**
* 当前用户生效的用户,仅在MULTI模式下有效在
*
* @return userId, 如果userId在输入的时候存在特殊字符。那么特殊字符将会被清楚
*/
String nowUser();
/**
* 查询原生的imei数据,如果没有权限,或者没有触发IMEI调用。那么查询结果为空
*
* @return 被替换前的IMEI
* @deprecated see {@link com.virjar.ratel.api.FingerPrintModel#imei}
*/
@Deprecated
String originIMEI();
/**
* 查询原生的AndroidId,如果没有触发AndroidId调用,那么查询结果为空
* <br>
* 当前已经没有意义,由于android本身是随机值,获取原始的随机只并没有意义
*
* @return 被替换前的AndroidId,这个数值意义不大。本身AndroidId就是在Settings中记录的一个随机数
*/
@Deprecated
String originAndroidId();
/**
* 查询原生的手机号,如果没有触发手机号获取调用,或者本app权限为空,那么查询结果为空
*
* @return 被替换前的手机号
* @deprecated see {@link com.virjar.ratel.api.FingerPrintModel#line1Number}
*/
@Deprecated
String originLine1Number();
/**
* MEID 另一个模式下的id,类似IMEI,平常使用较少
*
* @return 被替换前的Meid
* @deprecated see {@link com.virjar.ratel.api.FingerPrintModel#meid}
*/
@Deprecated
String originMeid();
/**
* @return IccSerialNumber
* @deprecated see {@link com.virjar.ratel.api.FingerPrintModel#iccSerialNumber}
*/
@Deprecated
String originIccSerialNumber();
/**
* 序列号为各厂商自定义,正常情况下,在同一个厂商的手机里面是唯一的。所以可以作为唯一的设备信息标记
*
* @return 被替换前的序列号
* @deprecated see {@link com.virjar.ratel.api.FingerPrintModel#serial}
*/
@Deprecated
String originSerialNumber();
RawFingerData rawFingerData();
String defaultMultiUserId = "default_0";
}
package com.virjar.ratel.api.hint;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* mark this is a beta api
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Beta {
}
package com.virjar.ratel.api.hint;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记这个接口(或者数据)在框架启动后才可以调用,也就是无法在
* public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable 函数体中使用
* 具体原因需参见ratel框架生命周期介绍
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface PostInited {
}
package com.virjar.ratel.api.hint;
public class RatelEngineHistory {
public static final String V_LOW = "lower";
public static final String V_1_2_5 = "1.2.5";
public static final String V_1_2_7 = "1.2.7";
public static final String V_1_2_8 = "1.2.8";
}
package com.virjar.ratel.api.hint;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记该API从那个版本的引擎开始支持,ratelAPI和引擎关系密切
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface RatelEngineVersion {
String value() default RatelEngineHistory.V_1_2_5;
}
package com.virjar.ratel.api.inspect;
import android.util.Log;
import com.virjar.ratel.api.RatelToolKit;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedBridge;
import com.virjar.ratel.api.rposed.RposedHelpers;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ClassLoadMonitor {
public interface OnClassLoader {
void onClassLoad(Class<?> clazz);
}
private static ConcurrentMap<String, Set<OnClassLoader>> callBacks = new ConcurrentHashMap<>();
private static Set<OnClassLoader> onClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>());
private static Set<ClassLoader> hookedClassLoader = Collections.newSetFromMap(new ConcurrentHashMap<>());
private static Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
static {
hookedClassLoader.add(Thread.currentThread().getContextClassLoader());
hookedClassLoader.add(ClassLoadMonitor.class.getClassLoader());
hookedClassLoader.add(RatelToolKit.sContext.getClassLoader());
}
public static void notifyClassInit(Class clazz) {
hookedClassLoader.add(clazz.getClassLoader());
for (OnClassLoader onClassLoader : onClassLoaders) {
try {
onClassLoader.onClassLoad(clazz);
} catch (Throwable throwable) {
Log.e(RatelToolKit.TAG, "error when callback for class load monitor", throwable);
}
}
Collection<OnClassLoader> onClassLoaders = callBacks.remove(clazz.getName());
if (onClassLoaders != null) {
for (OnClassLoader onClassLoader : onClassLoaders) {
try {
onClassLoader.onClassLoad(clazz);
} catch (Throwable throwable) {
Log.e(RatelToolKit.TAG, "error when callback for class load monitor", throwable);
}
}
}
}
public static void addClassLoadMonitor(String className, OnClassLoader onClassLoader) {
addClassLoadMonitor(className, onClassLoader, false);
}
/**
* 增加某个class的加载监听,注意该方法不做重入消重工作,需要调用方自己实现回调消重逻辑。<br>
* 该函数将会尽可能早的的回调到业务方,常常用来注册挂钩函数(这样可以实现挂钩函数注册过晚导致感兴趣的逻辑拦截失败)
*
* @param className 将要监听的className,如果存在多个class name相同的类,存在于不同的classloader,可能会导致监听失败
* @param onClassLoader 监听的回调
* @param retryWithSystem 是否测试当前系统的classloader,对于指令抽取class的壳来说,同一个class可能存在duplicate load,此时
*/
public static void addClassLoadMonitor(String className, OnClassLoader onClassLoader, boolean retryWithSystem) {
if (retryWithSystem) {
for (ClassLoader classLoader : hookedClassLoader) {
Class<?> classIfExists = RposedHelpers.findClassIfExists(className, classLoader);
if (classIfExists == null) {
continue;
}
if (ClassStatusUtils.isInitialized(classIfExists)) {
onClassLoader.onClassLoad(classIfExists);
return;
}
}
}
Set<OnClassLoader> onClassLoaders = callBacks.get(className);
if (onClassLoaders == null) {
onClassLoaders = Collections.newSetFromMap(new ConcurrentHashMap<>());
//putIfAbsent maybe null
callBacks.putIfAbsent(className, onClassLoaders);
onClassLoaders = callBacks.get(className);
}
onClassLoaders.add(onClassLoader);
}
public static void addClassLoadMonitor(OnClassLoader onClassLoader) {
onClassLoaders.add(onClassLoader);
}
/**
* 尝试加载一个class,无需感知classloader,的存在
*
* @param className className
* @return class对象,如果无法加载,返回null
*/
public static Class<?> tryLoadClass(String className) {
Class<?> ret = classCache.get(className);
if (ret != null) {
return ret;
}
for (ClassLoader classLoader : hookedClassLoader) {
try {
Class<?> aClass = RposedHelpers.findClassIfExists(className, classLoader);
if (aClass != null) {
classCache.put(className, aClass);
return aClass;
}
} catch (Throwable throwable) {
// 可能有虚拟机相关的class加载失败异常,所以这里catch Throwable
// ignore
}
}
return RposedHelpers.findClassIfExists(className, null);
}
public static void findAndHookMethod(String className, final String methodName, final Object... parameterTypesAndCallback) {
addClassLoadMonitor(className, clazz -> RposedHelpers.findAndHookMethod(clazz, methodName, parameterTypesAndCallback), true);
}
public static void findAndHookMethodWithSupper(String className, final String methodName, final Object... parameterTypesAndCallback) {
addClassLoadMonitor(className, clazz -> {
Throwable t = null;
while (clazz != Object.class) {
try {
RposedHelpers.findAndHookMethod(clazz, methodName, parameterTypesAndCallback);
return;
} catch (Throwable throwable) {
if (t == null) {
t = throwable;
}
clazz = clazz.getSuperclass();
}
}
throw new IllegalStateException(t);
}, true);
}
public static void hookAllMethod(String className, RC_MethodHook callback) {
hookAllMethod(className, null, callback);
}
public static void hookAllMethod(final String className, final String methodName, final RC_MethodHook callback) {
addClassLoadMonitor(className, clazz -> {
if (Modifier.isInterface(clazz.getModifiers())) {
Log.e(RatelToolKit.TAG, "the class : {" + clazz.getName() + "} is interface can not hook any method!!");
return;
}
for (Method method : clazz.getDeclaredMethods()) {
if (methodName != null && !method.getName().equals(methodName)) {
continue;
}
if (Modifier.isAbstract(method.getModifiers())) {
continue;
}
RposedBridge.hookMethod(method, callback);
}
}, true);
}
public static void hookAllConstructor(String className, final RC_MethodHook callback) {
addClassLoadMonitor(className, clazz -> RposedBridge.hookAllConstructors(clazz, callback), true);
}
}
package com.virjar.ratel.api.inspect;
import android.os.Build;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
public class ClassStatusUtils {
static Field fieldStatusOfClass;
static {
try {
fieldStatusOfClass = Class.class.getDeclaredField("status");
fieldStatusOfClass.setAccessible(true);
} catch (NoSuchFieldException e) {
}
}
public static int getClassStatus(Class clazz, boolean isUnsigned) {
if (clazz == null) {
return 0;
}
int status = 0;
try {
status = fieldStatusOfClass.getInt(clazz);
} catch (Throwable e) {
}
if (isUnsigned) {
status = (int) (toUnsignedLong(status) >> (32 - 4));
}
return status;
}
public static long toUnsignedLong(int x) {
return ((long) x) & 0xffffffffL;
}
/**
* 5.0-8.0: kInitialized = 10 int
* 8.1: kInitialized = 11 int
* 9.0: kInitialized = 14 uint8_t
*/
public static boolean isInitialized(Class clazz) {
if (fieldStatusOfClass == null)
return true;
if (Build.VERSION.SDK_INT >= 28) {
return getClassStatus(clazz, true) == 14;
} else if (Build.VERSION.SDK_INT == 27) {
return getClassStatus(clazz, false) == 11;
} else {
return getClassStatus(clazz, false) == 10;
}
}
public static boolean isStaticAndNoInited(Member hookMethod) {
if (hookMethod == null || hookMethod instanceof Constructor) {
return false;
}
Class declaringClass = hookMethod.getDeclaringClass();
return Modifier.isStatic(hookMethod.getModifiers())
&& !ClassStatusUtils.isInitialized(declaringClass);
}
public static boolean isNotInited(Member hookMethod) {
if (hookMethod == null || hookMethod instanceof Constructor) {
return false;
}
Class declaringClass = hookMethod.getDeclaringClass();
return !ClassStatusUtils.isInitialized(declaringClass);
}
}
package com.virjar.ratel.api.inspect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* some utils migrated from guava
*/
public class Lists {
public static <E> ArrayList<E> newArrayList(E... elements) {
// Avoid integer overflow when a large array is passed in
int capacity = computeArrayListCapacity(elements.length);
ArrayList<E> list = new ArrayList<E>(capacity);
Collections.addAll(list, elements);
return list;
}
static int computeArrayListCapacity(int arraySize) {
return saturatedCast(5L + arraySize + (arraySize / 10));
}
/**
* Returns the {@code int} nearest in value to {@code value}.
*
* @param value any {@code long} value
* @return the same value cast to {@code int} if it is in the range of the
* {@code int} type, {@link Integer#MAX_VALUE} if it is too large,
* or {@link Integer#MIN_VALUE} if it is too small
*/
public static int saturatedCast(long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (value < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) value;
}
public static <E> LinkedList<E> newLinkedList() {
return new LinkedList<E>();
}
static <T> Collection<T> cast(Iterable<T> iterable) {
return (Collection<T>) iterable;
}
public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
Iterable<? extends E> elements) {
// We copy elements to an ArrayList first, rather than incurring the
// quadratic cost of adding them to the COWAL directly.
Collection<? extends E> elementsCollection =
(elements instanceof Collection)
? cast(elements)
: newArrayList(elements);
return new CopyOnWriteArrayList<E>(elementsCollection);
}
public static <E> ArrayList<E> newArrayList(Iterable<? extends E> elements) {
// Let ArrayList's sizing logic work, if possible
return (elements instanceof Collection)
? new ArrayList<E>(cast(elements))
: newArrayList(elements.iterator());
}
public static <E> ArrayList<E> newArrayList(Iterator<? extends E> elements) {
ArrayList<E> list = newArrayList();
addAll(list, elements);
return list;
}
public static <T> boolean addAll(Collection<T> addTo, Iterator<? extends T> iterator) {
boolean wasModified = false;
while (iterator.hasNext()) {
wasModified |= addTo.add(iterator.next());
}
return wasModified;
}
public static <T> List<T> newArrayListWithCapacity(int length) {
return new ArrayList<>(length);
}
}
package com.virjar.ratel.api.inspect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by virjar on 2018/3/27.<br>
* quick search if a package is a subPackage for packageNameList<br>
* <p>
* <pre>
* PackageSearchNode root = new PackageSearchNode();
* root.addToTree("android");
* root.addToTree("java.lang");
* root.addToTree("com.alibaba");
* root.addToTree("com.alipay");
* root.addToTree("com.baidu");
* root.addToTree("com.tencent");
* root.addToTree("com.google");
* root.addToTree("com.networkbench");
* root.addToTree("com.sina.weibo");
* root.addToTree("com.taobao");
* root.addToTree("com.tendcloud");
* root.addToTree("com.umeng.message");
* root.addToTree("org.android");
* root.addToTree("org.aspectj");
* root.addToTree("org.java_websocket");
* </pre>
* <p>
* then
* <p>
* <pre>
* root.isSubPackage("com.alibaba.fastjson.JSONObject"); return true
* root.isSubPackage("com.163"); return false
* root.isSubPackage("com"); return false
* </pre>
*
* @since 0.3.0
*/
public class PackageTrie {
// private static final Splitter dotSplitter = Splitter.on(".").omitEmptyStrings();
private Map<String, PackageTrie> children = new HashMap<>();
private void addToTree(ArrayList<String> packageSplitItems, int index) {
if (index > packageSplitItems.size() - 1) {
return;
}
String node = packageSplitItems.get(index);
PackageTrie packageTrie = children.get(node);
if (packageTrie == null) {
packageTrie = new PackageTrie();
children.put(node, packageTrie);
}
packageTrie.addToTree(packageSplitItems, index + 1);
}
public void addToTree(Collection<String> basePackageList) {
for (String str : basePackageList) {
addToTree(str);
}
}
public void addToTree(String packageName) {
addToTree(Lists.newArrayList(split(packageName)), 0);
}
public boolean isSubPackage(String packageName) {
return isSubPackage(Lists.newArrayList(split(packageName)), 0);
}
private boolean isSubPackage(ArrayList<String> packageSplitItems, int index) {
if (children.size() == 0) {
return true;
}
if (index > packageSplitItems.size() - 1) {
return false;
}
String node = packageSplitItems.get(index);
return children.containsKey(node) && children.get(node).isSubPackage(packageSplitItems, index + 1);
}
private List<String> split(String input) {
if (input == null) {
return Collections.emptyList();
}
List<String> ret = new ArrayList<>();
String[] strings = input.split("\\.");
for (String str : strings) {
str = str.trim();
if (str.length() == 0) {
continue;
}
ret.add(str);
}
return ret;
}
}
\ No newline at end of file
package com.virjar.ratel.api.providers;
import android.content.ContentValues;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class ContentProviderFake {
public Bundle call(Object thisObject, Method method, String arg, Bundle extras) throws InvocationTargetException {
return call(thisObject, method, new Object[]{arg, extras});
}
public Uri insert(Object thisObject, Method method, Uri url, ContentValues initialValues) throws InvocationTargetException {
return (Uri) call(thisObject, method, new Object[]{url, initialValues});
}
public Cursor query(Object thisObject, Method method, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, Bundle originQueryArgs) throws InvocationTargetException {
return (Cursor) call(thisObject, method, new Object[]{url, projection, selection, selectionArgs, sortOrder, originQueryArgs});
}
public String getType(Object thisObject, Method method, Uri url) throws InvocationTargetException {
return (String) call(thisObject, method, new Object[]{url});
}
public int bulkInsert(Object thisObject, Method method, Uri url, ContentValues[] initialValues) throws InvocationTargetException {
return (int) call(thisObject, method, new Object[]{url, initialValues});
}
public int delete(Object thisObject, Method method, Uri url, String selection, String[] selectionArgs) throws InvocationTargetException {
return (int) call(thisObject, method, new Object[]{url, selection, selectionArgs});
}
public int update(Object thisObject, Method method, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws InvocationTargetException {
return (int) call(thisObject, method, new Object[]{url, values, selection, selectionArgs});
}
public ParcelFileDescriptor openFile(Object thisObject, Method method, Uri url, String mode) throws InvocationTargetException {
return (ParcelFileDescriptor) call(thisObject, method, new Object[]{url, mode});
}
public AssetFileDescriptor openAssetFile(Object thisObject, Method method, Uri url, String mode) throws InvocationTargetException {
return (AssetFileDescriptor) call(thisObject, method, new Object[]{url, mode});
}
@SuppressWarnings("unchecked")
public <T> T call(Object who, Method method, Object[] args) throws InvocationTargetException {
try {
return (T) method.invoke(who, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
package com.virjar.ratel.api.providers;
public interface ContentProviderFakeRegister {
void register(String authority, ContentProviderFake contentProviderFake);
}
package com.virjar.ratel.api.rposed;
import android.os.Build;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* ratelEngine 1.3.2之后,将会取消 hidden policy bypass,此时RposedHelper的反射操作可能会被阻断。
* 提供这个帮助类,可以实现hidden policy的绕过
* <p>
* 这个限制在android9(api level = 28)之后才会出现
*/
public class FreeReflection {
private static Method getMethodMethod = null;
// private static Method forNameMethod = null;
private static Method getFiledMethod = null;
private static Method getDeclaredConstructorMethod = null;
private static Method getDeclaredFieldsMethod = null;
static {
init();
}
private static void init() {
try {
getMethodMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
// forNameMethod = Class.class.getDeclaredMethod("forName", String.class);
getFiledMethod = Class.class.getDeclaredMethod("getDeclaredField", String.class);
getDeclaredConstructorMethod = Class.class.getDeclaredMethod("getDeclaredConstructor", Class[].class);
getDeclaredFieldsMethod = Class.class.getDeclaredMethod("getDeclaredFields");
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
private static boolean use() {
return Build.VERSION.SDK_INT >= 28;
}
public static Method getDeclaredMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
if (use()) {
try {
return (Method) getMethodMethod.invoke(clazz, methodName, parameterTypes);
} catch (Throwable e) {
//ignore
}
}
return clazz.getDeclaredMethod(methodName, parameterTypes);
}
public static Constructor<?> getDeclaredConstructor(Class<?> clazz, Class<?>... parameterTypes) throws NoSuchMethodException {
if (use()) {
try {
return (Constructor) getDeclaredConstructorMethod.invoke(clazz, (Object) parameterTypes);
} catch (Throwable e) {
//ignore
}
}
return clazz.getDeclaredConstructor(parameterTypes);
}
public static Field getDeclaredField(Class<?> clazz, String field) throws NoSuchFieldException {
if (use()) {
try {
return (Field) getFiledMethod.invoke(clazz, field);
} catch (Throwable e) {
//ignore
}
}
return clazz.getDeclaredField(field);
}
public static Field[] getDeclaredField(Class<?> clazz) {
if (use()) {
try {
return (Field[]) getDeclaredFieldsMethod.invoke(clazz);
} catch (Throwable e) {
//ignore
}
}
return clazz.getDeclaredFields();
}
}
package com.virjar.ratel.api.rposed;
import android.app.Application;
import com.virjar.ratel.api.rposed.callbacks.RC_LoadPackage;
import com.virjar.ratel.api.xposed.IRXposedHookLoadPackage;
/**
* Get notified when an app ("Android package") is loaded.
* This is especially useful to hook some app-specific methods.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IRposedHookLoadPackage extends IRposedMod, IRXposedHookLoadPackage {
/**
* This method is called when an app is loaded. It's called very early, even before
* {@link Application#onCreate} is called.
* Modules can set up their app-specific hooks here.
*
* @param lpparam Information about the app.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleLoadPackage(RC_LoadPackage.LoadPackageParam lpparam) throws Throwable;
/**
* @hide
*/
final class Wrapper extends RC_LoadPackage {
private final IRposedHookLoadPackage instance;
public Wrapper(IRposedHookLoadPackage instance) {
this.instance = instance;
}
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
}
}
package com.virjar.ratel.api.rposed;
/** Marker interface for Xposed modules. Cannot be implemented directly. */
/* package */ public interface IRposedMod {}
package com.virjar.ratel.api.rposed;
import com.virjar.ratel.api.rposed.callbacks.IRUnhook;
import com.virjar.ratel.api.rposed.callbacks.RCallback;
import java.lang.reflect.Member;
/**
* Callback class for method hooks.
* <p>
* <p>Usually, anonymous subclasses of this class are created which override
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
*/
public abstract class RC_MethodHook extends RCallback {
/**
* Creates a new callback with default priority.
*/
@SuppressWarnings("deprecation")
public RC_MethodHook() {
super();
}
/**
* Creates a new callback with a specific priority.
* <p>
* <p class="note">Note that {@link #afterHookedMethod} will be called in reversed order, i.e.
* the callback with the highest priority will be called last. This way, the callback has the
* final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
* highest priority first.
*
* @param priority See {@link RCallback#priority}.
*/
public RC_MethodHook(int priority) {
super(priority);
}
/**
* Called before the invocation of the method.
* <p>
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to prevent the original method from being called.
* <p>
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
beforeHookedMethod(param);
}
/**
* Called after the invocation of the method.
* <p>
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to modify the return value of the original method.
* <p>
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
}
public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
afterHookedMethod(param);
}
/**
* Wraps information about the method call and allows to influence it.
*/
public static class MethodHookParam extends Param {
/**
* @hide
*/
@SuppressWarnings("deprecation")
public MethodHookParam() {
super();
}
/**
* The hooked method/constructor.
*/
public Member method;
/**
* The {@code this} reference for an instance method, or {@code null} for static methods.
*/
public Object thisObject;
/**
* Arguments to the method call.
*/
public Object[] args;
private Object result = null;
private Throwable throwable = null;
public boolean returnEarly = false;
/**
* Returns the result of the method call.
*/
public Object getResult() {
return result;
}
/**
* Modify the result of the method call.
* <p>
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setResult(Object result) {
this.result = result;
this.throwable = null;
this.returnEarly = true;
}
/**
* Returns the {@link Throwable} thrown by the method, or {@code null}.
*/
public Throwable getThrowable() {
return throwable;
}
/**
* Returns true if an exception was thrown by the method.
*/
public boolean hasThrowable() {
return throwable != null;
}
/**
* Modify the exception thrown of the method call.
* <p>
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
this.result = null;
this.returnEarly = true;
}
/**
* Returns the result of the method call, or throws the Throwable caused by it.
*/
public Object getResultOrThrowable() throws Throwable {
if (throwable != null)
throw throwable;
return result;
}
}
/**
* An object with which the method/constructor can be unhooked.
*/
public class Unhook implements IRUnhook<RC_MethodHook> {
private final Member hookMethod;
/*package*/
public Unhook(Member hookMethod) {
this.hookMethod = hookMethod;
}
/**
* Returns the method/constructor that has been hooked.
*/
public Member getHookedMethod() {
return hookMethod;
}
@Override
public RC_MethodHook getCallback() {
return RC_MethodHook.this;
}
@SuppressWarnings("deprecation")
@Override
public void unhook() {
RposedBridge.unhookMethod(hookMethod, RC_MethodHook.this);
}
}
}
package com.virjar.ratel.api.rposed;
import com.virjar.ratel.api.rposed.callbacks.RCallback;
/**
* A special case of {@link RC_MethodHook} which completely replaces the original method.
*/
public abstract class RC_MethodReplacement extends RC_MethodHook {
/**
* Creates a new callback with default priority.
*/
public RC_MethodReplacement() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link RCallback#priority}.
*/
public RC_MethodReplacement(int priority) {
super(priority);
}
/** @hide */
@Override
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
try {
Object result = replaceHookedMethod(param);
param.setResult(result);
} catch (Throwable t) {
param.setThrowable(t);
}
}
/** @hide */
@Override
@SuppressWarnings("EmptyMethod")
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {}
/**
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
* instead of the result of the original method (which will not be called).
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
*/
@SuppressWarnings("UnusedParameters")
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
/**
* Predefined callback that skips the method without replacements.
*/
public static final RC_MethodReplacement DO_NOTHING = new RC_MethodReplacement(PRIORITY_HIGHEST*2) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null;
}
};
/**
* Creates a callback which always returns a specific value.
*
* @param result The value that should be returned to callers of the hooked method.
*/
public static RC_MethodReplacement returnConstant(final Object result) {
return returnConstant(PRIORITY_DEFAULT, result);
}
/**
* Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
*
* @param priority See {@link RCallback#priority}.
* @param result The value that should be returned to callers of the hooked method.
*/
public static RC_MethodReplacement returnConstant(int priority, final Object result) {
return new RC_MethodReplacement(priority) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return result;
}
};
}
}
This diff is collapsed.
This diff is collapsed.
package com.virjar.ratel.api.rposed.callbacks;
/**
* Interface for objects that can be used to remove callbacks.
*
* <p class="warning">Just like hooking methods etc., unhooking applies only to the current process.
* In other process (or when the app is removed from memory and then restarted), the hook will still
* be active. The Zygote process is an exception, the hook won't
* be inherited by any future processes forked from it in the future.
*
* @param <T> The class of the callback.
*/
public interface IRUnhook<T> {
/**
* Returns the callback that has been registered.
*/
T getCallback();
/**
* Removes the callback.
*/
void unhook();
}
package com.virjar.ratel.api.rposed.callbacks;
import android.content.pm.ApplicationInfo;
import com.virjar.ratel.api.rposed.IRposedHookLoadPackage;
import com.virjar.ratel.api.rposed.RposedBridge;
/**
* This class is only used for internal purposes, except for the {@link LoadPackageParam}
* subclass.
*/
public abstract class RC_LoadPackage extends RCallback implements IRposedHookLoadPackage {
/**
* Creates a new callback with default priority.
* @hide
*/
@SuppressWarnings("deprecation")
public RC_LoadPackage() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link RCallback#priority}.
* @hide
*/
public RC_LoadPackage(int priority) {
super(priority);
}
/**
* Wraps information about the app being loaded.
*/
public static final class LoadPackageParam extends Param {
/** @hide */
public LoadPackageParam(RposedBridge.CopyOnWriteSortedSet<RC_LoadPackage> callbacks) {
super(callbacks);
}
/** The name of the package being loaded. */
public String packageName;
/** The process in which the package is executed. */
public String processName;
/** The ClassLoader used for this package. */
public ClassLoader classLoader;
/** More information about the application being loaded. */
public ApplicationInfo appInfo;
/** Set to {@code true} if this is the first (and main) application for this process. */
public boolean isFirstApplication;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof LoadPackageParam)
handleLoadPackage((LoadPackageParam) param);
}
}
package com.virjar.ratel.api.rposed.callbacks;
import android.os.Bundle;
import com.virjar.ratel.api.rposed.RposedBridge;
import java.io.Serializable;
/**
* Base class for Xposed callbacks.
* <p>
* This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses.
*/
public abstract class RCallback implements Comparable<RCallback> {
/**
* Callback priority, higher number means earlier execution.
*
* <p>This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should
* be executed earlier or later a value between {@link #PRIORITY_HIGHEST} and {@link #PRIORITY_LOWEST}
* can be set instead. The values are just for orientation though, Xposed doesn't enforce any
* boundaries on the priority values.
*/
public final int priority;
/**
* @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
*/
@Deprecated
public RCallback() {
this.priority = PRIORITY_DEFAULT;
}
/**
* @hide
*/
public RCallback(int priority) {
this.priority = priority;
}
/**
* Base class for Xposed callback parameters.
*/
public static abstract class Param {
/**
* @hide
*/
public Object[] callbacks;
public Bundle extra;
/**
* @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it!
*/
@Deprecated
protected Param() {
callbacks = null;
}
/**
* @hide
*/
protected Param(RposedBridge.CopyOnWriteSortedSet<? extends RCallback> callbacks) {
this.callbacks = callbacks.getSnapshot();
}
/**
* This can be used to store any data for the scope of the callback.
*
* <p>Use this instead of instance variables, as it has a clear reference to e.g. each
* separate call to a method, even when the same method is called recursively.
*
* @see #setObjectExtra
* @see #getObjectExtra
*/
public synchronized Bundle getExtra() {
if (extra == null)
extra = new Bundle();
return extra;
}
/**
* Returns an object stored with {@link #setObjectExtra}.
*/
public Object getObjectExtra(String key) {
Serializable o = getExtra().getSerializable(key);
if (o instanceof SerializeWrapper)
return ((SerializeWrapper) o).object;
return null;
}
/**
* Stores any object for the scope of the callback. For data types that support it, use
* the {@link Bundle} returned by {@link #getExtra} instead.
*/
public void setObjectExtra(String key, Object o) {
getExtra().putSerializable(key, new SerializeWrapper(o));
}
private static class SerializeWrapper implements Serializable {
private static final long serialVersionUID = 1L;
private final Object object;
public SerializeWrapper(Object o) {
object = o;
}
}
}
/**
* @hide
*/
public static void callAll(Param param) {
if (param.callbacks == null)
throw new IllegalStateException("This object was not created for use with callAll");
for (int i = 0; i < param.callbacks.length; i++) {
try {
((RCallback) param.callbacks[i]).call(param);
} catch (Throwable t) {
RposedBridge.log(t);
}
}
}
/**
* @hide
*/
protected void call(Param param) throws Throwable {
}
/**
* @hide
*/
@Override
public int compareTo(RCallback other) {
if (this == other)
return 0;
// order descending by priority
if (other.priority != this.priority)
return other.priority - this.priority;
// then randomly
else if (System.identityHashCode(this) < System.identityHashCode(other))
return -1;
else
return 1;
}
/**
* The default priority, see {@link #priority}.
*/
public static final int PRIORITY_DEFAULT = 50;
/**
* Execute this callback late, see {@link #priority}.
*/
public static final int PRIORITY_LOWEST = -10000;
/**
* Execute this callback early, see {@link #priority}.
*/
public static final int PRIORITY_HIGHEST = 10000;
}
package com.virjar.ratel.api.scheduler;
import java.util.Map;
public interface RatelTask {
/**
* 加载调度任务参数,每次调度任务执行之前,可能需要初始化任务相关参数。
* <br>
* 非常重要的是,这次调用发生在manager进程,你不能将加载好的数据放到静态变量或者当前进程内存中,否则整个调度过程可能由于app重启导致内存数据丢失
*
* @return 任务参数map, KV均为字符串,可为空
*/
Map<String, String> loadTaskParams();
/**
* 执行调度任务
* <br>
* 该任务发生在slave app中
*
* @param params 调度任务参数
*/
void doRatelTask(Map<String, String> params);
}
package com.virjar.ratel.api.scheduler;
public interface SchedulerTaskBeanHandler {
void finishedMTask();
}
package com.virjar.ratel.api.xposed;
public interface IRXposedHookInitPackageResources {
}
package com.virjar.ratel.api.xposed;
public interface IRXposedHookLoadPackage {
//标记接口为xposed的入口
}
package com.virjar.ratel.api.xposed;
public interface IRXposedHookZygoteInit {
/**
* Called very early during startup of Zygote.
*
* @param startupParam Details about the module itself and the started process.
* @throws Throwable everything is caught, but will prevent further initialization of the module.
*/
void initZygote(StartupParam startupParam) throws Throwable;
/**
* Data holder for {@link #initZygote}.
*/
class StartupParam {
public StartupParam() {
}
/**
* The path to the module's APK.
*/
public String modulePath;
/**
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server.
*/
public boolean startsSystemServer;
}
}
package com.virjar.ratel.api.xposed;
public interface IRXposedMod {
}
This diff is collapsed.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* @author Alexander Y. Kleymenov
*/
package external.okio;
import java.io.UnsupportedEncodingException;
final class Base64 {
private Base64() {
}
public static byte[] decode(String in) {
// Ignore trailing '=' padding and whitespace from the input.
int limit = in.length();
for (; limit > 0; limit--) {
char c = in.charAt(limit - 1);
if (c != '=' && c != '\n' && c != '\r' && c != ' ' && c != '\t') {
break;
}
}
// If the input includes whitespace, this output array will be longer than necessary.
byte[] out = new byte[(int) (limit * 6L / 8L)];
int outCount = 0;
int inCount = 0;
int word = 0;
for (int pos = 0; pos < limit; pos++) {
char c = in.charAt(pos);
int bits;
if (c >= 'A' && c <= 'Z') {
// char ASCII value
// A 65 0
// Z 90 25 (ASCII - 65)
bits = c - 65;
} else if (c >= 'a' && c <= 'z') {
// char ASCII value
// a 97 26
// z 122 51 (ASCII - 71)
bits = c - 71;
} else if (c >= '0' && c <= '9') {
// char ASCII value
// 0 48 52
// 9 57 61 (ASCII + 4)
bits = c + 4;
} else if (c == '+' || c == '-') {
bits = 62;
} else if (c == '/' || c == '_') {
bits = 63;
} else if (c == '\n' || c == '\r' || c == ' ' || c == '\t') {
continue;
} else {
return null;
}
// Append this char's 6 bits to the word.
word = (word << 6) | (byte) bits;
// For every 4 chars of input, we accumulate 24 bits of output. Emit 3 bytes.
inCount++;
if (inCount % 4 == 0) {
out[outCount++] = (byte) (word >> 16);
out[outCount++] = (byte) (word >> 8);
out[outCount++] = (byte) word;
}
}
int lastWordChars = inCount % 4;
if (lastWordChars == 1) {
// We read 1 char followed by "===". But 6 bits is a truncated byte! Fail.
return null;
} else if (lastWordChars == 2) {
// We read 2 chars followed by "==". Emit 1 byte with 8 of those 12 bits.
word = word << 12;
out[outCount++] = (byte) (word >> 16);
} else if (lastWordChars == 3) {
// We read 3 chars, followed by "=". Emit 2 bytes for 16 of those 18 bits.
word = word << 6;
out[outCount++] = (byte) (word >> 16);
out[outCount++] = (byte) (word >> 8);
}
// If we sized our out array perfectly, we're done.
if (outCount == out.length) return out;
// Copy the decoded bytes to a new, right-sized array.
byte[] prefix = new byte[outCount];
System.arraycopy(out, 0, prefix, 0, outCount);
return prefix;
}
private static final byte[] MAP = new byte[] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '+', '/'
};
private static final byte[] URL_MAP = new byte[] {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '-', '_'
};
public static String encode(byte[] in) {
return encode(in, MAP);
}
public static String encodeUrl(byte[] in) {
return encode(in, URL_MAP);
}
private static String encode(byte[] in, byte[] map) {
int length = (in.length + 2) / 3 * 4;
byte[] out = new byte[length];
int index = 0, end = in.length - in.length % 3;
for (int i = 0; i < end; i += 3) {
out[index++] = map[(in[i] & 0xff) >> 2];
out[index++] = map[((in[i] & 0x03) << 4) | ((in[i + 1] & 0xff) >> 4)];
out[index++] = map[((in[i + 1] & 0x0f) << 2) | ((in[i + 2] & 0xff) >> 6)];
out[index++] = map[(in[i + 2] & 0x3f)];
}
switch (in.length % 3) {
case 1:
out[index++] = map[(in[end] & 0xff) >> 2];
out[index++] = map[(in[end] & 0x03) << 4];
out[index++] = '=';
out[index++] = '=';
break;
case 2:
out[index++] = map[(in[end] & 0xff) >> 2];
out[index++] = map[((in[end] & 0x03) << 4) | ((in[end + 1] & 0xff) >> 4)];
out[index++] = map[((in[end + 1] & 0x0f) << 2)];
out[index++] = '=';
break;
}
try {
return new String(out, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2014 Square, Inc.
*
* 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 external.okio;
import java.io.IOException;
import java.util.zip.Deflater;
import static external.okio.Util.checkOffsetAndCount;
/**
* A sink that uses <a href="http://tools.ietf.org/html/rfc1951">DEFLATE</a> to
* compress data written to another source.
*
* <h3>Sync flush</h3>
* Aggressive flushing of this stream may result in reduced compression. Each
* call to {@link #flush} immediately compresses all currently-buffered data;
* this early compression may be less effective than compression performed
* without flushing.
*
* <p>This is equivalent to using {@link Deflater} with the sync flush option.
* This class does not offer any partial flush mechanism. For best performance,
* only call {@link #flush} when application behavior requires it.
*/
public final class DeflaterSink implements Sink {
private final BufferedSink sink;
private final Deflater deflater;
private boolean closed;
public DeflaterSink(Sink sink, Deflater deflater) {
this(Okio.buffer(sink), deflater);
}
/**
* This package-private constructor shares a buffer with its trusted caller.
* In general we can't share a BufferedSource because the deflater holds input
* bytes until they are inflated.
*/
DeflaterSink(BufferedSink sink, Deflater deflater) {
if (sink == null) throw new IllegalArgumentException("source == null");
if (deflater == null) throw new IllegalArgumentException("inflater == null");
this.sink = sink;
this.deflater = deflater;
}
@Override public void write(Buffer source, long byteCount) throws IOException {
checkOffsetAndCount(source.size, 0, byteCount);
while (byteCount > 0) {
// Share bytes from the head segment of 'source' with the deflater.
Segment head = source.head;
int toDeflate = (int) Math.min(byteCount, head.limit - head.pos);
deflater.setInput(head.data, head.pos, toDeflate);
// Deflate those bytes into sink.
deflate(false);
// Mark those bytes as read.
source.size -= toDeflate;
head.pos += toDeflate;
if (head.pos == head.limit) {
source.head = head.pop();
SegmentPool.recycle(head);
}
byteCount -= toDeflate;
}
}
private void deflate(boolean syncFlush) throws IOException {
Buffer buffer = sink.buffer();
while (true) {
Segment s = buffer.writableSegment(1);
// The 4-parameter overload of deflate() doesn't exist in the RI until
// Java 1.7, and is public (although with @hide) on Android since 2.3.
// The @hide tag means that this code won't compile against the Android
// 2.3 SDK, but it will run fine there.
int deflated = syncFlush
? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
: deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit);
if (deflated > 0) {
s.limit += deflated;
buffer.size += deflated;
sink.emitCompleteSegments();
} else if (deflater.needsInput()) {
if (s.pos == s.limit) {
// We allocated a tail segment, but didn't end up needing it. Recycle!
buffer.head = s.pop();
SegmentPool.recycle(s);
}
return;
}
}
}
@Override public void flush() throws IOException {
deflate(true);
sink.flush();
}
void finishDeflate() throws IOException {
deflater.finish();
deflate(false);
}
@Override public void close() throws IOException {
if (closed) return;
// Emit deflated data to the underlying sink. If this fails, we still need
// to close the deflater and the sink; otherwise we risk leaking resources.
Throwable thrown = null;
try {
finishDeflate();
} catch (Throwable e) {
thrown = e;
}
try {
deflater.end();
} catch (Throwable e) {
if (thrown == null) thrown = e;
}
try {
sink.close();
} catch (Throwable e) {
if (thrown == null) thrown = e;
}
closed = true;
if (thrown != null) Util.sneakyRethrow(thrown);
}
@Override public Timeout timeout() {
return sink.timeout();
}
@Override public String toString() {
return "DeflaterSink(" + sink + ")";
}
}
/*
* Copyright (C) 2014 Square, Inc.
*
* 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 external.okio;
import java.io.IOException;
/** A {@link Sink} which forwards calls to another. Useful for subclassing. */
public abstract class ForwardingSink implements Sink {
private final Sink delegate;
public ForwardingSink(Sink delegate) {
if (delegate == null) throw new IllegalArgumentException("delegate == null");
this.delegate = delegate;
}
/** {@link Sink} to which this instance is delegating. */
public final Sink delegate() {
return delegate;
}
@Override public void write(Buffer source, long byteCount) throws IOException {
delegate.write(source, byteCount);
}
@Override public void flush() throws IOException {
delegate.flush();
}
@Override public Timeout timeout() {
return delegate.timeout();
}
@Override public void close() throws IOException {
delegate.close();
}
@Override public String toString() {
return getClass().getSimpleName() + "(" + delegate.toString() + ")";
}
}
/*
* Copyright (C) 2014 Square, Inc.
*
* 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 external.okio;
import java.io.IOException;
/** A {@link Source} which forwards calls to another. Useful for subclassing. */
public abstract class ForwardingSource implements Source {
private final Source delegate;
public ForwardingSource(Source delegate) {
if (delegate == null) throw new IllegalArgumentException("delegate == null");
this.delegate = delegate;
}
/** {@link Source} to which this instance is delegating. */
public final Source delegate() {
return delegate;
}
@Override public long read(Buffer sink, long byteCount) throws IOException {
return delegate.read(sink, byteCount);
}
@Override public Timeout timeout() {
return delegate.timeout();
}
@Override public void close() throws IOException {
delegate.close();
}
@Override public String toString() {
return getClass().getSimpleName() + "(" + delegate.toString() + ")";
}
}
/*
* Copyright (C) 2015 Square, Inc.
*
* 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 external.okio;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/** A {@link Timeout} which forwards calls to another. Useful for subclassing. */
public class ForwardingTimeout extends Timeout {
private Timeout delegate;
public ForwardingTimeout(Timeout delegate) {
if (delegate == null) throw new IllegalArgumentException("delegate == null");
this.delegate = delegate;
}
/** {@link Timeout} instance to which this instance is currently delegating. */
public final Timeout delegate() {
return delegate;
}
public final ForwardingTimeout setDelegate(Timeout delegate) {
if (delegate == null) throw new IllegalArgumentException("delegate == null");
this.delegate = delegate;
return this;
}
@Override public Timeout timeout(long timeout, TimeUnit unit) {
return delegate.timeout(timeout, unit);
}
@Override public long timeoutNanos() {
return delegate.timeoutNanos();
}
@Override public boolean hasDeadline() {
return delegate.hasDeadline();
}
@Override public long deadlineNanoTime() {
return delegate.deadlineNanoTime();
}
@Override public Timeout deadlineNanoTime(long deadlineNanoTime) {
return delegate.deadlineNanoTime(deadlineNanoTime);
}
@Override public Timeout clearTimeout() {
return delegate.clearTimeout();
}
@Override public Timeout clearDeadline() {
return delegate.clearDeadline();
}
@Override public void throwIfReached() throws IOException {
delegate.throwIfReached();
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2018 Square, Inc.
*
* 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 external.okio;
import java.io.IOException;
/**
* A {@link Source} which peeks into an upstream {@link BufferedSource} and allows reading and
* expanding of the buffered data without consuming it. Does this by requesting additional data from
* the upstream source if needed and copying out of the internal buffer of the upstream source if
* possible.
*
* <p>This source also maintains a snapshot of the starting location of the upstream buffer which it
* validates against on every read. If the upstream buffer is read from, this source will become
* invalid and throw {@link IllegalStateException} on any future reads.
*/
final class PeekSource implements Source {
private final BufferedSource upstream;
private final Buffer buffer;
private Segment expectedSegment;
private int expectedPos;
private boolean closed;
private long pos;
PeekSource(BufferedSource upstream) {
this.upstream = upstream;
this.buffer = upstream.buffer();
this.expectedSegment = buffer.head;
this.expectedPos = expectedSegment != null ? expectedSegment.pos : -1;
}
@Override public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
// Source becomes invalid if there is an expected Segment and it and the expected position
// do not match the current head and head position of the upstream buffer
if (expectedSegment != null
&& (expectedSegment != buffer.head || expectedPos != buffer.head.pos)) {
throw new IllegalStateException("Peek source is invalid because upstream source was used");
}
if (byteCount == 0L) return 0L;
if (!upstream.request(pos + 1)) return -1L;
if (expectedSegment == null && buffer.head != null) {
// Only once the buffer actually holds data should an expected Segment and position be
// recorded. This allows reads from the peek source to repeatedly return -1 and for data to be
// added later. Unit tests depend on this behavior.
expectedSegment = buffer.head;
expectedPos = buffer.head.pos;
}
long toCopy = Math.min(byteCount, buffer.size - pos);
buffer.copyTo(sink, pos, toCopy);
pos += toCopy;
return toCopy;
}
@Override public Timeout timeout() {
return upstream.timeout();
}
@Override public void close() throws IOException {
closed = true;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment