refactor(accessibility): 重构无障碍服务辅助类并优化微信拨号逻辑
- 新增 AccessibilityServiceHelper 类,提供无障碍服务启用状态检查、列表获取和状态设置功能 - 优化 DialerAccessibilityService 中的点击操作,移除模拟点击参数,统一使用节点点击方式 - 简化微信自动接听逻辑,合并多个相似方法为 findWithScroll 统一处理滚动查找 - 移除不再使用的回调接口和常量,精简代码结构 - 修复 MainActivity 中页面跳转逻辑,正确区分应用前后台切换状态 - 在设置页面集成无障碍服务开关控制,支持调试模式下直接启用服务 - 添加输入法隐藏功能,兼容不同安卓版本的键盘管理机制 - 优化节点查找算法,提升微信联系人搜索和拨号的稳定性
This commit is contained in:
@@ -6,10 +6,13 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
|
||||||
import com.ttstd.dialer.utils.Logger;
|
import com.ttstd.dialer.utils.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AccessibilityServiceHelper {
|
public class AccessibilityServiceHelper {
|
||||||
@@ -44,6 +47,38 @@ public class AccessibilityServiceHelper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isAdbAccessibilityServiceEnabled(Context context, Class<?> serviceClass) {
|
||||||
|
List<String> enabledServices = getEnabledAccessibilityServicesList(context);
|
||||||
|
String expectedServiceId = new ComponentName(context, serviceClass).flattenToShortString();
|
||||||
|
return enabledServices.contains(expectedServiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getEnabledAccessibilityServicesList(Context context) {
|
||||||
|
String enabledAccessibilityServices = Settings.Secure.getString(context.getContentResolver(), "enabled_accessibility_services");
|
||||||
|
if (enabledAccessibilityServices != null) {
|
||||||
|
return new ArrayList<>(Arrays.asList(enabledAccessibilityServices.split(":")));
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setAccessibilityServiceStatus(Context context, Class<?> serviceClass, boolean enable) {
|
||||||
|
List<String> enabledServices = getEnabledAccessibilityServicesList(context);
|
||||||
|
String expectedServiceId = new ComponentName(context, serviceClass).flattenToShortString();
|
||||||
|
if (enable) {
|
||||||
|
if (!enabledServices.contains(expectedServiceId)) {
|
||||||
|
enabledServices.add(expectedServiceId);
|
||||||
|
return Settings.Secure.putString(context.getContentResolver(), "enabled_accessibility_services", TextUtils.join(":", enabledServices));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (enabledServices.contains(expectedServiceId)) {
|
||||||
|
enabledServices.remove(expectedServiceId);
|
||||||
|
return Settings.Secure.putString(context.getContentResolver(), "enabled_accessibility_services", TextUtils.join(":", enabledServices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isAdbAccessibilityServiceEnabled(context, serviceClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跳转到系统的无障碍服务设置页面
|
* 跳转到系统的无障碍服务设置页面
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,14 +13,19 @@ import android.graphics.Rect;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.accessibility.AccessibilityWindowInfo;
|
import android.view.accessibility.AccessibilityWindowInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import com.blankj.utilcode.util.ToastUtils;
|
import com.blankj.utilcode.util.ToastUtils;
|
||||||
import com.kongzue.dialogx.dialogs.PopTip;
|
import com.kongzue.dialogx.dialogs.PopTip;
|
||||||
@@ -31,13 +36,12 @@ import com.ttstd.dialer.utils.Logger;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class DialerAccessibilityService extends AccessibilityService {
|
public class DialerAccessibilityService extends AccessibilityService {
|
||||||
private static final String TAG = "AccessibilityService";
|
private static final String TAG = "AccessibilityService";
|
||||||
|
|
||||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
private final MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
public static final int ACTION_VIDEO = 1;
|
public static final int ACTION_VIDEO = 1;
|
||||||
public static final int ACTION_AUDIO = 2;
|
public static final int ACTION_AUDIO = 2;
|
||||||
@@ -49,7 +53,6 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private static final String CONTACT_TEXT = "通讯录";
|
private static final String CONTACT_TEXT = "通讯录";
|
||||||
private static final String SEARCH_TEXT = "搜索";
|
private static final String SEARCH_TEXT = "搜索";
|
||||||
private static final String TAG_TEXT = "标签";
|
private static final String TAG_TEXT = "标签";
|
||||||
private static final String MORE_NAME = "更多功能按钮,已折叠";
|
|
||||||
private static final String PARENT_VIDEO_TEXT = "视频通话";
|
private static final String PARENT_VIDEO_TEXT = "视频通话";
|
||||||
|
|
||||||
private static final String VIDEO_TEXT = "视频通话";
|
private static final String VIDEO_TEXT = "视频通话";
|
||||||
@@ -68,6 +71,16 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private static final int MSG_DELAY_CLICK = 1002;
|
private static final int MSG_DELAY_CLICK = 1002;
|
||||||
private static final int MSG_RETRY_STEP = 1003;
|
private static final int MSG_RETRY_STEP = 1003;
|
||||||
|
|
||||||
|
private static final int MAX_FIND_COUNT = 5;
|
||||||
|
|
||||||
|
public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action";
|
||||||
|
public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action";
|
||||||
|
public static final String HIDE_IME_ACTION = "hide_ime_action";
|
||||||
|
|
||||||
|
//聊天页面的+号
|
||||||
|
|
||||||
|
private static final String MORE_FUNCTION_BUTTON_ID = "com.tencent.mm:id/bjz";
|
||||||
|
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
private Step mCurrentStep = Step.WAITING;
|
private Step mCurrentStep = Step.WAITING;
|
||||||
private String mName = "";
|
private String mName = "";
|
||||||
@@ -79,15 +92,9 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private ContactInfo mContactInfo;
|
private ContactInfo mContactInfo;
|
||||||
private int mCallType = ACTION_VIDEO;
|
private int mCallType = ACTION_VIDEO;
|
||||||
private int mFindCount = 0;
|
private int mFindCount = 0;
|
||||||
private static final int MAX_FIND_COUNT = 5;
|
|
||||||
|
|
||||||
private final Random mRandom = new Random();
|
private final Random mRandom = new Random();
|
||||||
|
private SettingReceiver mSettingReceiver;
|
||||||
public interface AccessibilityEventCallback {
|
|
||||||
void onAccessibilityEventCallback(AccessibilityEvent accessibilityEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AccessibilityEventCallback mAccessibilityEventCallback;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
@@ -97,7 +104,6 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
registerSettingReceiver();
|
registerSettingReceiver();
|
||||||
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
|
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
|
||||||
mAutoHandsFree = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_HNADS_FREE, false);
|
mAutoHandsFree = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_HNADS_FREE, false);
|
||||||
analysisAccessibilityEvent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initHandler() {
|
private void initHandler() {
|
||||||
@@ -110,7 +116,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
break;
|
break;
|
||||||
case MSG_DELAY_CLICK:
|
case MSG_DELAY_CLICK:
|
||||||
ClickInfo clickInfo = (ClickInfo) msg.obj;
|
ClickInfo clickInfo = (ClickInfo) msg.obj;
|
||||||
performClick(clickInfo.node, clickInfo.simulate);
|
performClick(clickInfo.node);
|
||||||
if (clickInfo.nextStep != null) {
|
if (clickInfo.nextStep != null) {
|
||||||
mCurrentStep = clickInfo.nextStep;
|
mCurrentStep = clickInfo.nextStep;
|
||||||
sendProcessStepMessage(DEFAULT_DELAY);
|
sendProcessStepMessage(DEFAULT_DELAY);
|
||||||
@@ -122,8 +128,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
processCurrentStep();
|
processCurrentStep();
|
||||||
} else {
|
} else {
|
||||||
Logger.e(TAG, "Max retry count reached, reset to waiting");
|
Logger.e(TAG, "Max retry count reached, reset to waiting");
|
||||||
mFindCount = 0;
|
resetToWaiting();
|
||||||
mCurrentStep = Step.WAITING;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -131,14 +136,19 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetToWaiting() {
|
||||||
|
mFindCount = 0;
|
||||||
|
mCurrentStep = Step.WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
private void sendProcessStepMessage(long delay) {
|
private void sendProcessStepMessage(long delay) {
|
||||||
mHandler.removeMessages(MSG_PROCESS_STEP);
|
mHandler.removeMessages(MSG_PROCESS_STEP);
|
||||||
mHandler.sendEmptyMessageDelayed(MSG_PROCESS_STEP, delay);
|
mHandler.sendEmptyMessageDelayed(MSG_PROCESS_STEP, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendDelayClickMessage(AccessibilityNodeInfo node, boolean simulate, Step nextStep) {
|
private void sendDelayClickMessage(AccessibilityNodeInfo node, Step nextStep) {
|
||||||
Message msg = mHandler.obtainMessage(MSG_DELAY_CLICK);
|
Message msg = mHandler.obtainMessage(MSG_DELAY_CLICK);
|
||||||
msg.obj = new ClickInfo(node, simulate, nextStep);
|
msg.obj = new ClickInfo(node, nextStep);
|
||||||
long delay = DEFAULT_DELAY + mRandom.nextInt(RANDOM_DELAY_RANGE);
|
long delay = DEFAULT_DELAY + mRandom.nextInt(RANDOM_DELAY_RANGE);
|
||||||
mHandler.sendMessageDelayed(msg, delay);
|
mHandler.sendMessageDelayed(msg, delay);
|
||||||
}
|
}
|
||||||
@@ -148,9 +158,6 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
mHandler.sendEmptyMessageDelayed(MSG_RETRY_STEP, delay);
|
mHandler.sendEmptyMessageDelayed(MSG_RETRY_STEP, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void analysisAccessibilityEvent() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
Logger.e(TAG, "onStartCommand: ");
|
Logger.e(TAG, "onStartCommand: ");
|
||||||
@@ -184,9 +191,6 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||||
Logger.v(TAG, "onAccessibilityEvent: event = " + event.toString());
|
Logger.v(TAG, "onAccessibilityEvent: event = " + event.toString());
|
||||||
checkClassName(event);
|
checkClassName(event);
|
||||||
if (mAccessibilityEventCallback != null) {
|
|
||||||
mAccessibilityEventCallback.onAccessibilityEventCallback(event);
|
|
||||||
}
|
|
||||||
if (mCurrentStep != Step.WAITING) {
|
if (mCurrentStep != Step.WAITING) {
|
||||||
sendProcessStepMessage(100);
|
sendProcessStepMessage(100);
|
||||||
}
|
}
|
||||||
@@ -197,34 +201,33 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startWeixin() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
|
||||||
|
intent.setAction(Intent.ACTION_MAIN);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
intent.setComponent(cmp);
|
||||||
|
try {
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "startWeixin: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkClassName(AccessibilityEvent event) {
|
private void checkClassName(AccessibilityEvent event) {
|
||||||
Logger.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
|
Logger.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
|
||||||
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
|
if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
||||||
String currentPackageName = event.getPackageName() != null ? event.getPackageName().toString() : "";
|
|| mCurrentStep == Step.WAITING) {
|
||||||
String currentClassName = event.getClassName() != null ? event.getClassName().toString() : "";
|
return;
|
||||||
|
}
|
||||||
switch (mCurrentStep) {
|
String currentClassName = event.getClassName() != null ? event.getClassName().toString() : "";
|
||||||
case WAITING:
|
if ("com.tencent.mm.plugin.account.ui.WelcomeActivity".equals(currentClassName)
|
||||||
if (!TextUtils.isEmpty(currentPackageName) && "com.android.incallui".equals(currentPackageName)) {
|
|| "com.tencent.mm.plugin.account.ui.LoginPasswordUI".equals(currentClassName)) {
|
||||||
Logger.e(TAG, "checkClassName: to dialer hands free");
|
PopTip.show("请先登录微信").iconWarning();
|
||||||
}
|
mCurrentStep = Step.WAITING;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (!TextUtils.isEmpty(currentClassName)) {
|
|
||||||
switch (currentClassName) {
|
|
||||||
case "com.tencent.mm.ui.LauncherUI":
|
|
||||||
break;
|
|
||||||
case "com.tencent.mm.plugin.account.ui.WelcomeActivity":
|
|
||||||
case "com.tencent.mm.plugin.account.ui.LoginPasswordUI":
|
|
||||||
PopTip.show("请先登录微信").iconWarning();
|
|
||||||
mCurrentStep = Step.WAITING;
|
|
||||||
break;
|
|
||||||
case "com.tencent.mm.plugin.label.ui.ContactLabelManagerUI":
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +269,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
putString(mName, Step.CLICK_SEARCH_CONTACT);
|
putString(mName, Step.CLICK_SEARCH_CONTACT);
|
||||||
break;
|
break;
|
||||||
case CLICK_SEARCH_CONTACT:
|
case CLICK_SEARCH_CONTACT:
|
||||||
if (findNodesByViewId("com.tencent.mm:id/gzf").size() > 0) {
|
if (!findNodesByViewId("com.tencent.mm:id/gzf").isEmpty()) {
|
||||||
findSearchContactAndClick("com.tencent.mm:id/odf", Step.CLICK_QUICK_WECHAT_CALL);
|
findSearchContactAndClick("com.tencent.mm:id/odf", Step.CLICK_QUICK_WECHAT_CALL);
|
||||||
} else {
|
} else {
|
||||||
PopTip.show("没有找到联系人").iconError();
|
PopTip.show("没有找到联系人").iconError();
|
||||||
@@ -274,7 +277,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLICK_QUICK_WECHAT_CALL:
|
case CLICK_QUICK_WECHAT_CALL:
|
||||||
clickViewById("com.tencent.mm:id/bjz", Step.CLICK_TARGET);
|
clickViewById(MORE_FUNCTION_BUTTON_ID, Step.CLICK_TARGET);
|
||||||
break;
|
break;
|
||||||
case CLICK_TARGET:
|
case CLICK_TARGET:
|
||||||
stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
|
stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
|
||||||
@@ -292,7 +295,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FIND_CONTACT:
|
case FIND_CONTACT:
|
||||||
findContactWithScroll(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
|
findWithScroll(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL, false);
|
||||||
break;
|
break;
|
||||||
case FIND_TAG:
|
case FIND_TAG:
|
||||||
findAndClick(Property.TEXT, TAG_TEXT, false, Step.CLICK_TAG);
|
findAndClick(Property.TEXT, TAG_TEXT, false, Step.CLICK_TAG);
|
||||||
@@ -304,10 +307,10 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CLICK_NAME:
|
case CLICK_NAME:
|
||||||
findContactWithScroll(Property.TEXT, mName, Step.CLICK_INFO);
|
findWithScroll(Property.TEXT, mName, Step.CLICK_INFO, false);
|
||||||
break;
|
break;
|
||||||
case CLICK_INFO:
|
case CLICK_INFO:
|
||||||
findAndClickWithScroll(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL);
|
findWithScroll(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL, true);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -316,43 +319,37 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private boolean findAndClick(Property type, String text, boolean searchWindows, Step nextStep) {
|
private boolean findAndClick(Property type, String text, boolean searchWindows, Step nextStep) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, searchWindows);
|
AccessibilityNodeInfo node = findNodeByProperty(type, text, searchWindows);
|
||||||
if (node != null && isNodeVisible(node)) {
|
if (node != null && isNodeVisible(node)) {
|
||||||
sendDelayClickMessage(node, false, nextStep);
|
sendDelayClickMessage(node, nextStep);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findAndClickWithScroll(Property type, String text, Step nextStep) {
|
/**
|
||||||
|
* 查找节点并点击,找不到时滚动重试。
|
||||||
|
*
|
||||||
|
* @param checkVisible 为 true 时,找到但不可见的节点会先滚动屏幕后重试
|
||||||
|
*/
|
||||||
|
private void findWithScroll(Property type, String text, Step nextStep, boolean checkVisible) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
|
AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
if (node.isVisibleToUser()) {
|
if (!checkVisible || node.isVisibleToUser()) {
|
||||||
sendDelayClickMessage(node, false, nextStep);
|
sendDelayClickMessage(node, nextStep);
|
||||||
mFindCount = 0;
|
mFindCount = 0;
|
||||||
} else {
|
} else {
|
||||||
scrollDown();
|
scrollDown();
|
||||||
sendRetryStepMessage(WAIT_TIME);
|
sendRetryStepMessage(WAIT_TIME);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleNotFoundWithRetry("没有找到联系人", nextStep);
|
handleNotFoundWithRetry("没有找到联系人");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findContactWithScroll(Property type, String text, Step nextStep) {
|
private void handleNotFoundWithRetry(String errorMsg) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
|
|
||||||
if (node != null) {
|
|
||||||
sendDelayClickMessage(node, false, nextStep);
|
|
||||||
mFindCount = 0;
|
|
||||||
} else {
|
|
||||||
handleNotFoundWithRetry("没有找到联系人", nextStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNotFoundWithRetry(String errorMsg, Step nextStep) {
|
|
||||||
if (mFindCount >= MAX_FIND_COUNT) {
|
if (mFindCount >= MAX_FIND_COUNT) {
|
||||||
Logger.e(TAG, "handleNotFoundWithRetry: max count reached");
|
Logger.e(TAG, "handleNotFoundWithRetry: max count reached");
|
||||||
ToastUtils.showShort(errorMsg);
|
ToastUtils.showShort(errorMsg);
|
||||||
mCurrentStep = Step.WAITING;
|
resetToWaiting();
|
||||||
mFindCount = 0;
|
|
||||||
} else {
|
} else {
|
||||||
Logger.e(TAG, "handleNotFoundWithRetry: not found, count=" + mFindCount);
|
Logger.e(TAG, "handleNotFoundWithRetry: not found, count=" + mFindCount);
|
||||||
mFindCount++;
|
mFindCount++;
|
||||||
@@ -364,26 +361,19 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private void handleHandsFree(Property type, String text, boolean isWechat) {
|
private void handleHandsFree(Property type, String text, boolean isWechat) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
|
AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
Point point = getPointByNode(node);
|
|
||||||
Logger.e(TAG, "handleHandsFree: " + point);
|
|
||||||
if (isWechat) {
|
if (isWechat) {
|
||||||
clickByPoint(point.x, point.y - 50);
|
doubleClickNodePoint(node);
|
||||||
clickByPoint(point.x, point.y);
|
|
||||||
} else {
|
} else {
|
||||||
sendDelayClickMessage(node, false, Step.WAITING);
|
sendDelayClickMessage(node, Step.WAITING);
|
||||||
}
|
}
|
||||||
mCurrentStep = Step.WAITING;
|
|
||||||
} else {
|
} else {
|
||||||
Logger.e(TAG, "handleHandsFree: not found");
|
Logger.e(TAG, "handleHandsFree: not found");
|
||||||
mCurrentStep = Step.WAITING;
|
|
||||||
}
|
}
|
||||||
|
mCurrentStep = Step.WAITING;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoAccept() {
|
private void autoAccept() {
|
||||||
if (findAndClickAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
|
if (findAndClickAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION) || clickNode("com.tencent.mm:id/kfp")) {
|
||||||
mCurrentStep = Step.WECHAT_HANDS_FREE;
|
|
||||||
ToastUtils.showShort("已自动接听视频/语音");
|
|
||||||
} else if (clickNode("com.tencent.mm:id/kfp", false)) {
|
|
||||||
mCurrentStep = Step.WECHAT_HANDS_FREE;
|
mCurrentStep = Step.WECHAT_HANDS_FREE;
|
||||||
ToastUtils.showShort("已自动接听视频/语音");
|
ToastUtils.showShort("已自动接听视频/语音");
|
||||||
} else {
|
} else {
|
||||||
@@ -394,48 +384,43 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
private boolean findAndClickAnswer(Property type, String text) {
|
private boolean findAndClickAnswer(Property type, String text) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
|
AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
Point point = getPointByNode(node);
|
doubleClickNodePoint(node);
|
||||||
Logger.e(TAG, "findAndClickAnswer: " + point);
|
|
||||||
clickByPoint(point.x, point.y - 50);
|
|
||||||
clickByPoint(point.x, point.y);
|
|
||||||
mCurrentStep = Step.WAITING;
|
mCurrentStep = Step.WAITING;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean clickNode(String id, boolean simulate) {
|
/**
|
||||||
findFloatWindowNode(id);
|
* 在节点中心点上方与中心点各点击一次(用于接听/免提按钮)
|
||||||
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(id);
|
*/
|
||||||
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
|
private void doubleClickNodePoint(AccessibilityNodeInfo node) {
|
||||||
if (optional.isPresent()) {
|
Point point = getPointByNode(node);
|
||||||
AccessibilityNodeInfo node = optional.get();
|
Logger.e(TAG, "doubleClickNodePoint: " + point);
|
||||||
if (node.isClickable()) {
|
clickByPoint(point.x, point.y - 50);
|
||||||
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
clickByPoint(point.x, point.y);
|
||||||
Logger.e(TAG, "clickNode: performAction = " + performAction);
|
}
|
||||||
node.recycle();
|
|
||||||
return performAction;
|
private boolean clickNode(String id) {
|
||||||
} else {
|
AccessibilityNodeInfo node = findFirstNodeById(id);
|
||||||
if (simulate) {
|
if (node == null) {
|
||||||
Point point = getPointByNode(node);
|
|
||||||
Logger.e(TAG, "clickNode: " + point);
|
|
||||||
clickByPoint(point.x, point.y);
|
|
||||||
Logger.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
|
|
||||||
} else {
|
|
||||||
AccessibilityNodeInfo clickableNode = findClickableNode(node);
|
|
||||||
if (clickableNode != null) {
|
|
||||||
sendDelayClickMessage(clickableNode, false, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Logger.e(TAG, "clickNode: not found");
|
Logger.e(TAG, "clickNode: not found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (node.isClickable()) {
|
||||||
|
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||||
|
Logger.e(TAG, "clickNode: performAction = " + performAction);
|
||||||
|
node.recycle();
|
||||||
|
return performAction;
|
||||||
|
}
|
||||||
|
AccessibilityNodeInfo clickableNode = findClickableNode(node);
|
||||||
|
if (clickableNode != null) {
|
||||||
|
sendDelayClickMessage(clickableNode, null);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performClick(AccessibilityNodeInfo node, boolean simulate) {
|
private void performClick(AccessibilityNodeInfo node) {
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
Logger.e(TAG, "performClick: node is null");
|
Logger.e(TAG, "performClick: node is null");
|
||||||
return;
|
return;
|
||||||
@@ -446,26 +431,20 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(TAG, "performClick: e = " + e.getMessage());
|
Logger.e(TAG, "performClick: e = " + e.getMessage());
|
||||||
}
|
}
|
||||||
if (node.isClickable()) {
|
boolean clickable = node.isClickable();
|
||||||
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
boolean performed = false;
|
||||||
Logger.e(TAG, "performClick: performAction = " + performAction);
|
if (clickable) {
|
||||||
if (!performAction) {
|
performed = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
|
||||||
Rect rect = new Rect();
|
Logger.e(TAG, "performClick: performAction = " + performed);
|
||||||
node.getBoundsInScreen(rect);
|
|
||||||
Logger.e(TAG, "performClick: rect = " + rect);
|
|
||||||
int centerX = (rect.left + rect.right) / 2;
|
|
||||||
int centerY = (rect.top + rect.bottom) / 2;
|
|
||||||
Logger.e(TAG, "performClick: clickByNode = " + clickByPoint(centerX, centerY));
|
|
||||||
}
|
|
||||||
node.recycle();
|
|
||||||
} else {
|
|
||||||
Rect rect = new Rect();
|
|
||||||
node.getBoundsInScreen(rect);
|
|
||||||
Logger.e(TAG, "performClick: rect = " + rect);
|
|
||||||
int centerX = (rect.left + rect.right) / 2;
|
|
||||||
int centerY = (rect.top + rect.bottom) / 2;
|
|
||||||
Logger.e(TAG, "performClick: clickByNode = " + clickByPoint(centerX, centerY));
|
|
||||||
}
|
}
|
||||||
|
if (!performed) {
|
||||||
|
Point point = getPointByNode(node);
|
||||||
|
Logger.e(TAG, "performClick: clickByNode = " + clickByPoint(point.x, point.y));
|
||||||
|
}
|
||||||
|
if (clickable) {
|
||||||
|
node.recycle();
|
||||||
|
}
|
||||||
|
hideInputMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
|
private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
|
||||||
@@ -475,52 +454,24 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
if (node.isClickable()) {
|
if (node.isClickable()) {
|
||||||
return node;
|
return node;
|
||||||
} else {
|
|
||||||
AccessibilityNodeInfo parent = node.getParent();
|
|
||||||
node.recycle();
|
|
||||||
return parent != null ? findClickableNode(parent) : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void findFloatWindowNode(String id) {
|
|
||||||
List<AccessibilityWindowInfo> windows = getWindows();
|
|
||||||
for (AccessibilityWindowInfo window : windows) {
|
|
||||||
if (isFloatingWindow(window)) {
|
|
||||||
AccessibilityNodeInfo rootNode = window.getRoot();
|
|
||||||
traverseNode(rootNode);
|
|
||||||
if (rootNode != null) {
|
|
||||||
rootNode.recycle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isFloatingWindow(AccessibilityWindowInfo window) {
|
|
||||||
Logger.e(TAG, "isFloatingWindow: " + window.getType());
|
|
||||||
return window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void traverseNode(AccessibilityNodeInfo node) {
|
|
||||||
if (node == null) return;
|
|
||||||
String text = node.getText() != null ? node.getText().toString() : "";
|
|
||||||
String id = node.getViewIdResourceName();
|
|
||||||
for (int i = 0; i < node.getChildCount(); i++) {
|
|
||||||
traverseNode(node.getChild(i));
|
|
||||||
}
|
}
|
||||||
|
AccessibilityNodeInfo parent = node.getParent();
|
||||||
|
node.recycle();
|
||||||
|
return parent != null ? findClickableNode(parent) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessibilityNodeInfo findNodeByProperty(Property type, String text, boolean searchWindows) {
|
private AccessibilityNodeInfo findNodeByProperty(Property type, String text, boolean searchWindows) {
|
||||||
if (searchWindows) {
|
if (searchWindows) {
|
||||||
return findNodeInWindows(type, text);
|
return findNodeInWindows(type, text);
|
||||||
} else {
|
} else {
|
||||||
return findNodeInActiveWindow(getRootInActiveWindow(), type, text);
|
return findNodeInTree(getRootInActiveWindow(), type, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessibilityNodeInfo findNodeInWindows(Property type, String text) {
|
private AccessibilityNodeInfo findNodeInWindows(Property type, String text) {
|
||||||
for (AccessibilityWindowInfo window : getWindows()) {
|
for (AccessibilityWindowInfo window : getWindows()) {
|
||||||
AccessibilityNodeInfo root = window.getRoot();
|
AccessibilityNodeInfo root = window.getRoot();
|
||||||
AccessibilityNodeInfo node = findNodeInActiveWindow(root, type, text);
|
AccessibilityNodeInfo node = findNodeInTree(root, type, text);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -532,20 +483,15 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessibilityNodeInfo findNodeInActiveWindow(AccessibilityNodeInfo root, Property type, String text) {
|
private AccessibilityNodeInfo findNodeInTree(AccessibilityNodeInfo root, Property type, String text) {
|
||||||
if (root == null) return null;
|
if (root == null) return null;
|
||||||
Logger.v(TAG, "findNodeInActiveWindow: getText = " + root.getText());
|
if (checkPropertyMatch(root, type, text)) {
|
||||||
Logger.v(TAG, "findNodeInActiveWindow: getClassName = " + root.getClassName());
|
|
||||||
Logger.v(TAG, "findNodeInActiveWindow: getContentDescription = " + root.getContentDescription());
|
|
||||||
boolean satisfied = checkPropertyMatch(root, type, text);
|
|
||||||
if (satisfied) {
|
|
||||||
return root;
|
return root;
|
||||||
} else {
|
}
|
||||||
for (int i = 0; i < root.getChildCount(); i++) {
|
for (int i = 0; i < root.getChildCount(); i++) {
|
||||||
AccessibilityNodeInfo result = findNodeInActiveWindow(root.getChild(i), type, text);
|
AccessibilityNodeInfo result = findNodeInTree(root.getChild(i), type, text);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.recycle();
|
root.recycle();
|
||||||
@@ -573,37 +519,38 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void clickViewById(String id, Step nextStep) {
|
private void clickViewById(String id, Step nextStep) {
|
||||||
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(id);
|
AccessibilityNodeInfo node = findFirstNodeById(id);
|
||||||
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
|
if (node != null) {
|
||||||
if (optional.isPresent()) {
|
sendDelayClickMessage(node, nextStep);
|
||||||
sendDelayClickMessage(optional.get(), false, nextStep);
|
|
||||||
} else {
|
} else {
|
||||||
findAndClick(Property.DESCRIPTION, SEARCH_TEXT, false, Step.CLICK_SEARCH);
|
findAndClick(Property.DESCRIPTION, SEARCH_TEXT, false, Step.CLICK_SEARCH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AccessibilityNodeInfo findFirstNodeById(String id) {
|
||||||
|
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(id);
|
||||||
|
return nodeInfos.isEmpty() ? null : nodeInfos.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
private List<AccessibilityNodeInfo> findNodesByViewId(String id) {
|
private List<AccessibilityNodeInfo> findNodesByViewId(String id) {
|
||||||
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
|
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
|
||||||
if (nodeInfo != null) {
|
if (nodeInfo != null) {
|
||||||
return nodeInfo.findAccessibilityNodeInfosByViewId(id);
|
return nodeInfo.findAccessibilityNodeInfosByViewId(id);
|
||||||
} else {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putString(String text, Step nextStep) {
|
private void putString(String text, Step nextStep) {
|
||||||
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId("com.tencent.mm:id/d98");
|
AccessibilityNodeInfo nodeInfo = findFirstNodeById("com.tencent.mm:id/d98");
|
||||||
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
|
if (nodeInfo != null) {
|
||||||
if (optional.isPresent()) {
|
|
||||||
AccessibilityNodeInfo nodeInfo = optional.get();
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
|
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
|
||||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
|
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
|
||||||
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
|
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= ACTION_IME_ENTER_VERSION) {
|
if (Build.VERSION.SDK_INT >= ACTION_IME_ENTER_VERSION) {
|
||||||
nodeInfo.performAction(ACTION_IME_ENTER_ID);
|
nodeInfo.performAction(ACTION_IME_ENTER_ID);
|
||||||
}
|
}
|
||||||
|
hideInputMethod();
|
||||||
mCurrentStep = nextStep;
|
mCurrentStep = nextStep;
|
||||||
sendProcessStepMessage(WAIT_TIME);
|
sendProcessStepMessage(WAIT_TIME);
|
||||||
} else {
|
} else {
|
||||||
@@ -613,31 +560,26 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void findSearchContactAndClick(String id, Step nextStep) {
|
private void findSearchContactAndClick(String id, Step nextStep) {
|
||||||
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(id);
|
AccessibilityNodeInfo node = findFirstNodeById(id);
|
||||||
Logger.e(TAG, "findSearchContactAndClick: " + nodeInfos);
|
if (node != null) {
|
||||||
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
|
sendDelayClickMessage(node, nextStep);
|
||||||
if (optional.isPresent()) {
|
|
||||||
sendDelayClickMessage(optional.get(), false, nextStep);
|
|
||||||
} else {
|
} else {
|
||||||
PopTip.show("没有找到联系人").iconError();
|
PopTip.show("没有找到联系人").iconError();
|
||||||
mCurrentStep = Step.WAITING;
|
mCurrentStep = Step.WAITING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stepCall(Property type, String text) {
|
private void stepCall(Property type, String text) {
|
||||||
AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
|
AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
Point point = getPointByNode(node);
|
Point point = getPointByNode(node);
|
||||||
Logger.e(TAG, "stepCall: " + point);
|
Logger.e(TAG, "stepCall: " + point);
|
||||||
clickByPoint(point.x, point.y);
|
clickByPoint(point.x, point.y);
|
||||||
Logger.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done");
|
|
||||||
mCurrentStep = Step.CLICK_CALL;
|
mCurrentStep = Step.CLICK_CALL;
|
||||||
Logger.e(TAG, "stepCall: next " + mCurrentStep);
|
Logger.e(TAG, "stepCall: next " + mCurrentStep);
|
||||||
sendProcessStepMessage(WAIT_TIME);
|
sendProcessStepMessage(WAIT_TIME);
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
Logger.e(TAG, "stepCall: not found");
|
Logger.e(TAG, "stepCall: not found");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,62 +604,43 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean clickByPoint(int x, int y) {
|
private boolean clickByPoint(int x, int y) {
|
||||||
Logger.e(TAG, "clickByPoint: x = " + x);
|
Logger.e(TAG, "clickByPoint: x = " + x + ", y = " + y);
|
||||||
Logger.e(TAG, "clickByPoint: y = " + y);
|
|
||||||
Point point = new Point(x, y);
|
|
||||||
Path path = new Path();
|
Path path = new Path();
|
||||||
path.moveTo(point.x, point.y);
|
path.moveTo(x, y);
|
||||||
GestureDescription.Builder builder = new GestureDescription.Builder();
|
|
||||||
int duration = 200 + mRandom.nextInt(100);
|
int duration = 200 + mRandom.nextInt(100);
|
||||||
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, duration));
|
return dispatchGesturePath(path, duration, "clickByPoint");
|
||||||
GestureDescription gesture = builder.build();
|
|
||||||
boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() {
|
|
||||||
@Override
|
|
||||||
public void onCompleted(GestureDescription gestureDescription) {
|
|
||||||
super.onCompleted(gestureDescription);
|
|
||||||
Logger.e("clickByPoint", "onCompleted: ");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCancelled(GestureDescription gestureDescription) {
|
|
||||||
super.onCancelled(gestureDescription);
|
|
||||||
Logger.e("clickByPoint", "onCancelled: ");
|
|
||||||
}
|
|
||||||
}, null);
|
|
||||||
return dispatched;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean scrollScreen(double startY, double endY) {
|
private boolean scrollScreen(double startY, double endY) {
|
||||||
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
|
||||||
DisplayMetrics dm = new DisplayMetrics();
|
DisplayMetrics dm = new DisplayMetrics();
|
||||||
wm.getDefaultDisplay().getRealMetrics(dm);
|
wm.getDefaultDisplay().getRealMetrics(dm);
|
||||||
int width = dm.widthPixels;
|
int centerX = dm.widthPixels / 2;
|
||||||
int height = dm.heightPixels;
|
int centerY = dm.heightPixels / 2;
|
||||||
Logger.e(TAG, "scrollScreen: screenWidth = " + width);
|
Logger.e(TAG, "scrollScreen: center position: (" + centerX + "," + centerY + ")");
|
||||||
Logger.e(TAG, "scrollScreen: screenHeight = " + height);
|
|
||||||
int center_X = width / 2;
|
|
||||||
int center_Y = height / 2;
|
|
||||||
Logger.e("scrollScreen", "center position:" + "(" + center_X + "," + center_Y + ")");
|
|
||||||
Path path = new Path();
|
Path path = new Path();
|
||||||
path.moveTo(center_X, (int) (center_Y * startY));
|
path.moveTo(centerX, (int) (centerY * startY));
|
||||||
path.lineTo(center_X, (int) (center_Y * endY));
|
path.lineTo(centerX, (int) (centerY * endY));
|
||||||
GestureDescription.Builder builder = new GestureDescription.Builder();
|
return dispatchGesturePath(path, 200, "scrollScreen");
|
||||||
GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build();
|
}
|
||||||
boolean dispatched = dispatchGesture(gestureDescription, new GestureResultCallback() {
|
|
||||||
|
private boolean dispatchGesturePath(Path path, int duration, String tag) {
|
||||||
|
GestureDescription gesture = new GestureDescription.Builder()
|
||||||
|
.addStroke(new GestureDescription.StrokeDescription(path, 0, duration))
|
||||||
|
.build();
|
||||||
|
return dispatchGesture(gesture, new GestureResultCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onCompleted(GestureDescription gestureDescription) {
|
public void onCompleted(GestureDescription gestureDescription) {
|
||||||
super.onCompleted(gestureDescription);
|
super.onCompleted(gestureDescription);
|
||||||
Logger.d("scrollScreen", "dispatchGesture ScrollUp onCompleted.");
|
Logger.e(tag, "onCompleted: ");
|
||||||
path.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelled(GestureDescription gestureDescription) {
|
public void onCancelled(GestureDescription gestureDescription) {
|
||||||
super.onCancelled(gestureDescription);
|
super.onCancelled(gestureDescription);
|
||||||
Logger.d("scrollScreen", "dispatchGesture ScrollUp cancel.");
|
Logger.e(tag, "onCancelled: ");
|
||||||
}
|
}
|
||||||
}, null);
|
}, null);
|
||||||
return dispatched;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean scrollDown() {
|
private boolean scrollDown() {
|
||||||
@@ -728,6 +651,87 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
return scrollScreen(0.5, 1.5);
|
return scrollScreen(0.5, 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏输入法(软键盘),兼容各安卓版本。
|
||||||
|
* 优先通过 InputMethodManager 直接隐藏;若不支持或失败,则模拟返回键收起键盘。
|
||||||
|
*/
|
||||||
|
public void hideInputMethod() {
|
||||||
|
if (!isInputMethodShown()) {
|
||||||
|
Logger.e(TAG, "hideInputMethod: 输入法未显示,无需隐藏");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hideInputMethodByImm() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
// 兜底方案:输入法显示时返回键只会收起键盘,不会退出当前页面
|
||||||
|
performGlobalAction(GLOBAL_ACTION_BACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断输入法(软键盘)是否正在显示。
|
||||||
|
* Android 5.0(API 21)及以上可通过无障碍窗口类型精确判断;
|
||||||
|
* 低版本无法可靠判断,默认返回 true 以尝试隐藏。
|
||||||
|
*/
|
||||||
|
private boolean isInputMethodShown() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
List<AccessibilityWindowInfo> windows = getWindows();
|
||||||
|
if (windows != null) {
|
||||||
|
for (AccessibilityWindowInfo window : windows) {
|
||||||
|
if (window.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过反射调用 InputMethodManager.hideSoftInputFromWindow 直接隐藏输入法,无返回键副作用。
|
||||||
|
* 依次尝试从 IMM 内部不同版本的字段中获取当前窗口 token。
|
||||||
|
*/
|
||||||
|
private boolean hideInputMethodByImm() {
|
||||||
|
try {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (imm == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IBinder token = getInputMethodWindowToken(imm);
|
||||||
|
if (token == null) {
|
||||||
|
Logger.e(TAG, "hideInputMethodByImm: 未获取到窗口 token");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Method hideMethod = InputMethodManager.class.getMethod("hideSoftInputFromWindow", IBinder.class, int.class);
|
||||||
|
return (Boolean) hideMethod.invoke(imm, token, 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "hideInputMethodByImm: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反射获取 InputMethodManager 持有的当前窗口 token(不同安卓版本字段名不同)。
|
||||||
|
*/
|
||||||
|
private IBinder getInputMethodWindowToken(InputMethodManager imm) {
|
||||||
|
String[] tokenFieldNames = {"mCurRootView", "mServedView", "mNextServedView"};
|
||||||
|
for (String fieldName : tokenFieldNames) {
|
||||||
|
try {
|
||||||
|
Field field = imm.getClass().getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
Object view = field.get(imm);
|
||||||
|
if (view instanceof View) {
|
||||||
|
IBinder token = ((View) view).getWindowToken();
|
||||||
|
if (token != null) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 尝试下一个字段名
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private enum Step {
|
private enum Step {
|
||||||
WAITING,
|
WAITING,
|
||||||
WECHAT_HANDS_FREE,
|
WECHAT_HANDS_FREE,
|
||||||
@@ -743,8 +747,7 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
FIND_TAG,
|
FIND_TAG,
|
||||||
CLICK_TAG,
|
CLICK_TAG,
|
||||||
CLICK_NAME,
|
CLICK_NAME,
|
||||||
CLICK_INFO,
|
CLICK_INFO
|
||||||
CLICK_VIDEO_CALL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Property {
|
private enum Property {
|
||||||
@@ -754,22 +757,15 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class ClickInfo {
|
private static class ClickInfo {
|
||||||
AccessibilityNodeInfo node;
|
final AccessibilityNodeInfo node;
|
||||||
boolean simulate;
|
final Step nextStep;
|
||||||
Step nextStep;
|
|
||||||
|
|
||||||
ClickInfo(AccessibilityNodeInfo node, boolean simulate, Step nextStep) {
|
ClickInfo(AccessibilityNodeInfo node, Step nextStep) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.simulate = simulate;
|
|
||||||
this.nextStep = nextStep;
|
this.nextStep = nextStep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action";
|
|
||||||
public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action";
|
|
||||||
|
|
||||||
private SettingReceiver mSettingReceiver;
|
|
||||||
|
|
||||||
private void registerSettingReceiver() {
|
private void registerSettingReceiver() {
|
||||||
if (mSettingReceiver == null) {
|
if (mSettingReceiver == null) {
|
||||||
mSettingReceiver = new SettingReceiver();
|
mSettingReceiver = new SettingReceiver();
|
||||||
@@ -777,7 +773,12 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(SETTING_CALL_TYPE_ACTION);
|
filter.addAction(SETTING_CALL_TYPE_ACTION);
|
||||||
filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION);
|
filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION);
|
||||||
registerReceiver(mSettingReceiver, filter);
|
filter.addAction(HIDE_IME_ACTION);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(mSettingReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||||
|
} else {
|
||||||
|
registerReceiver(mSettingReceiver, filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SettingReceiver extends BroadcastReceiver {
|
private class SettingReceiver extends BroadcastReceiver {
|
||||||
@@ -788,33 +789,19 @@ public class DialerAccessibilityService extends AccessibilityService {
|
|||||||
if (TextUtils.isEmpty(action)) return;
|
if (TextUtils.isEmpty(action)) return;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SETTING_CALL_TYPE_ACTION:
|
case SETTING_CALL_TYPE_ACTION:
|
||||||
int callType = intent.getIntExtra("call_type", ACTION_VIDEO);
|
mCallType = intent.getIntExtra("call_type", ACTION_VIDEO);
|
||||||
mCallType = callType;
|
Logger.e("SettingReceiver", "onReceive: callType = " + mCallType);
|
||||||
Logger.e("SettingReceiver", "onReceive: callType = " + callType);
|
|
||||||
break;
|
break;
|
||||||
case SETTING_AUTOMATIC_ANSWER_ACTION:
|
case SETTING_AUTOMATIC_ANSWER_ACTION:
|
||||||
boolean autoAnswer = intent.getBooleanExtra("auto_answer", false);
|
mAutoAccept = intent.getBooleanExtra("auto_answer", false);
|
||||||
mAutoAccept = autoAnswer;
|
Logger.e("SettingReceiver", "onReceive: autoAnswer = " + mAutoAccept);
|
||||||
Logger.e("SettingReceiver", "onReceive: autoAnswer = " + autoAnswer);
|
break;
|
||||||
|
case HIDE_IME_ACTION:
|
||||||
|
hideInputMethod();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startWeixin() {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI");
|
|
||||||
intent.setAction(Intent.ACTION_MAIN);
|
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
intent.setComponent(cmp);
|
|
||||||
try {
|
|
||||||
startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.e(TAG, "startWeixin: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,14 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
private int mCurrentIndex = -1;
|
private int mCurrentIndex = -1;
|
||||||
private int mFragmentSize = 0;
|
private int mFragmentSize = 0;
|
||||||
|
|
||||||
|
// 标记 MainActivity 是否已真正退到后台(经历过 onStop)。
|
||||||
|
// 用于区分两种场景:
|
||||||
|
// - 从其他应用按 home 返回(经历过 onStop,第一次)→ 保持当前 fragment 不重置
|
||||||
|
// - 已经在主界面时再次按 home(未经历 onStop,第二次)→ 回到默认 fragment
|
||||||
|
// 注意:不能用 onPause/onResume 判断,因为跳转其他应用时 onPause 会触发而 onResume 不一定,
|
||||||
|
// 且已在主界面按 home 时可能先 onPause 再 onNewIntent,会误判。
|
||||||
|
private boolean mIsStopped = false;
|
||||||
|
|
||||||
private ScaleCircleNavigator mScaleCircleNavigator;
|
private ScaleCircleNavigator mScaleCircleNavigator;
|
||||||
|
|
||||||
private List<AppInfo> mAppInfos;
|
private List<AppInfo> mAppInfos;
|
||||||
@@ -261,14 +269,18 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
Log.e(TAG, "onNewIntent: " + intent.getAction());
|
Log.e(TAG, "onNewIntent: " + intent.getAction());
|
||||||
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
|
if (Intent.ACTION_MAIN.equals(intent.getAction())) {
|
||||||
mViewDataBinding.viewPager.setCurrentItem(mDefaultIndex, true);
|
Logger.e(TAG, "onNewIntent: mIsStopped = " + mIsStopped);
|
||||||
|
if (!mIsStopped) {
|
||||||
|
// 仍在主界面(未经历 onStop),再次按 home 键 → 回到默认 fragment(类似 launcher)
|
||||||
|
mViewDataBinding.viewPager.setCurrentItem(mDefaultIndex, true);
|
||||||
|
}
|
||||||
|
// 否则:从其他应用按 home 返回(第一次,经历过 onStop)→ 保持当前所在的 fragment 不重置
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
//修补autozie fragment item大小不一致
|
|
||||||
Logger.e(TAG, "onResume: ");
|
Logger.e(TAG, "onResume: ");
|
||||||
mViewModel.getOutsideApp();
|
mViewModel.getOutsideApp();
|
||||||
mViewModel.getHotseatApp();
|
mViewModel.getHotseatApp();
|
||||||
@@ -278,6 +290,19 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
mIsStopped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
Logger.e(TAG, "onStop: ");
|
||||||
|
mIsStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package com.ttstd.dialer.activity.settings.call;
|
package com.ttstd.dialer.activity.settings.call;
|
||||||
|
|
||||||
|
import android.provider.Settings;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.ttstd.dialer.BuildConfig;
|
||||||
import com.ttstd.dialer.R;
|
import com.ttstd.dialer.R;
|
||||||
import com.ttstd.dialer.activity.settings.home.SettingsViewModel;
|
import com.ttstd.dialer.accessibility.AccessibilityServiceHelper;
|
||||||
|
import com.ttstd.dialer.accessibility.DialerAccessibilityService;
|
||||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||||
import com.ttstd.dialer.databinding.ActivitySettingsBinding;
|
|
||||||
import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
|
import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
|
||||||
|
import com.ttstd.dialer.utils.Logger;
|
||||||
|
import com.ttstd.dialer.view.SwitchButton;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class SettingsCallActivity extends BaseMvvmActivity<SettingsCallViewModel, ActivitySettingsCallBinding> {
|
public class SettingsCallActivity extends BaseMvvmActivity<SettingsCallViewModel, ActivitySettingsCallBinding> {
|
||||||
private static final String TAG = "SettingsActivity";
|
private static final String TAG = "SettingsActivity";
|
||||||
@@ -33,7 +39,24 @@ public class SettingsCallActivity extends BaseMvvmActivity<SettingsCallViewModel
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initView() {
|
protected void initView() {
|
||||||
|
if (AccessibilityServiceHelper.isAccessibilityServiceEnabled(SettingsCallActivity.this, DialerAccessibilityService.class)) {
|
||||||
|
mViewDataBinding.siAccessibilityService.setToggleStatu(true);
|
||||||
|
} else {
|
||||||
|
mViewDataBinding.siAccessibilityService.setToggleStatu(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewDataBinding.siAccessibilityService.setOnToggleChanged(new SwitchButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(@Nullable SwitchButton view, boolean isChecked) {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
AccessibilityServiceHelper.setAccessibilityServiceStatus(SettingsCallActivity.this, DialerAccessibilityService.class, isChecked);
|
||||||
|
String enabledAccessibilityServices = Settings.Secure.getString(getContentResolver(), "enabled_accessibility_services");
|
||||||
|
Logger.e(TAG, "setAccessibilityServiceStatus: enabledAccessibilityServices = " + enabledAccessibilityServices);
|
||||||
|
} else {
|
||||||
|
AccessibilityServiceHelper.jumpToAccessibilitySettings(SettingsCallActivity.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -226,6 +226,10 @@ public class BaseApplication extends Application {
|
|||||||
filter.addAction(SystemIntentAction.ACTION_ROLLBACK_COMMITTED);
|
filter.addAction(SystemIntentAction.ACTION_ROLLBACK_COMMITTED);
|
||||||
filter.addDataScheme("package");
|
filter.addDataScheme("package");
|
||||||
registerReceiver(mAppChangedReceiver, filter);
|
registerReceiver(mAppChangedReceiver, filter);
|
||||||
|
|
||||||
|
// 监听系统语言切换,刷新桌面应用名称(该广播不带 package data,需单独注册)
|
||||||
|
IntentFilter localeFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
|
||||||
|
registerReceiver(mAppChangedReceiver, localeFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ public abstract class BaseTransparentActivity extends BaseRxActivity {
|
|||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 修补autozie RecyclerView item大小不一致
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ public class CallFragment extends BaseMvvmDialogFragment<CallViewModel, DialogFr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
public void call() {
|
public void call() {
|
||||||
try {
|
try {
|
||||||
String phone = mContactInfo.getPhoneNumber();
|
String phone = mContactInfo.getPhoneNumber();
|
||||||
|
|||||||
@@ -495,6 +495,48 @@ public class AppManager {
|
|||||||
|
|
||||||
|
|
||||||
// 重构getAllApp方法,复用现有处理逻辑
|
// 重构getAllApp方法,复用现有处理逻辑
|
||||||
|
/**
|
||||||
|
* 系统语言切换时,刷新所有已存在应用的名称(label)。
|
||||||
|
* 因为 label 在首次创建时写入数据库后不会自动更新,
|
||||||
|
* 切换语言后需要重新读取当前系统语言下的应用名称并同步数据库。
|
||||||
|
*/
|
||||||
|
public void refreshAppLabels() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
V26Helper.refreshAppLabels(this);
|
||||||
|
} else {
|
||||||
|
ASYNC_EXECUTOR.execute(this::refreshAppLabelsSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshAppLabelsSync() {
|
||||||
|
try {
|
||||||
|
List<AppInfo> allApps = getAllDesktopSortApps();
|
||||||
|
if (allApps.isEmpty()) return;
|
||||||
|
|
||||||
|
boolean changed = false;
|
||||||
|
for (AppInfo app : allApps) {
|
||||||
|
if (app.getComponentName() == null) continue;
|
||||||
|
String newLabel = ApkUtils.getAppName(mContext, app.getComponentName());
|
||||||
|
if (!Objects.equals(newLabel, app.getLabel())) {
|
||||||
|
app.setLabel(newLabel);
|
||||||
|
try {
|
||||||
|
mAppRepository.update(app);
|
||||||
|
changed = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "刷新应用名称失败: " + app.getPackageName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
Logger.i(TAG, "系统语言切换,应用名称刷新完成");
|
||||||
|
LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "刷新应用名称时发生异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshAllApps() {
|
public void refreshAllApps() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
V26Helper.refreshAllApps(this);
|
V26Helper.refreshAllApps(this);
|
||||||
@@ -917,6 +959,16 @@ public class AppManager {
|
|||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static void refreshAppLabels(final AppManager manager) {
|
||||||
|
CompletableFuture.runAsync(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
manager.refreshAppLabelsSync();
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
static void refreshAllApps(final AppManager manager) {
|
static void refreshAllApps(final AppManager manager) {
|
||||||
CompletableFuture.runAsync(new Runnable() {
|
CompletableFuture.runAsync(new Runnable() {
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ public class AppChangedReceiver extends BroadcastReceiver {
|
|||||||
if (TextUtils.isEmpty(action)) {
|
if (TextUtils.isEmpty(action)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 系统语言切换,刷新所有应用名称(该广播无 package data)
|
||||||
|
if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
|
||||||
|
sExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
AppManager.getInstance().refreshAppLabels();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onReceive: refreshAppLabels " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
String packageName = intent.getDataString().replace("package:", "");
|
String packageName = intent.getDataString().replace("package:", "");
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case Intent.ACTION_PACKAGE_ADDED:
|
case Intent.ACTION_PACKAGE_ADDED:
|
||||||
|
|||||||
@@ -82,6 +82,14 @@
|
|||||||
android:background="@drawable/settings_card_bg"
|
android:background="@drawable/settings_card_bg"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.ttstd.dialer.view.SettingItem
|
||||||
|
android:id="@+id/si_accessibility_service"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:disableText="未开启,无障碍服务不可用"
|
||||||
|
app:enableText="已开启"
|
||||||
|
app:optionsText="无障碍服务" />
|
||||||
|
|
||||||
<com.ttstd.dialer.view.SettingItem
|
<com.ttstd.dialer.view.SettingItem
|
||||||
android:id="@+id/si_fast_call_phone"
|
android:id="@+id/si_fast_call_phone"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="@dimen/dp_16"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="@dimen/dp_8"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/switch_button"
|
app:layout_constraintEnd_toStartOf="@+id/switch_button"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
android:id="@+id/switch_button"
|
android:id="@+id/switch_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginEnd="@dimen/dp_16"
|
||||||
app:sb_show_indicator="false"
|
app:sb_show_indicator="false"
|
||||||
app:sb_checked="false"
|
app:sb_checked="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
@@ -62,9 +62,9 @@
|
|||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_more"
|
android:id="@+id/iv_more"
|
||||||
android:layout_width="20dp"
|
android:layout_width="@dimen/dp_20"
|
||||||
android:layout_height="20dp"
|
android:layout_height="@dimen/dp_20"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="@dimen/dp_16"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/icon_more"
|
android:src="@drawable/icon_more"
|
||||||
@@ -77,8 +77,8 @@
|
|||||||
android:id="@+id/divider"
|
android:id="@+id/divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="@dimen/dp_16"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="@dimen/dp_16"
|
||||||
android:background="@color/lightGray"
|
android:background="@color/lightGray"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<string name="accessibility_service_description">使用拨号助手一键拨号</string>
|
<string name="accessibility_service_description">使用拨号助手一键拨号</string>
|
||||||
|
|
||||||
<!--call start-->
|
<!--call start-->
|
||||||
<string name="options_text_fast_call_phone">开启快捷通话</string>
|
<string name="options_text_fast_call_phone">快捷通话</string>
|
||||||
<string name="enable_text_fast_call_phone">已开启,快捷联系人通话类型</string>
|
<string name="enable_text_fast_call_phone">已开启,快捷联系人通话类型</string>
|
||||||
<string name="disable_text_fast_call_phone">未开启,快捷联系人通话类型</string>
|
<string name="disable_text_fast_call_phone">未开启,快捷联系人通话类型</string>
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<string name="enable_text_auto_accept">已开启,自动接听视频和语音</string>
|
<string name="enable_text_auto_accept">已开启,自动接听视频和语音</string>
|
||||||
<string name="disable_text_auto_accept">未开启,自动接听视频和语音</string>
|
<string name="disable_text_auto_accept">未开启,自动接听视频和语音</string>
|
||||||
|
|
||||||
<string name="options_text_hands_free">自动开启免提</string>
|
<string name="options_text_hands_free">自动打开免提</string>
|
||||||
<string name="enable_text_hands_free">已开启,自动开启免提</string>
|
<string name="enable_text_hands_free">已开启,自动开启免提</string>
|
||||||
<string name="disable_text_hands_free">未开启,自动开启免提</string>
|
<string name="disable_text_hands_free">未开启,自动开启免提</string>
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<!--call end-->
|
<!--call end-->
|
||||||
|
|
||||||
<!--utils start-->
|
<!--utils start-->
|
||||||
<string name="options_text_float">开启悬浮按钮</string>
|
<string name="options_text_float">全局悬浮按钮</string>
|
||||||
<string name="enable_text_float">已开启,点小圆点可以直接返回桌面</string>
|
<string name="enable_text_float">已开启,点小圆点可以直接返回桌面</string>
|
||||||
<string name="disable_text_float">未开启,点小圆点可以直接返回桌面</string>
|
<string name="disable_text_float">未开启,点小圆点可以直接返回桌面</string>
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
<string name="enable_text_default_launcher">已设置为默认桌面</string>
|
<string name="enable_text_default_launcher">已设置为默认桌面</string>
|
||||||
<string name="disable_text_default_launcher">未设置为默认桌面</string>
|
<string name="disable_text_default_launcher">未设置为默认桌面</string>
|
||||||
|
|
||||||
<string name="options_text_hourly_chime">开启整点报时</string>
|
<string name="options_text_hourly_chime">整点报时</string>
|
||||||
<string name="enable_text_hourly_chime">已开启,整点时将自动语音报时</string>
|
<string name="enable_text_hourly_chime">已开启,整点时将自动语音报时</string>
|
||||||
<string name="disable_text_hourly_chime">未开启,整点时将自动语音报时</string>
|
<string name="disable_text_hourly_chime">未开启,整点时将自动语音报时</string>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user