diff --git a/app/build.gradle b/app/build.gradle
index 860bb0a..1c2fc3a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -181,6 +181,8 @@ ext {
dependencies {
// implementation fileTree(dir: 'libs', include: ['*.jar'])
+ compileOnly files('libs/framework.jar')
+
//Android 4.4+
implementation files('libs/QWeather_Public_Android_V5.1.2.jar')
implementation 'net.i2p.crypto:eddsa:0.3.0'
@@ -333,3 +335,29 @@ dependencies {
// implementation 'me.jessyan:autosize:1.2.1'
}
+
+
+preBuild {
+ doLast {
+ def imlFile = file(project.name + ".iml")
+// def imlFile = file("..\\.idea\\modules\\" + project.name + "\\" + rootProject.name + "." + project.name + ".iml")
+ println 'Change ' + project.name + '.iml order'
+ try {
+ def parsedXml = (new XmlParser()).parse(imlFile)
+ def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
+ parsedXml.component[1].remove(jdkNode)
+ def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform"
+ println 'what' + sdkString
+ new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
+ groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
+ } catch (FileNotFoundException e) {
+ // nop, iml not found
+ println "no iml found"
+ }
+ }
+ //https://www.pianshen.com/article/93481144911/
+ //https://blog.csdn.net/dhl_1986/article/details/102856026
+ //https://blog.csdn.net/zhonghe1114/article/details/80923730
+ //https://blog.csdn.net/u014175785/article/details/116235760
+ //使用系统编译后的framework.jar
+}
\ No newline at end of file
diff --git a/app/libs/framework.jar b/app/libs/framework.jar
new file mode 100644
index 0000000..fefe7ec
Binary files /dev/null and b/app/libs/framework.jar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9ca73d3..2874ff8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,9 +6,16 @@
+
-
+
+
+
+
+
+
+
@@ -43,11 +50,14 @@
android:name=".activity.main.MainActivity"
android:exported="true"
android:launchMode="singleTask"
+ android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:theme="@style/AppThemeFitsSystemWindows">
+
+
@@ -67,10 +77,28 @@
android:name=".activity.app.AppListActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
-
+
+
+
+
+
+
{
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsActivity.java b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsActivity.java
new file mode 100644
index 0000000..6f74ad9
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsActivity.java
@@ -0,0 +1,61 @@
+package com.ttstd.dialer.activity.settings.home;
+
+import android.content.Intent;
+import android.view.View;
+
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.activity.settings.call.SettingsCallActivity;
+import com.ttstd.dialer.activity.settings.utils.SettingsUtilsActivity;
+import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
+import com.ttstd.dialer.databinding.ActivitySettingsBinding;
+
+public class SettingsActivity extends BaseMvvmActivity {
+ private static final String TAG = "SettingsActivity";
+
+ @Override
+ public boolean setfitWindow() {
+ return true;
+ }
+
+ @Override
+ public boolean setNightMode() {
+ return true;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_settings;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mViewModel.setContext(this);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView() {
+
+ }
+
+ @Override
+ protected void initData() {
+
+ }
+
+ public class BtnClick {
+ public void exit(View view) {
+ finish();
+ }
+
+ public void openCallSettings(View view) {
+ startActivity(new Intent(SettingsActivity.this, SettingsCallActivity.class));
+ }
+
+ public void openUtilsSettings(View view) {
+ startActivity(new Intent(SettingsActivity.this, SettingsUtilsActivity.class));
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java
new file mode 100644
index 0000000..d741965
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/settings/home/SettingsViewModel.java
@@ -0,0 +1,10 @@
+package com.ttstd.dialer.activity.settings.home;
+
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.databinding.ActivitySettingsBinding;
+import com.ttstd.dialer.databinding.ActivityTemplateBinding;
+
+public class SettingsViewModel extends BaseViewModel {
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsActivity.java b/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsActivity.java
new file mode 100644
index 0000000..60bae6c
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsActivity.java
@@ -0,0 +1,125 @@
+package com.ttstd.dialer.activity.settings.utils;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.util.Log;
+import android.view.View;
+
+import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.BuildConfig;
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.activity.main.MainActivity;
+import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
+import com.ttstd.dialer.config.CommonConfig;
+import com.ttstd.dialer.databinding.ActivitySettingsUtilsBinding;
+import com.ttstd.dialer.service.main.MainService;
+import com.ttstd.dialer.utils.SystemUtils;
+import com.ttstd.dialer.view.ToggleButton;
+
+public class SettingsUtilsActivity extends BaseMvvmActivity {
+ private static final String TAG = "SettingsActivity";
+
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
+ @Override
+ public boolean setfitWindow() {
+ return true;
+ }
+
+ @Override
+ public boolean setNightMode() {
+ return true;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_settings_utils;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mViewModel.setContext(this);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView() {
+
+ mViewDataBinding.siFloatWindow.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
+ @Override
+ public void onToggle(boolean on) {
+ Intent intent = new Intent(SettingsUtilsActivity.this, MainService.class);
+ if (on) {
+ intent.setAction(MainService.SHOW_FLOAT_WINDOW_ACTION);
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 1);
+ } else {
+ intent.setAction(MainService.HIDE_FLOAT_WINDOW_ACTION);
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
+ }
+ startService(intent);
+ }
+ });
+
+
+ mViewDataBinding.siFloatWindowKill.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
+ @Override
+ public void onToggle(boolean on) {
+ if (on) {
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_KILL_APP, 1);
+ } else {
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
+ }
+ }
+ });
+
+ mViewDataBinding.siDefaultLauncher.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
+ @Override
+ public void onToggle(boolean on) {
+ if (on) {
+// SystemUtils.setDefaultLauncher(SettingsUtilsActivity.this, MainActivity.class);
+// SystemUtils.setDefaultLauncher(SettingsUtilsActivity.this);
+ SystemUtils.addRoleHolderAsUser(SettingsUtilsActivity.this, BuildConfig.APPLICATION_ID);
+ } else {
+ SystemUtils.setOtherDefaultLauncher(SettingsUtilsActivity.this);
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void initData() {
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ int enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
+ Log.e(TAG, "initView: enableFloatWindow = " + enableFloatWindow);
+ mViewDataBinding.siFloatWindow.setToggleStatu(enableFloatWindow == 1);
+
+ int floatWindowKillApp = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
+ Log.e(TAG, "initView: floatWindowKillApp = " + floatWindowKillApp);
+ mViewDataBinding.siFloatWindowKill.setToggleStatu(floatWindowKillApp == 1);
+
+ boolean defaultLauncher = SystemUtils.isDefaultLauncher(SettingsUtilsActivity.this, MainActivity.class);
+ Log.e(TAG, "initView: defaultLauncher = " + defaultLauncher);
+ mViewDataBinding.siDefaultLauncher.setToggleStatu(defaultLauncher);
+ }
+
+ public class BtnClick {
+ public void exit(View view) {
+ finish();
+ }
+
+ public void screenshotSnap(View view) {
+// Bitmap bitmap = SystemUtils.takeFullScreenshot(SettingsUtilsActivity.this);
+ Bitmap bitmap = SystemUtils.takeScreenshotHighVersion();
+ if (bitmap != null) {
+ mViewDataBinding.ivSnap.setImageBitmap(bitmap);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsViewModel.java
new file mode 100644
index 0000000..2a97966
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/settings/utils/SettingsUtilsViewModel.java
@@ -0,0 +1,10 @@
+package com.ttstd.dialer.activity.settings.utils;
+
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
+import com.ttstd.dialer.databinding.ActivitySettingsUtilsBinding;
+
+public class SettingsUtilsViewModel extends BaseViewModel {
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/template/TemplateActivity.java b/app/src/main/java/com/ttstd/dialer/activity/template/TemplateActivity.java
new file mode 100644
index 0000000..83acdea
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/template/TemplateActivity.java
@@ -0,0 +1,46 @@
+package com.ttstd.dialer.activity.template;
+
+import android.view.View;
+
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
+import com.ttstd.dialer.databinding.ActivityTemplateBinding;
+
+public class TemplateActivity extends BaseMvvmActivity {
+ private static final String TAG = "TemplateActivity";
+
+
+ @Override
+ public boolean setfitWindow() {
+ return true;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_template;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mViewModel.setContext(this);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView() {
+
+ }
+
+ @Override
+ protected void initData() {
+
+ }
+
+ public class BtnClick {
+ public void exit(View view) {
+ finish();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/template/TemplateViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/template/TemplateViewModel.java
new file mode 100644
index 0000000..33a392d
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/template/TemplateViewModel.java
@@ -0,0 +1,9 @@
+package com.ttstd.dialer.activity.template;
+
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.databinding.ActivityTemplateBinding;
+
+public class TemplateViewModel extends BaseViewModel {
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
index 24d0099..5768c53 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
@@ -35,6 +35,12 @@ public class WeatherMainActivity extends BaseMvvmActivity {
private static final String TAG = "WeatherAdapter";
@@ -25,6 +32,8 @@ public class WeatherAdapter extends RecyclerView.Adapter mWeatherDailyList;
+ private RequestBuilder requestBuilder;
+
public void setWeatherDailyList(List weatherDailyList) {
mWeatherDailyList = weatherDailyList;
@@ -34,31 +43,54 @@ public class WeatherAdapter extends RecyclerView.Adapter appInfos) {
mAppInfos = appInfos;
Log.e(TAG, "AppFragment: mAppInfos = " + mAppInfos.hashCode());
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
index 2fbb7d4..06ff019 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
@@ -26,6 +26,8 @@ public class ContactFragment extends BaseMvvmFragment packages) {
+
+ }
+
+ @Override
+ public List getInstallPackageTrustList() {
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java b/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java
index 739c60c..f4dc589 100644
--- a/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java
+++ b/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java
@@ -1,45 +1,135 @@
package com.ttstd.dialer.service;
import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.GestureDescription;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import com.blankj.utilcode.util.ToastUtils;
+import com.hjq.toast.Toaster;
+import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.db.contact.ContactInfo;
-import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.SystemUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.functions.Consumer;
+
+/**
+ * 通过微信标签最高支持8.0.49,8.0.50 获取不到数据
+ * 8.0.54 可以获取
+ * 通过 {@link AccessibilityService#getWindows}和修改accessibility-service 配置能遍历屏幕元素
+ */
public class DialerAccessibilityService extends AccessibilityService {
private static final String TAG = "AccessibilityService";
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
public static final int ACTION_VIDEO = 1;
public static final int ACTION_AUDIO = 2;
- private ContactInfo mContactInfo;
+ private static final int ACTION_IME_ENTER_VERSION = 30;
+ private static final int ACTION_IME_ENTER_ID = 16908372;
+ private static final String DIALER_TEXT = "音视频通话";
+ private static final String CONTACT_TEXT = "通讯录";
+ private static final String SEARCH_TEXT = "搜索";
+ private static final String TAG_TEXT = "标签";
+ private static final String MORE_NAME = "更多功能按钮,已折叠";
+ private static final String PARENT_VIDEO_TEXT = "视频通话";
+
+ private static final String VIDEO_TEXT = "视频通话";
+ private static final String CALL_TEXT = "语音通话";
+
+ private static final String RECEIVE_DESCRIPTION = "接听";
+ private static final String HANDS_FREE_TEXT = "扬声器已关";
+ private static final String DIALER_HANDS_FREE_TEXT = "免提";
+ private static final String DIALER_HANDS_FREE_CLOSE_TEXT = "免提,已关闭";
+
+ private static final int WAIT_TIME = 1600;
+
+ private Handler mHandler;
+
+ private Step mCurrentStep = Step.WAITING;
+ private String mName = "";//微信昵称
+ private String mTagName = "";//微信联系人标签名
+
+ private boolean mAutoAccept = false;
+ private boolean mAutoHandsFree = false;
+
+ private ContactInfo mContactInfo;
private int mCallType = ACTION_VIDEO;
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
+ public interface AccessibilityEventCallback {
+ void onAccessibilityEventCallback(AccessibilityEvent accessibilityEvent);
}
- @Override
- public void onInterrupt() {
-
- }
+ private AccessibilityEventCallback mAccessibilityEventCallback;
@Override
public void onCreate() {
super.onCreate();
+ Log.e(TAG, "onCreate: ");
+ registerSettingReceiver();
+ mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
+ mAutoHandsFree = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_HNADS_FREE, false);
+ analysisAccessibilityEvent();
}
+ private void analysisAccessibilityEvent() {
+ Observable.create(new ObservableOnSubscribe() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
+ mAccessibilityEventCallback = emitter::onNext;
+ }
+ }).throttleLast(WAIT_TIME, TimeUnit.MILLISECONDS)
+ .subscribe(new Consumer() {
+ @Override
+ public void accept(AccessibilityEvent accessibilityEvent) throws Throwable {
+ Log.e(TAG, "analysisAccessibilityEvent accept: ");
+ _onAccessibilityEvent(accessibilityEvent);
+ }
+ });
+
+ }
+
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ Log.e(TAG, "onStartCommand: ");
if (intent != null) {
mContactInfo = (ContactInfo) intent.getSerializableExtra("ContactInfo");
- mCallType = intent.getIntExtra("callType", 1);
+ Log.e(TAG, "onStartCommand: contactInfo = " + mContactInfo);
+ mCallType = intent.getIntExtra("call_type", ACTION_VIDEO);
+ mName = mContactInfo.getName();
+ mCurrentStep = Step.CLICK_HOME;
if (mContactInfo != null) {
startWeixin();
}
@@ -50,6 +140,847 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onDestroy() {
super.onDestroy();
+ Log.e(TAG, "onDestroy: ");
+ if (mSettingReceiver != null) {
+ unregisterReceiver(mSettingReceiver);
+ }
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ Log.v(TAG, "onAccessibilityEvent: event = " + event.toString());
+ checkClassName(event);
+ mAccessibilityEventCallback.onAccessibilityEventCallback(event);
+ }
+
+ private void checkClassName(AccessibilityEvent event) {
+ Log.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
+ if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ String currentPackageName = event.getPackageName().toString();
+ String currentClassName = event.getClassName().toString();
+
+ switch (mCurrentStep) {
+ case WAITING:
+ if (!TextUtils.isEmpty(currentPackageName) && "com.android.incallui".equals(currentPackageName)) {
+ Log.e(TAG, "checkClassName: to dialer hands free");
+// mCurrentStep = Step.DIALER_HANDS_FREE;
+ }
+ break;
+ default:
+ if (!TextUtils.isEmpty(currentClassName)) {
+ switch (currentClassName) {
+ case "com.tencent.mm.ui.LauncherUI":
+// if (mCurrentStep != Step.FIND_CONTACT) {
+// mCurrentStep = Step.CLICK_CONTACT;
+// }
+ break;
+ case "com.tencent.mm.plugin.account.ui.WelcomeActivity":
+ case "com.tencent.mm.plugin.account.ui.LoginPasswordUI":
+ Toaster.showLong("请先登录微信");
+ mCurrentStep = Step.WAITING;
+ break;
+ case "com.tencent.mm.plugin.label.ui.ContactLabelManagerUI":
+
+ break;
+ default:
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 1.在微信页面直接找到联系人拨打电话
+ * 2.在联系人页面找到并拨打
+ * 3.通过进入联系人-标签找到并拨打
+ *
+ * @param event
+ */
+ private void _onAccessibilityEvent(AccessibilityEvent event) {
+ Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep);
+ switch (mCurrentStep) {
+ case WAITING:
+ mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
+ Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept);
+ if (mAutoAccept) {
+ autoAccept();
+ }
+ break;
+ case WECHAT_HANDS_FREE:
+ mAutoHandsFree = mMMKV.decodeInt(CommonConfig.WECHAT_AUTO_HNADS_FREE, 0) == 1;
+ Log.e(TAG, "_onAccessibilityEvent: mAutoHandsFree = " + mAutoHandsFree);
+ if (mAutoHandsFree) {
+ handsFree(Property.DESCRIPTION, HANDS_FREE_TEXT);
+ } else {
+ Log.e(TAG, "_onAccessibilityEvent: not enable auto handsfree");
+ }
+ case DIALER_HANDS_FREE:
+ if (findHandsFree(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT)) {
+ dialerHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT);
+ } else {
+ mCurrentStep = Step.WAITING;
+ }
+ break;
+ case CLICK_HOME://主页能找到直接点击进去更多
+ if (stepHome(Property.TEXT, mName)) {
+ Log.e(TAG, "_onAccessibilityEvent: not found contact in home");
+ } else {
+ clickViewById("com.tencent.mm:id/jha", Step.CLICK_SEARCH);
+// step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
+ }
+ break;
+ case CLICK_SEARCH:
+ putString(mName, Step.CLICK_SEARCH_CONTACT);
+ break;
+ case CLICK_SEARCH_CONTACT:
+ if (findSearchContact(Step.FIND_CONTACT)) {
+ findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
+ } else {
+ Toaster.show("没有找到联系人");
+ }
+ break;
+ case CLICK_QUICK_WECHAT_CALL://点击更多页面
+ clickViewById("com.tencent.mm:id/bjz", Step.CLICK_TARGET);
+// step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET);
+ break;
+ case CLICK_TARGET://点击视频通话
+ stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
+// clickVideoCall();
+ break;
+ case CLICK_CALL://打视频或者电话
+ if (mCallType == ACTION_AUDIO) {
+ step(Property.TEXT, VIDEO_TEXT, Step.WAITING);
+ } else if (mCallType == ACTION_VIDEO) {
+ step(Property.TEXT, CALL_TEXT, Step.WAITING);
+ }
+ break;
+
+ case CLICK_CONTACT://进入通讯录界面
+ if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) {
+ Log.e(TAG, "_onAccessibilityEvent: enter contact");
+ } else {
+ touchContact();
+ }
+ break;
+ case FIND_CONTACT://模拟滑动找到联系人
+ findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
+ break;
+ case FIND_TAG:
+ step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG);
+ break;
+ case CLICK_TAG:
+ if (!step(Property.TEXT, mTagName, Step.CLICK_NAME)) {
+ Toaster.show("没有找到标签");
+ mCurrentStep = Step.WAITING;
+ }
+ break;
+ case CLICK_NAME://点击item
+ findContact(Property.TEXT, mName, Step.CLICK_INFO);
+ break;
+ case CLICK_INFO://进入个人信息页面
+ stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL);
+ break;
+
+// case CLICK_VIDEO_CALL:
+// if (step(Property.TEXT, VIDEO_TEXT)) {
+// Log.d(TAG, "finish, now: " + mCurrentStep);
+// ToastUtils.show("成功发起视频聊天");
+// }
+// break;
+ default:
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+ Log.e(TAG, "onInterrupt: ");
+
+ }
+
+ @Override
+ protected void onServiceConnected() {
+ super.onServiceConnected();
+ Log.e(TAG, "onServiceConnected: ");
+ }
+
+ /**
+ * 打开这个界面可以直接选择联系人拨打
+ */
+ private void openVoip() {
+ Intent intent = new Intent();
+ ComponentName component = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.contact.VoipAddressUI");
+ intent.setComponent(component);
+ intent.putExtra("voip_video", false);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ private void autoAccept() {
+ if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
+ mCurrentStep = Step.WECHAT_HANDS_FREE;
+ ToastUtils.showShort("已自动接听视频/语音");
+ } else if (clickNode("com.tencent.mm:id/kfp", false)) {
+ mCurrentStep = Step.WECHAT_HANDS_FREE;
+ ToastUtils.showShort("已自动接听视频/语音");
+ } else {
+ mCurrentStep = Step.WAITING;
+// clickAnswer();
+ }
+ }
+
+ /**
+ * @param text 对应文本
+ * @param simulate 是否通过坐标模拟点击
+ * @return
+ */
+ private boolean clickNode(String text, boolean simulate) {
+ findFloatWindowNode(text);
+ List nodeInfos = findNodesByViewId(text);
+ Optional optional = nodeInfos.stream().findAny();
+ if (optional.isPresent()) {
+ AccessibilityNodeInfo node = optional.get();
+ if (node.isClickable()) {
+ boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ Log.e(TAG, "clickNode: performAction = " + performAction);
+ node.recycle();
+ return performAction;
+ } else {
+ if (simulate) {
+ Point point = getPointtByNode(node);
+ Log.e(TAG, "clickNode: " + point);
+ clickByPoint(point.x, point.y);
+ Log.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
+ } else {
+ clickNode(findClickableNode(node));
+ }
+ }
+ return true;
+ } else {
+ Log.e(TAG, "clickNode: not found");
+ return false;
+ }
+ }
+
+ private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
+ if (node == null) {
+ Log.e(TAG, "findClickableNode: node is null");
+ return null;
+ }
+ if (node.isClickable()) {
+ return node;
+ } else {
+ return findClickableNode(node);
+ }
+ }
+
+ public void findFloatWindowNode(String id) {
+ List windows = getWindows();
+ for (AccessibilityWindowInfo window : windows) {
+ // 筛选悬浮窗窗口
+ if (isFloatingWindow(window)) {
+ AccessibilityNodeInfo rootNode = window.getRoot();
+ // 处理悬浮窗节点
+ traverseNode(rootNode);
+ rootNode.recycle(); // 释放资源
+ }
+ }
+
+ }
+
+ private boolean isFloatingWindow(AccessibilityWindowInfo window) {
+ Log.e(TAG, "isFloatingWindow: " + window.getType());
+ // 根据窗口类型或标题筛选
+ return window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY;
+ }
+
+ private void traverseNode(AccessibilityNodeInfo node) {
+ if (node == null) return;
+ // 提取节点信息(如文本、ID等)
+ String text = node.getText() != null ? node.getText().toString() : "";
+ String id = node.getViewIdResourceName();
+ // 递归遍历子节点
+ for (int i = 0; i < node.getChildCount(); i++) {
+ traverseNode(node.getChild(i));
+ }
+ }
+
+ @Deprecated
+ private void clickAnswer() {
+ String className = SystemUtils.getForegroundActivityClassName(DialerAccessibilityService.this);
+ Log.e(TAG, "clickAnswer: " + className);
+ if (!TextUtils.isEmpty(className)) {
+ if ("com.tencent.mm.plugin.voip.ui.VideoActivity".contentEquals(className)) {
+ boolean successful = clickByPoint(595, 1376);
+ Log.e(TAG, "clickAnswer: " + successful);
+ if (successful) {
+ ToastUtils.showShort("已自动接听视频/语音");
+ }
+ } else {
+ Log.e(TAG, "clickAnswer: Not in the answering interface");
+ }
+ }
+ }
+
+ private boolean step(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
+ if (node != null) {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Log.e(TAG, "step: rect = " + rect);
+ if (rect.left < 0 || rect.top < 0 || rect.right < 0 || rect.bottom < 0) {
+ return false;
+ }
+ clickNode(node);
+ Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = nextStep;
+ Log.e(TAG, "step: next: " + mCurrentStep);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // TODO: 2025/2/8 先把通讯录点击的换成node
+ private boolean stepHome(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNode(getWindows(), type, text);
+ if (node != null) {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Log.e(TAG, "step: rect = " + rect);
+ if (rect.left < 0 || rect.top < 0 || rect.right < 0 || rect.bottom < 0) {
+ return false;
+ }
+ clickNode(node);
+ Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = nextStep;
+ Log.e(TAG, "step: next: " + mCurrentStep);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Deprecated
+ private void touchContact() {
+ boolean successful = clickByPoint(268, 1440);
+ if (successful) {
+ mCurrentStep = Step.FIND_TAG;
+ } else {
+ mCurrentStep = Step.WAITING;
+ Toaster.show("点击失败,请重试");
+ }
+ }
+
+ private boolean findSearchContact(Step nextStep) {
+ List nodeInfos = findNodesByViewId("com.tencent.mm:id/gzf");
+ Log.e(TAG, "findSearchContact: " + nodeInfos);
+ Optional optional = nodeInfos.stream().findAny();
+ return optional.isPresent();
+ }
+
+ private void findSearchContact(Property type, String text, Step nextStep) {
+ List nodeInfos = findNodesByViewId("com.tencent.mm:id/odf");
+ Log.e(TAG, "findSearchContact: " + nodeInfos);
+ Optional optional = nodeInfos.stream().findAny();
+ if (optional.isPresent()) {
+ AccessibilityNodeInfo nodeInfo = optional.get();
+ clickNode(nodeInfo);
+ mCurrentStep = nextStep;
+ } else {
+ Toaster.show("没有找到联系人");
+ mCurrentStep = Step.WAITING;
+ }
+ }
+
+ private void putString(String text, Step nextStep) {
+ List nodeInfos = findNodesByViewId("com.tencent.mm:id/d98");
+ Optional optional = nodeInfos.stream().findAny();
+ if (optional.isPresent()) {
+ AccessibilityNodeInfo nodeInfo = optional.get();
+ Bundle args = new Bundle();
+ args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
+ nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
+ nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); // 确保焦点在输入框
+
+ if (Build.VERSION.SDK_INT >= ACTION_IME_ENTER_VERSION) {
+ //see https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER
+ nodeInfo.performAction(ACTION_IME_ENTER_ID);
+ }
+ mCurrentStep = nextStep;
+ } else {
+ Toaster.show("没有找到搜索框");
+ mCurrentStep = Step.WAITING;
+ }
+ }
+
+ private void clickViewById(String id, Step nextStep) {
+ List nodeInfos = findNodesByViewId(id);
+ Optional optional = nodeInfos.stream().findAny();
+ if (optional.isPresent()) {
+ AccessibilityNodeInfo nodeInfo = optional.get();
+ clickNode(nodeInfo);
+ mCurrentStep = nextStep;
+ } else {
+// Toaster.show("没有找到搜索按钮");
+ step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
+ }
+ }
+
+ private List findNodesByViewId(String id) {
+ AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
+ if (nodeInfo != null) {
+ List accessibilityNodeInfos = nodeInfo.findAccessibilityNodeInfosByViewId(id);
+ return accessibilityNodeInfos;
+ } else {
+ return new ArrayList<>();
+ }
+ }
+
+ private AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) {
+ if (root == null) return null;
+ Log.e(TAG, "findNodeByText: getText = " + root.getText());
+ Log.e(TAG, "findNodeByText: getContentDescription = " + root.getContentDescription());
+ boolean found = root.getText() != null && text.contentEquals(root.getText());
+ if (found) {
+ return root;
+ } else {
+ for (int i = 0; i < root.getChildCount(); i++) {
+ AccessibilityNodeInfo result = findNodeByText(root.getChild(i), text);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ root.recycle();
+ return null;
+ }
+
+ private boolean stepCallDialog(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
+ if (node != null) {
+ Log.e(TAG, "stepCallDialog: isVisibleToUser: " + node.isVisibleToUser());
+ if (node.isVisibleToUser()) {
+ clickNode(node);
+ Log.e(TAG, "stepCallDialog: mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = nextStep;
+ Log.e(TAG, "stepCallDialog: next: " + mCurrentStep);
+ return true;
+ } else {
+ scrollDown();
+ return false;
+ }
+ } else {
+ if (mFindCount == mMaxCount) {
+ Log.e("stepCallDialog", "mCurrentStep: max");
+ ToastUtils.showShort("没有找到联系人");
+ mCurrentStep = Step.WAITING;
+ mFindCount = 0;
+ return false;
+ } else {
+ Log.e("stepCallDialog", "mCurrentStep: not found");
+ mFindCount++;
+ Log.e("stepCallDialog", "mCurrentStep: mFindCount = " + mFindCount);
+ scrollDown();
+ return false;
+ }
+ }
+ }
+
+ private boolean stepHome(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
+ if (node != null) {
+ clickNode(node);
+ Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = Step.CLICK_QUICK_WECHAT_CALL;
+ Log.e(TAG, "stepHome: next: " + mCurrentStep);
+ return true;
+ } else {
+ mCurrentStep = Step.CLICK_SEARCH;
+ return false;
+ }
+ }
+
+ private int mFindCount = 0;
+ private int mMaxCount = 5;
+
+
+ private boolean findContact(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
+ if (node != null) {
+ clickNode(node);
+ Log.e("findContact", "mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = nextStep;
+ Log.e("findContact", "next: " + mCurrentStep);
+ mFindCount = 0;
+ return true;
+ } else {
+ if (mFindCount == mMaxCount) {
+ Log.e("findContact", "mCurrentStep: max");
+ ToastUtils.showShort("没有找到联系人");
+ mCurrentStep = Step.WAITING;
+ mFindCount = 0;
+ return false;
+ } else {
+ Log.e("findContact", "mCurrentStep: not found");
+ mFindCount++;
+ Log.e("findContact", "mCurrentStep: mFindCount = " + mFindCount);
+ scrollDown();
+ return false;
+ }
+ }
+ }
+
+
+ private AccessibilityNodeInfo findNode(AccessibilityNodeInfo root, Property type, String text) {
+ if (root == null) return null;
+// Log.v(TAG, "findNode: getPackageName = " + root.getPackageName());
+ Log.v(TAG, "findNode: getText = " + root.getText());
+ Log.v(TAG, "findNode: getClassName = " + root.getClassName());
+ Log.v(TAG, "findNode: getContentDescription = " + root.getContentDescription());
+ boolean satisfied = false;
+ switch (type) {
+ case TEXT:
+ satisfied = root.getText() != null && text.contentEquals(root.getText());
+ break;
+ case CLASS_NAME:
+ satisfied = root.getClassName() != null && text.contentEquals(root.getClassName());
+ break;
+ case DESCRIPTION:
+ satisfied = root.getContentDescription() != null && text.contentEquals(root.getContentDescription());
+ break;
+ default:
+ }
+ if (satisfied) {
+ return root;
+ } else {
+ for (int i = 0; i < root.getChildCount(); i++) {
+ AccessibilityNodeInfo result = findNode(root.getChild(i), type, text);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ root.recycle();
+ return null;
+ }
+
+ private AccessibilityNodeInfo findNode(List windows, Property type, String text) {
+ for (AccessibilityWindowInfo accessibilityWindowInfo : windows) {
+ AccessibilityNodeInfo nodeInfo = findNode(accessibilityWindowInfo.getRoot(), type, text);
+ if (nodeInfo != null) {
+ return nodeInfo;
+ }
+ }
+ Log.e(TAG, "findNode windows: not found");
+ return null;
+ }
+
+
+ private void clickNode(AccessibilityNodeInfo node) {
+ if (node == null) {
+ Log.e(TAG, "clickNode: node is null");
+ return;
+ }
+ try {
+ Log.e(TAG, "clickNode: getText = " + node.getText());
+ Log.e(TAG, "clickNode: isClickable = " + node.isClickable());
+ } catch (Exception e) {
+ Log.e(TAG, "clickNode: e = " + e.getMessage());
+ }
+ if (node.isClickable()) {
+ //防检测机制:
+ //添加随机延迟(避免高频操作)
+// handler.postDelayed(new Runnable() {
+// @Override
+// public void run() {
+//
+// }
+// }, 1000 + new Random().nextInt(100));
+ boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ Log.e(TAG, "clickNode: performAction = " + performAction);
+ if (!performAction) {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Log.e(TAG, "clickNode: rect = " + rect);
+ // 点击节点的中心位置
+ int centerX = (rect.left + rect.right) / 2;
+ int centerY = (rect.top + rect.bottom) / 2;
+ Log.e(TAG, "clickNode: clickByNode = " + clickByPoint(centerX, centerY));
+ }
+ node.recycle();
+ } else {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Log.e(TAG, "clickNode: rect = " + rect);
+ // 点击节点的中心位置
+ int centerX = (rect.left + rect.right) / 2;
+ int centerY = (rect.top + rect.bottom) / 2;
+ Log.e(TAG, "clickNode: clickByNode = " + clickByPoint(centerX, centerY));
+ }
+// else {
+// AccessibilityNodeInfo parent = node.getParent();
+// node.recycle();
+// clickNode(parent);
+// }
+ }
+
+ @Deprecated
+ private boolean stepCall(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
+ if (node != null) {
+ Point point = getPointtByNode(node);
+ Log.e(TAG, "stepCall: " + point);
+ clickByPoint(point.x, point.y);
+// clickNode(node);
+ Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done");
+ mCurrentStep = Step.CLICK_CALL;
+ Log.e(TAG, "stepCall: next " + mCurrentStep);
+ return true;
+ } else {
+ Log.e(TAG, "stepCall: not found");
+ return false;
+ }
+ }
+
+ private void clickVideoCall() {
+ List nodeInfos = findNodesByViewId("com.tencent.mm:id/a12");
+ Optional accessibilityNodeInfo = nodeInfos.stream().findAny();
+ if (accessibilityNodeInfo.isPresent()) {
+ AccessibilityNodeInfo nodeInfo = accessibilityNodeInfo.get();
+ clickNode(nodeInfo);
+ mCurrentStep = Step.CLICK_CALL;
+ } else {
+ Toaster.show("没有找到通话按钮");
+ }
+ }
+
+ private boolean stepAnswer(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getWindows(), type, text);
+ if (node != null) {
+ Point point = getPointtByNode(node);
+ Log.e(TAG, "stepAnswer: " + point);
+ clickByPoint(point.x, point.y - 50);
+ clickByPoint(point.x, point.y);
+// clickNode(node);
+ Log.e(TAG, "stepAnswer: mCurrentStep " + mCurrentStep + " done");
+ mCurrentStep = Step.WAITING;
+ Log.e(TAG, "stepAnswer: next " + mCurrentStep);
+ return true;
+ } else {
+ Log.e(TAG, "stepAnswer: not found");
+ return false;
+ }
+ }
+
+ private boolean dialerHandsFree(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getWindows(), type, text);
+ if (node != null) {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Log.e(TAG, "dialerHandsFree: rect = " + rect);
+ clickNode(node);
+ Log.e(TAG, "dialerHandsFree: mCurrentStep: " + mCurrentStep + " done");
+ mCurrentStep = Step.WAITING;
+ Log.e(TAG, "dialerHandsFree: next: " + mCurrentStep);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean findHandsFree(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getWindows(), type, text);
+ if (node != null) {
+ Log.e(TAG, "findHandsFree: true");
+ return true;
+ } else {
+ Log.e(TAG, "findHandsFree: false");
+ return false;
+ }
+ }
+
+ private boolean handsFree(Property type, String text) {
+ AccessibilityNodeInfo node = findNode(getWindows(), type, text);
+ if (node != null) {
+ Point point = getPointtByNode(node);
+ Log.e(TAG, "handsFree: " + point);
+ clickByPoint(point.x, point.y - 50);
+ clickByPoint(point.x, point.y);
+// clickNode(node);
+ Log.e(TAG, "handsFree: mCurrentStep " + mCurrentStep + " done");
+ mCurrentStep = Step.WAITING;
+ Log.e(TAG, "handsFree: next " + mCurrentStep);
+ return true;
+ } else {
+ Log.e(TAG, "handsFree: not found");
+ mCurrentStep = Step.WAITING;
+ return false;
+ }
+ }
+
+ //根据节点信息可获得对应的x,y坐标
+ static Point getPointtByNode(AccessibilityNodeInfo node) {
+ if (node == null) {
+ return new Point(0, 0);
+ }
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Point point = new Point(rect.centerX(), rect.centerY());
+ return point;
+ }
+
+ //实现对(x,y)坐标进行点击操作。
+ private boolean clickByPoint(int x, int y) {
+ Log.e(TAG, "clickByNode: x = " + x);
+ Log.e(TAG, "clickByNode: y = " + y);
+ Point point = new Point(x, y);
+ Path path = new Path();
+ path.moveTo(point.x, point.y);
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ //防检测机制:
+ //添加随机延迟(避免高频操作)
+ builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200 + new Random().nextInt(100)));
+ GestureDescription gesture = builder.build();
+ boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() {
+ @Override
+ public void onCompleted(GestureDescription gestureDescription) {
+ super.onCompleted(gestureDescription);
+ Log.e("clickByNode", "onCompleted: ");
+ }
+
+ @Override
+ public void onCancelled(GestureDescription gestureDescription) {
+ super.onCancelled(gestureDescription);
+ Log.e("clickByNode", "onCompleted: ");
+ }
+ }, null);
+ return dispatched;
+ }
+
+ private boolean scrollScreen(double startY, double endY) {
+ WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics dm = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(dm);
+ int width = dm.widthPixels; // 屏幕宽度(像素)
+ int height = dm.heightPixels; // 屏幕高度(像素)
+ float density = dm.density; // 屏幕密度(0.75 / 1.0 / 1.5)
+ int densityDpi = dm.densityDpi; // 屏幕密度dpi(120 / 160 / 240)
+ // 屏幕宽度算法:屏幕宽度(像素)/屏幕密度
+// int screenWidth = (int) (width / density); // 屏幕宽度(dp)
+// int screenHeight = (int) (height / density);// 屏幕高度(dp)
+ Log.e(TAG, "scrollScreen: screenWidth = " + width);
+ Log.e(TAG, "scrollScreen: screenHeight = " + height);
+ int center_X = width / 2;
+ int center_Y = height / 2;
+ Log.e("scrollScreen", "center position:" + "(" + center_X + "," + center_Y + ")");
+ Path path = new Path();
+ path.moveTo(center_X, (int) (center_Y * startY)); //起点坐标。
+ path.lineTo(center_X, (int) (center_Y * endY)); //终点坐标。
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build();
+ boolean dispatched = dispatchGesture(gestureDescription, new GestureResultCallback() {
+ @Override
+ public void onCompleted(GestureDescription gestureDescription) {
+ super.onCompleted(gestureDescription);
+ Log.d("scrollScreen", "dispatchGesture ScrollUp onCompleted.");
+ path.close();
+ }
+
+ @Override
+ public void onCancelled(GestureDescription gestureDescription) {
+ super.onCancelled(gestureDescription);
+ Log.d("scrollScreen", "dispatchGesture ScrollUp cancel.");
+ }
+ }, null);
+ return dispatched;
+ }
+
+ private boolean scrollDown() {
+ return scrollScreen(1.5, 0.5);
+ }
+
+ private boolean scrollUp() {
+ return scrollScreen(0.5, 1.5);
+ }
+
+ private enum Step {
+ WAITING,
+ //微信免提
+ WECHAT_HANDS_FREE,
+ //电话免提
+ DIALER_HANDS_FREE,
+
+ //1-1微信主页找用户名
+ CLICK_HOME,
+ //2-2进入搜索界面
+ CLICK_SEARCH,
+ //2-3是否弹出了联系人列表
+ CLICK_SEARCH_CONTACT,
+ //1-2 2-4聊天界面+号
+ CLICK_QUICK_WECHAT_CALL,
+ //1-3 2-5更多里面视频通话
+ CLICK_TARGET,
+ /*1-4 语音通话*/
+ CLICK_CALL,
+
+
+ //主页点击导航栏通讯录
+ CLICK_CONTACT,
+ FIND_CONTACT,
+ //通讯录页面点击标签
+ FIND_TAG,
+ //点击对应的标签名
+ CLICK_TAG,
+ CLICK_NAME,
+ CLICK_INFO,
+ CLICK_VIDEO_CALL;
+
+ private Step next() {
+ return values()[(this.ordinal() + 1) % values().length];
+ }
+ }
+
+ private enum Property {
+ TEXT,
+ CLASS_NAME,
+ DESCRIPTION
+ }
+
+ 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() {
+ if (mSettingReceiver == null) {
+ mSettingReceiver = new SettingReceiver();
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(SETTING_CALL_TYPE_ACTION);
+ filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION);
+ registerReceiver(mSettingReceiver, filter);
+ }
+
+ private class SettingReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.e("SettingReceiver", "onReceive: " + action);
+ if (TextUtils.isEmpty(action)) return;
+ switch (action) {
+ case SETTING_CALL_TYPE_ACTION:
+ int callType = intent.getIntExtra("call_type", ACTION_VIDEO);
+ mCallType = callType;
+ Log.e("SettingReceiver", "onReceive: callType = " + callType);
+ break;
+ case SETTING_AUTOMATIC_ANSWER_ACTION:
+ boolean autoAnswer = intent.getBooleanExtra("auto_answer", false);
+ mAutoAccept = autoAnswer;
+ Log.e("SettingReceiver", "onReceive: autoAnswer = " + autoAnswer);
+ break;
+ default:
+ }
+ }
}
private void startWeixin() {
@@ -64,7 +995,7 @@ public class DialerAccessibilityService extends AccessibilityService {
try {
startActivity(intent);
} catch (Exception e) {
- Log.e(TAG, "launchWeChat: " + e.getMessage());
+ Log.e(TAG, "startWeixin: " + e.getMessage());
}
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
new file mode 100644
index 0000000..143c16d
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
@@ -0,0 +1,258 @@
+package com.ttstd.dialer.service.main;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+
+import com.hjq.toast.Toaster;
+import com.shehuan.niv.NiceImageView;
+import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.BuildConfig;
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.activity.main.MainActivity;
+import com.ttstd.dialer.config.CommonConfig;
+import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.SystemUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class MainService extends Service {
+ private static final String TAG = "MainService";
+
+ public static final String SHOW_FLOAT_WINDOW_ACTION = "show_float_window";
+ public static final String HIDE_FLOAT_WINDOW_ACTION = "hide_float_window";
+
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
+ private WindowManager mWindowManager;
+
+ private View floatingView;
+ private WindowManager.LayoutParams mLayoutParams;
+ private boolean mFloatWindowShowing = false;
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.e(TAG, "onCreate: ");
+
+ mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
+
+ boolean enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0) == 1;
+ Log.e(TAG, "onCreate: enableFloatWindow = " + enableFloatWindow);
+ if (enableFloatWindow) {
+ showFloatWindow();
+ }
+
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ String action = intent.getAction();
+ Log.e(TAG, "onStartCommand: action = " + action);
+ if (!TextUtils.isEmpty(action)) {
+ switch (action) {
+ case SHOW_FLOAT_WINDOW_ACTION:
+ showFloatWindow();
+ break;
+ case HIDE_FLOAT_WINDOW_ACTION:
+ hideFloatWindow();
+ break;
+ }
+ }
+ }
+ return START_STICKY;
+ }
+
+ private void showFloatWindow() {
+ Log.e(TAG, "showFloatWindow: ");
+ initFloatingView();
+
+ if (Settings.canDrawOverlays(this)) {
+ // 4. 将 View 添加到 WindowManager
+ try {
+ mWindowManager.addView(floatingView, mLayoutParams);
+ mFloatWindowShowing = true;
+ } catch (Exception e) {
+ Log.e(TAG, "onCreate: addView = " + e.getMessage());
+ }
+ }
+
+ }
+
+ private void initFloatingView() {
+ Log.e(TAG, "initFloatingView: ");
+ floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window, null);
+
+ int layoutType;
+ // Android 8.0 (API 26) 及以上需要使用 TYPE_APPLICATION_OVERLAY
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ layoutType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ } else {
+ layoutType = WindowManager.LayoutParams.TYPE_PHONE;
+ }
+
+ mLayoutParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ layoutType,
+ // FLAG_NOT_FOCUSABLE 确保悬浮窗不会拦截原本属于背后应用/桌面的按键事件
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT
+ );
+
+ // 初始化位置
+ mLayoutParams.gravity = Gravity.TOP | Gravity.START;
+ mLayoutParams.x = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_X, 0);
+ mLayoutParams.y = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_Y, 0);
+
+ NiceImageView nvBack = floatingView.findViewById(R.id.nv_back);
+ nvBack.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Log.e(TAG, "onClick: ");
+// stopSelf(); // 停止服务,触发 onDestroy 移除悬浮窗
+// mWindowManager.removeView(floatingView);
+// floatingView = null;
+// mFloatWindowShowing = false;
+ String foreground = SystemUtils.getForegroundActivityPackageName(MainService.this);
+ if (BuildConfig.APPLICATION_ID.equals(foreground)) {
+ int floatWindowKillApp = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
+ if (floatWindowKillApp == 1) {
+ killApp();
+ } else {
+
+ }
+ } else {
+ Intent intent = new Intent(MainService.this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+ }
+ });
+
+ nvBack.setOnTouchListener(new View.OnTouchListener() {
+ private int initialX;
+ private int initialY;
+ private float initialTouchX;
+ private float initialTouchY;
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ Log.e(TAG, "onTouch: ");
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ // 记录初始位置
+ initialX = mLayoutParams.x;
+ initialY = mLayoutParams.y;
+ initialTouchX = event.getRawX();
+ initialTouchY = event.getRawY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ // 计算偏移量并更新位置
+ mLayoutParams.x = initialX + (int) (event.getRawX() - initialTouchX);
+ mLayoutParams.y = initialY + (int) (event.getRawY() - initialTouchY);
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_X, mLayoutParams.x);
+ mMMKV.encode(CommonConfig.FLOAT_WINDOW_Y, mLayoutParams.y);
+ mWindowManager.updateViewLayout(floatingView, mLayoutParams);
+ break;
+ case MotionEvent.ACTION_UP:
+ break;
+ default:
+ }
+ return false;
+ }
+ });
+
+ }
+
+ private void hideFloatWindow() {
+ Log.e(TAG, "hideFloatWindow: ");
+ if (mFloatWindowShowing) {
+ if (floatingView != null) {
+ mWindowManager.removeView(floatingView);
+ floatingView = null;
+ }
+ mFloatWindowShowing = false;
+ }
+ }
+
+ private static final Set mWhiteRunningAppSets = new HashSet() {{
+ this.add(BuildConfig.APPLICATION_ID);
+ this.add("com.tencent.mm");
+ this.add("com.ss.android.ugc.aweme");
+
+ this.add("com.tencent.wetype");
+ this.add("com.tencent.qqpinyin");
+ this.add("com.google.android.inputmethod.pinyin");
+ this.add("com.sohu.inputmethod.sogou");
+ this.add("com.iflytek.inputmethod");
+ this.add("com.baidu.input");
+ }};
+
+ private void killApp() {
+ Log.e(TAG, "killApp: ");
+ List runningPackages = SystemUtils.getRunningTaskPackages(MainService.this);
+ int i = 0;
+ for (String pkg : runningPackages) {
+ if (mWhiteRunningAppSets.contains(pkg)) {
+ continue;
+ }
+ if (ApkUtils.isSystemApp(MainService.this, pkg)) {
+ continue;
+ }
+ i++;
+ SystemUtils.killBackgroundProcesses(MainService.this, pkg);
+ }
+ Toaster.show(String.format(getString(R.string.kill_app_number), i));
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // 务必在服务销毁时移除 View,防止内存泄漏或系统崩溃
+ if (floatingView != null) {
+ mWindowManager.removeView(floatingView);
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onRebind(Intent intent) {
+ super.onRebind(intent);
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/BitmapUtils.java b/app/src/main/java/com/ttstd/dialer/utils/BitmapUtils.java
new file mode 100644
index 0000000..4384360
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/BitmapUtils.java
@@ -0,0 +1,4 @@
+package com.ttstd.dialer.utils;
+
+public class BitmapUtils {
+}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
index af9a545..dae14a0 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
@@ -2,14 +2,47 @@ package com.ttstd.dialer.utils;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.ActivityTaskManager;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.os.Process;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import com.ttstd.dialer.BuildConfig;
+
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
public class SystemUtils {
+ private static final String TAG = "SystemUtils";
/**
* 获取设备序列号
@@ -72,4 +105,364 @@ public class SystemUtils {
}
+ public static String getForegroundActivityPackageName(Context context) {
+ ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
+ List runningTaskInfos = activityManager.getRunningTasks(1);
+ if (runningTaskInfos != null && !runningTaskInfos.isEmpty()) {
+ ComponentName componentName = runningTaskInfos.get(0).topActivity;
+ if (componentName != null) {
+ String currentPackageName = componentName.getPackageName();
+ return currentPackageName;
+ }
+ }
+ return "";
+ }
+
+ /**
+ * 获取栈顶的应用包名
+ */
+ public static String getForegroundActivityClassName(Context context) {
+ ActivityManager manager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
+ String currentClassName = manager.getRunningTasks(1).get(0).topActivity.getClassName();
+ return currentClassName;
+ }
+
+ public static List getRunningTaskPackages(Context context) {
+ ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
+ List runningTaskInfos = activityManager.getRunningTasks(Integer.MAX_VALUE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return runningTaskInfos.stream().map(new Function() {
+ @Override
+ public String apply(ActivityManager.RunningTaskInfo runningTaskInfo) {
+ return runningTaskInfo.topActivity.getPackageName();
+ }
+ }).collect(Collectors.toList());
+ } else {
+ List packageNames = new ArrayList<>();
+ for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {
+ packageNames.add(runningTaskInfo.topActivity.getPackageName());
+ }
+ return packageNames;
+ }
+ }
+
+ public static void killBackgroundProcesses(Context context, String processName) {
+ Log.e(TAG, "killBackgroundProcesses: " + processName);
+ ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ String packageName;
+ try {
+ if (!processName.contains(":")) {
+ packageName = processName;
+ } else {
+ packageName = processName.split(":")[0];
+ }
+ activityManager.killBackgroundProcesses(packageName);
+ activityManager.forceStopPackage(packageName);
+// removeTask(context, processName);
+
+// Method forceStopPackage = activityManager.getClass()
+// .getDeclaredMethod("forceStopPackage", String.class);
+// forceStopPackage.setAccessible(true);
+// forceStopPackage.invoke(activityManager, packageName);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void removeTask(Context context, String packageName) {
+ Log.e(TAG, "removeTask: " + packageName);
+// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
+ List list = getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), getCurrentUserId());
+ HashMap taskMap = new HashMap<>();
+ for (ActivityManager.RecentTaskInfo info : list) {
+ taskMap.put(info.realActivity.getPackageName(), info.id);
+ }
+ try {
+ ActivityManagerNative.getDefault().removeTask(taskMap.get(packageName));
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ Log.e(TAG, "removeTask: " + e.getMessage());
+ } catch (NullPointerException e) {
+ Log.e(TAG, "removeTask: " + e.getMessage());
+ }
+// } else {
+// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+//
+// }
+ }
+
+ /**
+ * @return a list of the recents tasks.
+ * 获取近期任务列表
+ */
+ public static List getRecentTasks(int numTasks, int userId) {
+ try {
+ return ActivityTaskManager.getService().getRecentTasks(numTasks,
+ RECENT_IGNORE_UNAVAILABLE, userId).getList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to get recent tasks " + e);
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * @return the current user's id.
+ * 获取userId
+ */
+ public static int getCurrentUserId() {
+ UserInfo ui;
+ try {
+ ui = ActivityManager.getService().getCurrentUser();
+ return ui != null ? ui.id : 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ public static boolean isDefaultLauncher(Context context, Class> launcherActivityClass) {
+ ComponentName componentName = new ComponentName(context, launcherActivityClass);
+ Log.e(TAG, "isDefaultLauncher: componentName = " + componentName);
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
+ if (resolveInfo != null && resolveInfo.activityInfo != null) {
+ ComponentName defaultComponentName = resolveInfo.getComponentInfo().getComponentName();
+ Log.e(TAG, "isDefaultLauncher: defaultComponentName = " + defaultComponentName);
+ return componentName.equals(defaultComponentName);
+ }
+ return false;
+ }
+
+ /**
+ * 将指定的 Activity 设置为系统默认桌面(需要系统签名)
+ *
+ * @param context 上下文
+ * @param launcherActivityClass 你的桌面 Activity 类,例如 MyLauncherActivity.class
+ */
+ public static void setDefaultLauncher(Context context, Class> launcherActivityClass) {
+ PackageManager pm = context.getPackageManager();
+
+ // 1. 创建桌面过滤的 IntentFilter
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+ filter.addCategory(Intent.CATEGORY_HOME);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+ // 2. 查询当前系统中所有的桌面应用(用来构建 ComponentName 数组)
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ List resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ int bestMatch = 0;
+ ComponentName[] set = new ComponentName[resolveInfos.size()];
+ for (int i = 0; i < resolveInfos.size(); i++) {
+ ResolveInfo info = resolveInfos.get(i);
+ set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ if (info.match > bestMatch) {
+ bestMatch = info.match; // 获取最匹配的值
+ }
+ }
+
+ // 3. 构建你自己的桌面 ComponentName
+ ComponentName myLauncher = new ComponentName(context, launcherActivityClass);
+
+ // 4. 替换首选 Activity(核心步骤)
+ try {
+ // 注意:API 29 (Android 10) 中此方法对第三方应用废弃,但对系统签名应用依然有效
+ pm.replacePreferredActivity(filter, bestMatch, set, myLauncher);
+ Log.i("LauncherHelper", "成功设置为默认桌面");
+
+ // 可选:发送一个回到桌面的 Intent 来验证
+// Intent startHome = new Intent(Intent.ACTION_MAIN);
+// startHome.addCategory(Intent.CATEGORY_HOME);
+// startHome.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+// context.startActivity(startHome);
+
+ } catch (SecurityException e) {
+ Log.e("LauncherHelper", "缺少权限或未生效,请检查系统签名是否正确", e);
+ } catch (Exception e) {
+ Log.e("LauncherHelper", "设置默认桌面失败", e);
+ }
+ }
+
+ public static void setDefaultLauncher(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE);
+
+ // 检查当前应用是否已经是桌面角色持有者
+ if (roleManager != null && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
+
+ // 检查该角色是否可用
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)) {
+ // 创建请求意图
+ Intent roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME);
+ // 注意:在普通应用中,这会弹出系统选择框
+ // 在系统签名应用中,这通常能直接提升优先级或简化流程
+ context.startActivity(roleRequestIntent);
+ }
+ }
+ } else {
+ // Android 10 以下的传统做法:清除当前默认并弹出选择
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ }
+ }
+
+ /**
+ * 将指定包名设置为默认桌面 (需要系统签名)
+ */
+ public static void addRoleHolderAsUser(Context context, String packageName) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+ Log.e(TAG, "RoleManager requires Android 10 or higher.");
+ return;
+ }
+
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ if (roleManager == null) return;
+
+ String roleName = RoleManager.ROLE_HOME;
+
+ // 1. 检查是否已经是当前角色持有者,避免重复调用
+ if (roleManager.isRoleHeld(roleName)) {
+ // 注意:这里最好再判断一下持有的包名是否为目标包名
+ Log.i(TAG, "Role " + roleName + " is already held by some package.");
+ // 如果已经是自己,直接返回
+ }
+
+ try {
+ UserHandle user = Process.myUserHandle();
+ Executor executor = context.getMainExecutor();
+
+ // 定义回调
+ Consumer callback = successful -> {
+ if (successful) {
+ Log.i(TAG, "Successfully set " + packageName + " as " + roleName);
+ } else {
+ Log.e(TAG, "Failed to set " + packageName + " as " + roleName + ". Check system logs.");
+ }
+ };
+
+ // 2. 使用反射调用隐藏方法 addRoleHolderAsUser
+ // 方法签名: addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ Method method = RoleManager.class.getMethod("addRoleHolderAsUser",
+ String.class, String.class, int.class, UserHandle.class, Executor.class, Consumer.class);
+
+ Log.d(TAG, "Invoking addRoleHolderAsUser for package: " + packageName);
+ method.invoke(roleManager, roleName, packageName, 0, user, executor, callback);
+
+// roleManager.addRoleHolderAsUser(roleName, packageName, 0, user, executor, callback);
+
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "Method not found. Is this a non-standard ROM?", e);
+ } catch (Exception e) {
+ Log.e(TAG, "Error invoking addRoleHolderAsUser", e);
+ }
+ }
+
+ public static void setOtherDefaultLauncher(Context context) {
+ PackageManager pm = context.getPackageManager();
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
+ filter.addCategory(Intent.CATEGORY_HOME);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+
+ Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+ homeIntent.addCategory(Intent.CATEGORY_HOME);
+ List resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
+ Optional systemLauncher = resolveInfos.stream().filter(new Predicate() {
+ @Override
+ public boolean test(ResolveInfo resolveInfo) {
+ return !BuildConfig.APPLICATION_ID.equals(resolveInfo.activityInfo.packageName);
+ }
+ }).filter(new Predicate() {
+ @Override
+ public boolean test(ResolveInfo resolveInfo) {
+ return ApkUtils.isSystemApp(context, resolveInfo.activityInfo.packageName);
+ }
+ }).findAny();
+ systemLauncher.ifPresent(resolveInfo -> addRoleHolderAsUser(context, resolveInfo.activityInfo.packageName));
+ }
+
+ /**
+ * 系统签名应用专用:静默获取全屏截图Bitmap
+ *
+ * @param context 上下文
+ * @return 全屏截图Bitmap,失败返回null
+ */
+ public static Bitmap takeFullScreenshot(Context context) {
+ // 获取屏幕真实宽高(包含状态栏、导航栏)
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getRealMetrics(metrics);
+ int screenWidth = metrics.widthPixels;
+ int screenHeight = metrics.heightPixels;
+
+ Log.e(TAG, "takeFullScreenshot: screenWidth " + screenWidth);
+ Log.e(TAG, "takeFullScreenshot: screenHeight " + screenHeight);
+
+ try {
+ // 注意:不同 Android 版本的 SurfaceControl.screenshot 方法签名可能不同
+ // 以下代码适用于 Android 9.0 及以上版本常见的隐藏 API 调用方式
+
+ // 1. 获取屏幕显示的 IBinder (通常为 DisplayControl 或 SurfaceControl 的内部方法)
+ // 在较高版本中,可能需要通过 SurfaceControl.getInternalDisplayToken() 获取
+
+ // 2. 反射调用 screenshot 方法
+ Class> surfaceControlClass = Class.forName("android.view.SurfaceControl");
+
+ // Android 10+ 建议使用新的反射路径,这里以通用逻辑为例:
+ // 对于 Android 11+,Google 引入了 SurfaceControl.LayerCaptureArgs 等内部类
+
+ // 这是一个针对 Android 9/10 的简化逻辑参考:
+ Bitmap bitmap = (Bitmap) surfaceControlClass.getDeclaredMethod("screenshot",
+ Rect.class, Integer.TYPE, Integer.TYPE, Integer.TYPE)
+ .invoke(null, new Rect(), screenWidth, screenHeight, 0);
+
+ return bitmap;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ public static Bitmap takeScreenshotHighVersion() {
+ try {
+ // 1. 获取主屏幕的 Token (Internal Display)
+ // 反射调用 SurfaceControl.getInternalDisplayToken()
+ Method getInternalDisplayTokenMethod = SurfaceControl.class.getDeclaredMethod("getInternalDisplayToken");
+ getInternalDisplayTokenMethod.setAccessible(true);
+ IBinder displayToken = (IBinder) getInternalDisplayTokenMethod.invoke(null);
+
+ if (displayToken == null) return null;
+
+ // 2. 构造 DisplayCaptureArgs.Builder (Android 11+ 的新包装类)
+ Class> builderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");
+ Constructor> builderConstructor = builderClass.getConstructor(IBinder.class);
+ Object builder = builderConstructor.newInstance(displayToken);
+
+ // 可以通过 Builder 设置缩放、格式等,这里直接 build()
+ Method buildMethod = builderClass.getDeclaredMethod("build");
+ Object captureArgs = buildMethod.invoke(builder);
+
+ // 3. 调用 SurfaceControl.screenshot(DisplayCaptureArgs)
+ // 返回值是一个 ScreenshotHardwareBuffer 对象
+ Class> captureArgsClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs");
+ Method screenshotMethod = SurfaceControl.class.getDeclaredMethod("screenshot", captureArgsClass);
+ screenshotMethod.setAccessible(true);
+
+ Object screenshotBuffer = screenshotMethod.invoke(null, captureArgs);
+
+ if (screenshotBuffer == null) return null;
+
+ // 4. 从 ScreenshotHardwareBuffer 中提取 Bitmap
+ Method asBitmapMethod = screenshotBuffer.getClass().getDeclaredMethod("asBitmap");
+ asBitmapMethod.setAccessible(true);
+ return (Bitmap) asBitmapMethod.invoke(screenshotBuffer);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
}
diff --git a/app/src/main/java/com/ttstd/dialer/view/SettingItem.java b/app/src/main/java/com/ttstd/dialer/view/SettingItem.java
new file mode 100644
index 0000000..8169597
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/view/SettingItem.java
@@ -0,0 +1,230 @@
+package com.ttstd.dialer.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+
+import com.ttstd.dialer.R;
+
+import org.jetbrains.annotations.NotNull;
+
+public class SettingItem extends ConstraintLayout {
+
+ private OnClickListener mRootOnClickListener;
+ private ToggleButton.OnToggleChanged mOnToggleChanged;
+
+ private static final String DefaultOptionsText = "设置选项";
+ private static final String DefaultEnableText = "开启描述";
+ private static final String DefaultDisableText = "关闭描述";
+
+ private String mOptionsText = "";
+ private String mEnableText = "";
+ private String mDisableText = "";
+
+ private int mOptionsTextColor = 0xFF000000;
+ private int mHintTextColor = 0xFF9D9D9D;
+ // private boolean mRootClick = false;
+ private boolean mShowHintText = true;
+ private boolean mShowToggle = true;
+ private boolean mShowMore = false;
+ private boolean mLinkage = true;
+ private boolean mShowDivider = true;
+
+ private ConstraintLayout cl_root;
+ private TextView tv_options, tv_hint;
+ private ToggleButton tb;
+ private ImageView iv_more;
+ private View dividerLine;
+
+ public void setRootOnClickListener(OnClickListener rootOnClickListener) {
+ mRootOnClickListener = rootOnClickListener;
+ }
+
+ public void setOnToggleChanged(ToggleButton.OnToggleChanged onToggleChanged) {
+ mOnToggleChanged = onToggleChanged;
+ tb.setOnToggleChanged(mOnToggleChanged);
+ }
+
+ public void setToggleStatu(boolean on) {
+ tb.setToggleStatu(on);
+ if (on) {
+ if (!TextUtils.isEmpty(mEnableText)) {
+ tv_hint.setText(mEnableText);
+ } else {
+ tv_hint.setText(DefaultEnableText);
+ }
+ } else {
+ if (!TextUtils.isEmpty(mDisableText)) {
+ tv_hint.setText(mDisableText);
+ } else {
+ tv_hint.setText(DefaultDisableText);
+ }
+ }
+ requestLayout();
+ }
+
+ public SettingItem(@NonNull @NotNull Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ }
+
+ public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.layout_setting_content_item, this, true);
+
+ cl_root = findViewById(R.id.cl_root);
+ tv_options = findViewById(R.id.tv_options);
+ tv_hint = findViewById(R.id.tv_hint);
+ tb = findViewById(R.id.tb);
+ iv_more = findViewById(R.id.iv_more);
+ dividerLine = findViewById(R.id.divider);
+
+ if (attrs != null) {
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SettingItem);
+ try {
+ mOptionsText = typedArray.getString(R.styleable.SettingItem_optionsText);
+ if (!TextUtils.isEmpty(mOptionsText)) {
+ tv_options.setText(mOptionsText);
+ } else {
+ tv_options.setText(DefaultOptionsText);
+ }
+ int optionsTextColor = typedArray.getColor(R.styleable.SettingItem_optionsTextColor, mOptionsTextColor);
+ tv_options.setTextColor(optionsTextColor);
+
+
+ mEnableText = typedArray.getString(R.styleable.SettingItem_enableText);
+ mDisableText = typedArray.getString(R.styleable.SettingItem_disableText);
+
+ int enableTextColor = typedArray.getColor(R.styleable.SettingItem_enableTextColor, mHintTextColor);
+ tv_hint.setTextColor(enableTextColor);
+
+// boolean rootClick = typedArray.getBoolean(R.styleable.SettingItem_rootClick, mRootClick);
+// if (rootClick) {
+// if (mRootOnClickListener != null) {
+// cl_root.setOnClickListener(mRootOnClickListener);
+// }
+// }
+ mShowHintText = typedArray.getBoolean(R.styleable.SettingItem_showHintText, mShowHintText);
+ mShowToggle = typedArray.getBoolean(R.styleable.SettingItem_showToggle, mShowToggle);
+ mShowMore = typedArray.getBoolean(R.styleable.SettingItem_showMore, mShowMore);
+ mLinkage = typedArray.getBoolean(R.styleable.SettingItem_linkage, mLinkage);
+ mShowDivider = typedArray.getBoolean(R.styleable.SettingItem_dividerLine, mShowDivider);
+ } finally {
+ typedArray.recycle();
+ }
+ } else {
+ tv_options.setText(mOptionsText);
+ tv_hint.setText(mEnableText);
+ tv_options.setTextColor(mOptionsTextColor);
+ tv_hint.setTextColor(mHintTextColor);
+ }
+ if (mShowHintText) {
+ tv_hint.setVisibility(VISIBLE);
+ } else {
+ tv_hint.setVisibility(GONE);
+ }
+ if (mShowToggle) {
+ tb.setVisibility(VISIBLE);
+ } else {
+ tb.setVisibility(GONE);
+ }
+ if (mShowMore) {
+ iv_more.setVisibility(VISIBLE);
+ } else {
+ iv_more.setVisibility(GONE);
+ }
+ if (mShowDivider) {
+ dividerLine.setVisibility(VISIBLE);
+ } else {
+ dividerLine.setVisibility(GONE);
+ }
+
+ if (tb.isToggleOn()) {
+ if (!TextUtils.isEmpty(mEnableText)) {
+ tv_hint.setText(mEnableText);
+ } else {
+ tv_hint.setText(DefaultEnableText);
+ }
+ } else {
+ if (!TextUtils.isEmpty(mDisableText)) {
+ tv_hint.setText(mDisableText);
+ } else {
+ tv_hint.setText(DefaultDisableText);
+ }
+ }
+ tb.setOnToggleInsideChanged(new ToggleButton.OnToggleInsideChanged() {
+ @Override
+ public void onToggle(boolean on) {
+ if (on) {
+ if (!TextUtils.isEmpty(mEnableText)) {
+ tv_hint.setText(mEnableText);
+ } else {
+ tv_hint.setText(DefaultEnableText);
+ }
+ } else {
+ if (!TextUtils.isEmpty(mDisableText)) {
+ tv_hint.setText(mDisableText);
+ } else {
+ tv_hint.setText(DefaultDisableText);
+ }
+ }
+ requestLayout();
+ }
+ });
+ if (mRootOnClickListener != null) {
+ cl_root.setOnClickListener(mRootOnClickListener);
+ }
+ if (mLinkage) {
+ cl_root.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (tb.isToggleOn()) {
+ tb.toggleOff();
+ } else {
+ tb.toggleOn();
+ }
+ }
+ });
+ }
+
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ }
+}
+
diff --git a/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java b/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java
new file mode 100644
index 0000000..64a1526
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java
@@ -0,0 +1,389 @@
+package com.ttstd.dialer.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+
+import com.facebook.rebound.SimpleSpringListener;
+import com.facebook.rebound.Spring;
+import com.facebook.rebound.SpringConfig;
+import com.facebook.rebound.SpringSystem;
+import com.facebook.rebound.SpringUtil;
+import com.ttstd.dialer.R;
+
+public class ToggleButton extends View {
+ private static final String TAG = "ToggleButton";
+ private SpringSystem springSystem;
+ private Spring spring;
+ /**
+ *
+ */
+ private float radius;
+ /**
+ * 开启颜色
+ */
+ private int onColor = Color.parseColor("#246FFE");
+ /**
+ * 关闭颜色
+ */
+ private int offBorderColor = Color.parseColor("#c7c7c7");
+ /**
+ * 灰色带颜色
+ */
+ private int offColor = Color.parseColor("#ffffff");
+ /**
+ * 手柄颜色
+ */
+ private int spotColor = Color.parseColor("#ffffff");
+ /**
+ * 边框颜色
+ */
+ private int borderColor = offBorderColor;
+ /**
+ * 画笔
+ */
+ private Paint paint;
+ /**
+ * 开关状态
+ */
+ private boolean toggleOn = false;
+ /**
+ * 边框大小
+ */
+ private int borderWidth = 2;
+ /**
+ * 垂直中心
+ */
+ private float centerY;
+ /**
+ * 按钮的开始和结束位置
+ */
+ private float startX, endX;
+ /**
+ * 手柄X位置的最小和最大值
+ */
+ private float spotMinX, spotMaxX;
+ /**
+ * 手柄大小
+ */
+ private int spotSize;
+ /**
+ * 手柄X位置
+ */
+ private float spotX;
+ /**
+ * 关闭时内部灰色带高度
+ */
+ private float offLineWidth;
+ /**
+ *
+ */
+ private RectF rect = new RectF();
+ /**
+ * 默认使用动画
+ */
+ private boolean defaultAnimate = true;
+ /**
+ * 是否默认处于打开状态
+ */
+ private boolean isDefaultOn = false;
+ /**
+ * 禁止点击
+ */
+ private boolean disable = false;
+
+ private OnToggleChanged listener;
+
+ public void setDisable(boolean dis) {
+ this.disable = dis;
+ }
+
+ private ToggleButton(Context context) {
+ super(context);
+ }
+
+ public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setup(attrs);
+ }
+
+ public ToggleButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setup(attrs);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ spring.removeListener(springListener);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ spring.addListener(springListener);
+ }
+
+ public void setup(AttributeSet attrs) {
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ springSystem = SpringSystem.create();
+ spring = springSystem.createSpring();
+ //张力(tension),摩擦力(friction)
+ //增大张力会使弹簧更快地向目标值运动,减小摩擦力会减少弹簧运动过程中的阻力,从而使回弹更加迅速和有力。
+ spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(80, 10));
+ this.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View arg0) {
+ if (disable) {
+
+ } else {
+ toggle(defaultAnimate);
+ }
+ }
+ });
+ TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ToggleButton);
+ offBorderColor = typedArray.getColor(R.styleable.ToggleButton_tbOffBorderColor, offBorderColor);
+ onColor = typedArray.getColor(R.styleable.ToggleButton_tbOnColor, onColor);
+ spotColor = typedArray.getColor(R.styleable.ToggleButton_tbSpotColor, spotColor);
+ offColor = typedArray.getColor(R.styleable.ToggleButton_tbOffColor, offColor);
+ borderWidth = typedArray.getDimensionPixelSize(R.styleable.ToggleButton_tbBorderWidth, borderWidth);
+ defaultAnimate = typedArray.getBoolean(R.styleable.ToggleButton_tbAnimate, defaultAnimate);
+ isDefaultOn = typedArray.getBoolean(R.styleable.ToggleButton_tbAsDefaultOn, isDefaultOn);
+ typedArray.recycle();
+ borderColor = offBorderColor;
+ if (isDefaultOn) {
+ toggleOn();
+ }
+ }
+
+ public void toggle() {
+ toggle(true);
+ }
+
+ public void toggle(boolean animate) {
+ toggleOn = !toggleOn;
+ Log.e(TAG, "toggle: toggleOn = " + toggleOn);
+ takeEffect(animate);
+ if (listener != null) {
+ listener.onToggle(toggleOn);
+ }
+ if (mOnToggleInsideChanged != null) {
+ mOnToggleInsideChanged.onToggle(toggleOn);
+ }
+ }
+
+ public void toggleOn() {
+ setToggleOn();
+ if (listener != null) {
+ listener.onToggle(toggleOn);
+ }
+ if (mOnToggleInsideChanged != null) {
+ mOnToggleInsideChanged.onToggle(toggleOn);
+ }
+ }
+
+ public void toggleOff() {
+ setToggleOff();
+ if (listener != null) {
+ listener.onToggle(toggleOn);
+ }
+ if (mOnToggleInsideChanged != null) {
+ mOnToggleInsideChanged.onToggle(toggleOn);
+ }
+ }
+
+ public void setToggleStatu(boolean on) {
+ if (on) {
+ setToggleOn();
+ } else {
+ setToggleOff();
+ }
+ }
+
+ /**
+ * 设置显示成打开样式,不会触发toggle事件
+ */
+ public void setToggleOn() {
+ setToggleOn(true);
+ }
+
+ /**
+ * @param animate asd
+ */
+ public void setToggleOn(boolean animate) {
+ toggleOn = true;
+ takeEffect(animate);
+ }
+
+ /**
+ * 设置显示成关闭样式,不会触发toggle事件
+ */
+ public void setToggleOff() {
+ setToggleOff(true);
+ }
+
+ public void setToggleOff(boolean animate) {
+ toggleOn = false;
+ takeEffect(animate);
+ }
+
+
+ public int getToggleOnStatu() {
+ Log.e(TAG, "getToggleOnStatu: " + toggleOn);
+ return toggleOn ? 1 : 0;
+ }
+
+ public boolean isToggleOn() {
+ Log.e(TAG, "isToggleOn: " + toggleOn);
+ return toggleOn;
+ }
+
+ private void takeEffect(boolean animate) {
+ if (animate) {
+ spring.setEndValue(toggleOn ? 1 : 0);
+ } else {
+//这里没有调用spring,所以spring里的当前值没有变更,这里要设置一下,同步两边的当前值
+ spring.setCurrentValue(toggleOn ? 1 : 0);
+ calculateEffect(toggleOn ? 1 : 0);
+ }
+ }
+
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ Resources r = Resources.getSystem();
+ if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
+ widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
+ }
+ if (heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST) {
+ heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right,
+ int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ final int width = getWidth();
+ final int height = getHeight();
+ radius = Math.min(width, height) * 0.5f;
+ centerY = radius;
+ startX = radius;
+ endX = width - radius;
+ spotMinX = startX + borderWidth;
+ spotMaxX = endX - borderWidth;
+ spotSize = height - 4 * borderWidth;
+ spotX = toggleOn ? spotMaxX : spotMinX;
+ offLineWidth = 0;
+ }
+
+ SimpleSpringListener springListener = new SimpleSpringListener() {
+ @Override
+ public void onSpringUpdate(Spring spring) {
+ final double value = spring.getCurrentValue();
+ calculateEffect(value);
+ }
+ };
+
+ private int clamp(int value, int low, int high) {
+ return Math.min(Math.max(value, low), high);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+//
+ super.draw(canvas);
+ rect.set(0, 0, getWidth(), getHeight());
+ paint.setColor(borderColor);
+ canvas.drawRoundRect(rect, radius, radius, paint);
+ if (offLineWidth > 0) {
+ final float cy = offLineWidth * 0.5f;
+ rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
+// paint.setColor(offColor);
+ canvas.drawRoundRect(rect, cy, cy, paint);
+ }
+ rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
+ paint.setColor(borderColor);
+ canvas.drawRoundRect(rect, radius, radius, paint);
+ final float spotR = spotSize * 0.5f;
+ rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
+ paint.setColor(spotColor);
+ canvas.drawRoundRect(rect, spotR, spotR, paint);
+ }
+
+ /**
+ * @param value
+ */
+ private void calculateEffect(final double value) {
+ final float mapToggleX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
+ spotX = mapToggleX;
+ float mapOffLineWidth = (float) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
+ offLineWidth = mapOffLineWidth;
+ final int fb = Color.blue(onColor);
+ final int fr = Color.red(onColor);
+ final int fg = Color.green(onColor);
+ final int tb = Color.blue(offBorderColor);
+ final int tr = Color.red(offBorderColor);
+ final int tg = Color.green(offBorderColor);
+ int sb = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
+ int sr = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
+ int sg = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);
+ sb = clamp(sb, 0, 255);
+ sr = clamp(sr, 0, 255);
+ sg = clamp(sg, 0, 255);
+ borderColor = Color.rgb(sr, sg, sb);
+ postInvalidate();
+ }
+
+ /**
+ * @author ThinkPad
+ */
+ public interface OnToggleChanged {
+ /**
+ * @param on = =
+ */
+ public void onToggle(boolean on);
+ }
+
+ public interface OnToggleInsideChanged {
+ /**
+ * @param on = =
+ */
+ void onToggle(boolean on);
+ }
+
+ private OnToggleInsideChanged mOnToggleInsideChanged;
+
+ public void setOnToggleInsideChanged(OnToggleInsideChanged onToggleChanged) {
+ mOnToggleInsideChanged = onToggleChanged;
+ }
+
+ public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
+ listener = onToggleChanged;
+ }
+
+ public boolean isAnimate() {
+ return defaultAnimate;
+ }
+
+ public void setAnimate(boolean animate) {
+ this.defaultAnimate = animate;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/icon_more.png b/app/src/main/res/drawable-hdpi/icon_more.png
new file mode 100644
index 0000000..e653dcf
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/icon_more.png differ
diff --git a/app/src/main/res/drawable-xhdpi/not_applicable.png b/app/src/main/res/drawable-xhdpi/not_applicable.png
deleted file mode 100644
index 4ce8569..0000000
Binary files a/app/src/main/res/drawable-xhdpi/not_applicable.png and /dev/null differ
diff --git a/app/src/main/res/drawable/call_phone_selector.xml b/app/src/main/res/drawable/call_phone_selector.xml
new file mode 100644
index 0000000..ea6fea9
--- /dev/null
+++ b/app/src/main/res/drawable/call_phone_selector.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/contact_edit_text_focused.xml b/app/src/main/res/drawable/contact_edit_text_focused.xml
new file mode 100644
index 0000000..d00f075
--- /dev/null
+++ b/app/src/main/res/drawable/contact_edit_text_focused.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/contact_edit_text_normal.xml b/app/src/main/res/drawable/contact_edit_text_normal.xml
new file mode 100644
index 0000000..4fc7f83
--- /dev/null
+++ b/app/src/main/res/drawable/contact_edit_text_normal.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/contact_edit_text_pressed.xml b/app/src/main/res/drawable/contact_edit_text_pressed.xml
new file mode 100644
index 0000000..7781267
--- /dev/null
+++ b/app/src/main/res/drawable/contact_edit_text_pressed.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/contact_edit_text_selector.xml b/app/src/main/res/drawable/contact_edit_text_selector.xml
new file mode 100644
index 0000000..64a9fef
--- /dev/null
+++ b/app/src/main/res/drawable/contact_edit_text_selector.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/default_negative_background.xml b/app/src/main/res/drawable/default_negative_background.xml
index aba0358..d7e663a 100644
--- a/app/src/main/res/drawable/default_negative_background.xml
+++ b/app/src/main/res/drawable/default_negative_background.xml
@@ -3,11 +3,11 @@
-
+
+ android:bottom="@dimen/dp_4"
+ android:left="@dimen/dp_20"
+ android:right="@dimen/dp_20"
+ android:top="@dimen/dp_4" />
\ No newline at end of file
diff --git a/app/src/main/res/drawable/default_positive_background.xml b/app/src/main/res/drawable/default_positive_background.xml
index 3f8e31c..bcec8fc 100644
--- a/app/src/main/res/drawable/default_positive_background.xml
+++ b/app/src/main/res/drawable/default_positive_background.xml
@@ -3,11 +3,11 @@
-
+
+ android:bottom="@dimen/dp_4"
+ android:left="@dimen/dp_20"
+ android:right="@dimen/dp_20"
+ android:top="@dimen/dp_4" />
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..4cdb27e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_confirm.xml b/app/src/main/res/drawable/ic_confirm.xml
new file mode 100644
index 0000000..1984bad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_confirm.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_default_avatar.xml b/app/src/main/res/drawable/ic_default_avatar.xml
index 7a09e7b..7db8344 100644
--- a/app/src/main/res/drawable/ic_default_avatar.xml
+++ b/app/src/main/res/drawable/ic_default_avatar.xml
@@ -4,9 +4,6 @@
android:viewportWidth="1024"
android:viewportHeight="1024">
-
+ android:pathData="M512,64C264.8,64 64,264.8 64,512s200.8,448 448,448 448,-200.8 448,-448S759.2,64 512,64zM384.8,376c4,-64 56,-115.2 120,-119.2 74.4,-4 135.2,55.2 135.2,128 0,70.4 -57.6,128 -128,128 -73.6,0 -132,-62.4 -127.2,-136.8zM768,746.4c0,12 -9.6,21.6 -21.6,21.6H278.4c-12,0 -21.6,-9.6 -21.6,-21.6v-64c0,-84.8 170.4,-128 255.2,-128 84.8,0 255.2,42.4 255.2,128l0.8,64z"
+ android:fillColor="#cdcdcd"/>
diff --git a/app/src/main/res/drawable/ic_dialer.xml b/app/src/main/res/drawable/ic_dialer.xml
new file mode 100644
index 0000000..0fe8edc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dialer.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_home_float_window.xml b/app/src/main/res/drawable/ic_home_float_window.xml
new file mode 100644
index 0000000..e0f1065
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home_float_window.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_not_applicable.xml b/app/src/main/res/drawable/ic_not_applicable.xml
new file mode 100644
index 0000000..0718ee7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_not_applicable.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_return.xml b/app/src/main/res/drawable/ic_return.xml
new file mode 100644
index 0000000..cea2b37
--- /dev/null
+++ b/app/src/main/res/drawable/ic_return.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/settings_card_bg.xml b/app/src/main/res/drawable/settings_card_bg.xml
new file mode 100644
index 0000000..00259f8
--- /dev/null
+++ b/app/src/main/res/drawable/settings_card_bg.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/weather_card_background.xml b/app/src/main/res/drawable/weather_card_background.xml
new file mode 100644
index 0000000..5d72803
--- /dev/null
+++ b/app/src/main/res/drawable/weather_card_background.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wechat_call_normal.xml b/app/src/main/res/drawable/wechat_call_normal.xml
new file mode 100644
index 0000000..13e0896
--- /dev/null
+++ b/app/src/main/res/drawable/wechat_call_normal.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wechat_call_video.xml b/app/src/main/res/drawable/wechat_call_video.xml
index 7d13fb6..ee41800 100644
--- a/app/src/main/res/drawable/wechat_call_video.xml
+++ b/app/src/main/res/drawable/wechat_call_video.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/app/src/main/res/layout/activity_app_list.xml b/app/src/main/res/layout/activity_app_list.xml
index a6ca4f4..ae94b40 100644
--- a/app/src/main/res/layout/activity_app_list.xml
+++ b/app/src/main/res/layout/activity_app_list.xml
@@ -5,6 +5,7 @@
tools:context=".activity.app.AppListActivity">
+
@@ -14,10 +15,46 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/bar" />
diff --git a/app/src/main/res/layout/activity_contact_add.xml b/app/src/main/res/layout/activity_contact_add.xml
index 94655b6..88b2aac 100644
--- a/app/src/main/res/layout/activity_contact_add.xml
+++ b/app/src/main/res/layout/activity_contact_add.xml
@@ -15,27 +15,77 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/bar">
+
+
+ android:layout_height="@dimen/dp_64"
+ android:layout_marginTop="@dimen/dp_8">
-
@@ -43,12 +93,13 @@
+ android:layout_height="@dimen/dp_64"
+ android:layout_marginTop="@dimen/dp_8">
-
@@ -77,13 +123,14 @@
+
+
+
+
+
+
+
+
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_contact_edit.xml b/app/src/main/res/layout/activity_contact_edit.xml
index fdc4c77..046dad0 100644
--- a/app/src/main/res/layout/activity_contact_edit.xml
+++ b/app/src/main/res/layout/activity_contact_edit.xml
@@ -1,6 +1,7 @@
@@ -13,5 +14,47 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_contact_list.xml b/app/src/main/res/layout/activity_contact_list.xml
index 7923fea..22cb149 100644
--- a/app/src/main/res/layout/activity_contact_list.xml
+++ b/app/src/main/res/layout/activity_contact_list.xml
@@ -11,27 +11,69 @@
type="com.ttstd.dialer.activity.contact.list.ContactListActivity.BtnClick" />
-
-
+ android:layout_height="@dimen/dp_48"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..1e4326f
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings_call.xml b/app/src/main/res/layout/activity_settings_call.xml
new file mode 100644
index 0000000..585a792
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings_call.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings_utils.xml b/app/src/main/res/layout/activity_settings_utils.xml
new file mode 100644
index 0000000..ce51acc
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings_utils.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_template.xml b/app/src/main/res/layout/activity_template.xml
new file mode 100644
index 0000000..5c7de6a
--- /dev/null
+++ b/app/src/main/res/layout/activity_template.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_weather_main.xml b/app/src/main/res/layout/activity_weather_main.xml
index fcb4995..d11ab62 100644
--- a/app/src/main/res/layout/activity_weather_main.xml
+++ b/app/src/main/res/layout/activity_weather_main.xml
@@ -17,104 +17,108 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent">
+ app:layout_constraintTop_toTopOf="parent">
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/dp_8"
+ android:background="@drawable/weather_card_background" />
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/dp_8"
+ android:background="@drawable/weather_card_background" />
diff --git a/app/src/main/res/layout/fragment_call.xml b/app/src/main/res/layout/fragment_call.xml
index afce9f3..5b37601 100644
--- a/app/src/main/res/layout/fragment_call.xml
+++ b/app/src/main/res/layout/fragment_call.xml
@@ -20,10 +20,10 @@
@@ -39,14 +39,13 @@
+ android:layout_height="@dimen/dp_56">
+ android:layout_height="@dimen/dp_56"
+ android:layout_marginTop="@dimen/dp_8">
+ android:layout_height="@dimen/dp_56"
+ android:layout_marginTop="@dimen/dp_8">
+ android:layout_height="@dimen/dp_56"
+ android:layout_marginTop="@dimen/dp_8">
+
@@ -29,9 +29,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
- android:minEms="4"
- android:textColor="@color/white"
- android:textSize="@dimen/sp_18"
+ android:minEms="3"
+ android:textColor="@color/black"
+ android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -39,11 +39,12 @@
@@ -51,8 +52,8 @@
@@ -60,10 +61,9 @@
android:id="@+id/tv_temp_min"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
- android:textColor="@color/white"
- android:textSize="@dimen/sp_18"
+ android:textColor="@color/black"
+ android:textSize="@dimen/sp_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -74,8 +74,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
- android:textColor="@color/white"
- android:textSize="@dimen/sp_18"
+ android:textColor="@color/black"
+ android:textSize="@dimen/sp_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/layout/layout_floating_window.xml b/app/src/main/res/layout/layout_floating_window.xml
new file mode 100644
index 0000000..2bb944f
--- /dev/null
+++ b/app/src/main/res/layout/layout_floating_window.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_setting_content_item.xml b/app/src/main/res/layout/layout_setting_content_item.xml
new file mode 100644
index 0000000..3b81a54
--- /dev/null
+++ b/app/src/main/res/layout/layout_setting_content_item.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-sw1280dp/dimens.xml b/app/src/main/res/values-sw1280dp/dimens.xml
index 0d70635..3760743 100644
--- a/app/src/main/res/values-sw1280dp/dimens.xml
+++ b/app/src/main/res/values-sw1280dp/dimens.xml
@@ -2,6 +2,7 @@
71.1111sp
28.4444dp
+
-213.3333dp
-106.6667dp
-71.1111dp
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..3ca73e9
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index bf50e31..d98edcb 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,14 +1,17 @@
- #6200EE
- #3700B3
- #03DAC5
+ #2196F3
+ #3F51B5
+ #03A9F4
#99bd2f25
#BD2F25
- #F8CBBD
+ #ECECEC
+ #000000
+ #000000
+ #9D9D9D
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index a93e9c3..94220af 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -3,6 +3,12 @@
20sp
8dp
+ 80dp
+ 18sp
+ 15sp
+ 2dp
+
+
-60dp
-30dp
-20dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5607ceb..fda7f5a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,6 +5,45 @@
Hello blank fragment
com.ttstd.dialer.view.ScrollAwareFABBehavior
+ 共清理了%d个应用
+
❤拨号助手无障碍服务👈🏻
使用拨号助手一键拨号
+
+
+ 开启快捷通话
+ 已开启,快捷联系人通话类型
+ 未开启,快捷联系人通话类型
+
+ 来电语音播报
+ 已开启,电话呼入时语音播报联系人
+ 未开启,电话呼入时语音播报联系人
+
+ 微信自动接听
+ 已开启,自动接听视频和语音
+ 未开启,自动接听视频和语音
+
+ 自动开启免提
+ 已开启,自动开启免提
+ 未开启,自动开启免提
+
+ 微信自动拨打视频
+ 已开启,无障碍模式实现微信一键通话
+ 未开启,无障碍模式实现微信一键通话
+
+
+
+ 开启悬浮按钮
+ 已开启,点小圆点可以直接返回桌面
+ 未开启,点小圆点可以直接返回桌面
+
+ 悬浮按钮清理内存
+ 已开启,在桌面时点击悬浮窗清理内存
+ 未开启,在桌面时点击悬浮窗清理内存
+
+ 设置为默认桌面
+ 已设置为默认桌面
+ 未设置为默认桌面
+
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 4e026d6..552a77e 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -13,6 +13,8 @@
- @color/colorPrimary
- @color/colorPrimaryDark
- @color/colorAccent
+ - @color/default_background_color
+
diff --git a/build.gradle b/build.gradle
index bcba65d..d8b82d9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -42,6 +42,12 @@ allprojects {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/google' }
}
+
+ gradle.projectsEvaluated {
+ tasks.withType(JavaCompile) {
+ options.compilerArgs.add('-Xbootclasspath/p:app/libs/framework.jar')
+ }
+ }
}
task clean(type: Delete) {