runningAppProcessInfoList = activityManager.getRunningAppProcesses();
+ if (runningAppProcessInfoList == null) {
+ return false;
+ }
+ for (ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {
+ if (processInfo.processName.equals(context.getPackageName())
+ && processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 判断手机是否在充电
+ */
+ public boolean isCharging(Context context) {
+ IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ Intent batteryStatus = context.registerReceiver(null, ifilter);
+ int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+ boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
+ status == BatteryManager.BATTERY_STATUS_FULL;
+
+ return isCharging;
+
+ }
+
+ /**
+ * 判断屏幕是否亮起
+ *
+ * @param context
+ * @return
+ */
+ public boolean isBright(Context context) {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ return pm.isInteractive();
+ }
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/DeviceStateListener.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/DeviceStateListener.java
new file mode 100644
index 000000000..d4bc98dd4
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/DeviceStateListener.java
@@ -0,0 +1,127 @@
+package com.example.androidpowercomsumption.utils.state;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PowerManager;
+
+public class DeviceStateListener {
+ private Context mContext;
+ private ScreenBroadcastReceiver mScreenBroadcastReceiver;
+ private ScreenStateListener mScreenStateListener;
+
+ public DeviceStateListener(Context context) {
+ mContext = context;
+ mScreenBroadcastReceiver = new ScreenBroadcastReceiver();
+ }
+
+ /**
+ * 设备屏幕状态广播接收者
+ */
+ private class ScreenBroadcastReceiver extends BroadcastReceiver {
+ private String action = null;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ action = intent.getAction();
+ if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ /**
+ * 屏幕亮
+ */
+ mScreenStateListener.onScreenOn();
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ /**
+ * 屏幕锁定
+ */
+ mScreenStateListener.onScreenOff();
+ } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
+ /**
+ * 屏幕解锁了且可以使用
+ */
+ mScreenStateListener.onUserPresent();
+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
+ // 开始充电
+ mScreenStateListener.onPowerConnected();
+ } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ // 停止充电
+ mScreenStateListener.onPowerDisconnected();
+ }
+ }
+ }
+
+ /**
+ * 开始监听屏幕开/关状态
+ *
+ * @param listener
+ */
+ public void register(ScreenStateListener listener) {
+ mScreenStateListener = listener;
+
+ /**
+ * 注册屏幕设备开屏/锁屏的状态监听
+ */
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+ mContext.registerReceiver(mScreenBroadcastReceiver, filter);
+
+ initScreenState();
+ }
+
+
+ /**
+ * 代码启动阶段获取设备屏幕初始状态
+ */
+ private void initScreenState() {
+ PowerManager manager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+ if (manager.isInteractive()) {
+ if (mScreenStateListener != null) {
+ mScreenStateListener.onScreenOn();
+ }
+ } else {
+ if (mScreenStateListener != null) {
+ mScreenStateListener.onScreenOff();
+ }
+ }
+ }
+
+
+ /**
+ * 注销屏幕设备开屏/锁屏的状态监听
+ */
+ public void unregister() {
+ mContext.unregisterReceiver(mScreenBroadcastReceiver);
+ mScreenBroadcastReceiver = null;
+ mScreenStateListener = null;
+ }
+
+
+ public interface ScreenStateListener {
+ /**
+ * 此时屏幕已经点亮,但可能是在锁屏状态
+ * 比如用户之前锁定了屏幕,按了电源键启动屏幕,则回调此方法
+ */
+ void onScreenOn();
+
+ /**
+ * 屏幕被锁定
+ */
+ void onScreenOff();
+
+ /**
+ * 屏幕解锁且可以正常使用
+ */
+ void onUserPresent();
+
+ // 充电
+ void onPowerConnected();
+
+ // 停止充电
+ void onPowerDisconnected();
+ }
+}
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcState.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcState.java
new file mode 100644
index 000000000..662e434ba
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcState.java
@@ -0,0 +1,101 @@
+package com.example.androidpowercomsumption.utils.state;
+
+public class ProcState {
+ public int id;
+ public String comm = "";
+ public String stat = "";
+
+ public long utime = -1;
+ public long stime = -1;
+
+ public long cutime = -1;
+
+ public long cstime = -1;
+
+ public int numThreads = 0; //进程下线程的数量
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getNumThreads() {
+ return numThreads;
+ }
+
+ public void setNumThreads(int numThreads) {
+ this.numThreads = numThreads;
+ }
+
+
+ public String getStat() {
+ return stat;
+ }
+
+ public void setStat(String stat) {
+ this.stat = stat;
+ }
+
+ public long getStime() {
+ return stime;
+ }
+
+ public void setStime(long stime) {
+ this.stime = stime;
+ }
+
+ public long getCstime() {
+ return cstime;
+ }
+
+ public void setCstime(long cstime) {
+ this.cstime = cstime;
+ }
+
+ public long getUtime() {
+ return utime;
+ }
+
+ public void setUtime(long utime) {
+ this.utime = utime;
+ }
+
+
+ public long getJiffies() {
+ return utime + stime + cutime + cstime;
+ }
+
+ public String getComm() {
+ return comm;
+ }
+
+ public void setComm(String comm) {
+ this.comm = comm;
+ }
+
+ public long getCutime() {
+ return cutime;
+ }
+
+
+ public void setCutime(long cutime) {
+ this.cutime = cutime;
+ }
+
+ @Override
+ public String toString() {
+ return "ProcState{" +
+ "id=" + id +
+ ", comm='" + comm + '\'' +
+ ", stat='" + stat + '\'' +
+ ", utime=" + utime +
+ ", stime=" + stime +
+ ", cutime=" + cutime +
+ ", cstime=" + cstime +
+ ", numThreads=" + numThreads +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcStateUtil.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcStateUtil.java
new file mode 100644
index 000000000..d470056c2
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/state/ProcStateUtil.java
@@ -0,0 +1,160 @@
+package com.example.androidpowercomsumption.utils.state;
+
+import android.os.Process;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * app 进程即线程状态,通过读 /proc/pid/stat 和 /proc/pid/task/tid/stat 文件里面的信息,
+ * 主要是得到 app 运行的进程、线程名,app 运行时和 app 内线程的用户时间、系统时间、等待的系统时间和用户时间
+ *
+ * /proc/pid/stat:
+ * 4218 (owercomsumption) R 301 301 0 0 -1 4194624 8707 0 0 0 32 34 0 0 10
+ * -10 18 0 195290 1306812416 30324 18446744073709551615 1504755712 1504782416
+ * 4289779952 0 0 0 4612 1 1073775864 0 0 0 17 1 0 0 0 0 0 1504791360 1504791360
+ * 1522069504 4289787537 4289787613 4289787613 4289789920 0
+ * 字段:
+ * - pid: 进程ID.
+ * - comm: task_struct结构体的进程名
+ * - state: 进程状态, 此处为R
+ * - ppid: 父进程ID (父进程是指通过fork方式, 通过clone并非父进程)
+ * - pgrp: 进程组ID
+ * - session: 进程会话组ID
+ * - tty_nr: 当前进程的tty终点设备号
+ * - tpgid: 控制进程终端的前台进程号
+ * - flags: 进程标识位, 定义在include/linux/sched.h中的PF_*, 此处等于1077952832
+ * - minflt: 次要缺页中断的次数, 即无需从磁盘加载内存页. 比如COW和匿名页
+ * - cminflt: 当前进程等待子进程的minflt
+ * - majflt: 主要缺页中断的次数, 需要从磁盘加载内存页. 比如map文件
+ * - majflt: 当前进程等待子进程的majflt
+ * - utime: 该进程处于用户态的时间, 单位jiffies, 此处等于166114
+ * - stime: 该进程处于内核态的时间, 单位jiffies, 此处等于129684
+ * - cutime: 当前进程等待子进程的utime
+ * - cstime: 当前进程等待子进程的utime
+ * - priority: 进程优先级, 此次等于10.
+ * - nice: nice值, 取值范围[19, -20], 此处等于-10
+ * - num_threads: 线程个数, 此处等于221
+ * - itrealvalue: 该字段已废弃, 恒等于0
+ * - starttime: 自系统启动后的进程创建时间, 单位jiffies, 此处等于2284
+ * - vsize: 进程的虚拟内存大小, 单位为bytes
+ * - rss: 进程独占内存+共享库, 单位pages, 此处等于93087
+ * - rsslim: rss大小上限
+ *
+ * 说明:
+ * 第10~17行主要是随着时间而改变的量;
+ * 内核时间单位, sysconf(_SC_CLK_TCK)一般地定义为jiffies(一般地等于10ms)
+ * starttime: 此值单位为jiffies, 结合/proc/stat的btime, 可知道每一个线程启动的时间点
+ * 1500827856 + 2284/100 = 1500827856, 转换成北京时间为2017/7/24 0:37:58
+ * 第四行数据很少使用,只说一下该行第7至9个数的含义:
+ * signal: 即将要处理的信号, 十进制, 此处等于6660
+ * blocked: 阻塞的信号, 十进制
+ * sigignore: 被忽略的信号, 十进制, 此处等于36088
+ */
+public class ProcStateUtil {
+ private static final String TAG = "AppStateApplication";
+
+ /**
+ * 根据pid/tid得到对应文件中的信息
+ *
+ * @param pid 进程号
+ * @param tid 线程号,等于-1即为不存在
+ * @return
+ */
+ public ProcState splicePath(int pid, int tid) {
+ if (tid == -1) {
+ return getInfoFromProcFile("/proc/" + pid + "/stat"); // 查看进程详细信息
+ } else {
+ return getInfoFromProcFile("/proc/" + pid + "/task/" + tid + "/stat"); // 查看线程详细信息
+ }
+ }
+
+ /**
+ * 读取 /proc/pid/stat 和 /proc/pid/task/tid/stat 文件里面的信息,
+ *
+ * @param path
+ */
+ public ProcState getInfoFromProcFile(String path) {
+ ProcState procState = new ProcState();
+ try {
+ RandomAccessFile reader = new RandomAccessFile(path, "r");
+ String procStr = reader.readLine();
+ // 找出线程的名称,需要特殊处理
+ int beginIndex = procStr.indexOf("(");
+ int lastIndex = procStr.indexOf(")");
+ String comm = procStr.substring(beginIndex + 1, lastIndex);
+ procStr = procStr.substring(0, beginIndex) + procStr.substring(lastIndex + 2);
+// Log.d(TAG, procStr);
+ String[] procStateInfo = procStr.split(" ");
+// Log.d(TAG, String.valueOf(procStateInfo.length));
+// for(String str:procStateInfo){
+// Log.d(TAG, str);
+// }
+ procState.setId(Integer.parseInt(procStateInfo[0]));
+ procState.setComm(comm);
+ procState.setStat(procStateInfo[1]);
+ procState.setUtime(Long.parseLong(procStateInfo[12]));
+ procState.setStime(Long.parseLong(procStateInfo[13]));
+ procState.setCutime(Long.parseLong(procStateInfo[14]));
+ procState.setCstime(Long.parseLong(procStateInfo[15]));
+ procState.setNumThreads(Integer.parseInt(procStateInfo[18]));
+ Log.d(TAG, procState.toString());
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return procState;
+
+ }
+
+ /**
+ * 获取进程下所有线程的线程信息
+ */
+ public List getAllThreadInfo() {
+ String rootPath = "/proc/" + Process.myPid() + "/task/";
+ List threadList = new ArrayList<>();
+ File taskDir = new File(rootPath);
+ try {
+ if (taskDir.isDirectory()) {
+ File[] subDirs = taskDir.listFiles();
+
+ for (File file : subDirs) {
+ if (!file.isDirectory()) {
+ continue;
+ }
+ try {
+ ProcState threadState = splicePath(Process.myPid(), Integer.parseInt(file.getName()));
+ threadList.add(threadState);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return threadList;
+ }
+
+ // 获得CPU的运行时间
+ public long getCPUStatus() {
+ try {
+ RandomAccessFile reader = new RandomAccessFile("/proc/stat/", "r");
+ String procStr = reader.readLine();
+ String[] cpuInfo = procStr.split(" ");
+ long cpuTime = 0;
+ for (int i = 1; i <= 7; i++) {
+ cpuTime += Long.parseLong(cpuInfo[i]);
+ }
+ reader.close();
+ return cpuTime;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return 0;
+ }
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/SimulateSystemService.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/SimulateSystemService.java
new file mode 100644
index 000000000..03dc4f047
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/SimulateSystemService.java
@@ -0,0 +1,129 @@
+package com.example.androidpowercomsumption.utils.systemservice;
+
+import android.annotation.SuppressLint;
+import android.app.*;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanResult;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
+import com.example.androidpowercomsumption.R;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class SimulateSystemService {
+ public static void wifi(Context context) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ wifiManager.startScan();
+ wifiManager.getScanResults();
+ }
+
+ @SuppressLint({"NewApi", "MissingPermission"})
+ public static void gps(Context mContext) {
+ final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters
+ final long MIN_TIME_BW_UPDATES = 1000 * 60; // 1 minute
+ LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ if (locationManager.isLocationEnabled()) {
+ if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+ if (ContextCompat.checkSelfPermission(mContext, "android.permission.ACCESS_FINE_LOCATION") == 1) {
+ locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+
+ }
+ }, Looper.getMainLooper());
+ }
+ }
+ }
+ }
+
+ public static void bluetooth(Context context) {
+ BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothAdapter adapter = bluetoothManager.getAdapter();
+
+ adapter.startDiscovery();
+ adapter.startLeScan(new BluetoothAdapter.LeScanCallback() {
+ @Override
+ public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
+ }
+ });
+
+ BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
+ scanner.startScan(new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ super.onScanResult(callbackType, result);
+ }
+ });
+
+ }
+
+
+ public static void alarm(Context context) {
+ final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent();
+ intent.setAction("ALARM_ACTION(" + 10000 + ")");
+ final AtomicReference operationRef = new AtomicReference<>();
+ intent.putExtra("extra_pid", 2233);
+ @SuppressLint("WrongConstant") PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 22, intent, 33);
+ operationRef.set(pendingIntent);
+
+ am.set(AlarmManager.RTC, System.currentTimeMillis(), pendingIntent);
+ }
+
+ public static void notify(Context context) {
+ final AtomicReference channelRef = new AtomicReference<>();
+ final AtomicReference notificationRef = new AtomicReference<>();
+
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ String channelId = "TEST_CHANNEL_ID";
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationChannel channel = new NotificationChannel(channelId, "TEST_CHANNEL_NAME", NotificationManager.IMPORTANCE_DEFAULT);
+ channelRef.set(channel);
+ manager.createNotificationChannel(channel);
+ }
+
+ Notification notification = new NotificationCompat.Builder(context, channelId)
+ .setContentTitle("NOTIFICATION_TILE")
+ .setContentText("NOTIFICATION_CONTENT")
+ .setAutoCancel(true)
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setWhen(System.currentTimeMillis())
+ .build();
+
+ notificationRef.set(notification);
+
+ notificationManager.notify(16657, notification);
+ }
+}
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/AlarmServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/AlarmServiceHooker.java
new file mode 100644
index 000000000..6bf357a2c
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/AlarmServiceHooker.java
@@ -0,0 +1,33 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+
+public class AlarmServiceHooker {
+ private static final String TAG = "ServiceController";
+
+ public int setTime = 0;
+
+
+ private ServiceHookCallback sHookCallback = new ServiceHookCallback() {
+ @Override
+ public void invoke(Method method, Object[] args) {
+ if (method.getName().equals("set")
+ || method.getName().equals("setRepeating") || method.getName().equals("setInexactRepeating")) {
+ setTime++;
+ Log.d(TAG, "AlarmServiceHooker:setTime++");
+ }
+ }
+
+ @Nullable
+ @Override
+ public Object intercept(Object receiver, Method method, Object[] args) throws Throwable {
+ return null;
+ }
+ };
+
+ public SystemServiceHooker sHookHelper = new SystemServiceHooker(Context.ALARM_SERVICE, "android.app.IAlarmManager", sHookCallback);
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/BluetoothServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/BluetoothServiceHooker.java
new file mode 100644
index 000000000..46746e3ca
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/BluetoothServiceHooker.java
@@ -0,0 +1,84 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.os.IBinder;
+import android.os.IInterface;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+public class BluetoothServiceHooker {
+ private static final String TAG = "ServiceController";
+
+ public int registerTime = 0;
+
+ public int discoveryTime = 0;
+
+ public int scanTime = 0;
+
+ private ServiceHookCallback sHookCallback = new ServiceHookCallback() {
+ @Override
+ public void invoke(Method method, Object[] args) {
+ }
+
+ @Nullable
+ @Override
+ public Object intercept(Object receiver, Method method, Object[] args) throws Throwable {
+ if ("registerAdapter".equals(method.getName())) {
+ Object blueTooth = method.invoke(receiver, args);
+ Object proxy = proxy(blueTooth, false);
+ return proxy == null ? blueTooth : proxy;
+ } else if ("getBluetoothGatt".equals(method.getName())) {
+ Object blueToothGatt = method.invoke(receiver, args);
+ Object proxy = proxy(blueToothGatt, true);
+ return proxy == null ? blueToothGatt : proxy;
+ }
+ return null;
+ }
+ };
+
+ public SystemServiceHooker sHookHelper = new SystemServiceHooker("bluetooth_manager", "android.bluetooth.IBluetoothManager", sHookCallback);
+
+ private Object proxy(Object delegate, boolean isGatt) {
+ try {
+ Class>[] interfaces;
+ if (!isGatt) {
+ interfaces = new Class>[]{IBinder.class, IInterface.class, Class.forName("android.bluetooth.IBluetooth")};
+ } else {
+ interfaces = new Class>[]{IBinder.class, IInterface.class, Class.forName("android.bluetooth.IBluetoothGatt")};
+ }
+ ClassLoader loader = delegate.getClass().getClassLoader();
+ InvocationHandler handler = (proxy, method, args) -> {
+ if (!isGatt)
+ proxyBluetooth(method);
+ else
+ proxyBluetoothGatt(method);
+ return method.invoke(delegate, args);
+ };
+ return Proxy.newProxyInstance(loader, interfaces, handler);
+ } catch (Throwable e) {
+ Log.d(TAG, "proxyBluetooth fail");
+ }
+ return null;
+ }
+
+ private void proxyBluetooth(Method method) {
+ if ("startDiscovery".equals(method.getName())) {
+ discoveryTime++;
+ Log.d(TAG, "BluetoothServiceHooker: discoveryTime++");
+ }
+ }
+
+ private void proxyBluetoothGatt(Method method) {
+ if ("registerScanner".equals(method.getName())) {
+ registerTime++;
+ Log.d(TAG, "BluetoothServiceHooker: registerTime++");
+ } else if ("startScan".equals(method.getName()) || "startScanForIntent".equals(method.getName())) {
+ scanTime++;
+ Log.d(TAG, "BluetoothServiceHooker: scanTime++");
+ }
+ }
+}
+
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/GPSServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/GPSServiceHooker.java
new file mode 100644
index 000000000..688890afe
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/GPSServiceHooker.java
@@ -0,0 +1,55 @@
+/*
+ * Tencent is pleased to support the open source community by making wechat-matrix available.
+ * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
+ * Licensed under the BSD 3-Clause License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+
+public class GPSServiceHooker {
+
+ private static final String TAG = "ServiceController";
+
+ public int scanTime = 0;
+
+ private ServiceHookCallback sHookCallback = new ServiceHookCallback() {
+ @Override
+ public void invoke(Method method, Object[] args) {
+ if ("requestLocationUpdates".equals(method.getName())) {
+ if (args != null) {
+ for (Object item : args) {
+ if (item != null && "android.location.LocationRequest".equals(item.getClass().getName())) {
+ scanTime++;
+ Log.d(TAG, "GPSServiceHooker: scanTime++");
+ }
+ }
+ }
+ }
+ }
+
+ @Nullable
+ @Override
+ public Object intercept(Object receiver, Method method, Object[] args) {
+ return null;
+ }
+ };
+
+ public SystemServiceHooker sHookHelper = new SystemServiceHooker(Context.LOCATION_SERVICE, "android.location.ILocationManager", sHookCallback);
+
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/NotificationServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/NotificationServiceHooker.java
new file mode 100644
index 000000000..8e7ee7827
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/NotificationServiceHooker.java
@@ -0,0 +1,37 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+
+public class NotificationServiceHooker {
+ private static final String TAG = "ServiceController";
+
+ public int createChannelTime = 0;
+
+ public int notifyTime = 0;
+
+ private ServiceHookCallback sHookCallback = new ServiceHookCallback() {
+ @Override
+ public void invoke(Method method, Object[] args) {
+ if ("createNotificationChannels".equals(method.getName())) {
+ createChannelTime++;
+ Log.d(TAG, "NotificationServiceHooker: createChannelTime++");
+ } else if ("enqueueNotificationWithTag".equals(method.getName())) {
+ notifyTime++;
+ Log.d(TAG, "NotificationServiceHooker: notifyTime++;");
+ }
+ }
+
+ @Nullable
+ @Override
+ public Object intercept(Object receiver, Method method, Object[] args) throws Throwable {
+ return null;
+ }
+ };
+
+ public SystemServiceHooker sHookHelper = new SystemServiceHooker(Context.NOTIFICATION_SERVICE, "android.app.INotificationManager", sHookCallback);
+
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/ServiceHookCallback.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/ServiceHookCallback.java
new file mode 100644
index 000000000..38b1e10e8
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/ServiceHookCallback.java
@@ -0,0 +1,12 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+
+public interface ServiceHookCallback {
+ void invoke(Method method, Object[] args);
+
+ @Nullable
+ Object intercept(Object receiver, Method method, Object[] args) throws Throwable;
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/SystemServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/SystemServiceHooker.java
new file mode 100644
index 000000000..76c078434
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/SystemServiceHooker.java
@@ -0,0 +1,123 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.os.IBinder;
+import android.os.IInterface;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Map;
+
+public class SystemServiceHooker {
+ private static final String TAG = "SystemServiceHooker";
+
+
+ private final String serviceName;
+ private final String serviceClass;
+ private final ServiceHookCallback hookCallback;
+
+ @Nullable
+ private IBinder baseServiceBinder;
+ @Nullable
+ private IBinder proxyServiceBinder;
+
+ public SystemServiceHooker(String serviceName, String serviceClass, ServiceHookCallback hookCallback) {
+ this.serviceName = serviceName;
+ this.serviceClass = serviceClass;
+ this.hookCallback = hookCallback;
+ }
+
+ public boolean doHook() {
+ try {
+ Class> serviceManager = Class.forName("android.os.ServiceManager");
+ Method getService = serviceManager.getDeclaredMethod("getService", String.class);
+ // hook服务的原始IBinder对象
+ this.baseServiceBinder = (IBinder) getService.invoke(null, serviceName);
+
+ this.proxyServiceBinder = (IBinder) Proxy.newProxyInstance(
+ serviceManager.getClassLoader(),
+ new Class>[]{IBinder.class},
+ new BinderProxyHandler(this.serviceClass, this.hookCallback, this.baseServiceBinder));
+
+
+ // 获取缓存池
+ Class> serviceManagerCls = Class.forName("android.os.ServiceManager");
+ Field cacheField = serviceManagerCls.getDeclaredField("sCache");
+ cacheField.setAccessible(true);
+ Map cache = (Map) cacheField.get(null);
+ cache.put(serviceName, this.proxyServiceBinder);
+
+ return true;
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public boolean doUnHook() {
+ try {
+ Class> serviceManager = Class.forName("android.os.ServiceManager");
+ Method method = serviceManager.getDeclaredMethod("getService", String.class);
+ IBinder currentBinder = (IBinder) method.invoke(null, serviceName);
+ if (currentBinder != proxyServiceBinder) return false;
+
+
+
+ Field cacheField = serviceManager.getDeclaredField("sCache");
+ cacheField.setAccessible(true);
+ Map cache = (Map) cacheField.get(null);
+ cache.put(serviceName, baseServiceBinder);
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+
+ static final class BinderProxyHandler implements InvocationHandler {
+
+ private final IBinder baseServiceBinder;
+
+ private final String serviceClassName;
+
+ private final ServiceHookCallback callback;
+
+ BinderProxyHandler(String serviceClassName, ServiceHookCallback callback, IBinder baseServiceBinder) throws Exception {
+ this.baseServiceBinder = baseServiceBinder;
+ this.serviceClassName = serviceClassName;
+ this.callback = callback;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if ("queryLocalInterface".equals(method.getName())) {
+ Class> serviceManagerStubCls = Class.forName(serviceClassName + "$Stub");
+ ClassLoader classLoader = serviceManagerStubCls.getClassLoader();
+ Method asInterfaceMethod = serviceManagerStubCls.getDeclaredMethod("asInterface", IBinder.class);
+
+ Class> serviceManagerCls = Class.forName(serviceClassName);
+ final Object originManagerService = asInterfaceMethod.invoke(null, baseServiceBinder);
+
+ return Proxy.newProxyInstance(classLoader,
+ new Class[]{IBinder.class, IInterface.class, serviceManagerCls},
+ (proxy1, method1, args1) -> {
+ if (callback != null) {
+ callback.invoke(method1, args1);
+ Object result = callback.intercept(originManagerService, method1, args1);
+ if (result != null) {
+ return result;
+ }
+ }
+ return method1.invoke(originManagerService, args1);
+ }
+ );
+ }
+ return method.invoke(baseServiceBinder, args);
+ }
+
+ }
+}
diff --git a/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/WifiServiceHooker.java b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/WifiServiceHooker.java
new file mode 100644
index 000000000..7762cb3c7
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/java/com/example/androidpowercomsumption/utils/systemservice/hooker/WifiServiceHooker.java
@@ -0,0 +1,44 @@
+package com.example.androidpowercomsumption.utils.systemservice.hooker;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Method;
+
+public class WifiServiceHooker {
+ private static final String TAG = "ServiceController";
+
+ public int scanTime;
+
+ public int getScanResultTime;
+
+ public ServiceHookCallback sHookCallback;
+
+ public SystemServiceHooker sHookHelper;
+
+ public WifiServiceHooker() {
+ scanTime = 0;
+ getScanResultTime = 0;
+ sHookCallback = new ServiceHookCallback() {
+ @Override
+ public void invoke(Method method, Object[] args) {
+ if ("startScan".equals(method.getName())) {
+ scanTime++;
+ Log.d(TAG, "WifiServiceHooker: scan++ ");
+ } else if ("getScanResults".equals(method.getName())) {
+ getScanResultTime++;
+ Log.d(TAG, "WifiServiceHooker: getScanResults++");
+ }
+ }
+
+ @Nullable
+ @Override
+ public Object intercept(Object receiver, Method method, Object[] args) throws Throwable {
+ return null;
+ }
+ };
+
+ sHookHelper = new SystemServiceHooker(Context.WIFI_SERVICE, "android.net.wifi.IWifiManager", sHookCallback);
+ }
+}
diff --git a/Android/dokit-powerdetection/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Android/dokit-powerdetection/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 000000000..1ee14938e
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher.png b/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher.png
new file mode 100644
index 000000000..dd9bf45d4
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher.png differ
diff --git a/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher_background.xml b/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 000000000..956b344de
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Android/dokit-powerdetection/src/main/res/layout/activity_main.xml b/Android/dokit-powerdetection/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..7790dce8d
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/layout/activity_main.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/Android/dokit-powerdetection/src/main/res/menu/menu_main.xml b/Android/dokit-powerdetection/src/main/res/menu/menu_main.xml
new file mode 100644
index 000000000..a2c54a537
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/menu/menu_main.xml
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000..bbd3e0212
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 000000000..bbd3e0212
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher.webp b/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 000000000..c209e78ec
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..b2dfe3d1b
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher.webp b/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 000000000..4f0f1d64e
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..62b611da0
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 000000000..948a3070f
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..1b9a6956b
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..28d4b77f9
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9287f5083
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 000000000..aa7d6427e
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 000000000..9126ae37c
Binary files /dev/null and b/Android/dokit-powerdetection/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/Android/dokit-powerdetection/src/main/res/navigation/nav_graph.xml b/Android/dokit-powerdetection/src/main/res/navigation/nav_graph.xml
new file mode 100644
index 000000000..4057f43fa
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/navigation/nav_graph.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values-land/dimens.xml b/Android/dokit-powerdetection/src/main/res/values-land/dimens.xml
new file mode 100644
index 000000000..22d7f0043
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values-land/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values-night/themes.xml b/Android/dokit-powerdetection/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..2f6d01b6d
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values-w1240dp/dimens.xml b/Android/dokit-powerdetection/src/main/res/values-w1240dp/dimens.xml
new file mode 100644
index 000000000..d73f4a359
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values-w1240dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 200dp
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values-w600dp/dimens.xml b/Android/dokit-powerdetection/src/main/res/values-w600dp/dimens.xml
new file mode 100644
index 000000000..22d7f0043
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values-w600dp/dimens.xml
@@ -0,0 +1,3 @@
+
+ 48dp
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values/colors.xml b/Android/dokit-powerdetection/src/main/res/values/colors.xml
new file mode 100644
index 000000000..f8c6127d3
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values/dimens.xml b/Android/dokit-powerdetection/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..125df8711
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+ 16dp
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values/strings.xml b/Android/dokit-powerdetection/src/main/res/values/strings.xml
new file mode 100644
index 000000000..5769f4723
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values/strings.xml
@@ -0,0 +1,15 @@
+
+ androidPowerComsumption
+ Settings
+
+ First Fragment
+ Second Fragment
+ Next
+ Previous
+
+ Hello first fragment
+ Hello second fragment. Arg: %1$s
+ 请输入端口
+ 请输入配对码
+ submit
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/main/res/values/themes.xml b/Android/dokit-powerdetection/src/main/res/values/themes.xml
new file mode 100644
index 000000000..746d42212
--- /dev/null
+++ b/Android/dokit-powerdetection/src/main/res/values/themes.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/ExampleUnitTest.java b/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/ExampleUnitTest.java
new file mode 100644
index 000000000..9963af6ff
--- /dev/null
+++ b/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.androidpowercomsumption;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/TestUtils.java b/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/TestUtils.java
new file mode 100644
index 000000000..298ce5256
--- /dev/null
+++ b/Android/dokit-powerdetection/src/test/java/com/example/androidpowercomsumption/TestUtils.java
@@ -0,0 +1,27 @@
+package com.example.androidpowercomsumption;
+
+import android.text.TextUtils;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+/**
+ * @author Kaede
+ * @since 2020/11/16
+ */
+public class TestUtils {
+ public static boolean isAssembleTest() {
+ StackTraceElement[] ste = Thread.currentThread().getStackTrace();
+ String outerMethodName = ste[2 + 1].getMethodName();
+ return !String.valueOf(InstrumentationRegistry.getArguments().get("class")).endsWith("#" + outerMethodName);
+ }
+
+ public static boolean isLaunchingFrom(String classReference) {
+ String testEntry = String.valueOf(InstrumentationRegistry.getArguments().get("class"));
+ if (!TextUtils.isEmpty(testEntry))
+ if (testEntry.contains("#")) {
+ return testEntry.substring(0, testEntry.lastIndexOf("#")).equals(classReference);
+ } else {
+ return testEntry.equals(classReference);
+ }
+ return false;
+ }
+}
diff --git a/Android/settings.gradle b/Android/settings.gradle
index 78543fa87..643ee7d83 100644
--- a/Android/settings.gradle
+++ b/Android/settings.gradle
@@ -14,3 +14,4 @@ include ':dokit-pthread-hook'
include ':dokit-autotest'
include ':dokit-test'
include ':dokit-gps-mock'
+include ':dokit-powerdetection'