diff --git a/app/build.gradle b/app/build.gradle
index 1c2fc3a..5c8d7c1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,7 +16,7 @@ android {
applicationId "com.ttstd.dialer"
//There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature.
- minSdkVersion 23
+ minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -25,6 +25,8 @@ android {
vectorDrawables.useSupportLibrary = true
+ multiDexEnabled true
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@@ -194,6 +196,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
//2.0.4以上无法预览
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.recyclerview:recyclerview:1.2.1"
// For control over item selection of both touch and mouse driven selection
implementation "androidx.recyclerview:recyclerview-selection:1.1.0"
@@ -210,6 +213,8 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata:2.3.0"
implementation "androidx.lifecycle:lifecycle-runtime:2.3.0"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.3.0"
+ // LifecycleService 核心库
+ implementation "androidx.lifecycle:lifecycle-service:2.3.0"
// 添加这行,使用 BOM 统一 Kotlin 相关库的版本
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.10"))
@@ -222,7 +227,7 @@ dependencies {
//glide
implementation 'com.github.bumptech.glide:glide:4.13.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.1'
- // SVG支持库
+ // SVG支持库f
implementation 'com.caverock:androidsvg:1.4'
// 如果需要,添加Glide SVG模块(社区版)
// implementation 'com.github.qoqa:glide-svg:2.0.4'
@@ -231,7 +236,8 @@ dependencies {
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
//
- implementation 'com.squareup.okhttp3:okhttp:4.7.0'
+ implementation 'com.squareup.okhttp3:okhttp:4.9.0'
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
@@ -266,6 +272,7 @@ dependencies {
//推送
//阿里云
implementation 'com.aliyun.ams:alicloud-android-push:3.9.3'
+
//极光
// 此处以JPush 5.6.0 版本为例,注意:从 5.0.0 版本开始可以自动拉取 JCore 包,无需另外配置
implementation "cn.jiguang.sdk:jpush:$jpush.version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2874ff8..9567e3a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,12 @@
package="com.ttstd.dialer"
android:sharedUserId="android.uid.system">
+
+
+
+
+
+
@@ -15,7 +21,8 @@
-
+
+
@@ -48,9 +55,9 @@
diff --git a/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java
index 35c7e94..980ca60 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java
@@ -1,9 +1,6 @@
package com.ttstd.dialer.activity.app;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.util.Log;
import androidx.lifecycle.MutableLiveData;
@@ -13,21 +10,16 @@ import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityAppListBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
-import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.Logger;
-import java.text.Collator;
-import java.util.Comparator;
import java.util.List;
-import java.util.Locale;
import java.util.concurrent.Callable;
-import java.util.stream.Collectors;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
-import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class AppListViewModel extends BaseViewModel {
@@ -55,23 +47,23 @@ public class AppListViewModel extends BaseViewModel>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("getDbAppList", "onSubscribe: ");
+ Logger.e("getDbAppList", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List appInfos) {
- Log.e("getDbAppList", "onNext: " + appInfos);
+ Logger.e("getDbAppList", "onNext: " + appInfos);
mDesktopSortAppData.setValue(appInfos);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("getDbAppList", "onError: " + e.getMessage());
+ Logger.e("getDbAppList", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("getDbAppList", "onComplete: ");
+ Logger.e("getDbAppList", "onComplete: ");
}
});
}
@@ -90,23 +82,23 @@ public class AppListViewModel extends BaseViewModel() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("updateAppInfo", "onSubscribe: ");
+ Logger.e("updateAppInfo", "onSubscribe: ");
}
@Override
public void onNext(@NonNull Integer row) {
- Log.e("updateAppInfo", "onNext: " + row);
+ Logger.e("updateAppInfo", "onNext: " + row);
mAppUpdateData.setValue(row);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("updateAppInfo", "onError: " + e.getMessage());
+ Logger.e("updateAppInfo", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("updateAppInfo", "onComplete: ");
+ Logger.e("updateAppInfo", "onComplete: ");
}
});
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/add/ContactAddViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/contact/add/ContactAddViewModel.java
index 23875eb..717c6ea 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/contact/add/ContactAddViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/add/ContactAddViewModel.java
@@ -1,7 +1,6 @@
package com.ttstd.dialer.activity.contact.add;
import android.content.Context;
-import android.util.Log;
import androidx.lifecycle.MutableLiveData;
@@ -11,6 +10,7 @@ import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityContactAddBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.utils.Logger;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
@@ -39,7 +39,7 @@ public class ContactAddViewModel extends BaseViewModel emitter) throws Throwable {
int count = mRepository.getTotalCount() + 1;
ContactInfo contactInfo = new ContactInfo(name, phone, count);
- Log.e(TAG, "saveContact: " + contactInfo);
+ Logger.e(TAG, "saveContact: " + contactInfo);
emitter.onNext(mRepository.insert(contactInfo));
emitter.onComplete();
}
@@ -49,23 +49,23 @@ public class ContactAddViewModel extends BaseViewModel() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("saveContact", "onSubscribe: ");
+ Logger.e("saveContact", "onSubscribe: ");
}
@Override
public void onNext(@NonNull Long aLong) {
- Log.e("saveContact", "onNext: " + aLong);
+ Logger.e("saveContact", "onNext: " + aLong);
mIntegerMutableLiveData.setValue(aLong);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("saveContact", "onError: " + e.getMessage());
+ Logger.e("saveContact", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("saveContact", "onComplete: ");
+ Logger.e("saveContact", "onComplete: ");
}
});
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
index 1fa759b..f5a9541 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
@@ -2,7 +2,6 @@ package com.ttstd.dialer.activity.contact.list;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
@@ -17,6 +16,7 @@ import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivityContactListBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.fragment.dialog.call.CallFragment;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.ItemTouchHelperCallback;
import java.util.List;
@@ -59,7 +59,7 @@ public class ContactListActivity extends BaseMvvmActivity>() {
@Override
public void onChanged(List contactInfos) {
- Log.e(TAG, "mContactListData: " + contactInfos);
+ Logger.e(TAG, "mContactListData: " + contactInfos);
mContactInfos = contactInfos;
mContactInfoAdapter.setContactInfos(mContactInfos);
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListViewModel.java
index ece0a01..67a6201 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListViewModel.java
@@ -1,7 +1,6 @@
package com.ttstd.dialer.activity.contact.list;
import android.content.Context;
-import android.util.Log;
import androidx.lifecycle.MutableLiveData;
@@ -11,6 +10,7 @@ import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityContactListBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.utils.Logger;
import java.util.List;
@@ -49,12 +49,12 @@ public class ContactListViewModel extends BaseViewModel>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("getAllContacts", "onSubscribe: ");
+ Logger.e("getAllContacts", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List contactInfos) {
- Log.e("getAllContacts", "onNext: ");
+ Logger.e("getAllContacts", "onNext: ");
mContactListData.setValue(contactInfos);
// List sorted = contacts.stream().sorted(new Comparator() {
@@ -68,18 +68,18 @@ public class ContactListViewModel extends BaseViewModel() {
@Override
public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
@@ -93,22 +93,22 @@ public class ContactListViewModel extends BaseViewModel() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("updateItemPosition", "onComplete: ");
+ Logger.e("updateItemPosition", "onComplete: ");
}
@Override
public void onNext(@NonNull Integer integer) {
- Log.e("updateItemPosition", "onNext: " + integer);
+ Logger.e("updateItemPosition", "onNext: " + integer);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("updateItemPosition", "onError: " + e.getMessage());
+ Logger.e("updateItemPosition", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("updateItemPosition", "onComplete: ");
+ Logger.e("updateItemPosition", "onComplete: ");
}
});
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java b/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
index b63a2ef..f7fe0a9 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.util.Log;
+import android.view.KeyEvent;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -22,6 +22,7 @@ import com.ttstd.dialer.fragment.contact.ContactFragment;
import com.ttstd.dialer.fragment.home.HomeFragment;
import com.ttstd.dialer.fragment.settings.SettingsFragment;
import com.ttstd.dialer.service.main.MainService;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.ApkPagerAdapter;
import com.ttstd.dialer.view.ScaleCircleNavigator;
@@ -124,7 +125,7 @@ public class MainActivity extends BaseMvvmActivity appInfos) {
- Log.e("getOutsideApp", "onNext: " + appInfos);
+ Logger.e("getOutsideApp", "onNext: " + appInfos);
mDesktopSortAppData.setValue(appInfos);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("getOutsideApp", "onError: " + e.getMessage());
+ Logger.e("getOutsideApp", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("getOutsideApp", "onComplete: ");
+ Logger.e("getOutsideApp", "onComplete: ");
}
});
}
+
}
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
index 6f74ad9..87e333a 100644
--- 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
@@ -42,6 +42,7 @@ public class SettingsActivity extends BaseMvvmActivity {
+ public void snRegister() {
+ OkHttpManager.getInstance().getSnRegisterObservable(getLifecycle())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e("snRegister", "onSubscribe: ");
+ }
+
+ @Override
+ public void onNext(@NonNull BaseResponse baseResponse) {
+ Logger.e("snRegister", "onNext: " + baseResponse);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e("snRegister", "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e("snRegister", "onComplete: ");
+ }
+ });
+
+
+ }
}
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
index 60bae6c..0e17582 100644
--- 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
@@ -2,7 +2,7 @@ package com.ttstd.dialer.activity.settings.utils;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.util.Log;
+import android.graphics.BitmapFactory;
import android.view.View;
import com.tencent.mmkv.MMKV;
@@ -13,9 +13,21 @@ 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.CameraUtil;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.SystemUtils;
import com.ttstd.dialer.view.ToggleButton;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.internal.observers.BlockingBaseObserver;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+
public class SettingsUtilsActivity extends BaseMvvmActivity {
private static final String TAG = "SettingsActivity";
@@ -97,15 +109,15 @@ public class SettingsUtilsActivity extends BaseMvvmActivity() {
+ @Override
+ public void onNext(@NonNull Integer integer) {
+ if (integer == 0) {
+ Bitmap bitmap = BitmapFactory.decodeFile(filepath);
+ mViewDataBinding.ivSnap.setImageBitmap(bitmap);
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+
+ }
+ });
}
+
+ public void getCameraSnap(View view) {
+ long createTime = System.currentTimeMillis() / 1000;
+ CameraUtil cameraUtil = new CameraUtil(SettingsUtilsActivity.this, new CameraUtil.CameraCallBack() {
+ @Override
+ public void onErr(String msg) {
+ Logger.e("CameraUtil", "onErr: " + msg);
+ }
+
+ @Override
+ public void onTakePhotoOk(String path) {
+ Logger.e("CameraUtil", "onTakePhotoOk: " + path);
+ File file = new File(path);
+ Bitmap bitmap = BitmapFactory.decodeFile(path);
+ mViewDataBinding.ivCamera.setImageBitmap(bitmap);
+
+ MediaType mediaType = MediaType.Companion.parse("image/png");
+ RequestBody fileBody = RequestBody.Companion.create(file, mediaType);
+ //设置一个file文件
+ MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
+ Map params = new HashMap<>();
+ params.put("sn", SystemUtils.getSerial());
+ params.put("createtime", String.valueOf(createTime));
+// Call call = NetInterfaceManager.getInstance().getScreenshotCall().sendScreenshot(params, body);
+// call.enqueue(new RetryCallback(call, 10, 30 * 1000) {
+// @Override
+// public void onRequestResponse(Call call, Response response) {
+// Logger.e(TAG, "onRequestResponse: " + response.body().toString());
+// }
+//
+// @Override
+// public void onRequestFail(Call call, Throwable t) {
+// Logger.e(TAG, "onRequestFail: ");
+// }
+//
+// @Override
+// public void onStartRetry() {
+// Logger.e(TAG, "onStartRetry: ");
+// }
+// });
+
+ }
+ });
+ cameraUtil.startTakePicture(getExternalCacheDir().getAbsolutePath() + File.separator + createTime + ".jpg");
+ }
+
}
}
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 5768c53..d9e9497 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
@@ -1,11 +1,8 @@
package com.ttstd.dialer.activity.weather.main;
-import android.util.Log;
-
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
-import com.baidu.location.BDLocation;
import com.jeremyliao.liveeventbus.LiveEventBus;
import com.qweather.sdk.response.weather.WeatherDaily;
import com.qweather.sdk.response.weather.WeatherHourly;
@@ -15,9 +12,11 @@ import com.ttstd.dialer.R;
import com.ttstd.dialer.adapter.HourlyWeatherAdapter;
import com.ttstd.dialer.adapter.WeatherAdapter;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
+import com.ttstd.dialer.bean.req.SnLocationReq;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.databinding.ActivityWeatherMainBinding;
import com.ttstd.dialer.utils.DateUtil;
+import com.ttstd.dialer.utils.Logger;
import java.util.List;
import java.util.Optional;
@@ -71,7 +70,7 @@ public class WeatherMainActivity extends BaseMvvmActivity() {
@Override
@@ -108,14 +107,14 @@ public class WeatherMainActivity extends BaseMvvmActivity() {
+ LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, SnLocationReq.class)
+ .observeSticky(this, new Observer() {
@Override
- public void onChanged(BDLocation bdLocation) {
- mViewDataBinding.tvLocation.setText(bdLocation.getDistrict());
- mViewModel.getWeatherNow(bdLocation.getAdCode());
- mViewModel.getWeather24h(bdLocation.getAdCode());
- mViewModel.getWeather10D(bdLocation.getAdCode());
+ public void onChanged(SnLocationReq snLocationReq) {
+ mViewDataBinding.tvLocation.setText(snLocationReq.getDistrict());
+ mViewModel.getWeatherNow(snLocationReq.getAdCode());
+ mViewModel.getWeather24h(snLocationReq.getAdCode());
+ mViewModel.getWeather10D(snLocationReq.getAdCode());
}
});
diff --git a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainViewModel.java
index 2cbc2fc..cf57c40 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainViewModel.java
@@ -1,7 +1,5 @@
package com.ttstd.dialer.activity.weather.main;
-import android.util.Log;
-
import androidx.lifecycle.MutableLiveData;
import com.qweather.sdk.Callback;
@@ -17,6 +15,7 @@ import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityWeatherMainBinding;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.manager.WeatherManager;
+import com.ttstd.dialer.utils.Logger;
import java.util.List;
@@ -26,25 +25,25 @@ public class WeatherMainViewModel extends BaseViewModel mWeatherNowData = new MutableLiveData<>();
public void getWeatherNow(String adCode) {
- Log.e(TAG, "getWeatherNow: " + adCode);
+ Logger.e(TAG, "getWeatherNow: " + adCode);
WeatherManager.getInstance().getWeatherNow(adCode, new Callback() {
@Override
public void onSuccess(WeatherNowResponse response) {
- Log.e("getWeatherNow", "onSuccess: " + response);
+ Logger.e("getWeatherNow", "onSuccess: " + response);
WeatherNow weatherNow = response.getNow();
- Log.e("getWeatherNow", "onSuccess: " + weatherNow);
+ Logger.e("getWeatherNow", "onSuccess: " + weatherNow);
mWeatherNowData.postValue(weatherNow);
}
@Override
public void onFailure(ErrorResponse errorResponse) {
- Log.e("getWeatherNow", "onFailure: " + errorResponse.getError().getStatus());
- Log.e("getWeatherNow", "onFailure: " + errorResponse.getError().getDetail());
+ Logger.e("getWeatherNow", "onFailure: " + errorResponse.getError().getStatus());
+ Logger.e("getWeatherNow", "onFailure: " + errorResponse.getError().getDetail());
}
@Override
public void onException(Throwable e) {
- Log.e("getWeatherNow", "onException: " + e.getMessage());
+ Logger.e("getWeatherNow", "onException: " + e.getMessage());
}
});
}
@@ -52,25 +51,25 @@ public class WeatherMainViewModel extends BaseViewModel> mWeather24hData = new MutableLiveData<>();
public void getWeather24h(String adCode) {
- Log.e(TAG, "getWeather24h: " + adCode);
+ Logger.e(TAG, "getWeather24h: " + adCode);
WeatherManager.getInstance().getWeather24h(adCode, new Callback() {
@Override
public void onSuccess(WeatherHourlyResponse weatherHourlyResponse) {
- Log.e("getWeather24h", "onSuccess: " + weatherHourlyResponse);
+ Logger.e("getWeather24h", "onSuccess: " + weatherHourlyResponse);
List weatherHourlies = weatherHourlyResponse.getHourly();
- Log.e("getWeather24h", "onSuccess: " + GsonUtils.toJSONString(weatherHourlies));
+ Logger.e("getWeather24h", "onSuccess: " + GsonUtils.toJSONString(weatherHourlies));
mWeather24hData.postValue(weatherHourlies);
}
@Override
public void onFailure(ErrorResponse errorResponse) {
- Log.e("getWeather24h", "onFailure: " + errorResponse.getError().getStatus());
- Log.e("getWeather24h", "onFailure: " + errorResponse.getError().getDetail());
+ Logger.e("getWeather24h", "onFailure: " + errorResponse.getError().getStatus());
+ Logger.e("getWeather24h", "onFailure: " + errorResponse.getError().getDetail());
}
@Override
public void onException(Throwable throwable) {
- Log.e("getWeather24h", "onException: " + throwable.getMessage());
+ Logger.e("getWeather24h", "onException: " + throwable.getMessage());
}
});
}
@@ -79,24 +78,24 @@ public class WeatherMainViewModel extends BaseViewModel> mWeatherDailyListData = new MutableLiveData<>();
public void getWeather10D(String adCode) {
- Log.e(TAG, "getWeather10D: " + adCode);
+ Logger.e(TAG, "getWeather10D: " + adCode);
WeatherManager.getInstance().getWeather10D(adCode, new Callback() {
@Override
public void onSuccess(WeatherDailyResponse weatherDailyResponse) {
- Log.e("getWeather10D", "onSuccess: ");
+ Logger.e("getWeather10D", "onSuccess: ");
List weatherDailyList = weatherDailyResponse.getDaily();
- Log.e("getWeather10D", "onSuccess: " + GsonUtils.toJSONString(weatherDailyList));
+ Logger.e("getWeather10D", "onSuccess: " + GsonUtils.toJSONString(weatherDailyList));
mWeatherDailyListData.postValue(weatherDailyList);
}
@Override
public void onFailure(ErrorResponse errorResponse) {
- Log.e("getWeather10D", "onFailure: ");
+ Logger.e("getWeather10D", "onFailure: ");
}
@Override
public void onException(Throwable throwable) {
- Log.e("getWeather10D", "onException: " + throwable.getMessage());
+ Logger.e("getWeather10D", "onException: " + throwable.getMessage());
}
});
}
diff --git a/app/src/main/java/com/ttstd/dialer/adapter/AppAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/AppAdapter.java
index 5799114..83646ec 100644
--- a/app/src/main/java/com/ttstd/dialer/adapter/AppAdapter.java
+++ b/app/src/main/java/com/ttstd/dialer/adapter/AppAdapter.java
@@ -3,7 +3,6 @@ package com.ttstd.dialer.adapter;
import android.content.ComponentName;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -24,6 +23,7 @@ import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.fragment.dialog.shortcut.ShortcutDialogFagment;
import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.iconloader.IconCacheManager;
import com.ttstd.iconloader.IconLoader;
@@ -132,7 +132,7 @@ public class AppAdapter extends RecyclerView.Adapter imple
@Override
public void onLoaderReset(@NonNull @NotNull Loader loader) {
- Log.e(TAG, "onLoaderReset: ");
+ Logger.e(TAG, "onLoaderReset: ");
}
public class AppHolder extends RecyclerView.ViewHolder {
diff --git a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
index 73d5861..33b2de4 100644
--- a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
+++ b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
@@ -1,6 +1,5 @@
package com.ttstd.dialer.adapter;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -14,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.shehuan.niv.NiceImageView;
import com.ttstd.dialer.R;
import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.utils.Logger;
import java.util.Collections;
import java.util.List;
@@ -69,7 +69,7 @@ public class ContactInfoAdapter extends RecyclerView.Adapter loader) {
- Log.e(TAG, "onLoaderReset: ");
+ Logger.e(TAG, "onLoaderReset: ");
}
public class AppHolder extends RecyclerView.ViewHolder {
diff --git a/app/src/main/java/com/ttstd/dialer/adapter/WeatherAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/WeatherAdapter.java
index c8bf9a5..c95224c 100644
--- a/app/src/main/java/com/ttstd/dialer/adapter/WeatherAdapter.java
+++ b/app/src/main/java/com/ttstd/dialer/adapter/WeatherAdapter.java
@@ -1,7 +1,6 @@
package com.ttstd.dialer.adapter;
import android.graphics.drawable.PictureDrawable;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -19,9 +18,9 @@ import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.glide.GlideApp;
import com.ttstd.dialer.glide.svg.SvgSoftwareLayerSetter;
import com.ttstd.dialer.utils.DateUtil;
+import com.ttstd.dialer.utils.Logger;
import java.util.List;
-import java.util.Locale;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
@@ -83,7 +82,7 @@ public class WeatherAdapter extends RecyclerView.Adapter) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
boolean isAbstract = Modifier.isAbstract(vmClass.getModifiers());
- Log.e(TAG, "isLocalClass:" + vmClass.getSimpleName().equals(ViewModel.class.getSimpleName()) + " isAbstract:" + isAbstract);
+ Logger.e(TAG, "isLocalClass:" + vmClass.getSimpleName().equals(ViewModel.class.getSimpleName()) + " isAbstract:" + isAbstract);
if (!isAbstract) {//不是一个抽象类
mViewModel = new ViewModelProvider(this).get(vmClass);
}
diff --git a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java
index 7c78404..38e781f 100644
--- a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java
@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -20,7 +19,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.ttstd.dialer.base.BaseDialogFragment;
-import com.ttstd.dialer.base.BaseFragment;
+import com.ttstd.dialer.utils.Logger;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
@@ -76,7 +75,7 @@ public abstract class BaseMvvmDialogFragment> isAttached >>", "flag = " + flag);
+ Logger.e(" >> isAttached >>", "flag = " + flag);
return flag;
}
@@ -234,7 +233,7 @@ public abstract class BaseMvvmDialogFragment> hideLoading :: isShow: %s", isShow);
// if (isShow)
-// mWaitDialog.dismiss();
+// mWaitDiaLogger.dismiss();
// } catch (Exception e) {
// e.printStackTrace();
// }
diff --git a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
index 40f2b1f..29fe070 100644
--- a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -20,6 +19,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.ttstd.dialer.base.BaseFragment;
+import com.ttstd.dialer.utils.Logger;
import java.lang.ref.WeakReference;
import java.lang.reflect.ParameterizedType;
@@ -75,7 +75,7 @@ public abstract class BaseMvvmFragment> isAttached >>", "flag = " + flag);
+ Logger.e(" >> isAttached >>", "flag = " + flag);
return flag;
}
@@ -233,7 +233,7 @@ public abstract class BaseMvvmFragment> hideLoading :: isShow: %s", isShow);
// if (isShow)
-// mWaitDialog.dismiss();
+// mWaitDiaLogger.dismiss();
// } catch (Exception e) {
// e.printStackTrace();
// }
diff --git a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java
index 0b2975a..a374548 100644
--- a/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java
+++ b/app/src/main/java/com/ttstd/dialer/base/rx/BaseRxService.java
@@ -1,7 +1,6 @@
package com.ttstd.dialer.base.rx;
import android.app.Service;
-import android.content.Intent;
import androidx.annotation.CheckResult;
import androidx.annotation.NonNull;
@@ -49,14 +48,9 @@ public abstract class BaseRxService extends Service implements LifecycleProvider
lifecycleSubject.onNext(ActivityEvent.CREATE);
}
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return super.onStartCommand(intent, flags, startId);
- }
-
@Override
public void onDestroy() {
super.onDestroy();
- lifecycleSubject.onNext(ActivityEvent.STOP);
+ lifecycleSubject.onNext(ActivityEvent.DESTROY);
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/bean/ApkInstalledInfo.java b/app/src/main/java/com/ttstd/dialer/bean/ApkInstalledInfo.java
new file mode 100644
index 0000000..74cb1ff
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/ApkInstalledInfo.java
@@ -0,0 +1,143 @@
+package com.ttstd.dialer.bean;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+public class ApkInstalledInfo implements Serializable {
+ private static final long serialVersionUID = 3322347581650924289L;
+
+ @SerializedName("package_name")
+ private String packageName;
+
+ @SerializedName("app_name")
+ private String appName;
+
+ @SerializedName("version_name")
+ private String versionName;
+
+ @SerializedName("version_code")
+ private long versionCode;
+
+ @SerializedName("install_time")
+ private long installTime;
+
+ @SerializedName("last_update_time")
+ private long lastUpdateTime;
+
+ @SerializedName("apk_size")
+ private long apkSize;
+
+ @SerializedName("data_size")
+ private long dataSize;
+
+ @SerializedName("cache_size")
+ private long cacheSize;
+
+ @SerializedName("md5")
+ private String md5;
+
+ @SerializedName("system_app")
+ private boolean systemApp;
+
+ public ApkInstalledInfo() {
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getVersionName() {
+ return versionName;
+ }
+
+ public void setVersionName(String versionName) {
+ this.versionName = versionName;
+ }
+
+ public long getVersionCode() {
+ return versionCode;
+ }
+
+ public void setVersionCode(long versionCode) {
+ this.versionCode = versionCode;
+ }
+
+ public long getInstallTime() {
+ return installTime;
+ }
+
+ public void setInstallTime(long installTime) {
+ this.installTime = installTime;
+ }
+
+ public long getLastUpdateTime() {
+ return lastUpdateTime;
+ }
+
+ public void setLastUpdateTime(long lastUpdateTime) {
+ this.lastUpdateTime = lastUpdateTime;
+ }
+
+ public long getApkSize() {
+ return apkSize;
+ }
+
+ public void setApkSize(long apkSize) {
+ this.apkSize = apkSize;
+ }
+
+ public long getDataSize() {
+ return dataSize;
+ }
+
+ public void setDataSize(long dataSize) {
+ this.dataSize = dataSize;
+ }
+
+ public long getCacheSize() {
+ return cacheSize;
+ }
+
+ public void setCacheSize(long cacheSize) {
+ this.cacheSize = cacheSize;
+ }
+
+ public String getMd5() {
+ return md5;
+ }
+
+ public void setMd5(String md5) {
+ this.md5 = md5;
+ }
+
+ public boolean isSystemApp() {
+ return systemApp;
+ }
+
+ public void setSystemApp(boolean systemApp) {
+ this.systemApp = systemApp;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/BaseResponse.java b/app/src/main/java/com/ttstd/dialer/bean/BaseResponse.java
new file mode 100644
index 0000000..c97057c
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/BaseResponse.java
@@ -0,0 +1,52 @@
+package com.ttstd.dialer.bean;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+/**
+ * 通用响应实体基类
+ *
+ * @param 具体数据实体
+ */
+public class BaseResponse {
+ private String code;
+ private String msg;
+ private T data;
+
+ // 判断请求是否成功(根据后端 code 定义)
+ public boolean isSuccess() {
+ return "00000".equals(code);
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public T getData() {
+ return data;
+ }
+
+ public void setData(T data) {
+ this.data = data;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/bean/DeveloperOptions.java b/app/src/main/java/com/ttstd/dialer/bean/DeveloperOptions.java
new file mode 100644
index 0000000..300ed6b
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/DeveloperOptions.java
@@ -0,0 +1,17 @@
+package com.ttstd.dialer.bean;
+
+import java.io.Serializable;
+
+public class DeveloperOptions implements Serializable {
+ private static final long serialVersionUID = 6146726170647484306L;
+
+ int developerOptions;
+
+ public int getDeveloperOptions() {
+ return developerOptions;
+ }
+
+ public void setDeveloperOptions(int developerOptions) {
+ this.developerOptions = developerOptions;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/PushMessage.java b/app/src/main/java/com/ttstd/dialer/bean/PushMessage.java
new file mode 100644
index 0000000..6a2d4bc
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/PushMessage.java
@@ -0,0 +1,115 @@
+package com.ttstd.dialer.bean;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import java.io.Serializable;
+
+import cn.jpush.android.api.CustomMessage;
+
+
+/**
+ * copy from
+ * {
+ * {@link cn.jpush.android.api.CustomMessage}
+ * }
+ * add Serializable
+ */
+public class PushMessage implements Serializable {
+ private static final long serialVersionUID = -3312355696577675582L;
+
+ public String messageId;
+ public String extra;
+ public String message;
+ public String contentType;
+ public String title;
+ public String senderId;
+ public String appId;
+ public byte platform;
+
+ public PushMessage() {
+ }
+
+ public PushMessage(CustomMessage customMessage) {
+ this.messageId = customMessage.messageId;
+ this.extra = customMessage.extra;
+ this.message = customMessage.message;
+ this.contentType = customMessage.contentType;
+ this.title = customMessage.title;
+ this.senderId = customMessage.senderId;
+ this.appId = customMessage.appId;
+ this.platform = customMessage.platform;
+ }
+
+ public String getMessageId() {
+ return messageId;
+ }
+
+ public void setMessageId(String messageId) {
+ this.messageId = messageId;
+ }
+
+ public String getExtra() {
+ return extra;
+ }
+
+ public void setExtra(String extra) {
+ this.extra = extra;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getSenderId() {
+ return senderId;
+ }
+
+ public void setSenderId(String senderId) {
+ this.senderId = senderId;
+ }
+
+ public String getAppId() {
+ return appId;
+ }
+
+ public void setAppId(String appId) {
+ this.appId = appId;
+ }
+
+ public byte getPlatform() {
+ return platform;
+ }
+
+ public void setPlatform(byte platform) {
+ this.platform = platform;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java
new file mode 100644
index 0000000..3070395
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnHardwareInfoReq.java
@@ -0,0 +1,141 @@
+package com.ttstd.dialer.bean.req;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+
+public class SnHardwareInfoReq implements Serializable {
+ private static final long serialVersionUID = 2728925136862797858L;
+
+ @SerializedName("sn_imei")
+ private String imei;
+
+ @SerializedName("sn_imsi")
+ private String imsi;
+
+ @SerializedName("sn_wlan_mac")
+ private String wlanMac;
+
+ @SerializedName("sn_device_mac")
+ private String deviceMac;
+
+ @SerializedName("sn_bluetooth_mac")
+ private String bluetoothMac;
+
+ @SerializedName("sn_model")
+ private String model;
+
+ @SerializedName("sn_brand")
+ private String brand;
+
+ @SerializedName("sn_board")
+ private String board;
+
+ @SerializedName("sn_android_version")
+ private String androidVersion;
+
+ @SerializedName("sn_android_api")
+ private int androidApi;
+
+ @SerializedName("sn_build_id")
+ private String buildId;
+
+ @SerializedName("sn_build_display_id")
+ private String buildDisplayId;
+
+ public String getImei() {
+ return imei;
+ }
+
+ public void setImei(String imei) {
+ this.imei = imei;
+ }
+
+ public String getImsi() {
+ return imsi;
+ }
+
+ public void setImsi(String imsi) {
+ this.imsi = imsi;
+ }
+
+ public String getWlanMac() {
+ return wlanMac;
+ }
+
+ public void setWlanMac(String wlanMac) {
+ this.wlanMac = wlanMac;
+ }
+
+ public String getDeviceMac() {
+ return deviceMac;
+ }
+
+ public void setDeviceMac(String deviceMac) {
+ this.deviceMac = deviceMac;
+ }
+
+ public String getBluetoothMac() {
+ return bluetoothMac;
+ }
+
+ public void setBluetoothMac(String bluetoothMac) {
+ this.bluetoothMac = bluetoothMac;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public void setModel(String model) {
+ this.model = model;
+ }
+
+ public String getBrand() {
+ return brand;
+ }
+
+ public void setBrand(String brand) {
+ this.brand = brand;
+ }
+
+ public String getBoard() {
+ return board;
+ }
+
+ public void setBoard(String board) {
+ this.board = board;
+ }
+
+ public String getAndroidVersion() {
+ return androidVersion;
+ }
+
+ public void setAndroidVersion(String androidVersion) {
+ this.androidVersion = androidVersion;
+ }
+
+ public int getAndroidApi() {
+ return androidApi;
+ }
+
+ public void setAndroidApi(int androidApi) {
+ this.androidApi = androidApi;
+ }
+
+ public String getBuildId() {
+ return buildId;
+ }
+
+ public void setBuildId(String buildId) {
+ this.buildId = buildId;
+ }
+
+ public String getBuildDisplayId() {
+ return buildDisplayId;
+ }
+
+ public void setBuildDisplayId(String buildDisplayId) {
+ this.buildDisplayId = buildDisplayId;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/bean/req/SnLocationReq.java b/app/src/main/java/com/ttstd/dialer/bean/req/SnLocationReq.java
new file mode 100644
index 0000000..0a6ba3a
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/bean/req/SnLocationReq.java
@@ -0,0 +1,224 @@
+package com.ttstd.dialer.bean.req;
+
+import com.baidu.location.BDLocation;
+import com.google.gson.annotations.SerializedName;
+import com.ttstd.dialer.manager.MapManager;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 设备定位信息请求模型
+ */
+public class SnLocationReq implements Serializable {
+ private static final long serialVersionUID = -1673953474925103299L;
+
+ @SerializedName("country")
+ private String country;
+
+ @SerializedName("countryCode")
+ private String countryCode;
+
+ @SerializedName("province")
+ private String province;
+
+ @SerializedName("city")
+ private String city;
+
+ @SerializedName("cityCode")
+ private String cityCode;
+
+ @SerializedName("district")
+ private String district;
+
+ @SerializedName("street")
+ private String street;
+
+ @SerializedName("streetNumber")
+ private String streetNumber;
+
+ @SerializedName("address")
+ private String address;
+
+ @SerializedName("adCode")
+ private String adCode;
+
+ @SerializedName("town")
+ private String town;
+
+ @SerializedName("townCode")
+ private String townCode;
+
+ @SerializedName("locationDescribe")
+ private String locationDescribe;
+
+ @SerializedName("longitude")
+ private String longitude;
+
+ @SerializedName("latitude")
+ private String latitude;
+
+ @SerializedName("mapError")
+ private String mapError;
+
+ @SerializedName("lastSuccessfulTime")
+ private LocalDateTime lastSuccessfulTime;
+
+ public SnLocationReq() {
+ }
+
+ public SnLocationReq(BDLocation bdLocation) {
+ setCountry(bdLocation.getCountry());
+ setCountryCode(bdLocation.getCountryCode());
+ setProvince(bdLocation.getProvince());
+ setCity(bdLocation.getCity());
+ setCityCode(bdLocation.getCityCode());
+ setDistrict(bdLocation.getDistrict());
+ setStreet(bdLocation.getStreet());
+ setStreetNumber(bdLocation.getStreetNumber());
+ setAddress(bdLocation.getAddrStr());
+ setAdCode(bdLocation.getAdCode());
+ setTown(bdLocation.getTown());
+ setTownCode(bdLocation.getTownCode());
+ setLocationDescribe(bdLocation.getLocationDescribe());
+ setLongitude(MapManager.doubleToStringExact(bdLocation.getLongitude()));
+ setLatitude(MapManager.doubleToStringExact(bdLocation.getLatitude()));
+ }
+
+ // Getters and Setters
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public void setCountryCode(String countryCode) {
+ this.countryCode = countryCode;
+ }
+
+ public String getProvince() {
+ return province;
+ }
+
+ public void setProvince(String province) {
+ this.province = province;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getCityCode() {
+ return cityCode;
+ }
+
+ public void setCityCode(String cityCode) {
+ this.cityCode = cityCode;
+ }
+
+ public String getDistrict() {
+ return district;
+ }
+
+ public void setDistrict(String district) {
+ this.district = district;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getStreetNumber() {
+ return streetNumber;
+ }
+
+ public void setStreetNumber(String streetNumber) {
+ this.streetNumber = streetNumber;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getAdCode() {
+ return adCode;
+ }
+
+ public void setAdCode(String adCode) {
+ this.adCode = adCode;
+ }
+
+ public String getTown() {
+ return town;
+ }
+
+ public void setTown(String town) {
+ this.town = town;
+ }
+
+ public String getTownCode() {
+ return townCode;
+ }
+
+ public void setTownCode(String townCode) {
+ this.townCode = townCode;
+ }
+
+ public String getLocationDescribe() {
+ return locationDescribe;
+ }
+
+ public void setLocationDescribe(String locationDescribe) {
+ this.locationDescribe = locationDescribe;
+ }
+
+ public String getLongitude() {
+ return longitude;
+ }
+
+ public void setLongitude(String longitude) {
+ this.longitude = longitude;
+ }
+
+ public String getLatitude() {
+ return latitude;
+ }
+
+ public void setLatitude(String latitude) {
+ this.latitude = latitude;
+ }
+
+ public String getMapError() {
+ return mapError;
+ }
+
+ public void setMapError(String mapError) {
+ this.mapError = mapError;
+ }
+
+ public LocalDateTime getLastSuccessfulTime() {
+ return lastSuccessfulTime;
+ }
+
+ public void setLastSuccessfulTime(LocalDateTime lastSuccessfulTime) {
+ this.lastSuccessfulTime = lastSuccessfulTime;
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
index 257fff9..74f9bf7 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
@@ -2,7 +2,6 @@ package com.ttstd.dialer.fragment.app;
import android.app.Activity;
import android.os.Bundle;
-import android.util.Log;
import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
@@ -13,9 +12,9 @@ import com.ttstd.dialer.adapter.AppAdapter;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentAppBinding;
import com.ttstd.dialer.db.app.AppInfo;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.EqualHeightDecoration;
-import java.io.Serializable;
import java.util.List;
public class AppFragment extends BaseMvvmFragment {
@@ -30,7 +29,7 @@ public class AppFragment extends BaseMvvmFragment apkList) {
// AppFragment appFragment = new AppFragment();
-// Log.e(TAG, "newInstance: " + appFragment);
+// Logger.e(TAG, "newInstance: " + appFragment);
// Bundle args = new Bundle();
// args.putSerializable(ARG_APP_LIST, (Serializable) apkList);
// appFragment.setArguments(args);
@@ -43,7 +42,7 @@ public class AppFragment extends BaseMvvmFragment appInfos) {
mAppInfos = appInfos;
- Log.e(TAG, "AppFragment: mAppInfos = " + mAppInfos.hashCode());
+ Logger.e(TAG, "AppFragment: mAppInfos = " + mAppInfos.hashCode());
}
public interface UpdateCallback {
@@ -72,7 +71,7 @@ public class AppFragment extends BaseMvvmFragment() {
@Override
public void onChanged(Integer integer) {
@@ -117,14 +116,14 @@ public class AppFragment extends BaseMvvmFragment() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("updateAppInfo", "onSubscribe: ");
+ Logger.e("updateAppInfo", "onSubscribe: ");
}
@Override
public void onNext(@NonNull Integer row) {
- Log.e("updateAppInfo", "onNext: " + row);
+ Logger.e("updateAppInfo", "onNext: " + row);
mAppUpdateData.setValue(row);
}
@Override
public void onError(@NonNull Throwable e) {
- Log.e("updateAppInfo", "onError: " + e.getMessage());
+ Logger.e("updateAppInfo", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
- Log.e("updateAppInfo", "onComplete: ");
+ Logger.e("updateAppInfo", "onComplete: ");
}
});
}
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 06ff019..23dba73 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
@@ -2,7 +2,6 @@ package com.ttstd.dialer.fragment.contact;
import android.app.Activity;
import android.os.Bundle;
-import android.util.Log;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -13,6 +12,7 @@ import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentContactBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.fragment.dialog.call.CallFragment;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.EqualHeightDecoration;
import java.util.List;
@@ -63,7 +63,7 @@ public class ContactFragment extends BaseMvvmFragment>() {
@Override
public void onChanged(List contactInfos) {
- Log.e(TAG, "mContactListData: " + contactInfos);
+ Logger.e(TAG, "mContactListData: " + contactInfos);
mContactInfos = contactInfos;
mHomeContactAdapter.setContactInfos(mContactInfos);
}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
index 97b6def..59bbe5d 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
@@ -1,7 +1,6 @@
package com.ttstd.dialer.fragment.contact;
import android.content.Context;
-import android.util.Log;
import androidx.lifecycle.MutableLiveData;
@@ -11,6 +10,7 @@ import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.FragmentContactBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.utils.Logger;
import java.util.List;
@@ -48,12 +48,12 @@ public class ContactViewModel extends BaseViewModel>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
- Log.e("getAllContacts", "onSubscribe: ");
+ Logger.e("getAllContacts", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List contactInfos) {
- Log.e("getAllContacts", "onNext: ");
+ Logger.e("getAllContacts", "onNext: ");
mContactListData.setValue(contactInfos);
// List sorted = contacts.stream().sorted(new Comparator() {
@@ -67,12 +67,12 @@ public class ContactViewModel extends BaseViewModel {
private static final String TAG = "CallFragment";
@@ -99,7 +99,7 @@ public class CallFragment extends BaseMvvmDialogFragment {
private static final String TAG = "ShortcutDialogFagment";
@@ -160,7 +160,7 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment {
private static final String TAG = "SettingsFragment";
@@ -55,7 +55,7 @@ public class SettingsFragment extends BaseMvvmFragment extends MutableLiveData {
public void observe(androidx.lifecycle.LifecycleOwner owner, final Observer super T> observer) {
// 警告:多个观察者同时注册时,只有一个会收到通知
if (hasActiveObservers()) {
- Log.w(TAG, "多个观察者注册,但仅第一个会收到事件");
+ Logger.w(TAG, "多个观察者注册,但仅第一个会收到事件");
}
// 包装观察者,仅在事件未处理时触发
diff --git a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
index 43ed6d3..73e8a8c 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java
@@ -1,33 +1,46 @@
package com.ttstd.dialer.manager;
import android.annotation.SuppressLint;
+import android.app.usage.StorageStats;
+import android.app.usage.StorageStatsManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.UserHandle;
import android.util.Log;
import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.bean.ApkInstalledInfo;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.HashUtils;
+import com.ttstd.dialer.utils.Logger;
+import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Function;
import java.util.function.IntConsumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -44,8 +57,10 @@ public class AppManager {
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@SuppressLint("StaticFieldLeak")
- private static AppManager INSTANCE;
+ private static volatile AppManager INSTANCE;
private Context mContext;
+ private PackageManager mPackageManager;
+
private AppRepository mAppRepository;
private Set mProgressCallbacks = new CopyOnWriteArraySet<>();
@@ -83,13 +98,17 @@ public class AppManager {
public static void init(Context context) {
if (INSTANCE == null) {
- INSTANCE = new AppManager(context);
+ synchronized (AppManager.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new AppManager(context);
+ }
+ }
}
}
public static AppManager getInstance() {
if (INSTANCE == null) {
- throw new IllegalStateException("You must be init AppManager first");
+ throw new IllegalStateException("You must first initialize the AppManager");
}
return INSTANCE;
}
@@ -97,6 +116,7 @@ public class AppManager {
private AppManager(Context context) {
this.mContext = context.getApplicationContext();
this.mAppRepository = new AppRepository(context);
+ this.mPackageManager = mContext.getPackageManager();
executeAppListProcessing();
}
@@ -138,7 +158,7 @@ public class AppManager {
}, ASYNC_EXECUTOR)
.thenRun(() -> notifyCompleted(true, "应用列表处理完成"))
.exceptionally(throwable -> {
- Log.e(TAG, "异步处理失败", throwable);
+ Logger.e(TAG, "异步处理失败", throwable);
notifyCompleted(false, "处理失败: " + throwable.getMessage());
return null;
});
@@ -150,7 +170,7 @@ public class AppManager {
List result = mAppRepository.getAllApp();
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
- Log.e(TAG, "获取桌面应用列表失败", e);
+ Logger.e(TAG, "获取桌面应用列表失败", e);
return Collections.emptyList();
}
}
@@ -159,13 +179,13 @@ public class AppManager {
private CompletableFuture processFirstTimeApps() {
return CompletableFuture.runAsync(() -> {
notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
- Log.i(TAG, "进入首次应用数据初始化流程");
+ Logger.i(TAG, "进入首次应用数据初始化流程");
try {
// 获取所有可启动应用
List allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext);
if (allLauncherApps.isEmpty()) {
- Log.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
+ Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
return;
}
@@ -196,13 +216,13 @@ public class AppManager {
try {
mAppRepository.insert(app);
} catch (Exception e) {
- Log.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
}
});
- Log.i(TAG, "首次初始化完成,插入默认应用: " + allFirstApps.size() + " 个");
+ Logger.i(TAG, "首次初始化完成,插入默认应用: " + allFirstApps.size() + " 个");
} catch (Exception e) {
- Log.e(TAG, "首次应用数据初始化失败", e);
+ Logger.e(TAG, "首次应用数据初始化失败", e);
throw new RuntimeException("首次初始化失败", e); // 抛出异常触发回调
}
}, ASYNC_EXECUTOR);
@@ -213,7 +233,7 @@ public class AppManager {
return CompletableFuture.runAsync(() -> {
notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
if (appInfos.isEmpty()) {
- Log.w(TAG, "桌面应用列表为空,跳过删除处理");
+ Logger.w(TAG, "桌面应用列表为空,跳过删除处理");
return;
}
@@ -224,7 +244,7 @@ public class AppManager {
try {
return mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
} catch (Exception e) {
- Log.e(TAG, "获取应用ID失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "获取应用ID失败: " + app.getPackageName(), e);
return -1;
}
})
@@ -236,10 +256,10 @@ public class AppManager {
try {
mAppRepository.deleteById(id);
} catch (Exception e) {
- Log.e(TAG, "删除应用失败, ID: " + id, e);
+ Logger.e(TAG, "删除应用失败, ID: " + id, e);
}
});
- Log.i(TAG, "成功删除 " + ids.size() + " 个未安装应用");
+ Logger.i(TAG, "成功删除 " + ids.size() + " 个未安装应用");
}
}, ASYNC_EXECUTOR);
}
@@ -250,7 +270,7 @@ public class AppManager {
try {
List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
if (resolveInfos.isEmpty()) {
- Log.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
+ Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
return;
}
@@ -269,15 +289,15 @@ public class AppManager {
try {
mAppRepository.insert(app);
} catch (Exception e) {
- Log.e(TAG, "插入新应用失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "插入新应用失败: " + app.getPackageName(), e);
}
});
- Log.i(TAG, "成功插入 " + newApps.size() + " 个新应用");
+ Logger.i(TAG, "成功插入 " + newApps.size() + " 个新应用");
} else {
- Log.i(TAG, "没有需要插入的新应用");
+ Logger.i(TAG, "没有需要插入的新应用");
}
} catch (Exception e) {
- Log.e(TAG, "处理新应用时发生异常", e);
+ Logger.e(TAG, "处理新应用时发生异常", e);
throw new RuntimeException("新应用处理失败", e);
}
}, ASYNC_EXECUTOR);
@@ -289,7 +309,7 @@ public class AppManager {
try {
List appInfos = getAllDesktopSortApps();
if (appInfos.isEmpty()) {
- Log.w(TAG, "无应用数据,跳过位置更新");
+ Logger.w(TAG, "无应用数据,跳过位置更新");
return;
}
@@ -305,12 +325,12 @@ public class AppManager {
try {
mAppRepository.update(app);
} catch (Exception e) {
- Log.e(TAG, "更新应用位置失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "更新应用位置失败: " + app.getPackageName(), e);
}
});
- Log.i(TAG, "成功更新 " + sortedApps.size() + " 个应用的位置");
+ Logger.i(TAG, "成功更新 " + sortedApps.size() + " 个应用的位置");
} catch (Exception e) {
- Log.e(TAG, "更新应用位置时发生异常", e);
+ Logger.e(TAG, "更新应用位置时发生异常", e);
throw new RuntimeException("位置更新失败", e);
}
}, ASYNC_EXECUTOR);
@@ -330,7 +350,7 @@ public class AppManager {
try {
return mAppRepository.checkAppInfoExists(app.getPackageName(), app.getClassName()) > 0;
} catch (Exception e) {
- Log.e(TAG, "检查应用存在性失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "检查应用存在性失败: " + app.getPackageName(), e);
return false;
}
}
@@ -353,7 +373,7 @@ public class AppManager {
try {
mAppRepository.insert(app);
} catch (Exception e) {
- Log.e(TAG, "更新应用插入失败: " + app.getPackageName(), e);
+ Logger.e(TAG, "更新应用插入失败: " + app.getPackageName(), e);
}
});
}
@@ -373,13 +393,12 @@ public class AppManager {
}
public String getDefaultAppList() {
- PackageManager pm = mContext.getPackageManager();
List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
List defaultApps = resolveInfos.stream()
.filter(ri -> DEFAULT_APP_PACKAGES.contains(ri.activityInfo.packageName))
.sorted((o1, o2) -> Collator.getInstance(Locale.CHINESE)
- .compare(o1.activityInfo.loadLabel(pm), o2.activityInfo.loadLabel(pm)))
+ .compare(o1.activityInfo.loadLabel(mPackageManager), o2.activityInfo.loadLabel(mPackageManager)))
.map(ri -> new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name))
.map(component -> new AppInfo(mContext, component))
.sorted((a, b) -> Boolean.compare(
@@ -392,4 +411,64 @@ public class AppManager {
return GsonUtils.toJSONString(defaultApps);
}
+
+ private static final Set WHITE_SYSTEM_PACKAGES = new HashSet() {{
+// this.add("com.android.luancher3");
+ }};
+
+ public List getApkInstallInfos() {
+ StorageStatsManager ssm = mContext.getSystemService(StorageStatsManager.class);
+ // 获取所有已安装的应用
+ List installedApps = mPackageManager.getInstalledPackages(0);
+ // 遍历并筛选第三方应用
+ List apkInstalledInfos = installedApps.stream().filter(new Predicate() {
+ @Override
+ public boolean test(PackageInfo packageInfo) {
+ String packageName = packageInfo.packageName;
+ return !ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName);
+ }
+ }).map(new Function() {
+ @Override
+ public ApkInstalledInfo apply(PackageInfo packageInfo) {
+ ApkInstalledInfo apkInstalledInfo = new ApkInstalledInfo();
+ apkInstalledInfo.setPackageName(packageInfo.packageName);
+ apkInstalledInfo.setAppName(packageInfo.applicationInfo.loadLabel(mPackageManager).toString());
+ apkInstalledInfo.setVersionName(packageInfo.versionName);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ apkInstalledInfo.setVersionCode(packageInfo.getLongVersionCode());
+ } else {
+ apkInstalledInfo.setVersionCode(packageInfo.versionCode);
+ }
+ apkInstalledInfo.setInstallTime(packageInfo.firstInstallTime);
+ apkInstalledInfo.setLastUpdateTime(packageInfo.lastUpdateTime);
+
+ try {
+ StorageStats stats = ssm.queryStatsForPackage(
+ UUID.fromString(packageInfo.applicationInfo.storageUuid.toString()),
+ mContext.getPackageName(),
+ UserHandle.of(UserHandle.myUserId())
+ );
+
+ long appSize = stats.getAppBytes(); // 应用大小
+ Log.e(TAG, "apply: appSize = " + appSize);
+ long dataSize = stats.getDataBytes(); // 用户数据
+ long cacheSize = stats.getCacheBytes(); // 缓存
+
+ apkInstalledInfo.setApkSize(appSize);
+ apkInstalledInfo.setDataSize(dataSize);
+ apkInstalledInfo.setCacheSize(cacheSize);
+ } catch (Exception e) {
+ Log.e(TAG, "getApkInstallInfos: " + e.getMessage());
+ }
+
+ Log.e(TAG, "apply: " + packageInfo.applicationInfo.publicSourceDir);
+ Log.e(TAG, "apply: publicSourceDir = " + new File(packageInfo.applicationInfo.publicSourceDir).length());
+ Log.e(TAG, "apply: " + packageInfo.applicationInfo.dataDir);
+ apkInstalledInfo.setMd5(HashUtils.getFileMd5(new File(packageInfo.applicationInfo.publicSourceDir)));
+ apkInstalledInfo.setSystemApp(ApkUtils.isSystemApp(mContext, packageInfo.packageName));
+ return apkInstalledInfo;
+ }
+ }).collect(Collectors.toList());
+ return apkInstalledInfos;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java b/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java
index 5867515..0e2883a 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java
@@ -1,12 +1,12 @@
package com.ttstd.dialer.manager;
import android.content.Context;
-import android.util.Log;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.ttstd.dialer.bean.CityInfo;
+import com.ttstd.dialer.utils.Logger;
import java.io.IOException;
import java.io.InputStream;
@@ -46,14 +46,14 @@ public class CsvDeserializer {
.withIgnoreEmptyLine(true) // 忽略空行
.withThrowExceptions(false) // 不抛出异常,便于调试
.build();
- Log.e(TAG, "deserializeFromAssets: finish " + (System.currentTimeMillis() - time) + "ms");
+ Logger.e(TAG, "deserializeFromAssets: finish " + (System.currentTimeMillis() - time) + "ms");
// 转换并返回结果列表
return csvToBean.parse();
} catch (IOException e) {
- Log.e(TAG, "Error reading CSV file: " + e.getMessage());
+ Logger.e(TAG, "Error reading CSV file: " + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
- Log.e(TAG, "Error during CSV deserialization: " + e.getMessage());
+ Logger.e(TAG, "Error during CSV deserialization: " + e.getMessage());
e.printStackTrace();
} finally {
// 确保关闭流,避免资源泄漏
@@ -65,7 +65,7 @@ public class CsvDeserializer {
inputStream.close();
}
} catch (IOException e) {
- Log.w(TAG, "Failed to close stream: " + e.getMessage());
+ Logger.w(TAG, "Failed to close stream: " + e.getMessage());
}
}
return new ArrayList<>();
@@ -93,7 +93,7 @@ public class CsvDeserializer {
return csvToBean.parse();
} catch (Exception e) {
- Log.e(TAG, "Error during CSV deserialization: " + e.getMessage());
+ Logger.e(TAG, "Error during CSV deserialization: " + e.getMessage());
e.printStackTrace();
}
diff --git a/app/src/main/java/com/ttstd/dialer/manager/MapManager.java b/app/src/main/java/com/ttstd/dialer/manager/MapManager.java
index 3a107e6..ad0c41a 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/MapManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/MapManager.java
@@ -15,11 +15,13 @@ import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.jeremyliao.liveeventbus.LiveEventBus;
import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.bean.req.SnLocationReq;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.SystemUtils;
+import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -237,39 +239,41 @@ public class MapManager {
/**
* 存储定位结果(解耦存储逻辑)
*/
- private void saveLocationResult(BDLocation location) {
- if (location == null) return;
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_ADDRESS_KEY, location.getAddrStr());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_LOCATION_DESCRIBE_KEY, location.getLocationDescribe());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_PROVINCE_KEY, location.getProvince());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_CITY_KEY, location.getCity());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_DISTRICT_KEY, location.getDistrict());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_TOWN_KEY, location.getTown());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_STREET_KEY, location.getStreet());
+ private void saveLocationResult(BDLocation bdLocation) {
+ if (bdLocation == null) return;
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_ADDRESS_KEY, bdLocation.getAddrStr());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_MAP_LOCATION_DESCRIBE_KEY, bdLocation.getLocationDescribe());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_PROVINCE_KEY, bdLocation.getProvince());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_CITY_KEY, bdLocation.getCity());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_DISTRICT_KEY, bdLocation.getDistrict());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_TOWN_KEY, bdLocation.getTown());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_STREET_KEY, bdLocation.getStreet());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_LONGITUDE_KEY, location.getLongitude());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_LATITUDE_KEY, location.getLatitude());
- mMMKV.encode(CommonConfig.CURRENT_LOCATION_AD_CODE_KEY, location.getAdCode());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_LONGITUDE_KEY, bdLocation.getLongitude());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_LATITUDE_KEY, bdLocation.getLatitude());
+ mMMKV.encode(CommonConfig.CURRENT_LOCATION_AD_CODE_KEY, bdLocation.getAdCode());
-// mMMKV.encode(CommonConfig.CURRENT_LOCATION_LOCATION_ID_KEY, location.getLocationID());
+// mMMKV.encode(CommonConfig.CURRENT_LOCATION_LOCATION_ID_KEY, bdLocation.getLocationID());
mMMKV.encode("MapError", "-");
Map params = new LinkedHashMap<>();
params.put("sn", SystemUtils.getSerial());
- params.put("address", location.getAddrStr());
- params.put("location_describe", location.getLocationDescribe());
- params.put("longitude", location.getLongitude());
- params.put("latitude", location.getLatitude());
- params.put("ad_code", location.getAdCode());
- params.put("coor_type", location.getCoorType());
+ params.put("address", bdLocation.getAddrStr());
+ params.put("location_describe", bdLocation.getLocationDescribe());
+ params.put("longitude", bdLocation.getLongitude());
+ params.put("latitude", bdLocation.getLatitude());
+ params.put("ad_code", bdLocation.getAdCode());
+ params.put("coor_type", bdLocation.getCoorType());
- Logger.e(TAG, "saveLocationResult", "location = " + GsonUtils.toJSONString(location));
+ Logger.e(TAG, "saveLocationResult", "bdLocation = " + GsonUtils.toJSONString(bdLocation));
- WeatherManager.getInstance().setAdCode(location.getAdCode());
+ WeatherManager.getInstance().setAdCode(bdLocation.getAdCode());
+
+ SnLocationReq snLocation = new SnLocationReq(bdLocation);
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY)
- .post(location);
+ .post(snLocation);
}
/**
@@ -362,4 +366,10 @@ public class MapManager {
}
mListeners.clear(); // 清除所有回调,避免内存泄漏
}
+
+ public static String doubleToStringExact(double value) {
+ // 使用 BigDecimal 避免科学计数法
+ return new BigDecimal(Double.toString(value)).toPlainString();
+ }
+
}
diff --git a/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java b/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java
index 43aff5d..bfef50a 100644
--- a/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java
+++ b/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java
@@ -3,7 +3,6 @@ package com.ttstd.dialer.manager;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
-import android.util.Log;
import com.jeremyliao.liveeventbus.LiveEventBus;
import com.qweather.sdk.Callback;
@@ -59,7 +58,7 @@ public class WeatherManager {
.setLogEnable(BuildConfig.DEBUG) // 启用调试日志(生产环境建议设置为 false)
.setTokenGenerator(jwt);
} catch (Throwable e) {
- Log.e(TAG, "QWeatherUtils: " + e.getMessage());
+ Logger.e(TAG, "QWeatherUtils: " + e.getMessage());
e.printStackTrace();
}
}
@@ -72,7 +71,7 @@ public class WeatherManager {
public static WeatherManager getInstance() {
if (INSTANCE == null) {
- throw new IllegalStateException("You must be init WeatherManager first");
+ throw new IllegalStateException("You must first initialize the WeatherManager");
}
return INSTANCE;
}
@@ -99,7 +98,7 @@ public class WeatherManager {
public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
long time = System.currentTimeMillis();
List cityInfos = CsvDeserializer.deserializeFromAssets(mContext, "China-City-List-latest.csv");
- Log.e(TAG, "subscribe: deserializeFromAssets time = " + (System.currentTimeMillis() - time) + "ms");
+ Logger.e(TAG, "subscribe: deserializeFromAssets time = " + (System.currentTimeMillis() - time) + "ms");
emitter.onNext(cityInfos);
}
})
@@ -152,7 +151,7 @@ public class WeatherManager {
public void getWeatherNow(String adCode, Callback callback) {
String locationID = getLocationID(adCode);
- Log.e(TAG, "getWeatherNow: locationID = " + locationID);
+ Logger.e(TAG, "getWeatherNow: locationID = " + locationID);
WeatherParameter parameter = new WeatherParameter(locationID)
.lang(Lang.ZH_HANS)
.unit(Unit.METRIC);
@@ -161,7 +160,7 @@ public class WeatherManager {
public void getWeather24h(String adCode, Callback callback) {
String locationID = getLocationID(adCode);
- Log.e(TAG, "getWeatherNow: locationID = " + locationID);
+ Logger.e(TAG, "getWeatherNow: locationID = " + locationID);
WeatherParameter parameter = new WeatherParameter(locationID)
.lang(Lang.ZH_HANS)
.unit(Unit.METRIC);
@@ -170,7 +169,7 @@ public class WeatherManager {
public void getWeather10D(String adCode, Callback callback) {
String locationID = getLocationID(adCode);
- Log.e(TAG, "getWeather10D: locationID = " + locationID);
+ Logger.e(TAG, "getWeather10D: locationID = " + locationID);
WeatherParameter parameter = new WeatherParameter(locationID)
.lang(Lang.ZH_HANS)
.unit(Unit.METRIC);
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/DeviceControlException.java b/app/src/main/java/com/ttstd/dialer/mdm/DeviceControlException.java
new file mode 100644
index 0000000..aa1e72a
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/DeviceControlException.java
@@ -0,0 +1,14 @@
+package com.ttstd.dialer.mdm;
+
+/**
+ * 设备控制统一异常:屏蔽不同SDK的异常差异
+ */
+public class DeviceControlException extends RuntimeException {
+ public DeviceControlException(String message) {
+ super(message);
+ }
+
+ public DeviceControlException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/DeviceControllerFactory.java b/app/src/main/java/com/ttstd/dialer/mdm/DeviceControllerFactory.java
new file mode 100644
index 0000000..c659004
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/DeviceControllerFactory.java
@@ -0,0 +1,59 @@
+package com.ttstd.dialer.mdm;
+
+import android.content.Context;
+
+import com.ttstd.dialer.mdm.impl.EmuiMdmDeviceController;
+import com.ttstd.dialer.mdm.impl.HonorHemDeviceController;
+import com.ttstd.dialer.mdm.impl.SystemSignatureController;
+
+/**
+ * 设备控制器工厂:根据配置创建对应厂家的适配器实例
+ */
+public class DeviceControllerFactory {
+ // 厂家类型枚举
+ public enum VendorType {
+ SYSTEM_SIGNATURE,//系统签名
+ HONOR_HEM, // 荣耀HEM
+ HUAWEI_MDM, // 华为MDM
+ XIAOMI_MDM // 小米MDM(扩展用)
+ }
+
+ // 单例工厂(可选,也可使用依赖注入)
+ private static DeviceControllerFactory instance;
+ private Context appContext;
+
+ private DeviceControllerFactory(Context context) {
+ this.appContext = context.getApplicationContext();
+ }
+
+ public static DeviceControllerFactory getInstance(Context context) {
+ if (instance == null) {
+ synchronized (DeviceControllerFactory.class) {
+ if (instance == null) {
+ instance = new DeviceControllerFactory(context);
+ }
+ }
+ }
+ return instance;
+ }
+
+ // 创建对应厂家的控制器
+ public IDeviceController createController(VendorType vendorType) {
+ switch (vendorType) {
+ case SYSTEM_SIGNATURE:
+ return new SystemSignatureController(appContext);
+ case HONOR_HEM:
+ return new HonorHemDeviceController(appContext);
+ case HUAWEI_MDM:
+ // 扩展:实现华为MDM的适配器
+ return new EmuiMdmDeviceController(appContext);
+// throw new UnsupportedOperationException("华为MDM适配器未实现");
+ case XIAOMI_MDM:
+ // 扩展:实现小米MDM的适配器
+ // return new XiaomiMdmDeviceController(appContext);
+ throw new UnsupportedOperationException("小米MDM适配器未实现");
+ default:
+ throw new IllegalArgumentException("不支持的厂家类型: " + vendorType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/DeviceManagerService.java b/app/src/main/java/com/ttstd/dialer/mdm/DeviceManagerService.java
new file mode 100644
index 0000000..8f3e21d
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/DeviceManagerService.java
@@ -0,0 +1,85 @@
+package com.ttstd.dialer.mdm;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+/**
+ * 业务层示例:只依赖抽象接口,不耦合具体SDK
+ */
+public class DeviceManagerService {
+ private static DeviceManagerService sInstance;
+ private Context mContext;
+ private IDeviceController deviceController;
+
+ public static void init(@NonNull Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null!");
+ }
+ if (sInstance == null) {
+ synchronized (DeviceManagerService.class) {
+ if (sInstance == null) {
+ sInstance = new DeviceManagerService(context.getApplicationContext());
+ }
+ }
+ }
+ }
+
+ public static DeviceManagerService getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("DeviceManagerService must be initialized first! Call init() in Application.");
+ }
+ return sInstance;
+ }
+
+ // 初始化:通过工厂创建控制器(可配置化,比如从sp/配置文件读取厂家类型)
+ public DeviceManagerService(Context context) {
+ this.mContext = context;
+ // 实际项目中可通过配置动态指定厂家(如从服务器下发、本地配置读取)
+ DeviceControllerFactory.VendorType vendor = DeviceControllerFactory.VendorType.SYSTEM_SIGNATURE;
+ this.deviceController = DeviceControllerFactory.getInstance(context)
+ .createController(vendor);
+ }
+
+
+ public String getSerial() {
+ return deviceController.getSerial();
+ }
+
+ public String getImei() {
+ return deviceController.getImei();
+ }
+
+ public String getImsi() {
+ return deviceController.getImsi();
+ }
+
+ public String getWlanMac() {
+ return deviceController.getWlanMac();
+ }
+
+ public String getDeviceMac() {
+ return deviceController.getDeviceMac();
+ }
+
+ public String getBluetoothMac() {
+ return deviceController.getBluetoothMac();
+ }
+
+ public String getBuildId() {
+ return deviceController.getBuildId();
+ }
+
+ public String getBuildDisplayId() {
+ return deviceController.getBuildDisplayId();
+ }
+
+ public boolean setDevelopmentOption(boolean enabled) {
+ return deviceController.setDevelopmentOption(enabled);
+ }
+
+ public boolean setUsbDebugMode(boolean enabled) {
+ return deviceController.setUsbDebugMode(enabled);
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/IDeviceController.java b/app/src/main/java/com/ttstd/dialer/mdm/IDeviceController.java
new file mode 100644
index 0000000..f172b5f
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/IDeviceController.java
@@ -0,0 +1,41 @@
+package com.ttstd.dialer.mdm;
+
+/**
+ * 设备控制抽象接口:定义所有需要的设备控制能力,与具体SDK解耦
+ */
+public interface IDeviceController {
+ //获取设备SN
+ String getSerial();
+
+ //IMEI
+ String getImei();
+
+ //IMSI
+ String getImsi();
+
+ //WLAN MAC
+ String getWlanMac();
+
+ //设备WLAN MAC
+ String getDeviceMac();
+
+ //蓝牙地址
+ String getBluetoothMac();
+
+ String getBuildId();
+
+ String getBuildDisplayId();
+
+ boolean setDevelopmentOption(boolean enabled);
+
+ boolean setUsbDebugMode(boolean enabled);
+
+ //设置默认桌面
+ boolean setDefaultLauncher(String packageNmae, String className);
+
+ //安装应用
+ void installPackage(String path);
+
+ //卸载应用
+ void uninstallPackage(String packageNmae);
+}
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/SystemDeviceController.java b/app/src/main/java/com/ttstd/dialer/mdm/SystemDeviceController.java
deleted file mode 100644
index 6be44ec..0000000
--- a/app/src/main/java/com/ttstd/dialer/mdm/SystemDeviceController.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.ttstd.dialer.mdm;
-
-import android.content.Context;
-
-import java.util.List;
-
-public class SystemDeviceController implements IDeviceController {
- private Context mContext;
-
- public SystemDeviceController(Context context) {
- mContext = context;
- }
-
- @Override
- public void addDisallowedRunningApp(String pkg) {
-
- }
-
- @Override
- public void removeDisallowedRunningApp(String pkg) {
-
- }
-
- @Override
- public boolean isDisallowedRunningApp(String pkg) {
- return false;
- }
-
- @Override
- public void uninstallPackage(String pkg) {
-
- }
-
- @Override
- public void shutdownDevice() {
-
- }
-
- @Override
- public void rebootDevice() {
-
- }
-
- @Override
- public void setDefaultLauncher(String pkg, String className) {
-
- }
-
- @Override
- public boolean disableBluetoothTransfer(boolean disable) {
- return false;
- }
-
- @Override
- public void addInstallPackageTrustList(List packages) {
-
- }
-
- @Override
- public List getInstallPackageTrustList() {
- return null;
- }
-}
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/impl/EmuiMdmDeviceController.java b/app/src/main/java/com/ttstd/dialer/mdm/impl/EmuiMdmDeviceController.java
new file mode 100644
index 0000000..c1abb61
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/impl/EmuiMdmDeviceController.java
@@ -0,0 +1,83 @@
+package com.ttstd.dialer.mdm.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.ttstd.dialer.mdm.IDeviceController;
+
+/**
+ * 华为MDM SDK适配器:适配抽象接口到华为MDM SDK的具体实现
+ */
+public class EmuiMdmDeviceController implements IDeviceController {
+ private ComponentName adminName;
+
+
+ public EmuiMdmDeviceController(Context context) {
+
+ }
+
+ @Override
+ public String getSerial() {
+ return null;
+ }
+
+ @Override
+ public String getImei() {
+ return null;
+ }
+
+ @Override
+ public String getImsi() {
+ return null;
+ }
+
+ @Override
+ public String getWlanMac() {
+ return null;
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return null;
+ }
+
+ @Override
+ public String getBluetoothMac() {
+ return null;
+ }
+
+ @Override
+ public String getBuildId() {
+ return null;
+ }
+
+ @Override
+ public String getBuildDisplayId() {
+ return null;
+ }
+
+ @Override
+ public boolean setDevelopmentOption(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean setUsbDebugMode(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean setDefaultLauncher(String packageNmae, String className) {
+ return false;
+ }
+
+ @Override
+ public void installPackage(String path) {
+
+ }
+
+ @Override
+ public void uninstallPackage(String packageNmae) {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/impl/HonorHemDeviceController.java b/app/src/main/java/com/ttstd/dialer/mdm/impl/HonorHemDeviceController.java
new file mode 100644
index 0000000..7f0b039
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/impl/HonorHemDeviceController.java
@@ -0,0 +1,86 @@
+package com.ttstd.dialer.mdm.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.ttstd.dialer.mdm.IDeviceController;
+
+/**
+ * 荣耀HEM SDK适配器:适配抽象接口到荣耀HEM SDK的具体实现
+ */
+public class HonorHemDeviceController implements IDeviceController {
+ private ComponentName adminName; // 荣耀HEM需要的管理员组件
+
+ // 构造器:初始化荣耀HEM SDK的核心实例
+ public HonorHemDeviceController(Context context) {
+
+ }
+
+ // 实现抽象接口:适配荣耀HEM的具体API
+ @Override
+ public String getSerial() {
+ return null;
+ }
+
+ @Override
+ public String getImei() {
+ return null;
+ }
+
+ @Override
+ public String getImsi() {
+ return null;
+ }
+
+ @Override
+ public String getWlanMac() {
+ return null;
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return null;
+ }
+
+ @Override
+ public String getBluetoothMac() {
+ return null;
+ }
+
+ @Override
+ public String getBuildId() {
+ return null;
+ }
+
+ @Override
+ public String getBuildDisplayId() {
+ return null;
+ }
+
+ @Override
+ public boolean setDevelopmentOption(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean setUsbDebugMode(boolean enabled) {
+ return false;
+ }
+
+ @Override
+ public boolean setDefaultLauncher(String packageNmae, String className) {
+ return false;
+ }
+
+ @Override
+ public void installPackage(String path) {
+
+ }
+
+ @Override
+ public void uninstallPackage(String packageNmae) {
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/mdm/impl/SystemSignatureController.java b/app/src/main/java/com/ttstd/dialer/mdm/impl/SystemSignatureController.java
new file mode 100644
index 0000000..4ed1915
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/mdm/impl/SystemSignatureController.java
@@ -0,0 +1,84 @@
+package com.ttstd.dialer.mdm.impl;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.ttstd.dialer.mdm.IDeviceController;
+import com.ttstd.dialer.utils.SystemUtils;
+
+/**
+ * 具有系统签名时使用这个
+ */
+public class SystemSignatureController implements IDeviceController {
+ private Context mContext;
+
+ public SystemSignatureController(Context context) {
+ mContext = context;
+ }
+
+
+ @Override
+ public String getSerial() {
+ return SystemUtils.getSerial();
+ }
+
+ @Override
+ public String getImei() {
+ return SystemUtils.getDefaultImei(mContext);
+ }
+
+ @Override
+ public String getImsi() {
+ return SystemUtils.getDefaultImSi(mContext);
+ }
+
+ @Override
+ public String getWlanMac() {
+ return SystemUtils.getWlanMacAddress(mContext);
+ }
+
+ @Override
+ public String getDeviceMac() {
+ return SystemUtils.getFactoryMacAddresses(mContext);
+ }
+
+ @Override
+ public String getBluetoothMac() {
+ return SystemUtils.getBluetoothMacAddress(mContext);
+ }
+
+ @Override
+ public String getBuildId() {
+ return SystemUtils.getBuildId();
+ }
+
+ @Override
+ public String getBuildDisplayId() {
+ return SystemUtils.getBuildDisplayId();
+ }
+
+ @Override
+ public boolean setDevelopmentOption(boolean enabled) {
+ return Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, enabled ? 1 : 0);
+ }
+
+ @Override
+ public boolean setUsbDebugMode(boolean enabled) {
+ return Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
+ }
+
+ @Override
+ public boolean setDefaultLauncher(String packageNmae, String className) {
+ return false;
+ }
+
+ @Override
+ public void installPackage(String path) {
+
+ }
+
+ @Override
+ public void uninstallPackage(String packageNmae) {
+
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/BaseObserver.java b/app/src/main/java/com/ttstd/dialer/network/BaseObserver.java
new file mode 100644
index 0000000..9f9d181
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/BaseObserver.java
@@ -0,0 +1,36 @@
+package com.ttstd.dialer.network;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+
+public abstract class BaseObserver implements Observer {
+
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ // 订阅开始(可添加加载框)
+ }
+
+ @Override
+ public void onNext(@NonNull T t) {
+ // 请求成功
+ onSuccess(t);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ // 请求失败
+ onFailure(e);
+ }
+
+ @Override
+ public void onComplete() {
+ // 请求完成
+ }
+
+ // 成功回调(子类实现)
+ public abstract void onSuccess(T t);
+
+ // 失败回调(子类实现)
+ public abstract void onFailure(Throwable e);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
new file mode 100644
index 0000000..d319f1d
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/OkHttpManager.java
@@ -0,0 +1,340 @@
+package com.ttstd.dialer.network;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.Gson;
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.bean.DeveloperOptions;
+import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
+import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.network.api.SnApi;
+import com.ttstd.dialer.network.interceptor.AuthInterceptor;
+import com.ttstd.dialer.utils.FileUtils;
+import com.ttstd.dialer.utils.Logger;
+import com.ttstd.dialer.utils.SystemUtils;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+import okhttp3.Cache;
+import okhttp3.ConnectionPool;
+import okhttp3.Dispatcher;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.RequestBody;
+import okhttp3.logging.HttpLoggingInterceptor;
+import retrofit2.Retrofit;
+import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+/**
+ * OkHttp + Retrofit 管理类
+ * 单例模式 + 避免内存泄漏 + 增强异常处理 + 提升扩展性
+ */
+public class OkHttpManager {
+ private static final String TAG = "OkHttpManager";
+
+ // 超时时间(抽成常量,便于统一维护)
+ private static final int DEFAULT_TIMEOUT_SECONDS = 5;
+ // 缓存大小 64MB(使用更语义化的命名)
+ private static final long CACHE_SIZE_BYTES = 1024 * 1024 * 64L;
+ // 缓存目录名称(避免硬编码)
+ private static final String OKHTTP_CACHE_DIR = "OkHttpCache";
+
+ // 静态单例(使用volatile保证可见性,防止指令重排)
+ private static volatile OkHttpManager sInstance;
+
+ // 使用Application Context避免内存泄漏
+ private final Context mAppContext;
+ private Retrofit mRetrofit;
+ private OkHttpClient mOkHttpClient;
+
+ /**
+ * 初始化(建议在Application中调用)
+ *
+ * @param context 上下文(内部会转为Application Context)
+ */
+ public static void init(@NonNull Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null!");
+ }
+ if (sInstance == null) {
+ synchronized (OkHttpManager.class) {
+ if (sInstance == null) {
+ sInstance = new OkHttpManager(context.getApplicationContext());
+ }
+ }
+ }
+ }
+
+ /**
+ * 获取单例实例
+ *
+ * @return OkHttpManager实例
+ * @throws IllegalStateException 未初始化时抛出
+ */
+ public static OkHttpManager getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("OkHttpManager must be initialized first! Call init() in Application.");
+ }
+ return sInstance;
+ }
+
+ /**
+ * 私有构造方法(禁止外部实例化)
+ *
+ * @param appContext Application Context
+ */
+ private OkHttpManager(@NonNull Context appContext) {
+ this.mAppContext = appContext;
+ initOkHttpClient();
+ initRetrofit();
+ }
+
+ /**
+ * 初始化OkHttpClient(拆分方法,单一职责)
+ */
+ private void initOkHttpClient() {
+ if (mOkHttpClient != null) {
+ return;
+ }
+
+ // 1. 构建调度器(控制并发)
+ Dispatcher dispatcher = new Dispatcher();
+ dispatcher.setMaxRequests(1); // 全局最大并发
+ dispatcher.setMaxRequestsPerHost(1); // 单Host最大并发
+
+ // 2. 构建缓存(增强异常处理)
+ Cache cache = createCache();
+
+ // 3. 构建OkHttpClient
+ OkHttpClient.Builder builder = new OkHttpClient.Builder()
+ .connectTimeout(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .writeTimeout(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .readTimeout(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+ .dispatcher(dispatcher)
+ .connectionPool(new ConnectionPool(20, 1, TimeUnit.SECONDS))
+ .addInterceptor(new AuthInterceptor()) // 添加认证拦截器
+ .addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
+ @Override
+ public void log(@NotNull String s) {
+ Logger.e("HttpLoggingInterceptor", "log: " + s);
+ }
+ }).setLevel(HttpLoggingInterceptor.Level.BASIC))
+ .retryOnConnectionFailure(true);// 连接失败重试
+
+
+ // 缓存非空时才设置(避免空指针)
+ if (cache != null) {
+ builder.cache(cache);
+ }
+
+ mOkHttpClient = builder.build();
+ }
+
+ /**
+ * 创建OkHttp缓存(单独抽离,便于维护)
+ *
+ * @return Cache实例(创建失败返回null)
+ */
+ private Cache createCache() {
+ try {
+ String cacheDirPath = FileUtils.getCacheDir(mAppContext);
+ if (cacheDirPath == null || cacheDirPath.isEmpty()) {
+ Log.w(TAG, "Cache directory path is empty, skip cache creation");
+ return null;
+ }
+
+ File cacheDir = new File(cacheDirPath, OKHTTP_CACHE_DIR);
+ // 确保缓存目录存在(否则OkHttp会抛异常)
+ if (!cacheDir.exists() && !cacheDir.mkdirs()) {
+ Log.e(TAG, "Failed to create cache directory: " + cacheDir.getAbsolutePath());
+ return null;
+ }
+
+ // 校验缓存目录可写
+ if (!cacheDir.canWrite()) {
+ Log.e(TAG, "Cache directory is not writable: " + cacheDir.getAbsolutePath());
+ return null;
+ }
+
+ Cache cache = new Cache(cacheDir, CACHE_SIZE_BYTES);
+ // 验证缓存是否可用(避免创建了但不可用的情况)
+// cache.getCacheDirectory().getFreeSpace();
+ return cache;
+ } catch (SecurityException e) {
+ Log.e(TAG, "No permission to create cache directory", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "Cache directory path is null", e);
+ }
+ return null;
+ }
+
+ /**
+ * 初始化Retrofit(拆分方法,单一职责)
+ */
+ private void initRetrofit() {
+ if (mRetrofit != null || mOkHttpClient == null) {
+ return;
+ }
+
+ mRetrofit = new Retrofit.Builder()
+ .client(mOkHttpClient)
+ .baseUrl(UrlConstants.ROOT_URL) // 抽离URL常量,便于环境切换(开发/测试/生产)
+ .addConverterFactory(GsonConverterFactory.create())
+ .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
+ .build();
+ }
+
+ /**
+ * 获取Retrofit实例(对外提供API创建入口)
+ *
+ * @return Retrofit实例
+ */
+ public Retrofit getRetrofit() {
+ if (mRetrofit == null) {
+ // 双重检查,防止并发问题
+ synchronized (this) {
+ if (mRetrofit == null) {
+ initRetrofit();
+ }
+ }
+ }
+ return mRetrofit;
+ }
+
+ /**
+ * 获取OkHttpClient实例(对外暴露,便于扩展)
+ *
+ * @return OkHttpClient实例
+ */
+ public OkHttpClient getOkHttpClient() {
+ return mOkHttpClient;
+ }
+
+ /**
+ * 销毁单例(可选,比如App退出时)
+ * 避免内存泄漏(虽然用了AppContext,但主动销毁更规范)
+ */
+ public static void destroy() {
+ if (sInstance != null) {
+ // 关闭OkHttp连接池,释放资源
+ sInstance.mOkHttpClient.connectionPool().evictAll();
+ // 关闭Dispatcher,取消所有请求
+ sInstance.mOkHttpClient.dispatcher().cancelAll();
+ sInstance.mRetrofit = null;
+ sInstance.mOkHttpClient = null;
+ sInstance = null;
+ }
+ }
+
+ public static MultipartBody.Part getBodyPart(String type, String name, File file) {
+ MediaType mediaType = MediaType.Companion.parse(type);
+ RequestBody fileBody = RequestBody.Companion.create(file, mediaType);
+ MultipartBody.Part part = MultipartBody.Part.createFormData(name, file.getName(), fileBody);
+ return part;
+ }
+
+ public static RequestBody convertToRequestBodyjson(Object param) {
+ Gson gson = new Gson();
+ String json = gson.toJson(param);
+ Log.e(TAG, "convertToRequestBodyjson: " + json);
+ MediaType mediaType = MediaType.Companion.parse("application/json; charset=utf-8");
+ RequestBody requestBody = RequestBody.Companion.create(json, mediaType);
+ return requestBody;
+ }
+
+ public static RequestBody convertToRequestBody(Object param) {
+ MediaType mediaType = MediaType.Companion.parse("text/plain");
+ RequestBody requestBody = RequestBody.Companion.create(String.valueOf(param), mediaType);
+ return requestBody;
+ }
+
+ public static RequestBody convertToRequestBody(String param) {
+ RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), param);
+ return requestBody;
+ }
+
+ public static RequestBody convertToRequestBody(int param) {
+ RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(param));
+ return requestBody;
+ }
+
+ public static RequestBody convertToRequestBody(long param) {
+ RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(param));
+ return requestBody;
+ }
+
+
+ /**
+ * 统一线程调度 + 生命周期管理
+ *
+ * @param observable 接口Observable
+ * @param provider 生命周期管理者(Activity/Fragment)
+ * @return 处理后的Observable
+ */
+ public Observable schedule(Observable observable, BehaviorSubject provider) {
+ return observable
+ // 子线程执行请求
+ .subscribeOn(Schedulers.io())
+ // 主线程回调
+ .observeOn(AndroidSchedulers.mainThread())
+ // 生命周期管理:页面销毁自动取消请求,防止内存泄漏
+ .compose(RxLifecycle.bindUntilEvent(provider, ActivityEvent.DESTROY));
+ }
+
+ public Observable schedule(Observable observable) {
+ return observable
+ // 子线程执行请求
+ .subscribeOn(Schedulers.io())
+ // 主线程回调
+ .observeOn(AndroidSchedulers.mainThread());
+ }
+
+ public Observable getSnRegisterObservable(BehaviorSubject provider) {
+ return schedule(mRetrofit.create(SnApi.class).register(SystemUtils.getSerial(), Build.MODEL), provider);
+ }
+
+ public Observable> getUpdateHardwareInfoObservable(SnHardwareInfoReq snHardwareInfoReq) {
+ RequestBody body = convertToRequestBodyjson(snHardwareInfoReq);
+ return schedule(mRetrofit.create(SnApi.class).updateHardwareInfo(body));
+ }
+
+ public Observable getUploadScreenshotObservable(MultipartBody.Part part) {
+ return schedule(mRetrofit.create(SnApi.class).uploadScreenshot(part));
+ }
+
+ public Observable getUploadLocationObservable(SnLocationReq locationReq, BehaviorSubject provider) {
+ return schedule(mRetrofit.create(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)), provider);
+ }
+
+ public Observable getUploadLocationObservable(SnLocationReq locationReq) {
+ return schedule(mRetrofit.create(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)));
+ }
+
+ public Observable> getDeveloperOptionsObservable(BehaviorSubject provider) {
+ return schedule(mRetrofit.create(SnApi.class).getDeveloperOptions(), provider);
+ }
+
+ public Observable> getDeveloperOptionsObservable() {
+ return schedule(mRetrofit.create(SnApi.class).getDeveloperOptions());
+ }
+
+ public Observable getUploadInstallApksObservable(RequestBody body) {
+ return schedule(mRetrofit.create(SnApi.class).uploadInstallApks(body));
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/network/RetryCallback.java b/app/src/main/java/com/ttstd/dialer/network/RetryCallback.java
new file mode 100644
index 0000000..b3052f7
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/RetryCallback.java
@@ -0,0 +1,92 @@
+package com.ttstd.dialer.network;
+
+import android.util.Log;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public abstract class RetryCallback implements Callback {
+
+ private static final String TAG = "RetryCallback";
+
+ private int mRetryCount;
+ private long mRetryInterval;
+
+ private int mCurrentRetryCount;
+
+ private boolean isExecuting;
+
+ private Call mCall;
+
+ private Timer timer = new Timer();
+
+ public RetryCallback(Call call, int retryCount, long retryInterval) {
+ isExecuting = true;
+ mCall = call;
+ mRetryCount = retryCount;
+ mRetryInterval = retryInterval;
+ }
+
+ @Override
+ public final void onResponse(@NonNull Call call, @NonNull Response response) {
+ Log.e(TAG, "onResponse");
+ isExecuting = false;
+ if (!response.isSuccessful() && mCurrentRetryCount < mRetryCount) {
+ mCurrentRetryCount++;
+ retryRequest(call);
+ } else {
+ onRequestResponse(call, response);
+ }
+ }
+
+ @Override
+ public final void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ Log.e(TAG, "onFailure");
+ isExecuting = false;
+ if (mCurrentRetryCount < mRetryCount) {
+ mCurrentRetryCount++;
+ Log.e(TAG, "onFailure: " + "RetryCount: " + mCurrentRetryCount);
+ Log.e(TAG, "onFailure: " + "RetryResponse: " + call.request());
+ retryRequest(call);
+ } else {
+ onRequestFail(call, t);
+ }
+ }
+
+ private void retryRequest(final Call call) {
+ Log.e(TAG, "retryRequest");
+ onStartRetry();
+ TimerTask timerTask = new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (RetryCallback.this) {
+ mCall = call.clone();
+ mCall.enqueue(RetryCallback.this);
+ isExecuting = true;
+ }
+ }
+ };
+ timer.schedule(timerTask, mRetryInterval);
+ }
+
+ public void cancelCall() {
+ synchronized (this) {
+ if (!isExecuting) {
+ timer.cancel();
+ } else {
+ mCall.cancel();
+ }
+ }
+ }
+
+ public abstract void onRequestResponse(Call call, Response response);
+
+ public abstract void onRequestFail(Call call, Throwable t);
+
+ public abstract void onStartRetry();
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/network/SecurityUtils.java b/app/src/main/java/com/ttstd/dialer/network/SecurityUtils.java
new file mode 100644
index 0000000..4a7aa9f
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/SecurityUtils.java
@@ -0,0 +1,28 @@
+package com.ttstd.dialer.network;
+
+import android.os.Build;
+
+import com.ttstd.dialer.utils.HashUtils;
+
+import java.util.SortedMap;
+import java.util.function.BiConsumer;
+
+public class SecurityUtils {
+ public static String generateSign(SortedMap params) {
+ StringBuilder sb = new StringBuilder();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ params.forEach(new BiConsumer() {
+ @Override
+ public void accept(String key, String value) {
+ sb.append(key).append("=").append(value).append("&");
+ }
+ });
+ } else {
+ for (String key : params.keySet()) {
+ sb.append(key).append("=").append(params.get(key)).append("&");
+ }
+ }
+ sb.setLength(sb.length() - 1);
+ return HashUtils.getSHA256String(sb.toString());
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/UrlAddress.java b/app/src/main/java/com/ttstd/dialer/network/UrlAddress.java
deleted file mode 100644
index 9e6bea6..0000000
--- a/app/src/main/java/com/ttstd/dialer/network/UrlAddress.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.ttstd.dialer.network;
-
-public class UrlAddress {
-
-}
diff --git a/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java b/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java
new file mode 100644
index 0000000..a00a4b7
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/UrlConstants.java
@@ -0,0 +1,19 @@
+package com.ttstd.dialer.network;
+
+public class UrlConstants {
+ public static final String ROOT_URL = "http://192.168.100.111:8000/api/v1/sn/";
+
+ //设备注册
+ public static final String SN_REGISTER = "register";
+ //上传硬件信息
+ public static final String UPDATE_HARDWARE_INFO = "update_hardware_info";
+ //上传屏幕截图
+ public static final String UPLOAD_SCREENSHOT = "upload_screenshot";
+ //上传定位地址
+ public static final String UPLOAD_LOCATION = "upload_location";
+ //获取开发者选项
+ public static final String DEVELOPER_OPTIONS = "get_developer_options";
+ /*上传设备已安装应用列表*/
+ public static final String UPLOAD_INSTALL_APKS = "upload_install_apks";
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
new file mode 100644
index 0000000..9daf590
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/api/SnApi.java
@@ -0,0 +1,61 @@
+package com.ttstd.dialer.network.api;
+
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.bean.DeveloperOptions;
+import com.ttstd.dialer.network.UrlConstants;
+
+import io.reactivex.rxjava3.core.Observable;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Field;
+import retrofit2.http.FormUrlEncoded;
+import retrofit2.http.GET;
+import retrofit2.http.Multipart;
+import retrofit2.http.POST;
+import retrofit2.http.Part;
+
+public interface SnApi {
+ @FormUrlEncoded
+ @POST(UrlConstants.SN_REGISTER)
+ Observable register(
+ @Field("sn") String sn,
+ @Field("model") String model
+ );
+
+
+ @POST(UrlConstants.UPDATE_HARDWARE_INFO)
+ Observable> updateHardwareInfo(
+ @Body RequestBody body
+ );
+
+ @Multipart
+ @POST(UrlConstants.UPLOAD_SCREENSHOT)
+ Observable uploadScreenshot(
+ @Part MultipartBody.Part body
+ );
+
+ @Multipart
+ @POST(UrlConstants.UPLOAD_SCREENSHOT)
+ Call uploadScreenshotCall(
+ @Part MultipartBody.Part body
+ );
+
+ @POST(UrlConstants.UPLOAD_LOCATION)
+ Observable uploadLocation(
+ @Body RequestBody body
+ );
+
+
+ @GET(UrlConstants.DEVELOPER_OPTIONS)
+ Observable> getDeveloperOptions(
+
+ );
+
+ @POST(UrlConstants.UPLOAD_INSTALL_APKS)
+ Observable uploadInstallApks(
+ @Body RequestBody body
+ );
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/network/interceptor/AuthInterceptor.java b/app/src/main/java/com/ttstd/dialer/network/interceptor/AuthInterceptor.java
new file mode 100644
index 0000000..0294dca
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/network/interceptor/AuthInterceptor.java
@@ -0,0 +1,65 @@
+package com.ttstd.dialer.network.interceptor;
+
+import androidx.annotation.NonNull;
+
+import com.ttstd.dialer.mdm.DeviceManagerService;
+import com.ttstd.dialer.network.SecurityUtils;
+import com.ttstd.dialer.utils.NativeUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import okhttp3.Headers;
+import okhttp3.Interceptor;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * 认证拦截器
+ * 功能:为指定请求自动添加 Authorization Header,支持白名单过滤。
+ */
+public class AuthInterceptor implements Interceptor {
+
+ // 定义白名单路径(这些路径的请求将不添加Header)
+ private static final Set WHITE_LIST_PATHS = new HashSet<>(Arrays.asList(
+ "/api/v1/example"
+ // 可以继续添加其他白名单路径
+ ));
+
+ @NonNull
+ @Override
+ public Response intercept(@NonNull Chain chain) throws IOException {
+ Request originalRequest = chain.request();
+
+ // 1. 白名单检查:如果请求的URL路径在白名单中,则直接放行,不添加Header
+ String path = originalRequest.url().encodedPath();
+ if (WHITE_LIST_PATHS.contains(path)) {
+ return chain.proceed(originalRequest);
+ }
+
+ String sn = DeviceManagerService.getInstance().getSerial();
+ long timestamp = System.currentTimeMillis();
+ String nonce = NativeUtils.getNonce();
+
+ SortedMap headersMap = new TreeMap<>();
+ headersMap.put("X-Device-SN", sn);
+ headersMap.put("X-Nonce", nonce);
+ headersMap.put("X-Timestamp", String.valueOf(timestamp));
+ String sign = SecurityUtils.generateSign(headersMap);
+ headersMap.put("X-Sign", sign);
+
+ // 2. 构建请求头(所有参数放header,防止URL被抓包泄露)
+ Headers headers = Headers.of(headersMap);
+
+ Request newRequest = originalRequest.newBuilder()
+ .headers(headers)
+ .build();
+
+ // 3. 继续执行请求链
+ return chain.proceed(newRequest);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
new file mode 100644
index 0000000..0b2490f
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/push/PushExecutor.java
@@ -0,0 +1,281 @@
+package com.ttstd.dialer.push;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.tencent.mmkv.MMKV;
+import com.ttstd.dialer.BuildConfig;
+import com.ttstd.dialer.bean.ApkInstalledInfo;
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.bean.DeveloperOptions;
+import com.ttstd.dialer.bean.PushMessage;
+import com.ttstd.dialer.bean.req.SnHardwareInfoReq;
+import com.ttstd.dialer.config.CommonConfig;
+import com.ttstd.dialer.manager.AppManager;
+import com.ttstd.dialer.manager.MapManager;
+import com.ttstd.dialer.mdm.DeviceManagerService;
+import com.ttstd.dialer.network.BaseObserver;
+import com.ttstd.dialer.network.OkHttpManager;
+import com.ttstd.dialer.utils.CmdUtil;
+import com.ttstd.dialer.utils.Logger;
+import com.ttstd.dialer.utils.RebootUtils;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Function;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import okhttp3.MultipartBody;
+import okhttp3.RequestBody;
+
+public class PushExecutor {
+ private static final String TAG = "PushExecutor";
+
+ private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+
+ @SuppressLint("StaticFieldLeak")
+ private static PushExecutor sInstance;
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+
+ private static final String DEVICE_REFRESH = "1";
+ private static final String DEVICE_SCREEN_SNAPSHOT = "2";
+ private static final String DEVICE_REBOOT = "3";
+ private static final String DEVICE_SHUTDOEN = "4";
+ private static final String DEVICE_LOCATE = "5";
+ private static final String DEVICE_RESTORE = "6";
+ private static final String DEVICE_DEVELOPER = "7";
+
+ private PushExecutor(Context context) {
+ if (context == null) {
+ throw new RuntimeException("Context is NULL");
+ }
+ this.mContext = context;
+ this.mPackageManager = mContext.getPackageManager();
+ }
+
+ public static void init(Context context) {
+ if (sInstance == null) {
+ synchronized (PushExecutor.class) {
+ if (sInstance == null) {
+ sInstance = new PushExecutor(context.getApplicationContext());
+ }
+ }
+ }
+ }
+
+ public static PushExecutor getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("You must first initialize the PushExecutor");
+ }
+ return sInstance;
+ }
+
+ /**
+ * @param pushMessage 将厂家推送消息转换为自定义消息
+ * 方便以后更换推送sdk
+ */
+ public void setPushMessage(PushMessage pushMessage) {
+ String contentType = pushMessage.contentType;
+ String title = pushMessage.title;
+ String content = pushMessage.message;
+ String extra = pushMessage.extra;
+
+ switch (contentType) {
+ case DEVICE_REFRESH:
+ getDeviceSettings();
+ uploadDeviceInfo();
+ break;
+ case DEVICE_SCREEN_SNAPSHOT:
+ screenSnapshot();
+ break;
+ case DEVICE_REBOOT:
+ RebootUtils.rebootDevice(mContext, "push");
+ break;
+ case DEVICE_SHUTDOEN:
+ RebootUtils.shutdownDevice(mContext);
+ case DEVICE_LOCATE:
+ MapManager.getInstance().startLocation();
+ break;
+ case DEVICE_RESTORE:
+
+ break;
+ case DEVICE_DEVELOPER:
+
+ break;
+ default:
+ Logger.e(TAG, "setPushMessage: default");
+ }
+ }
+
+ private void getDeviceSettings() {
+ OkHttpManager.getInstance().getDeveloperOptionsObservable()
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e("getDeveloperOptions", "onSubscribe: ");
+ }
+
+ @Override
+ public void onNext(@NonNull BaseResponse baseResponse) {
+ Logger.e("getDeveloperOptions", "onNext: " + baseResponse);
+ if (baseResponse.isSuccess()) {
+ DeveloperOptions developerOptions = baseResponse.getData();
+ if (!BuildConfig.DEBUG) {
+ DeviceManagerService.getInstance().setDevelopmentOption(developerOptions.getDeveloperOptions() == 1);
+ DeviceManagerService.getInstance().setUsbDebugMode(developerOptions.getDeveloperOptions() == 1);
+ }
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e("getDeveloperOptions", "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e("getDeveloperOptions", "onComplete: ");
+ }
+ });
+ }
+
+ private void uploadDeviceInfo() {
+ Observable
+ .fromCallable(new Callable>() {
+ @Override
+ public List call() throws Exception {
+ long time = System.currentTimeMillis();
+ List apkInstalledInfos = AppManager.getInstance().getApkInstallInfos();
+ Log.e(TAG, "call: get apkInstalledInfos time: " + (System.currentTimeMillis() - time) + "ms");
+ //io 14738ms
+ //newThread 14351ms
+
+ return apkInstalledInfos;
+ }
+ })
+ .subscribeOn(Schedulers.computation())
+ .observeOn(AndroidSchedulers.mainThread())
+ .flatMap(new Function, Observable>() {
+ @Override
+ public Observable apply(List apkInstalledInfos) throws Throwable {
+ RequestBody body = OkHttpManager.convertToRequestBodyjson(apkInstalledInfos);
+ return OkHttpManager.getInstance().getUploadInstallApksObservable(body);
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e("uploadDeviceInfo", "onSubscribe: ");
+ }
+
+ @Override
+ public void onNext(@NonNull BaseResponse baseResponse) {
+ Logger.e("uploadDeviceInfo", "onNext: " + baseResponse);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e("uploadDeviceInfo", "onError: ", e);
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e("uploadDeviceInfo", "onComplete: ");
+ }
+ });
+
+
+ SnHardwareInfoReq snHardwareInfoReq = new SnHardwareInfoReq();
+ snHardwareInfoReq.setImei(DeviceManagerService.getInstance().getImei());
+ snHardwareInfoReq.setImsi(DeviceManagerService.getInstance().getImsi());
+ snHardwareInfoReq.setWlanMac(DeviceManagerService.getInstance().getWlanMac());
+ snHardwareInfoReq.setDeviceMac(DeviceManagerService.getInstance().getDeviceMac());
+ snHardwareInfoReq.setBluetoothMac(DeviceManagerService.getInstance().getBluetoothMac());
+
+ snHardwareInfoReq.setModel(Build.MODEL);
+ snHardwareInfoReq.setBrand(Build.BRAND);
+ snHardwareInfoReq.setBoard(Build.BOARD);
+ snHardwareInfoReq.setAndroidApi(Build.VERSION.SDK_INT);
+ snHardwareInfoReq.setAndroidVersion(Build.VERSION.RELEASE);
+ snHardwareInfoReq.setBuildId(Build.ID);
+ snHardwareInfoReq.setBuildDisplayId(Build.DISPLAY);
+
+ OkHttpManager.getInstance().getUpdateHardwareInfoObservable(snHardwareInfoReq)
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Log.e("updateHardwareInfo", "onSubscribe: ");
+ }
+
+ @Override
+ public void onNext(@NonNull BaseResponse voidBaseResponse) {
+ Log.e("updateHardwareInfo", "onNext: " + voidBaseResponse);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Log.e("updateHardwareInfo", "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Log.e("updateHardwareInfo", "onComplete: ");
+ }
+ });
+ }
+
+ private void screenSnapshot() {
+ String filepath = mContext.getExternalCacheDir().getAbsolutePath() + File.separator + "screenshot_" + System.currentTimeMillis() + ".png";
+
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Integer call() throws Exception {
+ CmdUtil.Result result = CmdUtil.execute("screencap -p " + filepath);
+ Logger.e(TAG, "call: " + result.error);
+ return result.code;
+ }
+ })
+ .subscribeOn(Schedulers.io()) // 在子线程执行截图
+ .flatMap(new Function>() {
+ @Override
+ public Observable apply(Integer integer) throws Throwable {
+ if (integer == 0) {
+ // 截图成功,上传文件
+ MultipartBody.Part part = OkHttpManager.getBodyPart("image/png", "file", new File(filepath));
+ return OkHttpManager.getInstance().getUploadScreenshotObservable(part)
+ .subscribeOn(Schedulers.io()); // 确保上传操作在子线程执行
+ } else {
+ // 截图失败,返回一个空的Observable
+ return Observable.empty();
+ }
+ }
+ })
+ .observeOn(AndroidSchedulers.mainThread()) // 在主线程回调
+ .subscribe(new BaseObserver() { // 注意这里应该是BaseResponse,不是Observable
+ @Override
+ public void onSuccess(BaseResponse baseResponse) {
+ Log.e("screenSnapshot", "onSuccess: " + baseResponse.toString());
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e("onFailure", "onFailure: " + e.getMessage());
+ }
+ });
+ }
+
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/push/jpush/PushMessageService.java b/app/src/main/java/com/ttstd/dialer/push/jpush/PushMessageService.java
index e221ab4..6123553 100644
--- a/app/src/main/java/com/ttstd/dialer/push/jpush/PushMessageService.java
+++ b/app/src/main/java/com/ttstd/dialer/push/jpush/PushMessageService.java
@@ -1,30 +1,95 @@
package com.ttstd.dialer.push.jpush;
import android.content.Context;
-import android.util.Log;
+import android.content.Intent;
+
+import com.ttstd.dialer.bean.PushMessage;
+import com.ttstd.dialer.push.PushExecutor;
+import com.ttstd.dialer.service.main.MainService;
+import com.ttstd.dialer.utils.Logger;
import cn.jpush.android.api.CustomMessage;
+import cn.jpush.android.api.JPushInterface;
import cn.jpush.android.api.JPushMessage;
+import cn.jpush.android.api.NotificationMessage;
import cn.jpush.android.service.JPushMessageReceiver;
public class PushMessageService extends JPushMessageReceiver {
private static final String TAG = "PushMessageService";
+
@Override
public void onMessage(Context context, CustomMessage customMessage) {
super.onMessage(context, customMessage);
+ Logger.e(TAG, "onMessage: " + customMessage);
+
+ PushMessage pushMessage = new PushMessage(customMessage);
+ PushExecutor.getInstance().setPushMessage(pushMessage);
+
+ Intent intent = new Intent(context, MainService.class);
+ intent.putExtra("PushMessage", pushMessage);
+ context.sendBroadcast(intent);
+
+ String contentType = customMessage.contentType;
+ String title = customMessage.title;
+ String content = customMessage.message;
+ String extra = customMessage.extra;
+
}
@Override
public void onAliasOperatorResult(Context context, JPushMessage jPushMessage) {
super.onAliasOperatorResult(context, jPushMessage);
int errorCode = jPushMessage.getErrorCode();
- Log.e(TAG, "onAliasOperatorResult: " + errorCode);
+ Logger.e(TAG, "onAliasOperatorResult: errorCode = " + errorCode);
if (errorCode == 0) {
- Log.e(TAG, "onAliasOperatorResult: " + jPushMessage.getAlias());
-
+ Logger.e(TAG, "onAliasOperatorResult: " + jPushMessage.getAlias());
+ Logger.e(TAG, "onAliasOperatorResult: registrationId = " + JPushInterface.getRegistrationID(context));
} else {
}
}
+
+ @Override
+ public void onTagOperatorResult(Context context, JPushMessage jPushMessage) {
+ super.onTagOperatorResult(context, jPushMessage);
+ Logger.e(TAG, "onTagOperatorResult: ");
+ }
+
+
+ @Override
+ public void onNotifyMessageOpened(Context context, NotificationMessage notificationMessage) {
+ super.onNotifyMessageOpened(context, notificationMessage);
+ Logger.e(TAG, "onNotifyMessageOpened: ");
+ }
+
+ @Override
+ public void onNotifyMessageArrived(Context context, NotificationMessage notificationMessage) {
+ super.onNotifyMessageArrived(context, notificationMessage);
+ Logger.e(TAG, "onNotifyMessageArrived: ");
+ }
+
+ @Override
+ public void onNotifyMessageUnShow(Context context, NotificationMessage notificationMessage) {
+ super.onNotifyMessageUnShow(context, notificationMessage);
+ Logger.e(TAG, "onNotifyMessageUnShow: ");
+ }
+
+ @Override
+ public void onNotifyMessageDismiss(Context context, NotificationMessage notificationMessage) {
+ super.onNotifyMessageDismiss(context, notificationMessage);
+ Logger.e(TAG, "onNotifyMessageDismiss: ");
+ }
+
+ @Override
+ public void onInAppMessageShow(Context context, NotificationMessage notificationMessage) {
+ super.onInAppMessageShow(context, notificationMessage);
+ Logger.e(TAG, "onInAppMessageShow: ");
+ }
+
+ @Override
+ public void onInAppMessageClick(Context context, NotificationMessage notificationMessage) {
+ super.onInAppMessageClick(context, notificationMessage);
+ Logger.e(TAG, "onInAppMessageClick: ");
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/receiver/AppChangedReceiver.java b/app/src/main/java/com/ttstd/dialer/receiver/AppChangedReceiver.java
index aeb0a14..6b2c6ca 100644
--- a/app/src/main/java/com/ttstd/dialer/receiver/AppChangedReceiver.java
+++ b/app/src/main/java/com/ttstd/dialer/receiver/AppChangedReceiver.java
@@ -4,10 +4,10 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
-import android.util.Log;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.config.CommonConfig;
+import com.ttstd.dialer.utils.Logger;
public class AppChangedReceiver extends BroadcastReceiver {
private static final String TAG = "ApkInstallReceiver";
@@ -17,20 +17,20 @@ public class AppChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
- Log.e(TAG, "onReceive: " + "action = " + action);
+ Logger.e(TAG, "onReceive: " + "action = " + action);
if (TextUtils.isEmpty(action)) {
return;
}
String packageName = intent.getDataString().replace("package:", "");
switch (action) {
case Intent.ACTION_PACKAGE_ADDED:
- Log.e(TAG, "onReceive: added " + packageName);
+ Logger.e(TAG, "onReceive: added " + packageName);
break;
case Intent.ACTION_PACKAGE_REPLACED:
- Log.e(TAG, "onReceive: replaced " + packageName);
+ Logger.e(TAG, "onReceive: replaced " + packageName);
break;
case Intent.ACTION_PACKAGE_REMOVED:
- Log.e(TAG, "onReceive: removed " + packageName);
+ Logger.e(TAG, "onReceive: removed " + packageName);
break;
default:
break;
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 f4dc589..d37643c 100644
--- a/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java
+++ b/app/src/main/java/com/ttstd/dialer/service/DialerAccessibilityService.java
@@ -13,9 +13,10 @@ import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
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;
@@ -26,31 +27,18 @@ 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.SystemUtils;
+import com.ttstd.dialer.utils.Logger;
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;
@@ -73,19 +61,27 @@ public class DialerAccessibilityService extends AccessibilityService {
private static final String DIALER_HANDS_FREE_CLOSE_TEXT = "免提,已关闭";
private static final int WAIT_TIME = 1600;
+ private static final int DEFAULT_DELAY = 500;
+ private static final int RANDOM_DELAY_RANGE = 300;
+
+ private static final int MSG_PROCESS_STEP = 1001;
+ private static final int MSG_DELAY_CLICK = 1002;
+ private static final int MSG_RETRY_STEP = 1003;
private Handler mHandler;
-
private Step mCurrentStep = Step.WAITING;
- private String mName = "";//微信昵称
- private String mTagName = "";//微信联系人标签名
+ private String mName = "";
+ private String mTagName = "";
private boolean mAutoAccept = false;
private boolean mAutoHandsFree = false;
private ContactInfo mContactInfo;
private int mCallType = ACTION_VIDEO;
+ private int mFindCount = 0;
+ private static final int MAX_FIND_COUNT = 5;
+ private final Random mRandom = new Random();
public interface AccessibilityEventCallback {
void onAccessibilityEventCallback(AccessibilityEvent accessibilityEvent);
@@ -96,42 +92,77 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onCreate() {
super.onCreate();
- Log.e(TAG, "onCreate: ");
+ Logger.e(TAG, "onCreate: ");
+ initHandler();
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() {
+ private void initHandler() {
+ mHandler = new Handler(Looper.getMainLooper()) {
@Override
- public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
- mAccessibilityEventCallback = emitter::onNext;
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PROCESS_STEP:
+ processCurrentStep();
+ break;
+ case MSG_DELAY_CLICK:
+ ClickInfo clickInfo = (ClickInfo) msg.obj;
+ performClick(clickInfo.node, clickInfo.simulate);
+ if (clickInfo.nextStep != null) {
+ mCurrentStep = clickInfo.nextStep;
+ sendProcessStepMessage(DEFAULT_DELAY);
+ }
+ break;
+ case MSG_RETRY_STEP:
+ mFindCount++;
+ if (mFindCount <= MAX_FIND_COUNT) {
+ processCurrentStep();
+ } else {
+ Logger.e(TAG, "Max retry count reached, reset to waiting");
+ mFindCount = 0;
+ mCurrentStep = Step.WAITING;
+ }
+ break;
+ }
}
- }).throttleLast(WAIT_TIME, TimeUnit.MILLISECONDS)
- .subscribe(new Consumer() {
- @Override
- public void accept(AccessibilityEvent accessibilityEvent) throws Throwable {
- Log.e(TAG, "analysisAccessibilityEvent accept: ");
- _onAccessibilityEvent(accessibilityEvent);
- }
- });
-
+ };
}
+ private void sendProcessStepMessage(long delay) {
+ mHandler.removeMessages(MSG_PROCESS_STEP);
+ mHandler.sendEmptyMessageDelayed(MSG_PROCESS_STEP, delay);
+ }
+
+ private void sendDelayClickMessage(AccessibilityNodeInfo node, boolean simulate, Step nextStep) {
+ Message msg = mHandler.obtainMessage(MSG_DELAY_CLICK);
+ msg.obj = new ClickInfo(node, simulate, nextStep);
+ long delay = DEFAULT_DELAY + mRandom.nextInt(RANDOM_DELAY_RANGE);
+ mHandler.sendMessageDelayed(msg, delay);
+ }
+
+ private void sendRetryStepMessage(long delay) {
+ mHandler.removeMessages(MSG_RETRY_STEP);
+ mHandler.sendEmptyMessageDelayed(MSG_RETRY_STEP, delay);
+ }
+
+ private void analysisAccessibilityEvent() {
+ }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- Log.e(TAG, "onStartCommand: ");
+ Logger.e(TAG, "onStartCommand: ");
if (intent != null) {
mContactInfo = (ContactInfo) intent.getSerializableExtra("ContactInfo");
- Log.e(TAG, "onStartCommand: contactInfo = " + mContactInfo);
+ Logger.e(TAG, "onStartCommand: contactInfo = " + mContactInfo);
mCallType = intent.getIntExtra("call_type", ACTION_VIDEO);
- mName = mContactInfo.getName();
+ mName = mContactInfo != null ? mContactInfo.getName() : "";
mCurrentStep = Step.CLICK_HOME;
if (mContactInfo != null) {
startWeixin();
+ sendProcessStepMessage(WAIT_TIME);
}
}
return super.onStartCommand(intent, flags, startId);
@@ -140,7 +171,10 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onDestroy() {
super.onDestroy();
- Log.e(TAG, "onDestroy: ");
+ Logger.e(TAG, "onDestroy: ");
+ if (mHandler != null) {
+ mHandler.removeCallbacksAndMessages(null);
+ }
if (mSettingReceiver != null) {
unregisterReceiver(mSettingReceiver);
}
@@ -148,31 +182,37 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
- Log.v(TAG, "onAccessibilityEvent: event = " + event.toString());
+ Logger.v(TAG, "onAccessibilityEvent: event = " + event.toString());
checkClassName(event);
- mAccessibilityEventCallback.onAccessibilityEventCallback(event);
+ if (mAccessibilityEventCallback != null) {
+ mAccessibilityEventCallback.onAccessibilityEventCallback(event);
+ }
+ if (mCurrentStep != Step.WAITING) {
+ sendProcessStepMessage(100);
+ }
+ }
+
+ @Override
+ public void onInterrupt() {
+
}
private void checkClassName(AccessibilityEvent event) {
- Log.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
+ Logger.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
- String currentPackageName = event.getPackageName().toString();
- String currentClassName = event.getClassName().toString();
+ String currentPackageName = event.getPackageName() != null ? event.getPackageName().toString() : "";
+ String currentClassName = event.getClassName() != null ? 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;
+ Logger.e(TAG, "checkClassName: to 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":
@@ -180,7 +220,6 @@ public class DialerAccessibilityService extends AccessibilityService {
mCurrentStep = Step.WAITING;
break;
case "com.tencent.mm.plugin.label.ui.ContactLabelManagerUI":
-
break;
default:
}
@@ -189,134 +228,159 @@ public class DialerAccessibilityService extends AccessibilityService {
}
}
- /**
- * 1.在微信页面直接找到联系人拨打电话
- * 2.在联系人页面找到并拨打
- * 3.通过进入联系人-标签找到并拨打
- *
- * @param event
- */
- private void _onAccessibilityEvent(AccessibilityEvent event) {
- Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep);
+ private void processCurrentStep() {
+ Logger.e(TAG, "processCurrentStep: " + mCurrentStep);
switch (mCurrentStep) {
case WAITING:
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
- Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept);
+ Logger.e(TAG, "processCurrentStep: 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);
+ Logger.e(TAG, "processCurrentStep: mAutoHandsFree = " + mAutoHandsFree);
if (mAutoHandsFree) {
- handsFree(Property.DESCRIPTION, HANDS_FREE_TEXT);
+ handleHandsFree(Property.DESCRIPTION, HANDS_FREE_TEXT, true);
} else {
- Log.e(TAG, "_onAccessibilityEvent: not enable auto handsfree");
+ Logger.e(TAG, "processCurrentStep: not enable auto handsfree");
+ mCurrentStep = Step.WAITING;
}
+ break;
case DIALER_HANDS_FREE:
- if (findHandsFree(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT)) {
- dialerHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT);
+ if (findNodeByProperty(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT, true) != null) {
+ handleHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT, false);
} else {
mCurrentStep = Step.WAITING;
}
break;
- case CLICK_HOME://主页能找到直接点击进去更多
- if (stepHome(Property.TEXT, mName)) {
- Log.e(TAG, "_onAccessibilityEvent: not found contact in home");
+ case CLICK_HOME:
+ if (findAndClick(Property.TEXT, mName, false, Step.CLICK_QUICK_WECHAT_CALL)) {
+ Logger.e(TAG, "processCurrentStep: 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);
+ if (findNodesByViewId("com.tencent.mm:id/gzf").size() > 0) {
+ findSearchContactAndClick("com.tencent.mm:id/odf", Step.CLICK_QUICK_WECHAT_CALL);
} else {
Toaster.show("没有找到联系人");
+ mCurrentStep = Step.WAITING;
}
break;
- case CLICK_QUICK_WECHAT_CALL://点击更多页面
+ 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://点击视频通话
+ case CLICK_TARGET:
stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
-// clickVideoCall();
break;
- case CLICK_CALL://打视频或者电话
+ case CLICK_CALL:
if (mCallType == ACTION_AUDIO) {
- step(Property.TEXT, VIDEO_TEXT, Step.WAITING);
+ findAndClick(Property.TEXT, VIDEO_TEXT, false, Step.WAITING);
} else if (mCallType == ACTION_VIDEO) {
- step(Property.TEXT, CALL_TEXT, Step.WAITING);
+ findAndClick(Property.TEXT, CALL_TEXT, false, Step.WAITING);
}
break;
-
- case CLICK_CONTACT://进入通讯录界面
- if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) {
- Log.e(TAG, "_onAccessibilityEvent: enter contact");
- } else {
+ case CLICK_CONTACT:
+ if (!findAndClick(Property.TEXT, CONTACT_TEXT, false, Step.FIND_TAG)) {
touchContact();
}
break;
- case FIND_CONTACT://模拟滑动找到联系人
- findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
+ case FIND_CONTACT:
+ findContactWithScroll(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
break;
case FIND_TAG:
- step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG);
+ findAndClick(Property.TEXT, TAG_TEXT, false, Step.CLICK_TAG);
break;
case CLICK_TAG:
- if (!step(Property.TEXT, mTagName, Step.CLICK_NAME)) {
+ if (!findAndClick(Property.TEXT, mTagName, false, Step.CLICK_NAME)) {
Toaster.show("没有找到标签");
mCurrentStep = Step.WAITING;
}
break;
- case CLICK_NAME://点击item
- findContact(Property.TEXT, mName, Step.CLICK_INFO);
+ case CLICK_NAME:
+ findContactWithScroll(Property.TEXT, mName, Step.CLICK_INFO);
break;
- case CLICK_INFO://进入个人信息页面
- stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL);
+ case CLICK_INFO:
+ findAndClickWithScroll(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: ");
-
+ private boolean findAndClick(Property type, String text, boolean searchWindows, Step nextStep) {
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, searchWindows);
+ if (node != null && isNodeVisible(node)) {
+ sendDelayClickMessage(node, false, nextStep);
+ return true;
+ }
+ return false;
}
- @Override
- protected void onServiceConnected() {
- super.onServiceConnected();
- Log.e(TAG, "onServiceConnected: ");
+ private void findAndClickWithScroll(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
+ if (node != null) {
+ if (node.isVisibleToUser()) {
+ sendDelayClickMessage(node, false, nextStep);
+ mFindCount = 0;
+ } else {
+ scrollDown();
+ sendRetryStepMessage(WAIT_TIME);
+ }
+ } else {
+ handleNotFoundWithRetry("没有找到联系人", nextStep);
+ }
}
- /**
- * 打开这个界面可以直接选择联系人拨打
- */
- 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 findContactWithScroll(Property type, String text, Step nextStep) {
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
+ if (node != null) {
+ sendDelayClickMessage(node, false, nextStep);
+ mFindCount = 0;
+ } else {
+ handleNotFoundWithRetry("没有找到联系人", nextStep);
+ }
+ }
+
+ private void handleNotFoundWithRetry(String errorMsg, Step nextStep) {
+ if (mFindCount >= MAX_FIND_COUNT) {
+ Logger.e(TAG, "handleNotFoundWithRetry: max count reached");
+ ToastUtils.showShort(errorMsg);
+ mCurrentStep = Step.WAITING;
+ mFindCount = 0;
+ } else {
+ Logger.e(TAG, "handleNotFoundWithRetry: not found, count=" + mFindCount);
+ mFindCount++;
+ scrollDown();
+ sendRetryStepMessage(WAIT_TIME);
+ }
+ }
+
+ private void handleHandsFree(Property type, String text, boolean isWechat) {
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
+ if (node != null) {
+ Point point = getPointByNode(node);
+ Logger.e(TAG, "handleHandsFree: " + point);
+ if (isWechat) {
+ clickByPoint(point.x, point.y - 50);
+ clickByPoint(point.x, point.y);
+ } else {
+ sendDelayClickMessage(node, false, Step.WAITING);
+ }
+ mCurrentStep = Step.WAITING;
+ } else {
+ Logger.e(TAG, "handleHandsFree: not found");
+ mCurrentStep = Step.WAITING;
+ }
}
private void autoAccept() {
- if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
+ if (findAndClickAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
ToastUtils.showShort("已自动接听视频/语音");
} else if (clickNode("com.tencent.mm:id/kfp", false)) {
@@ -324,171 +388,206 @@ public class DialerAccessibilityService extends AccessibilityService {
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);
+ private boolean findAndClickAnswer(Property type, String text) {
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, true);
+ if (node != null) {
+ Point point = getPointByNode(node);
+ Logger.e(TAG, "findAndClickAnswer: " + point);
+ clickByPoint(point.x, point.y - 50);
+ clickByPoint(point.x, point.y);
+ mCurrentStep = Step.WAITING;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean clickNode(String id, boolean simulate) {
+ findFloatWindowNode(id);
+ List nodeInfos = findNodesByViewId(id);
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);
+ Logger.e(TAG, "clickNode: performAction = " + performAction);
node.recycle();
return performAction;
} else {
if (simulate) {
- Point point = getPointtByNode(node);
- Log.e(TAG, "clickNode: " + point);
+ Point point = getPointByNode(node);
+ Logger.e(TAG, "clickNode: " + point);
clickByPoint(point.x, point.y);
- Log.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
+ Logger.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
} else {
- clickNode(findClickableNode(node));
+ AccessibilityNodeInfo clickableNode = findClickableNode(node);
+ if (clickableNode != null) {
+ sendDelayClickMessage(clickableNode, false, null);
+ }
}
}
return true;
} else {
- Log.e(TAG, "clickNode: not found");
+ Logger.e(TAG, "clickNode: not found");
return false;
}
}
+ private void performClick(AccessibilityNodeInfo node, boolean simulate) {
+ if (node == null) {
+ Logger.e(TAG, "performClick: node is null");
+ return;
+ }
+ try {
+ Logger.e(TAG, "performClick: getText = " + node.getText());
+ Logger.e(TAG, "performClick: isClickable = " + node.isClickable());
+ } catch (Exception e) {
+ Logger.e(TAG, "performClick: e = " + e.getMessage());
+ }
+ if (node.isClickable()) {
+ boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+ Logger.e(TAG, "performClick: performAction = " + performAction);
+ if (!performAction) {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Logger.e(TAG, "performClick: rect = " + rect);
+ int centerX = (rect.left + rect.right) / 2;
+ int centerY = (rect.top + rect.bottom) / 2;
+ Logger.e(TAG, "performClick: clickByNode = " + clickByPoint(centerX, centerY));
+ }
+ node.recycle();
+ } else {
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ Logger.e(TAG, "performClick: rect = " + rect);
+ int centerX = (rect.left + rect.right) / 2;
+ int centerY = (rect.top + rect.bottom) / 2;
+ Logger.e(TAG, "performClick: clickByNode = " + clickByPoint(centerX, centerY));
+ }
+ }
+
private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
if (node == null) {
- Log.e(TAG, "findClickableNode: node is null");
+ Logger.e(TAG, "findClickableNode: node is null");
return null;
}
if (node.isClickable()) {
return node;
} else {
- return findClickableNode(node);
+ AccessibilityNodeInfo parent = node.getParent();
+ node.recycle();
+ return parent != null ? findClickableNode(parent) : null;
}
}
public void findFloatWindowNode(String id) {
List windows = getWindows();
for (AccessibilityWindowInfo window : windows) {
- // 筛选悬浮窗窗口
if (isFloatingWindow(window)) {
AccessibilityNodeInfo rootNode = window.getRoot();
- // 处理悬浮窗节点
traverseNode(rootNode);
- rootNode.recycle(); // 释放资源
+ if (rootNode != null) {
+ rootNode.recycle();
+ }
}
}
-
}
private boolean isFloatingWindow(AccessibilityWindowInfo window) {
- Log.e(TAG, "isFloatingWindow: " + window.getType());
- // 根据窗口类型或标题筛选
+ Logger.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("已自动接听视频/语音");
+ private AccessibilityNodeInfo findNodeByProperty(Property type, String text, boolean searchWindows) {
+ if (searchWindows) {
+ return findNodeInWindows(type, text);
+ } else {
+ return findNodeInActiveWindow(getRootInActiveWindow(), type, text);
+ }
+ }
+
+ private AccessibilityNodeInfo findNodeInWindows(Property type, String text) {
+ for (AccessibilityWindowInfo window : getWindows()) {
+ AccessibilityNodeInfo root = window.getRoot();
+ AccessibilityNodeInfo node = findNodeInActiveWindow(root, type, text);
+ if (node != null) {
+ return node;
+ }
+ if (root != null) {
+ root.recycle();
+ }
+ }
+ Logger.e(TAG, "findNodeInWindows: not found");
+ return null;
+ }
+
+ private AccessibilityNodeInfo findNodeInActiveWindow(AccessibilityNodeInfo root, Property type, String text) {
+ if (root == null) return null;
+ Logger.v(TAG, "findNodeInActiveWindow: getText = " + root.getText());
+ Logger.v(TAG, "findNodeInActiveWindow: getClassName = " + root.getClassName());
+ Logger.v(TAG, "findNodeInActiveWindow: getContentDescription = " + root.getContentDescription());
+ boolean satisfied = checkPropertyMatch(root, type, text);
+ if (satisfied) {
+ return root;
+ } else {
+ for (int i = 0; i < root.getChildCount(); i++) {
+ AccessibilityNodeInfo result = findNodeInActiveWindow(root.getChild(i), type, text);
+ if (result != null) {
+ return result;
}
- } else {
- Log.e(TAG, "clickAnswer: Not in the answering interface");
}
}
+ root.recycle();
+ return null;
}
- 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) {
+ private boolean checkPropertyMatch(AccessibilityNodeInfo node, Property type, String text) {
+ switch (type) {
+ case TEXT:
+ return node.getText() != null && text.contentEquals(node.getText());
+ case CLASS_NAME:
+ return node.getClassName() != null && text.contentEquals(node.getClassName());
+ case DESCRIPTION:
+ return node.getContentDescription() != null && text.contentEquals(node.getContentDescription());
+ default:
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;
- }
+ private boolean isNodeVisible(AccessibilityNodeInfo node) {
+ if (node == null) return false;
+ Rect rect = new Rect();
+ node.getBoundsInScreen(rect);
+ return rect.left >= 0 && rect.top >= 0 && rect.right >= 0 && rect.bottom >= 0;
}
- @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);
+ 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;
+ sendDelayClickMessage(optional.get(), false, nextStep);
} else {
- Toaster.show("没有找到联系人");
- mCurrentStep = Step.WAITING;
+ findAndClick(Property.DESCRIPTION, SEARCH_TEXT, false, Step.CLICK_SEARCH);
+ }
+ }
+
+ private List findNodesByViewId(String id) {
+ AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
+ if (nodeInfo != null) {
+ return nodeInfo.findAccessibilityNodeInfosByViewId(id);
+ } else {
+ return new ArrayList<>();
}
}
@@ -500,358 +599,89 @@ public class DialerAccessibilityService extends AccessibilityService {
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); // 确保焦点在输入框
+ 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;
+ sendProcessStepMessage(WAIT_TIME);
} else {
Toaster.show("没有找到搜索框");
mCurrentStep = Step.WAITING;
}
}
- private void clickViewById(String id, Step nextStep) {
+ private void findSearchContactAndClick(String id, Step nextStep) {
List nodeInfos = findNodesByViewId(id);
+ Logger.e(TAG, "findSearchContactAndClick: " + nodeInfos);
Optional optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
- AccessibilityNodeInfo nodeInfo = optional.get();
- clickNode(nodeInfo);
- mCurrentStep = nextStep;
+ sendDelayClickMessage(optional.get(), false, nextStep);
} else {
-// Toaster.show("没有找到搜索按钮");
- step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
+ Toaster.show("没有找到联系人");
+ mCurrentStep = Step.WAITING;
}
}
- 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);
+ AccessibilityNodeInfo node = findNodeByProperty(type, text, false);
if (node != null) {
- Point point = getPointtByNode(node);
- Log.e(TAG, "stepCall: " + point);
+ Point point = getPointByNode(node);
+ Logger.e(TAG, "stepCall: " + point);
clickByPoint(point.x, point.y);
-// clickNode(node);
- Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done");
+ Logger.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done");
mCurrentStep = Step.CLICK_CALL;
- Log.e(TAG, "stepCall: next " + mCurrentStep);
+ Logger.e(TAG, "stepCall: next " + mCurrentStep);
+ sendProcessStepMessage(WAIT_TIME);
return true;
} else {
- Log.e(TAG, "stepCall: not found");
+ Logger.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;
+ private void touchContact() {
+ boolean successful = clickByPoint(268, 1440);
+ if (successful) {
+ mCurrentStep = Step.FIND_TAG;
+ sendProcessStepMessage(WAIT_TIME);
} 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;
+ Toaster.show("点击失败,请重试");
}
}
- 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) {
+ static Point getPointByNode(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;
+ return new Point(rect.centerX(), rect.centerY());
}
- //实现对(x,y)坐标进行点击操作。
private boolean clickByPoint(int x, int y) {
- Log.e(TAG, "clickByNode: x = " + x);
- Log.e(TAG, "clickByNode: y = " + y);
+ Logger.e(TAG, "clickByPoint: x = " + x);
+ Logger.e(TAG, "clickByPoint: 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)));
+ int duration = 200 + mRandom.nextInt(100);
+ builder.addStroke(new GestureDescription.StrokeDescription(path, 0, duration));
GestureDescription gesture = builder.build();
boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
- Log.e("clickByNode", "onCompleted: ");
+ Logger.e("clickByPoint", "onCompleted: ");
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
- Log.e("clickByNode", "onCompleted: ");
+ Logger.e("clickByPoint", "onCancelled: ");
}
}, null);
return dispatched;
@@ -861,35 +691,30 @@ public class DialerAccessibilityService extends AccessibilityService {
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 width = dm.widthPixels;
+ int height = dm.heightPixels;
+ Logger.e(TAG, "scrollScreen: screenWidth = " + width);
+ Logger.e(TAG, "scrollScreen: screenHeight = " + height);
int center_X = width / 2;
int center_Y = height / 2;
- Log.e("scrollScreen", "center position:" + "(" + center_X + "," + center_Y + ")");
+ Logger.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)); //终点坐标。
+ 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.");
+ Logger.d("scrollScreen", "dispatchGesture ScrollUp onCompleted.");
path.close();
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
- Log.d("scrollScreen", "dispatchGesture ScrollUp cancel.");
+ Logger.d("scrollScreen", "dispatchGesture ScrollUp cancel.");
}
}, null);
return dispatched;
@@ -905,39 +730,21 @@ public class DialerAccessibilityService extends AccessibilityService {
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];
- }
+ CLICK_VIDEO_CALL
}
private enum Property {
@@ -946,6 +753,18 @@ public class DialerAccessibilityService extends AccessibilityService {
DESCRIPTION
}
+ private static class ClickInfo {
+ AccessibilityNodeInfo node;
+ boolean simulate;
+ Step nextStep;
+
+ ClickInfo(AccessibilityNodeInfo node, boolean simulate, Step nextStep) {
+ this.node = node;
+ this.simulate = simulate;
+ this.nextStep = nextStep;
+ }
+ }
+
public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action";
public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action";
@@ -965,18 +784,18 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- Log.e("SettingReceiver", "onReceive: " + action);
+ Logger.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);
+ Logger.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);
+ Logger.e("SettingReceiver", "onReceive: autoAnswer = " + autoAnswer);
break;
default:
}
@@ -995,7 +814,7 @@ public class DialerAccessibilityService extends AccessibilityService {
try {
startActivity(intent);
} catch (Exception e) {
- Log.e(TAG, "startWeixin: " + e.getMessage());
+ Logger.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
index 143c16d..c70828d 100644
--- a/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
+++ b/app/src/main/java/com/ttstd/dialer/service/main/MainService.java
@@ -1,6 +1,5 @@
package com.ttstd.dialer.service.main;
-import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
@@ -15,22 +14,27 @@ import android.view.View;
import android.view.WindowManager;
import androidx.annotation.Nullable;
+import androidx.lifecycle.Observer;
import com.hjq.toast.Toaster;
+import com.jeremyliao.liveeventbus.LiveEventBus;
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.base.BaseService;
+import com.ttstd.dialer.bean.req.SnLocationReq;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.utils.ApkUtils;
+import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.SystemUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-public class MainService extends Service {
+public class MainService extends BaseService {
private static final String TAG = "MainService";
public static final String SHOW_FLOAT_WINDOW_ACTION = "show_float_window";
@@ -38,6 +42,8 @@ public class MainService extends Service {
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
+ private MainServiceModel mViewModel;
+
private WindowManager mWindowManager;
private View floatingView;
@@ -53,12 +59,25 @@ public class MainService extends Service {
@Override
public void onCreate() {
super.onCreate();
- Log.e(TAG, "onCreate: ");
+ Logger.e(TAG, "onCreate: ");
+ // 初始化ViewModel(绑定上下文+View回调)
+ mViewModel = new MainServiceModel();
+ // 绑定Service生命周期(关键:让请求跟随生命周期)
+ mViewModel.setLifecycleSubject(getLifecycleSubject());
+
+ LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, SnLocationReq.class)
+ .observeSticky(this, new Observer() {
+ @Override
+ public void onChanged(SnLocationReq snLocationReq) {
+ Log.e(TAG, "onChanged: receive snLocationReq");
+ mViewModel.uploadLocation(snLocationReq);
+ }
+ });
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
boolean enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0) == 1;
- Log.e(TAG, "onCreate: enableFloatWindow = " + enableFloatWindow);
+ Logger.e(TAG, "onCreate: enableFloatWindow = " + enableFloatWindow);
if (enableFloatWindow) {
showFloatWindow();
}
@@ -67,9 +86,10 @@ public class MainService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
if (intent != null) {
String action = intent.getAction();
- Log.e(TAG, "onStartCommand: action = " + action);
+ Logger.e(TAG, "onStartCommand: action = " + action);
if (!TextUtils.isEmpty(action)) {
switch (action) {
case SHOW_FLOAT_WINDOW_ACTION:
@@ -85,7 +105,7 @@ public class MainService extends Service {
}
private void showFloatWindow() {
- Log.e(TAG, "showFloatWindow: ");
+ Logger.e(TAG, "showFloatWindow: ");
initFloatingView();
if (Settings.canDrawOverlays(this)) {
@@ -94,14 +114,14 @@ public class MainService extends Service {
mWindowManager.addView(floatingView, mLayoutParams);
mFloatWindowShowing = true;
} catch (Exception e) {
- Log.e(TAG, "onCreate: addView = " + e.getMessage());
+ Logger.e(TAG, "onCreate: addView = " + e.getMessage());
}
}
}
private void initFloatingView() {
- Log.e(TAG, "initFloatingView: ");
+ Logger.e(TAG, "initFloatingView: ");
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window, null);
int layoutType;
@@ -130,7 +150,7 @@ public class MainService extends Service {
nvBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Log.e(TAG, "onClick: ");
+ Logger.e(TAG, "onClick: ");
// stopSelf(); // 停止服务,触发 onDestroy 移除悬浮窗
// mWindowManager.removeView(floatingView);
// floatingView = null;
@@ -159,7 +179,7 @@ public class MainService extends Service {
@Override
public boolean onTouch(View v, MotionEvent event) {
- Log.e(TAG, "onTouch: ");
+ Logger.e(TAG, "onTouch: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录初始位置
@@ -187,7 +207,7 @@ public class MainService extends Service {
}
private void hideFloatWindow() {
- Log.e(TAG, "hideFloatWindow: ");
+ Logger.e(TAG, "hideFloatWindow: ");
if (mFloatWindowShowing) {
if (floatingView != null) {
mWindowManager.removeView(floatingView);
@@ -211,7 +231,7 @@ public class MainService extends Service {
}};
private void killApp() {
- Log.e(TAG, "killApp: ");
+ Logger.e(TAG, "killApp: ");
List runningPackages = SystemUtils.getRunningTaskPackages(MainService.this);
int i = 0;
for (String pkg : runningPackages) {
diff --git a/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java b/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java
new file mode 100644
index 0000000..6c63764
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/service/main/MainServiceModel.java
@@ -0,0 +1,60 @@
+package com.ttstd.dialer.service.main;
+
+import android.util.Log;
+
+import androidx.lifecycle.ViewModel;
+
+import com.trello.rxlifecycle4.LifecycleTransformer;
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.network.BaseObserver;
+import com.ttstd.dialer.network.OkHttpManager;
+
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+public class MainServiceModel extends ViewModel {
+ private static final String TAG = "MainServiceModel";
+
+ private BehaviorSubject lifecycleSubject;
+
+ // 设置Service生命周期Subject
+ public void setLifecycleSubject(BehaviorSubject lifecycleSubject) {
+ this.lifecycleSubject = lifecycleSubject;
+ }
+
+ // 绑定请求到Service销毁事件(自动取消请求)
+ private LifecycleTransformer bindToLifecycle() {
+ if (lifecycleSubject == null) {
+ throw new IllegalStateException("请先设置LifecycleSubject!");
+ }
+ return RxLifecycle.bindUntilEvent(lifecycleSubject, ActivityEvent.DESTROY);
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ // 清空生命周期引用,防止内存泄漏
+ this.lifecycleSubject = null;
+ }
+
+
+ public void uploadLocation(SnLocationReq locationReq) {
+ Log.e(TAG, "uploadLocation: ");
+ OkHttpManager.getInstance().getUploadLocationObservable(locationReq)
+ .compose(bindToLifecycle())
+ .subscribe(new BaseObserver() {
+ @Override
+ public void onSuccess(BaseResponse baseResponse) {
+ Log.e("uploadLocation", "onSuccess: " + baseResponse);
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e("uploadLocation", "onFailure: " + e.getMessage());
+ }
+ });
+
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/AccessibilityServiceHelper.java b/app/src/main/java/com/ttstd/dialer/utils/AccessibilityServiceHelper.java
index 2271c60..7ac9f58 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/AccessibilityServiceHelper.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/AccessibilityServiceHelper.java
@@ -6,7 +6,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
-import android.util.Log;
import android.view.accessibility.AccessibilityManager;
import com.ttstd.dialer.service.DialerAccessibilityService;
@@ -35,9 +34,9 @@ public class AccessibilityServiceHelper {
// 构建你服务的完整组件标识符
String expectedServiceId = new ComponentName(context, serviceClass).flattenToShortString();
- Log.e(TAG, "isAccessibilityServiceEnabled: expectedServiceId = " + expectedServiceId);
+ Logger.e(TAG, "isAccessibilityServiceEnabled: expectedServiceId = " + expectedServiceId);
for (AccessibilityServiceInfo serviceInfo : enabledServices) {
- Log.e(TAG, "isAccessibilityServiceEnabled: serviceInfo.getId() = " + serviceInfo.getId());
+ Logger.e(TAG, "isAccessibilityServiceEnabled: serviceInfo.getId() = " + serviceInfo.getId());
if (expectedServiceId.equals(serviceInfo.getId())) {
return true;
}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java b/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
index d4f3cce..32c6728 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/ApkUtils.java
@@ -11,7 +11,6 @@ import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
-import android.util.Log;
import com.hjq.toast.Toaster;
import com.ttstd.dialer.R;
@@ -40,7 +39,7 @@ public class ApkUtils {
pkgContext.startActivity(intent);
return true;
}
- Log.e(TAG, "openPackage: can not open " + packageName);
+ Logger.e(TAG, "openPackage: can not open " + packageName);
Toaster.show("打开失败");
return false;
}
@@ -96,7 +95,7 @@ public class ApkUtils {
context.startActivity(intent);
return true;
} catch (Exception e) {
- Log.e(TAG, "openApp: " + e.getMessage());
+ Logger.e(TAG, "openApp: " + e.getMessage());
}
return false;
}
@@ -166,7 +165,7 @@ public class ApkUtils {
PackageManager pm = context.getPackageManager();
pi = pm.getPackageInfo(pkgName, 0);
} catch (PackageManager.NameNotFoundException e) {
- Log.e("isSystemApp: ", "NameNotFoundException:" + e.getMessage());
+ Logger.e("isSystemApp: ", "NameNotFoundException:" + e.getMessage());
}
// 是系统中已安装的应用
if (pi != null) {
@@ -185,7 +184,7 @@ public class ApkUtils {
try {
context.startActivity(storeIntent);
} catch (Exception e1) {
- Log.e(TAG, "openWeixin storeIntent: " + e1.getMessage());
+ Logger.e(TAG, "openWeixin storeIntent: " + e1.getMessage());
}
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java b/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java
new file mode 100644
index 0000000..a6824c9
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/CameraUtil.java
@@ -0,0 +1,567 @@
+package com.ttstd.dialer.utils;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * @作者 Liushihua
+ * @创建时间 2021-2-3 10:54
+ * @描述
+ */
+public class CameraUtil {
+ private static final String TAG = "CameraUtil";
+
+ private Context context;
+ private CameraCallBack cameraCallBack;
+ private CameraManager cameraManager;
+ // 默认相机id是0 LENS_FACING_FRONT,LENS_FACING_BACK
+ private int cameraId = CameraCharacteristics.LENS_FACING_FRONT;
+ private CameraDevice mCameraDevice;
+ private String savePath;
+
+ private CaptureRequest.Builder mPreviewRequestBuilder;
+ private CameraCaptureSession mCameraCaptureSession;
+ private CaptureRequest request;
+ private ExecutorService service;
+ private boolean isTakedPicture = false;//是否已经拍照
+
+ private int needSetOrientation = 0;// 设置默认的拍照方向
+ private boolean isInitOk = false;// 是否初始化成功
+ private boolean isSessionClosed = true;// captureSession是否被关闭
+ private boolean isCameraDoing = false;// 是否正在使用相机
+ private final long CAPTURE_DELAY_TIME_LONG = 1200;// 延时拍照——聚焦需要时间
+
+ private final int HANDLER_ERR = 3;// 拍照失败
+ private final int HANDLER_TAKE_PHOTO_SUCCESS = 5;// 拍照成功
+ private List enableCameraList;//可用摄像头列表
+ private boolean mFlashSupported = false;//是否支持闪光灯
+ private List recordSizeList;// 录制尺寸
+ private Size mPreviewOutputSize;// 预览尺寸
+ private List imgOutputSizes;// 拍照尺寸
+
+ private final int PREVIEW_TYPE_NORMAL = 0;// 默认预览
+ private final int PREVIEW_TYPE_RECORD = 1;// 录屏预览
+ private final int PREVIEW_TYPE_TAKE_PHOTO = 2;// 拍照预览
+ private int previewType = 0;//默认预览
+
+ private boolean mTakePictureFinish = false;
+
+ private long lastSaveFileTime = 0;
+
+ public interface CameraCallBack {
+ void onErr(String msg);
+
+ void onTakePhotoOk(String path);
+ }
+
+ /**
+ * 处理静态图片的输出
+ */
+ private ImageReader imageReader;
+
+ private Handler handler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ switch (msg.what) {
+ case HANDLER_ERR:
+ cameraCallBack.onErr("" + msg.obj);
+ isCameraDoing = false;
+ break;
+ case HANDLER_TAKE_PHOTO_SUCCESS:
+ if (!mTakePictureFinish) {
+ cameraCallBack.onTakePhotoOk(savePath);
+ mTakePictureFinish = true;
+ }
+ break;
+ default:
+ }
+ }
+ };
+
+ /**
+ * 当相机设备的状态发生改变的时候,将会回调。
+ */
+ protected final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
+ /**
+ * 当相机打开的时候,调用
+ * @param cameraDevice
+ */
+ @Override
+ public void onOpened(@NonNull CameraDevice cameraDevice) {
+ Logger.e(TAG, "onOpened");
+ mCameraDevice = cameraDevice;
+ startPreview();
+ }
+
+ @Override
+ public void onDisconnected(@NonNull CameraDevice cameraDevice) {
+ Logger.e(TAG, "onDisconnected");
+ cameraDevice.close();
+ mCameraDevice = null;
+ Message message = new Message();
+ message.what = HANDLER_ERR;
+ message.obj = "后台相机断开连接";
+ handler.sendMessage(message);
+ }
+
+ /**
+ * 发生异常的时候调用
+ *
+ * 这里释放资源,然后关闭界面
+ * @param cameraDevice
+ * @param error
+ */
+ @Override
+ public void onError(@NonNull CameraDevice cameraDevice, int error) {
+ Logger.e(TAG, "onError 相机设备异常,请重启!");
+ cameraDevice.close();
+ mCameraDevice = null;
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "相机设备异常,请重启!";
+ handler.sendMessage(messagef);
+ }
+
+ /**
+ *当相机被关闭的时候
+ */
+ @Override
+ public void onClosed(@NonNull CameraDevice camera) {
+ super.onClosed(camera);
+ Logger.e(TAG, "onClosed");
+ mCameraDevice = null;
+ isCameraDoing = false;
+ }
+ };
+
+ /**
+ * 相机状态回调
+ */
+ private CameraManager.AvailabilityCallback callback = new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraAvailable(@NonNull String cameraId) {// 相机可用
+ super.onCameraAvailable(cameraId);
+ Logger.e(TAG, "相机可用");
+ }
+
+ @Override
+ public void onCameraUnavailable(@NonNull String cameraId) {// 相机不可用
+ super.onCameraUnavailable(cameraId);
+ Logger.e(TAG, "相机不可用");
+ }
+ };
+
+
+ /**
+ * 初始化
+ *
+ * @param activity
+ * @param cameraCallBack 回调
+ */
+ public CameraUtil(Context activity, @NonNull CameraCallBack cameraCallBack) {
+ this.context = activity;
+ this.cameraCallBack = cameraCallBack;
+ cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
+ // 对于静态图片,使用可用的最大值来拍摄。
+ if (cameraManager != null) {
+ isInitOk = true;
+ cameraManager.registerAvailabilityCallback(callback, null);
+ getCameraInfo();
+ setupImageReader();
+ service = Executors.newSingleThreadExecutor();
+ }
+ }
+
+ private void setupImageReader() {
+// Size size = getOutputSize(imgOutputSizes);
+ //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据,本例的3代表ImageReader中最多可以获取2帧图像流
+ imageReader = ImageReader.newInstance(1600, 1200, ImageFormat.JPEG, 1);
+ //监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
+ imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ Image image = reader.acquireLatestImage();
+ //我们可以将这帧数据转成字节数组,类似于Camera1的PreviewCallback回调的预览帧数据
+ ByteBuffer buffer = image.getPlanes()[0].getBuffer();
+ byte[] data = new byte[buffer.remaining()];
+ buffer.get(data);
+ image.close();
+ saveFile(data, savePath);
+ }
+ }, null);
+ }
+
+ /**
+ * 打开相机
+ */
+ private void openCamera() {
+ cameraId = getFrontCameraId();
+ Logger.e(TAG, "openCamera: getFrontCameraId = " + getFrontCameraId());
+ Logger.e(TAG, "openCamera:" + cameraId);
+ isCameraDoing = true;
+ // 设置TextureView的缓冲区大小
+ // 获取Surface显示预览数据
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ Message message = new Message();
+ message.what = HANDLER_ERR;
+ message.obj = "权限不足";
+ handler.sendMessage(message);
+ return;
+ }
+ try {
+ cameraManager.openCamera(Integer.toString(cameraId), stateCallback, null);
+ Logger.e(TAG, "打开相机成功!");
+ } catch (Exception e) {
+ Logger.e(TAG, "打开相机失败-Exception:" + e.getMessage());
+ e.printStackTrace();
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "打开相机失败";
+ handler.sendMessage(messagef);
+ }
+ }
+
+ private int getFrontCameraId() {
+ int numberOfCameras = Camera.getNumberOfCameras();
+ for (int i = 0; i < numberOfCameras; i++) {
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * 开始视频录制预览
+ */
+ private void startPreview() {
+ Logger.e(TAG, "startPreview");
+ // CaptureRequest添加imageReaderSurface,不加的话就会导致ImageReader的onImageAvailable()方法不会回调
+ // 创建CaptureSession时加上imageReaderSurface,如下,这样预览数据就会同时输出到previewSurface和imageReaderSurface了
+ try {
+ // 创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求
+ mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ mPreviewRequestBuilder.addTarget(imageReader.getSurface());// 设置Surface作为预览数据的显示界面
+ // 创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
+ mCameraDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureSessionStateCallBack, null);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "捕获帧失败";
+ handler.sendMessage(messagef);
+ Logger.e(TAG, "Camera获取成功,创建录制请求或捕获Session失败" + e.getMessage());
+ }
+ }
+
+ /**
+ * 捕获图片数据
+ */
+ private CameraCaptureSession.StateCallback captureSessionStateCallBack = new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(CameraCaptureSession session) {
+ try {
+ mCameraCaptureSession = session;
+ isSessionClosed = false;
+ request = mPreviewRequestBuilder.build();
+ // 设置反复捕获数据的请求,这样预览界面就会一直有数据显示
+ mCameraCaptureSession.setRepeatingRequest(request, null, null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "开启图像预览失败";
+ handler.sendMessage(messagef);
+ }
+ if (!isTakedPicture) {
+ isTakedPicture = true;
+ handler.postDelayed(() -> takePicture(), CAPTURE_DELAY_TIME_LONG);
+ }
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession session) {
+ Logger.e(TAG, "onConfigureFailed");
+ }
+ };
+
+
+ public void startTakePicture(String savePath) {
+ Logger.e(TAG, "拍照:" + savePath);
+ this.savePath = savePath;
+ if (isCameraDoing) {
+ Logger.e(TAG, "相机使用中...");
+ } else {
+ isTakedPicture = false;
+ openCamera();
+ }
+ }
+
+ /**
+ * 拍照
+ */
+ private void takePicture() {
+ Logger.e(TAG, "takePicture");
+ try {
+ if (mCameraDevice == null || mPreviewRequestBuilder == null) return;
+ mPreviewRequestBuilder.addTarget(imageReader.getSurface());
+ //设置拍照方向
+ mPreviewRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, this.needSetOrientation);
+ //这个回调接口用于拍照结束时重启预览,因为拍照会导致预览停止
+ CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
+ Logger.e(TAG, "拍照完成");
+ onStop();
+ }
+ };
+ //开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
+ mCameraCaptureSession.capture(mPreviewRequestBuilder.build(), mImageSavedCallback, null);
+ } catch (CameraAccessException e) {
+ Logger.e(TAG, "takePhoto CameraAccessException:" + e.getMessage());
+ e.printStackTrace();
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "拍照失败";
+ handler.sendMessage(messagef);
+ }
+ }
+
+ /**
+ * 停止预览,释放资源
+ */
+ public void stopRecord() {
+ Logger.e(TAG, "停止预览,释放资源");
+ try {
+ if (mCameraCaptureSession != null && !isSessionClosed) {
+ mCameraCaptureSession.stopRepeating();
+ mCameraCaptureSession.abortCaptures();
+ mCameraCaptureSession.close();
+ isSessionClosed = true;
+ imageReader.close();
+ imageReader = null;
+ }
+ if (mCameraDevice != null)
+ mCameraDevice.close();
+ isCameraDoing = false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ Logger.e(TAG, "stopRecord-Exception:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 重置后,开始预览
+ */
+ public void reset() {
+ previewType = PREVIEW_TYPE_NORMAL;
+ stopRecord();
+ openCamera();
+ }
+
+ /**
+ * 在 activity,fragment的onStop中调用
+ */
+ public void onStop() {
+ stopRecord();
+ }
+
+ /**
+ * 注销 回调
+ */
+ public void onDestroy() {
+ this.cameraCallBack = null;
+ if (cameraManager != null)
+ cameraManager.unregisterAvailabilityCallback(callback);
+ }
+
+ /**
+ * 获得可用的摄像头
+ *
+ * @return SparseArray of available cameras ids。key为摄像头方位,见CameraCharacteristics#LENS_FACING,value为对应的摄像头id
+ */
+ public void getCameras() {
+ if (cameraManager == null) return;
+ enableCameraList = new ArrayList<>();
+ try {
+ String[] camerasAvailable = cameraManager.getCameraIdList();
+ CameraCharacteristics cam;
+ Integer characteristic;
+ Logger.e(TAG, "-------------------------------------");
+ for (String id : camerasAvailable) {
+ Logger.e(TAG, "getCameras:" + id);
+ try {
+ enableCameraList.add(Integer.parseInt(id));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ Logger.e(TAG, "-------------------------------------");
+ } catch (CameraAccessException e) {
+ Logger.e(TAG, "getCameras CameraAccessException:" + e.getMessage());
+ }
+ }
+
+ /**
+ * 设置输出数据尺寸选择器,在selectCamera之前设置有效
+ * (一般手机支持多种输出尺寸,请用户根据自身需要选择最合适的一种)
+ * 举例:
+ * SizeSelector maxPreview = SizeSelectors.and(SizeSelectors.maxWidth(720), SizeSelectors.maxHeight(480));
+ * SizeSelector minPreview = SizeSelectors.and(SizeSelectors.minWidth(320), SizeSelectors.minHeight(240));
+ * camera.setmOutputSizeSelector(SizeSelectors.or(
+ * SizeSelectors.and(maxPreview, minPreview)//先在最大和最小中寻找
+ * , SizeSelectors.and(maxPreview, SizeSelectors.biggest())//找不到则按不超过最大尺寸的那个选择
+ * ));
+ */
+ public void getOutputSizeSelector() {
+
+ }
+
+
+ /**
+ * 获取摄像头信息
+ */
+ public void getCameraInfo() {
+ if (enableCameraList == null) {
+ getCameras();
+ }
+ try {
+ CameraCharacteristics mCameraCharacteristics = cameraManager.getCameraCharacteristics(String.valueOf(cameraId));
+ // 设置是否支持闪光灯
+ Boolean available = mCameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ mFlashSupported = available == null ? false : available;
+ StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ if (map == null) {
+ Logger.e(TAG, "Could not get configuration map.");
+ return;
+ }
+ int mSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);//这个方法来获取CameraSensor的方向。
+ Logger.e(TAG, "camera sensor orientation:" + mSensorOrientation + ",display rotation=" + context.getDisplay().getRotation());
+
+ int[] formats = map.getOutputFormats();//获得手机支持的输出格式,其中jpeg是一定会支持的,yuv_420_888是api21才开始支持的
+ for (int format : formats) {
+ Logger.e(TAG, "手机格式支持: " + format);
+ }
+ Size[] yuvOutputSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
+ Size[] mediaOutputSizes = map.getOutputSizes(MediaRecorder.class);
+ Size[] previewOutputSizes = map.getOutputSizes(SurfaceTexture.class);
+ Size[] jpegOutputSizes = map.getOutputSizes(ImageFormat.JPEG);
+
+ recordSizeList = new ArrayList<>();
+ imgOutputSizes = new ArrayList<>();
+
+ Logger.e(TAG, "---------------------------------------------------");
+ for (Size size : mediaOutputSizes) {
+ recordSizeList.add(new Size(size.getWidth(), size.getHeight()));
+ Logger.e(TAG, "mediaOutputSizes: " + size.toString());
+ }
+ for (Size size : jpegOutputSizes) {
+ imgOutputSizes.add(new Size(size.getWidth(), size.getHeight()));
+ Logger.e(TAG, "jpegOutputSizes: " + size.toString());
+ }
+ for (Size size : previewOutputSizes) {
+ Logger.e(TAG, "previewOutputSizes: " + size.toString());
+ }
+ for (Size size : yuvOutputSizes) {
+ Logger.e(TAG, "yuvOutputSizes: " + size.toString());
+ }
+ Logger.e(TAG, "---------------------------------------------------");
+ } catch (Exception e) {
+ Logger.e(TAG, "selectCamera Exception:" + e.getMessage());
+ }
+ }
+
+ private Size getOutputSize(List sizes) {
+ DisplayMetrics dm = context.getResources().getDisplayMetrics();
+ int screenWidth = dm.widthPixels;
+ int screenHeight = dm.heightPixels;
+ Logger.e(TAG, "getOutputSize: screenWidth = " + screenWidth);
+ Logger.e(TAG, "getOutputSize: screenHeight = " + screenHeight);
+ List sorted = sizes.stream().sorted(new Comparator() {
+ @Override
+ public int compare(Size o1, Size o2) {
+ return Long.compare(o1.getHeight() * o1.getWidth(), o2.getHeight() * o2.getWidth());
+ }
+ }).collect(Collectors.toList());
+ for (Size size : sorted) {
+ if (size.getWidth() > screenHeight && size.getHeight() > screenWidth) {
+ return size;
+ }
+ }
+ return new Size(1600, 1200);
+ }
+
+ //覆盖性保存
+ private void saveFile(final byte[] data, final String savePath) {
+ if (data == null || data.length == 0) return;
+ if (System.currentTimeMillis() - lastSaveFileTime > 1000)
+ service.execute(() -> {
+ File file = new File(savePath);
+ File parent = file.getParentFile();
+ if (parent != null && !parent.exists()) {
+ parent.mkdirs();
+ }
+ if (file.exists()) {
+ file.delete();
+ }
+ try {
+ file.createNewFile();
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(data);
+ fos.flush();
+ fos.close();
+ lastSaveFileTime = System.currentTimeMillis();
+ Message message = new Message();
+ message.what = HANDLER_TAKE_PHOTO_SUCCESS;
+ message.obj = savePath;
+ handler.sendMessage(message);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Logger.e(TAG, "图片保存-IOException:" + e.getMessage());
+ Message messagef = new Message();
+ messagef.what = HANDLER_ERR;
+ messagef.obj = "图片保存失败";
+ handler.sendMessage(messagef);
+ }
+ });
+ }
+}
+
diff --git a/app/src/main/java/com/ttstd/dialer/utils/CmdUtil.java b/app/src/main/java/com/ttstd/dialer/utils/CmdUtil.java
new file mode 100644
index 0000000..43433d5
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/CmdUtil.java
@@ -0,0 +1,144 @@
+package com.ttstd.dialer.utils;
+
+import android.text.TextUtils;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class CmdUtil {
+ private static final String TAG = "CmdUtil";
+
+ private static final String COMMAND_SH = "sh";
+ private static final String COMMAND_EXIT = "exit\n";
+ private static final String COMMAND_LINE_END = "\n";
+
+
+ /**
+ * 运行命令
+ *
+ * @param command 命令
+ * @return 结果
+ */
+ public static Result execute(String command) {
+ Logger.i(TAG, "execute() command = " + command);
+ Result result = new Result();
+
+ if (TextUtils.isEmpty(command)) {
+ Logger.w(TAG, "WARNING: command should not be null or empty");
+ return result;
+ }
+
+ Process process = null;
+ DataOutputStream dos = null;
+
+ try {
+ process = Runtime.getRuntime().exec(COMMAND_SH);
+ dos = new DataOutputStream(process.getOutputStream());
+ dos.write(command.trim().getBytes());
+ dos.writeBytes(COMMAND_LINE_END);
+ dos.flush();
+ dos.writeBytes(COMMAND_EXIT);
+ dos.flush();
+ result.code = process.waitFor();
+ result.success = readBuffer(new BufferedReader(new InputStreamReader(process.getInputStream())));
+ result.error = readBuffer(new BufferedReader(new InputStreamReader(process.getErrorStream())));
+ Logger.i(TAG, "result = " + result);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ Logger.e(TAG, ioe.getMessage());
+ } catch (InterruptedException ie) {
+ ie.printStackTrace();
+ Logger.e(TAG, ie.getMessage());
+ } finally {
+ try {
+ if (null != dos) {
+ dos.close();
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ Logger.e(TAG, ioe.getMessage());
+ }
+ if (null != process) {
+ process.destroy();
+ }
+ }
+ return result;
+ }
+
+ private static String readBuffer(BufferedReader bufferedReader) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ String s;
+ while ((s = bufferedReader.readLine()) != null) {
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Command执行结果
+ */
+ public static final class Result {
+
+ public static final int SUCCESS = 0;
+ public static final int ERROR = -1;
+
+ public int code = ERROR;
+ public String error;
+ public String success;
+
+ @Override
+ public String toString() {
+ return "Result{" +
+ "code=" + code +
+ ", error='" + error + '\'' +
+ ", success='" + success + '\'' +
+ '}';
+ }
+ }
+
+
+ /**
+ * 执行 adb 命令
+ *
+ * @param cmd 命令
+ * @return
+ */
+ public static StringBuffer shellExec(String cmd) {
+ Runtime mRuntime = Runtime.getRuntime(); //执行命令的方法
+ try {
+ //Process中封装了返回的结果和执行错误的结果
+ Process mProcess = mRuntime.exec(cmd); //加入参数
+ //使用BufferReader缓冲各个字符,实现高效读取
+ //InputStreamReader将执行命令后得到的字节流数据转化为字符流
+ //mProcess.getInputStream()获取命令执行后的的字节流结果
+ BufferedReader mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
+ //实例化一个字符缓冲区
+ StringBuffer mRespBuff = new StringBuffer();
+ //实例化并初始化一个大小为1024的字符缓冲区,char类型
+ char[] buff = new char[1024];
+ int ch = 0;
+ //read()方法读取内容到buff缓冲区中,大小为buff的大小,返回一个整型值,即内容的长度
+ //如果长度不为null
+ while ((ch = mReader.read(buff)) != -1) {
+ //就将缓冲区buff的内容填进字符缓冲区
+ mRespBuff.append(buff, 0, ch);
+ }
+ //结束缓冲
+ mReader.close();
+ Logger.i("shell", "shellExec: " + mRespBuff);
+ //弹出结果
+// Logger.i("shell", "执行命令: " + cmd + "执行成功");
+ return mRespBuff;
+
+ } catch (IOException e) {
+ // 异常处理
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
+
diff --git a/app/src/main/java/com/ttstd/dialer/utils/FileUtils.java b/app/src/main/java/com/ttstd/dialer/utils/FileUtils.java
new file mode 100644
index 0000000..ce475ac
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/FileUtils.java
@@ -0,0 +1,24 @@
+package com.ttstd.dialer.utils;
+
+import android.content.Context;
+import android.os.Environment;
+
+public class FileUtils {
+ public static String getCacheDir(Context context) {
+ String cachePath;
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
+ || !Environment.isExternalStorageRemovable()) {
+ if (context.getExternalCacheDir() != null) {
+ cachePath = context.getExternalCacheDir().getPath();
+ } else if (context.getExternalFilesDir("cache") != null) {
+ cachePath = context.getExternalFilesDir("cache").getPath();
+ } else {
+ cachePath = context.getCacheDir().getPath();
+ }
+ } else {
+ cachePath = context.getCacheDir().getPath();
+ }
+ return cachePath;
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/HashUtils.java b/app/src/main/java/com/ttstd/dialer/utils/HashUtils.java
new file mode 100644
index 0000000..6904d5f
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/HashUtils.java
@@ -0,0 +1,145 @@
+package com.ttstd.dialer.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+public class HashUtils {
+
+ public static String getFileMd5(File file) {
+ if (file == null || !file.exists()) {
+ return null;
+ }
+
+ MessageDigest digest;
+ InputStream inputStream = null;
+
+ try {
+ digest = MessageDigest.getInstance("MD5");
+ inputStream = new FileInputStream(file);
+
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ digest.update(buffer, 0, len);
+ }
+
+ return bytesToHex(digest.digest());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * 计算字符串的 SHA-256 哈希值
+ *
+ * @param input 需要计算的字符串
+ * @return 64位长度的十六进制哈希字符串
+ */
+ public static String getSHA256String(String input) {
+ if (input == null || input.isEmpty()) {
+ return null;
+ }
+ try {
+ // 获取 SHA-256 实例 (原生支持,无需外部依赖)
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ // 将字符串转换为字节数组并进行 Hash 计算
+ byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
+ // 将字节数组转换为十六进制字符串
+ return bytesToHex(hashBytes);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 计算文件的 SHA-256 哈希值 (常用于文件完整性校验)
+ *
+ * @param file 需要计算的文件
+ * @return 64位长度的十六进制哈希字符串
+ */
+ public static String getFileSHA256(File file) {
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ FileInputStream fis = null;
+ try {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ fis = new FileInputStream(file);
+ byte[] buffer = new byte[8192]; // 8KB 缓冲区
+ int length;
+ // 分块读取文件并更新 Hash
+ while ((length = fis.read(buffer)) != -1) {
+ digest.update(buffer, 0, length);
+ }
+ return bytesToHex(digest.digest());
+ } catch (NoSuchAlgorithmException | IOException e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * 将字节数组转换为小写的十六进制字符串
+ */
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder hexString = new StringBuilder(2 * bytes.length);
+ for (byte b : bytes) {
+ // 将 byte 转换为无符号整数并格式化为两位的十六进制数
+ String hex = Integer.toHexString(0xff & b);
+ if (hex.length() == 1) {
+ hexString.append('0');
+ }
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+
+ /**
+ * ⚠️ 特别提示:如果是为了“加密密码”
+ * 如果你的目的是在本地或传输前对用户密码进行 Hash,单纯的 SHA-256 是不够安全的(容易被彩虹表破解)。
+ * 对于密码存储,建议使用加盐的迭代算法。
+ * 在低版本 Android 上兼容性最好的原生方案是 PBKDF2WithHmacSHA1:
+ *
+ * @param password
+ * @param salt
+ * @return
+ */
+ public static String hashPasswordPBKDF2(String password, byte[] salt) {
+ try {
+ // 迭代 10000 次,生成 256 位的密钥。PBKDF2WithHmacSHA1 兼容 API 10+
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
+ SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ byte[] hash = skf.generateSecret(spec).getEncoded();
+ return bytesToHex(hash);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/utils/Logger.java b/app/src/main/java/com/ttstd/dialer/utils/Logger.java
index 948db68..9830cea 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/Logger.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/Logger.java
@@ -9,6 +9,8 @@ import com.ttstd.dialer.BuildConfig;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -16,113 +18,228 @@ import java.util.Locale;
public class Logger {
// 日志级别枚举
public enum LogLevel {
- DEBUG, INFO, WARN, ERROR
+ VERBOSE, DEBUG, INFO, WARN, ERROR
}
private static final String TAG = "AppLogger";
private static final String LOG_FILE_NAME = "app_log.txt";
private static File logFile;
private static LogLevel logLevel = LogLevel.DEBUG; // 默认日志级别
- private static boolean isDebugMode = BuildConfig.DEBUG; // 默认关闭Debug输出
+ private static boolean isDebugMode = BuildConfig.DEBUG; // Debug模式开关
// 初始化日志系统(需在应用启动时调用)
public static void initialize(Context context, boolean debugMode) {
+ if (context == null) {
+ Log.e(TAG, "Logger initialize failed: context is null");
+ return;
+ }
isDebugMode = debugMode;
try {
File storageDir = context.getExternalFilesDir("log");
- logFile = new File(storageDir, LOG_FILE_NAME);
+ if (storageDir != null) {
+ // 确保日志目录存在
+ if (!storageDir.exists()) {
+ boolean mkdirSuccess = storageDir.mkdirs();
+ if (!mkdirSuccess) {
+ Log.e(TAG, "Create log directory failed");
+ }
+ }
+ logFile = new File(storageDir, LOG_FILE_NAME);
+ } else {
+ Log.e(TAG, "Get external files dir failed");
+ }
} catch (Exception e) {
- Log.e(TAG, "Log file init failed: " + e.getMessage());
+ Log.e(TAG, "Log file init failed: " + e.getMessage(), e);
}
}
// 设置全局日志级别
public static void setLogLevel(LogLevel level) {
- logLevel = level;
+ if (level != null) {
+ logLevel = level;
+ }
}
- // 核心日志方法
- private static void log(LogLevel level, Object context, String name, String message) {
- if (level.ordinal() < logLevel.ordinal()) return; // 低于设定级别不记录
- String tag;
- if (TextUtils.isEmpty(context.getClass().getSimpleName())) {
- tag = context.toString();
- } else {
- tag = context.getClass().getSimpleName();
+ // 核心日志方法(支持异常)
+ private static void log(LogLevel level, Object contextObj, String name, String message, Throwable throwable) {
+ // 级别过滤
+ if (level == null || level.ordinal() < logLevel.ordinal()) {
+ return;
}
- String logMsg = formatLog(level, tag, name, message);
- writeToFile(logMsg); // 始终写入文件
+ // 处理Context TAG
+ String tag = getTagFromContext(contextObj);
+ // 格式化日志内容
+ String logMsg = formatLog(level, tag, name, message, throwable);
+
+ // 写入文件
+ writeToFile(logMsg);
+
+ // Debug模式输出到控制台
if (isDebugMode) {
- outputToConsole(level, tag, name, message); // 仅Debug模式输出到控制台
+ outputToConsole(level, tag, name, message, throwable);
}
}
- // 格式化日志(含时间戳、线程信息)
- private static String formatLog(LogLevel level, String tag, String name, String message) {
+ // 从Context对象获取TAG
+ private static String getTagFromContext(Object contextObj) {
+ if (contextObj == null) {
+ return TAG;
+ }
+ if (contextObj instanceof Class>) {
+ return ((Class>) contextObj).getSimpleName();
+ } else if (contextObj instanceof String) {
+ return (String) contextObj;
+ } else {
+ String simpleName = contextObj.getClass().getSimpleName();
+ return TextUtils.isEmpty(simpleName) ? contextObj.toString() : simpleName;
+ }
+ }
+
+ // 格式化日志(含时间戳、线程信息、异常堆栈)
+ private static String formatLog(LogLevel level, String tag, String name, String message, Throwable throwable) {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(new Date());
String threadName = Thread.currentThread().getName();
- return String.format("%s [%s] %s/%s: %s %s\n", time, threadName, level.name(), tag, name, message);
+
+ // 基础日志内容
+ StringBuilder logBuilder = new StringBuilder();
+ logBuilder.append(String.format("%s [%s] %s/%s: %s %s",
+ time, threadName, level.name(), tag,
+ TextUtils.isEmpty(name) ? "" : name,
+ TextUtils.isEmpty(message) ? "" : message));
+
+ // 追加异常堆栈
+ if (throwable != null) {
+ logBuilder.append("\n").append(getStackTraceString(throwable));
+ }
+
+ logBuilder.append("\n");
+ return logBuilder.toString();
+ }
+
+ // 将异常堆栈转为字符串
+ private static String getStackTraceString(Throwable throwable) {
+ if (throwable == null) {
+ return "";
+ }
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ throwable.printStackTrace(pw);
+ pw.flush();
+ return sw.toString();
}
// 写入日志文件
private static void writeToFile(String logMsg) {
- if (logFile == null) return;
+ if (logFile == null || TextUtils.isEmpty(logMsg)) {
+ return;
+ }
try (FileWriter writer = new FileWriter(logFile, true)) {
writer.append(logMsg);
} catch (IOException e) {
- Log.e(TAG, "File write failed: " + e.getMessage());
+ Log.e(TAG, "File write failed: " + e.getMessage(), e);
}
}
// 输出到Android控制台(Logcat)
- private static void outputToConsole(LogLevel level, String tag, String name, String message) {
+ private static void outputToConsole(LogLevel level, String tag, String name, String message, Throwable throwable) {
+ String logContent = TextUtils.isEmpty(name) ? message : (name + " : " + (message == null ? "" : message));
+
switch (level) {
+ case VERBOSE:
+ Log.v(tag, logContent, throwable);
+ break;
case DEBUG:
- Log.d(tag, name + " : " + message);
+ Log.d(tag, logContent, throwable);
break;
case INFO:
- Log.i(tag, name + " : " + message);
+ Log.i(tag, logContent, throwable);
break;
case WARN:
- Log.w(tag, name + " : " + message);
+ Log.w(tag, logContent, throwable);
break;
case ERROR:
- Log.e(tag, name + " : " + message);
+ Log.e(tag, logContent, throwable);
+ break;
+ default:
+ Log.d(tag, logContent, throwable);
break;
}
}
- // 快捷调用方法(自动使用类名作为TAG)
+ // ===================== 基础快捷方法 =====================
+ public static void v(Object context, String name, String message) {
+ log(LogLevel.VERBOSE, context, name, message, null);
+ }
+
public static void d(Object context, String name, String message) {
- log(LogLevel.DEBUG, context.getClass().getSimpleName(), name, message);
+ log(LogLevel.DEBUG, context, name, message, null);
}
public static void i(Object context, String name, String message) {
- log(LogLevel.INFO, context.getClass().getSimpleName(), name, message);
+ log(LogLevel.INFO, context, name, message, null);
}
public static void w(Object context, String name, String message) {
- log(LogLevel.WARN, context.getClass().getSimpleName(), name, message);
+ log(LogLevel.WARN, context, name, message, null);
}
public static void e(Object context, String name, String message) {
- log(LogLevel.ERROR, context, name, message);
+ log(LogLevel.ERROR, context, name, message, null);
+ }
+
+ // ===================== 支持异常的快捷方法 =====================
+ public static void e(Object context, String name, String message, Throwable throwable) {
+ log(LogLevel.ERROR, context, name, message, throwable);
+ }
+
+ public static void w(Object context, String name, String message, Throwable throwable) {
+ log(LogLevel.WARN, context, name, message, throwable);
+ }
+
+ // ===================== 简化参数的快捷方法 =====================
+ public static void v(Object context, String name) {
+ log(LogLevel.VERBOSE, context, name, "", null);
}
public static void d(Object context, String name) {
- log(LogLevel.DEBUG, context.getClass().getSimpleName(), name, "");
+ log(LogLevel.DEBUG, context, name, "", null);
}
public static void i(Object context, String name) {
- log(LogLevel.INFO, context.getClass().getSimpleName(), name, "");
+ log(LogLevel.INFO, context, name, "", null);
}
public static void w(Object context, String name) {
- log(LogLevel.WARN, context.getClass().getSimpleName(), name, "");
+ log(LogLevel.WARN, context, name, "", null);
}
public static void e(Object context, String name) {
- log(LogLevel.ERROR, context.getClass().getSimpleName(), name, "");
+ log(LogLevel.ERROR, context, name, "", null);
}
-}
\ No newline at end of file
+
+ // ===================== 兼容原生Log调用的静态方法 =====================
+ public static void v(String tag, String msg) {
+ if (isDebugMode) Log.v(tag, msg);
+ }
+
+ public static void d(String tag, String msg) {
+ if (isDebugMode) Log.d(tag, msg);
+ }
+
+ public static void i(String tag, String msg) {
+ if (isDebugMode) Log.i(tag, msg);
+ }
+
+ public static void w(String tag, String msg) {
+ if (isDebugMode) Log.w(tag, msg);
+ }
+
+ public static void e(String tag, String msg) {
+ if (isDebugMode) Log.e(tag, msg);
+ }
+
+ public static void e(String tag, String msg, Throwable tr) {
+ if (isDebugMode) Log.e(tag, msg, tr);
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java b/app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java
index 7bf4a85..7377eb0 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/NativeUtils.java
@@ -11,7 +11,7 @@ public class NativeUtils {
*
* @return
*/
- public static native String getKey();
+ public static native String getHashKey();
/**
* 获取 获取绑定状态的code
@@ -20,6 +20,21 @@ public class NativeUtils {
*/
public static native String getBindStatuSigCode();
+
+ /**
+ * 获取app接口请求安全密钥
+ *
+ * @return
+ */
+ public static native String getAppSecureSecretKey();
+
+ /**
+ * 获取随机字符串
+ *
+ * @return
+ */
+ public static native String getNonce();
+
/**
* 和风天气API Host
*
diff --git a/app/src/main/java/com/ttstd/dialer/utils/RebootUtils.java b/app/src/main/java/com/ttstd/dialer/utils/RebootUtils.java
new file mode 100644
index 0000000..20a8479
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/utils/RebootUtils.java
@@ -0,0 +1,165 @@
+package com.ttstd.dialer.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.PowerManager;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import java.io.DataOutputStream;
+import java.lang.reflect.Method;
+
+public class RebootUtils {
+
+ /**
+ * 重启设备(兼容高低版本)
+ *
+ * @param context 上下文
+ * @param reason 重启原因(可为空)
+ */
+ public static void rebootDevice(Context context, String reason) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ // Android 9.0+ 使用PowerManager.reboot()
+ rebootWithPowerManager(context, reason);
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ // Android 6.0+ 尝试多种方式
+ rebootWithMultipleMethods(context);
+ } else {
+ // 低版本使用传统方式
+ rebootLegacy(context);
+ }
+ } catch (Exception e) {
+ Log.e("RebootUtils", "重启失败: " + e.getMessage());
+ // 尝试最终的回退方案
+ rebootFallback();
+ }
+ }
+
+ /**
+ * Android 9.0+ 推荐方式
+ */
+ @RequiresApi(api = Build.VERSION_CODES.P)
+ private static void rebootWithPowerManager(Context context, String reason) {
+ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ if (powerManager != null) {
+ powerManager.reboot(reason);
+ }
+ }
+
+ /**
+ * Android 6.0+ 多方法尝试
+ */
+ private static void rebootWithMultipleMethods(Context context) {
+ // 方法1: 使用Intent.ACTION_REBOOT
+ try {
+ Intent rebootIntent = new Intent(Intent.ACTION_REBOOT);
+ rebootIntent.putExtra("nowait", 1);
+ rebootIntent.putExtra("interval", 1);
+ rebootIntent.putExtra("window", 0);
+ context.sendBroadcast(rebootIntent);
+ } catch (SecurityException e) {
+ // 方法2: 使用su命令(需要root权限)
+ rebootWithSuCommand();
+ }
+ }
+
+ /**
+ * 低版本传统方式
+ */
+ private static void rebootLegacy(Context context) {
+ try {
+ // 尝试发送重启广播
+ Intent intent = new Intent("android.intent.action.REBOOT");
+ intent.putExtra("nowait", 1);
+ context.sendBroadcast(intent);
+ } catch (Exception e) {
+ rebootWithSuCommand();
+ }
+ }
+
+ /**
+ * 使用su命令重启(需要root权限)
+ */
+ private static void rebootWithSuCommand() {
+ try {
+ Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "reboot"});
+ process.waitFor();
+ } catch (Exception e) {
+ Log.e("RebootUtils", "su命令执行失败: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 最终回退方案
+ */
+ private static void rebootFallback() {
+ try {
+ // 尝试直接执行reboot命令
+ Runtime.getRuntime().exec("reboot");
+ } catch (Exception e) {
+ Log.e("RebootUtils", "所有重启方法都失败了");
+ }
+ }
+
+
+ public static void shutdownDevice(Context context) {
+ try {
+ PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ if (pm != null) {
+ Method method = pm.getClass().getMethod("shutdown",
+ boolean.class, String.class, boolean.class);
+ method.invoke(pm, false, null, false);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ // 备用方案
+ fallbackShutdown(context);
+ }
+ }
+
+ private static void fallbackShutdown(Context context) {
+ try {
+ String action;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Android 8.1+ 使用新的action
+ action = "com.android.internal.intent.action.REQUEST_SHUTDOWN";
+ } else {
+ // Android 8.1以下使用旧的action
+ action = "android.intent.action.ACTION_REQUEST_SHUTDOWN";
+ }
+
+ Intent shutdown = new Intent(action);
+ shutdown.putExtra("android.intent.extra.KEY_CONFIRM", false);
+ shutdown.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(shutdown);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static boolean shutdownWithRoot() {
+ try {
+ Process process = Runtime.getRuntime().exec("su");
+ DataOutputStream os = new DataOutputStream(process.getOutputStream());
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // Android 5.0+
+ os.writeBytes("svc power shutdown\n");
+ } else {
+ // 旧版本
+ os.writeBytes("reboot -p\n");
+ }
+
+ os.writeBytes("exit\n");
+ os.flush();
+ int exitCode = process.waitFor();
+ return exitCode == 0;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java b/app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java
index dba8a28..13d43c4 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/ScreenUtils.java
@@ -3,7 +3,6 @@ package com.ttstd.dialer.utils;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.util.Log;
import java.lang.reflect.Method;
@@ -47,19 +46,19 @@ public class ScreenUtils {
Method getMethod = systemPropertiesClass.getDeclaredMethod("get", String.class);
// 3. 调用方法获取属性值
String characteristics = (String) getMethod.invoke(null, "ro.build.characteristics");
- Log.e(TAG, "isTablet: " + characteristics);
+ Logger.e(TAG, "isTablet: " + characteristics);
// 4. 判断是否包含 "tablet" 标识
return characteristics != null && characteristics.contains("tablet");
} catch (Exception e) {
// 反射失败时的处理(如属性不存在或权限问题)
- Log.e(TAG, "Reflection failed: " + e.getMessage());
+ Logger.e(TAG, "Reflection failed: " + e.getMessage());
return false;
}
}
public static boolean isTablet(Context context) {
boolean isTablet = (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
- Log.e(TAG, "isTablet: " + isTablet);
+ Logger.e(TAG, "isTablet: " + isTablet);
return isTablet;
}
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 dae14a0..27d549a 100644
--- a/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
+++ b/app/src/main/java/com/ttstd/dialer/utils/SystemUtils.java
@@ -5,7 +5,9 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityTaskManager;
import android.app.role.RoleManager;
+import android.bluetooth.BluetoothAdapter;
import android.content.ComponentName;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -14,61 +16,55 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.os.Build;
+import android.os.Environment;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
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.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.net.NetworkInterface;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Optional;
+import java.util.concurrent.Callable;
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 io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.Observer;
+
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
public class SystemUtils {
private static final String TAG = "SystemUtils";
- /**
- * 获取设备序列号
- *
- * @return
- */
- @SuppressLint("MissingPermission")
- public static String getSerial() {
- String serial = "unknow";
- try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
- serial = Build.getSerial();
- } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
- serial = Build.SERIAL;
- } else {//8.0-
- Class> c = Class.forName("android.os.SystemProperties");
- Method get = c.getMethod("get", String.class);
- serial = (String) get.invoke(c, "ro.serialno");
- }
- } catch (Exception e) {
- e.printStackTrace();
- Log.e("e", "读取设备序列号异常:" + e.toString());
- }
- return serial;
- }
-
/**
* 获取系统配置信息
*
@@ -81,7 +77,7 @@ public class SystemUtils {
try {
Class> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class, String.class);
- value = (String) (get.invoke(c, key, "unknown"));
+ value = (String) (get.invoke(c, key, Build.UNKNOWN));
} catch (Exception e) {
e.printStackTrace();
} finally {
@@ -89,6 +85,251 @@ public class SystemUtils {
}
}
+ /**
+ * 获取设备序列号
+ *
+ * @return
+ */
+ @SuppressLint("MissingPermission")
+ public static String getSerial() {
+ String serial = Build.UNKNOWN;
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {//9.0+
+ serial = Build.getSerial();
+ } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {//8.0+
+ serial = Build.SERIAL;
+ } else {//8.0-
+// Class> c = Class.forName("android.os.SystemProperties");
+// Method get = c.getMethod("get", String.class);
+// serial = (String) get.invoke(c, "ro.serialno");
+ getProperty("ro.serialno", serial);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ Logger.e("e", "读取设备序列号异常:" + e.toString());
+ }
+ return serial;
+ }
+
+ /**
+ * 获取卡槽 0 (主卡) 的 IMEI
+ *
+ * @param context 上下文
+ * @return IMEI 字符串,如果获取失败则返回空字符串
+ */
+ @SuppressLint("MissingPermission") // 系统应用已通过 manifest 赋予权限,此处屏蔽 Lint 警告
+ public static String getDefaultImei(Context context) {
+ return getImei(context, 0);
+ }
+
+ /**
+ * 根据卡槽 ID 获取对应的 IMEI
+ * 该方法适用于 Android 6.0 (API 23) 及以上的所有版本。
+ * 在 Android 10+ 上,只有系统应用能成功返回数据。
+ *
+ * @param context 上下文
+ * @param slotIndex 卡槽索引 (0 代表卡槽1, 1 代表卡槽2)
+ * @return IMEI 字符串,如果获取失败则返回空字符串
+ */
+ @SuppressLint("MissingPermission")
+ public static String getImei(Context context, int slotIndex) {
+ String imei = "";
+ try {
+ TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ // 优先使用标准的 getImei(int slotIndex) 方法 (API 23+)
+ // 对于低版本系统,可以使用反射调用,但现代系统推荐直接调用
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ imei = tm.getImei(slotIndex);
+ } else {
+ // 针对极少数的 Android 6.0 以下的老旧系统兜底
+ imei = tm.getDeviceId(slotIndex);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return imei == null ? "" : imei;
+ }
+
+ /**
+ * 获取所有卡槽的 IMEI 数组
+ *
+ * @param context 上下文
+ * @return 包含各个卡槽 IMEI 的字符串数组
+ */
+ @SuppressLint("MissingPermission")
+ public static String[] getAllImeis(Context context) {
+ String[] imeis = new String[2]; // 假设最多两个卡槽
+ imeis[0] = getImei(context, 0);
+ imeis[1] = getImei(context, 1);
+ return imeis;
+ }
+
+ public static String getDefaultImSi(Context context) {
+ return getImsiBySubId(context, 0);
+ }
+
+ public static String getImsiBySubId(Context context, int count) {
+ SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
+ SubscriptionInfo subInfo = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(count);
+ if (subInfo != null) {
+ TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ int subId = subInfo.getSubscriptionId();
+ TelephonyManager tm = telephonyManager.createForSubscriptionId(subId); //关键点,拿到对应subId的tm
+ String mImsi = tm.getSubscriberId(subId); //imsi?not subid?
+ Log.e(TAG, "getSubInfo: mImsi = " + mImsi);
+ return mImsi;
+ } else {
+ return Build.UNKNOWN;
+ }
+ }
+
+ /**
+ * 获取MAC地址
+ *
+ * @param context
+ * @return
+ */
+ public static String getWlanMacAddress(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return getMacDefault(context);
+ } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ return getMacAddressM();
+ } else {
+ return getMacFromHardware();
+ }
+ }
+
+ /**
+ * Android 6.0 之前(不包括6.0)
+ *
+ * @param context
+ * @return
+ */
+ private static String getMacDefault(Context context) {
+ String mac = "未获取到设备Mac地址";
+ if (context == null) {
+ return mac;
+ }
+ WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo info = null;
+ try {
+ info = wifi.getConnectionInfo();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (info == null) {
+ return mac;
+ }
+ mac = info.getMacAddress();
+ if (!TextUtils.isEmpty(mac)) {
+ mac = mac.toUpperCase(Locale.ENGLISH);
+ return mac;
+ } else {
+ return "02:00:00:00:00:00";
+ }
+ }
+
+ /**
+ * Android 6.0(包括) - Android 7.0(不包括)
+ *
+ * @return
+ */
+ private static String getMacAddressM() {
+ String mac = "未获取到设备Mac地址";
+
+ try {
+ mac = new BufferedReader(new FileReader("/sys/class/net/wlan0/address")).readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return mac;
+ }
+
+ /**
+ * 遍历循环所有的网络接口,找到接口是 wlan0
+ *
+ * @return
+ */
+ private static String getMacFromHardware() {
+ try {
+ List all = Collections.list(NetworkInterface.getNetworkInterfaces());
+
+ for (NetworkInterface nif : all) {
+ if (!nif.getName().equalsIgnoreCase("wlan0")) {
+ continue;
+ }
+ byte[] macBytes = nif.getHardwareAddress();
+ StringBuilder res1 = new StringBuilder();
+ for (byte b : macBytes) {
+ res1.append(String.format("%02X:", b));
+ }
+ if (res1 != null) {
+ res1.deleteCharAt(res1.length() - 1);
+ }
+ return res1.toString();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "未获取到设备Mac地址";
+ }
+
+ public static String getFactoryMacAddresses(Context context) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ String[] factoryMacAddresses = wifiManager.getFactoryMacAddresses();
+ String str = (factoryMacAddresses == null || factoryMacAddresses.length <= 0) ? Build.UNKNOWN : factoryMacAddresses[0];
+ return str.toUpperCase(Locale.ENGLISH);
+ }
+
+ public static String getBluetoothMacAddress(Context context) {
+ String mac = Settings.Secure.getString(
+ context.getContentResolver(),
+ "bluetooth_address"
+ );
+ return mac;
+ }
+
+ @Deprecated
+ public static String getBtAddressByReflection() {
+ String def = "02:00:00:00:00:00";
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ return def;
+ }
+ return bluetoothAdapter.getAddress();
+
+// Field field;
+// try {
+// field = BluetoothAdapter.class.getDeclaredField("mService");
+// field.setAccessible(true);
+// Object bluetoothManagerService = field.get(bluetoothAdapter);
+// if (bluetoothManagerService == null) {
+// return def;
+// }
+// Method method = bluetoothManagerService.getClass().getMethod("getAddress");
+// if (method != null) {
+// Object obj = method.invoke(bluetoothManagerService);
+// if (obj != null) {
+// return obj.toString();
+// }
+// }
+// } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
+// e.printStackTrace();
+// }
+// return def;
+ }
+
+ public static String getBuildId() {
+ return getProperty(Build.ID, Build.UNKNOWN);
+ }
+
+ public static String getBuildDisplayId() {
+ return getProperty(Build.DISPLAY, Build.UNKNOWN);
+ }
+
public static boolean isMainProcessName(Context cxt, int pid) {
String packageName = cxt.getPackageName();
ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
@@ -147,7 +388,7 @@ public class SystemUtils {
}
public static void killBackgroundProcesses(Context context, String processName) {
- Log.e(TAG, "killBackgroundProcesses: " + processName);
+ Logger.e(TAG, "killBackgroundProcesses: " + processName);
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
String packageName;
try {
@@ -170,7 +411,7 @@ public class SystemUtils {
}
public static void removeTask(Context context, String packageName) {
- Log.e(TAG, "removeTask: " + packageName);
+ Logger.e(TAG, "removeTask: " + packageName);
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
List list = getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), getCurrentUserId());
HashMap taskMap = new HashMap<>();
@@ -181,9 +422,9 @@ public class SystemUtils {
ActivityManagerNative.getDefault().removeTask(taskMap.get(packageName));
} catch (RemoteException e) {
e.printStackTrace();
- Log.e(TAG, "removeTask: " + e.getMessage());
+ Logger.e(TAG, "removeTask: " + e.getMessage());
} catch (NullPointerException e) {
- Log.e(TAG, "removeTask: " + e.getMessage());
+ Logger.e(TAG, "removeTask: " + e.getMessage());
}
// } else {
// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
@@ -200,7 +441,7 @@ public class SystemUtils {
return ActivityTaskManager.getService().getRecentTasks(numTasks,
RECENT_IGNORE_UNAVAILABLE, userId).getList();
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get recent tasks " + e);
+ Logger.e(TAG, "Failed to get recent tasks " + e);
return new ArrayList<>();
}
}
@@ -221,13 +462,13 @@ public class SystemUtils {
public static boolean isDefaultLauncher(Context context, Class> launcherActivityClass) {
ComponentName componentName = new ComponentName(context, launcherActivityClass);
- Log.e(TAG, "isDefaultLauncher: componentName = " + componentName);
+ Logger.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);
+ Logger.e(TAG, "isDefaultLauncher: defaultComponentName = " + defaultComponentName);
return componentName.equals(defaultComponentName);
}
return false;
@@ -269,7 +510,7 @@ public class SystemUtils {
try {
// 注意:API 29 (Android 10) 中此方法对第三方应用废弃,但对系统签名应用依然有效
pm.replacePreferredActivity(filter, bestMatch, set, myLauncher);
- Log.i("LauncherHelper", "成功设置为默认桌面");
+ Logger.i("LauncherHelper", "成功设置为默认桌面");
// 可选:发送一个回到桌面的 Intent 来验证
// Intent startHome = new Intent(Intent.ACTION_MAIN);
@@ -278,9 +519,9 @@ public class SystemUtils {
// context.startActivity(startHome);
} catch (SecurityException e) {
- Log.e("LauncherHelper", "缺少权限或未生效,请检查系统签名是否正确", e);
+ Logger.e("LauncherHelper", "缺少权限或未生效,请检查系统签名是否正确", e);
} catch (Exception e) {
- Log.e("LauncherHelper", "设置默认桌面失败", e);
+ Logger.e("LauncherHelper", "设置默认桌面失败", e);
}
}
@@ -314,7 +555,7 @@ public class SystemUtils {
*/
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.");
+ Logger.e(TAG, "RoleManager requires Android 10 or higher.");
return;
}
@@ -326,7 +567,7 @@ public class SystemUtils {
// 1. 检查是否已经是当前角色持有者,避免重复调用
if (roleManager.isRoleHeld(roleName)) {
// 注意:这里最好再判断一下持有的包名是否为目标包名
- Log.i(TAG, "Role " + roleName + " is already held by some package.");
+ Logger.i(TAG, "Role " + roleName + " is already held by some package.");
// 如果已经是自己,直接返回
}
@@ -337,9 +578,9 @@ public class SystemUtils {
// 定义回调
Consumer callback = successful -> {
if (successful) {
- Log.i(TAG, "Successfully set " + packageName + " as " + roleName);
+ Logger.i(TAG, "Successfully set " + packageName + " as " + roleName);
} else {
- Log.e(TAG, "Failed to set " + packageName + " as " + roleName + ". Check system logs.");
+ Logger.e(TAG, "Failed to set " + packageName + " as " + roleName + ". Check system logs.");
}
};
@@ -348,15 +589,15 @@ public class SystemUtils {
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);
+ Logger.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);
+ Logger.e(TAG, "Method not found. Is this a non-standard ROM?", e);
} catch (Exception e) {
- Log.e(TAG, "Error invoking addRoleHolderAsUser", e);
+ Logger.e(TAG, "Error invoking addRoleHolderAsUser", e);
}
}
@@ -398,8 +639,8 @@ public class SystemUtils {
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
- Log.e(TAG, "takeFullScreenshot: screenWidth " + screenWidth);
- Log.e(TAG, "takeFullScreenshot: screenHeight " + screenHeight);
+ Logger.e(TAG, "takeFullScreenshot: screenWidth " + screenWidth);
+ Logger.e(TAG, "takeFullScreenshot: screenHeight " + screenHeight);
try {
// 注意:不同 Android 版本的 SurfaceControl.screenshot 方法签名可能不同
@@ -425,6 +666,7 @@ public class SystemUtils {
return null;
}
}
+
public static Bitmap takeScreenshotHighVersion() {
try {
// 1. 获取主屏幕的 Token (Internal Display)
@@ -465,4 +707,43 @@ public class SystemUtils {
return null;
}
+ /**
+ * 保存Bitmap到公共图库(Android 10+ 无需存储权限)
+ *
+ * @param context 上下文
+ * @param bitmap 截图Bitmap
+ * @return 保存成功返回true
+ */
+ public static boolean saveScreenshotToGallery(Context context, Bitmap bitmap) {
+ if (bitmap == null) return false;
+
+ // 使用MediaStore保存到Pictures目录(Android 10+ 推荐)
+ ContentValues values = new android.content.ContentValues();
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
+ values.put(MediaStore.Images.Media.DISPLAY_NAME, "Screenshot_" + System.currentTimeMillis() + ".png");
+ values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/SystemScreenshots");
+
+ android.net.Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+ if (uri == null) return false;
+
+ try (OutputStream outputStream = context.getContentResolver().openOutputStream(uri)) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+ return true;
+ } catch (java.io.IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public static void screenshotCmd(String filepath, Observer observer) {
+ Observable.fromCallable(new Callable() {
+ @Override
+ public Integer call() throws Exception {
+ CmdUtil.Result result = CmdUtil.execute("screencap -p " + filepath);
+ Logger.e(TAG, "call: " + result.error);
+ return result.code;
+ }
+ }).subscribe(observer);
+ }
+
}
diff --git a/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java b/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java
index 64a1526..6ed2085 100644
--- a/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java
+++ b/app/src/main/java/com/ttstd/dialer/view/ToggleButton.java
@@ -8,7 +8,6 @@ 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;
@@ -18,6 +17,7 @@ import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;
import com.ttstd.dialer.R;
+import com.ttstd.dialer.utils.Logger;
public class ToggleButton extends View {
private static final String TAG = "ToggleButton";
@@ -172,7 +172,7 @@ public class ToggleButton extends View {
public void toggle(boolean animate) {
toggleOn = !toggleOn;
- Log.e(TAG, "toggle: toggleOn = " + toggleOn);
+ Logger.e(TAG, "toggle: toggleOn = " + toggleOn);
takeEffect(animate);
if (listener != null) {
listener.onToggle(toggleOn);
@@ -239,12 +239,12 @@ public class ToggleButton extends View {
public int getToggleOnStatu() {
- Log.e(TAG, "getToggleOnStatu: " + toggleOn);
+ Logger.e(TAG, "getToggleOnStatu: " + toggleOn);
return toggleOn ? 1 : 0;
}
public boolean isToggleOn() {
- Log.e(TAG, "isToggleOn: " + toggleOn);
+ Logger.e(TAG, "isToggleOn: " + toggleOn);
return toggleOn;
}
diff --git a/app/src/main/jni/ttstd.cpp b/app/src/main/jni/ttstd.cpp
index 76e931f..ee81bf4 100644
--- a/app/src/main/jni/ttstd.cpp
+++ b/app/src/main/jni/ttstd.cpp
@@ -3,13 +3,14 @@
#include
// 日志打印
#include
+#include
#define LOG_TAG "TAG_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
-Java_com_ttstd_dialer_utils_NativeUtils_getKey(JNIEnv *env, jclass clazz) {
+Java_com_ttstd_dialer_utils_NativeUtils_getHashKey(JNIEnv *env, jclass clazz) {
std::string key = "Ls0HSh9fNfeZ8Qu5";
return env->NewStringUTF(key.c_str());
}
@@ -47,4 +48,38 @@ JNIEXPORT jstring JNICALL
Java_com_ttstd_dialer_utils_NativeUtils_getQWeatherCredentialId(JNIEnv *env, jclass clazz) {
std::string key = "KB5C8PK5TY";
return env->NewStringUTF(key.c_str());
+}
+
+extern "C"
+JNIEXPORT jstring JNICALL
+Java_com_ttstd_dialer_utils_NativeUtils_getAppSecureSecretKey(JNIEnv *env, jclass clazz) {
+
+}
+
+// 生成指定长度的随机字符串
+std::string generateRandomString(int length) {
+ const char charset[] =
+ "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ std::uniform_int_distribution<> dis(0, sizeof(charset) - 2); // 减2是因为包含结束符
+
+ std::string result;
+ result.reserve(length);
+
+ for (int i = 0; i < length; ++i) {
+ result += charset[dis(gen)];
+ }
+
+ return result;
+}
+
+extern "C"
+JNIEXPORT jstring JNICALL
+Java_com_ttstd_dialer_utils_NativeUtils_getNonce(JNIEnv *env, jclass clazz) {
+ std::string randomStr = generateRandomString(8); // 生成长度为32的随机字符串
+ return env->NewStringUTF(randomStr.c_str());
}
\ 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
index ce51acc..398768a 100644
--- a/app/src/main/res/layout/activity_settings_utils.xml
+++ b/app/src/main/res/layout/activity_settings_utils.xml
@@ -116,6 +116,7 @@
android:text="截图"
android:onClick="@{click::screenshotSnap}"
app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/iv_snap"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -129,6 +130,30 @@
+
+
+
+
+
+
+
+
diff --git a/iconloader/src/main/java/com/ttstd/iconloader/IconCacheManager.java b/iconloader/src/main/java/com/ttstd/iconloader/IconCacheManager.java
index a5ad624..e56b973 100644
--- a/iconloader/src/main/java/com/ttstd/iconloader/IconCacheManager.java
+++ b/iconloader/src/main/java/com/ttstd/iconloader/IconCacheManager.java
@@ -11,7 +11,6 @@ import android.graphics.Canvas;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.Log;
@@ -44,7 +43,7 @@ public class IconCacheManager {
public static IconCacheManager getInstance() {
if (INSTANCE == null) {
- throw new IllegalStateException("You must be init IconCacheManager first");
+ throw new IllegalStateException("You must first initialize the IconCacheManager");
}
return INSTANCE;
}