feat: 系统功能抽象

This commit is contained in:
2026-05-08 00:26:27 +08:00
parent b0ea6eff0a
commit 4dcb82b9f4
82 changed files with 4758 additions and 1019 deletions

View File

@@ -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<ActivityAppListBinding, ActivityEvent> {
@@ -55,23 +47,23 @@ public class AppListViewModel extends BaseViewModel<ActivityAppListBinding, Acti
.subscribe(new Observer<List<AppInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getDbAppList", "onSubscribe: ");
Logger.e("getDbAppList", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List<AppInfo> 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<ActivityAppListBinding, Acti
.subscribe(new Observer<Integer>() {
@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: ");
}
});
}

View File

@@ -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<ActivityContactAddBinding
public void subscribe(@NonNull ObservableEmitter<Long> 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<ActivityContactAddBinding
.subscribe(new Observer<Long>() {
@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: ");
}
});
}

View File

@@ -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<ContactListViewModel,
mContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
@Override
public void onItemMove(int fromPosition, int toPosition) {
Log.e(TAG, "onItemMove: ");
Logger.e(TAG, "onItemMove: ");
ContactInfo fromContactInfo = mContactInfos.get(fromPosition);
int fromContactPosition = fromContactInfo.getPosition();
ContactInfo toContactInfo = mContactInfos.get(toPosition);
@@ -72,7 +72,7 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
@Override
public void onItemRemoved(int position) {
Log.e(TAG, "onItemRemoved: ");
Logger.e(TAG, "onItemRemoved: ");
}
});
@@ -98,7 +98,7 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
mViewModel.mContactListData.observe(this, new Observer<List<ContactInfo>>() {
@Override
public void onChanged(List<ContactInfo> contactInfos) {
Log.e(TAG, "mContactListData: " + contactInfos);
Logger.e(TAG, "mContactListData: " + contactInfos);
mContactInfos = contactInfos;
mContactInfoAdapter.setContactInfos(mContactInfos);
}

View File

@@ -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<ActivityContactListBindi
.subscribe(new Observer<List<ContactInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getAllContacts", "onSubscribe: ");
Logger.e("getAllContacts", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List<ContactInfo> contactInfos) {
Log.e("getAllContacts", "onNext: ");
Logger.e("getAllContacts", "onNext: ");
mContactListData.setValue(contactInfos);
// List<Contact> sorted = contacts.stream().sorted(new Comparator<Contact>() {
@@ -68,18 +68,18 @@ public class ContactListViewModel extends BaseViewModel<ActivityContactListBindi
@Override
public void onError(@NonNull Throwable e) {
Log.e("getAllContacts", "onError: " + e.getMessage());
Logger.e("getAllContacts", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e("getAllContacts", "onComplete: ");
Logger.e("getAllContacts", "onComplete: ");
}
});
}
public void updateItemPosition(ContactInfo contactInfo) {
Log.e(TAG, "updateItemPosition: " + contactInfo);
Logger.e(TAG, "updateItemPosition: " + contactInfo);
Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Throwable {
@@ -93,22 +93,22 @@ public class ContactListViewModel extends BaseViewModel<ActivityContactListBindi
.subscribe(new Observer<Integer>() {
@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: ");
}
});
}

View File

@@ -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<MainViewModel, ActivityMainBi
mViewDataBinding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.e(TAG, "onPageScrolled: position = " + position);
Logger.e(TAG, "onPageScrolled: position = " + position);
if (mCurrentIndex == -1 && position == 0) {
mViewDataBinding.viewPager.setCurrentItem(mDefaultIndex);
mCurrentIndex = mDefaultIndex;
@@ -135,21 +136,21 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
@Override
public void onPageSelected(int position) {
Log.e(TAG, "onPageSelected: position = " + position);
Logger.e(TAG, "onPageSelected: position = " + position);
}
@Override
public void onPageScrollStateChanged(int state) {
Log.e(TAG, "onPageScrollStateChanged: state = " + state);
Logger.e(TAG, "onPageScrollStateChanged: state = " + state);
}
});
mViewDataBinding.magicIndicator.setNavigator(mScaleCircleNavigator);
ViewPagerHelper.bind(mViewDataBinding.magicIndicator, mViewDataBinding.viewPager);
mViewDataBinding.viewPager.setCurrentItem(mDefaultIndex);
Log.e(TAG, "initView: mCurrentIndex = " + mCurrentIndex);
Log.e(TAG, "initView: mDefaultIndex = " + mDefaultIndex);
Log.e(TAG, "initView: mFragmentSize = " + mFragmentSize);
Logger.e(TAG, "initView: mCurrentIndex = " + mCurrentIndex);
Logger.e(TAG, "initView: mDefaultIndex = " + mDefaultIndex);
Logger.e(TAG, "initView: mFragmentSize = " + mFragmentSize);
Intent intent = new Intent(this, MainService.class);
startService(intent);
@@ -163,25 +164,37 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
@Override
public void onChanged(List<AppInfo> appInfos) {
mAppInfos = appInfos;
Log.e(TAG, "onChanged: mAppInfos size = " + mAppInfos.size());
Logger.e(TAG, "onChanged: mAppInfos size = " + mAppInfos.size());
setAppList();
}
});
mViewModel.getOutsideApp();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Logger.e(TAG, "onKeyDown: keyCode = " + keyCode);
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
return false;
}
if (keyCode == KeyEvent.KEYCODE_HOME) {
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onResume() {
super.onResume();
//修补autozie fragment item大小不一致
Log.e(TAG, "onResume: ");
Logger.e(TAG, "onResume: ");
mViewModel.getOutsideApp();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
Logger.e(TAG, "onDestroy: ");
unregisterReceivers();
}
@@ -213,23 +226,23 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
private class PackageChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "onReceive: " + intent.getAction());
Logger.e(TAG, "onReceive: " + intent.getAction());
}
}
private void setAppList() {
Log.e(TAG, "setAppList: mFragments size = " + mFragments.size());
Logger.e(TAG, "setAppList: mFragments size = " + mFragments.size());
mFragments = mFragments.subList(0, mFragmentSize);
Log.e(TAG, "setAppList: subList mFragments size = " + mFragments.size());
Logger.e(TAG, "setAppList: subList mFragments size = " + mFragments.size());
int fragmentCount = (int) Math.ceil((double) mAppInfos.size() / APK_PER_FRAGMENT);
for (int i = 0; i < fragmentCount; i++) {
int start = i * APK_PER_FRAGMENT;
int end = Math.min(start + APK_PER_FRAGMENT, mAppInfos.size());
List<AppInfo> subList = new ArrayList<>(mAppInfos.subList(start, end));
Log.e(TAG, "setAppList: subList size = " + subList.size());
Logger.e(TAG, "setAppList: subList size = " + subList.size());
// AppFragment appFragment = AppFragment.newInstance(subList);
AppFragment appFragment = new AppFragment(subList);
Log.e(TAG, "setAppList: appFragment hashCode = " + appFragment.hashCode());
Logger.e(TAG, "setAppList: appFragment hashCode = " + appFragment.hashCode());
appFragment.setUpdateCallback(new AppFragment.UpdateCallback() {
@Override
public void onUpdate() {
@@ -237,7 +250,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
}
});
mFragments.add(appFragment);
Log.e(TAG, "setAppList: add mFragments size = " + mFragments.size());
Logger.e(TAG, "setAppList: add mFragments size = " + mFragments.size());
}
mScaleCircleNavigator.setCircleCount(mFragments.size());
mScaleCircleNavigator.notifyDataSetChanged();

View File

@@ -1,17 +1,16 @@
package com.ttstd.dialer.activity.main;
import android.content.Context;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.trello.rxlifecycle4.android.FragmentEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityMainBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
import com.ttstd.dialer.utils.Logger;
import java.util.List;
import java.util.concurrent.Callable;
@@ -48,24 +47,25 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
.subscribe(new Observer<List<AppInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getOutsideApp", "onSubscribe: ");
Logger.e("getOutsideApp", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List<AppInfo> 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: ");
}
});
}
}

View File

@@ -42,6 +42,7 @@ public class SettingsActivity extends BaseMvvmActivity<SettingsViewModel, Activi
@Override
protected void initData() {
mViewModel.snRegister();
}

View File

@@ -2,9 +2,41 @@ package com.ttstd.dialer.activity.settings.home;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.databinding.ActivitySettingsBinding;
import com.ttstd.dialer.databinding.ActivityTemplateBinding;
import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.utils.Logger;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
public class SettingsViewModel extends BaseViewModel<ActivitySettingsBinding, ActivityEvent> {
public void snRegister() {
OkHttpManager.getInstance().getSnRegisterObservable(getLifecycle())
.subscribe(new Observer<BaseResponse>() {
@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: ");
}
});
}
}

View File

@@ -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<SettingsUtilsViewModel, ActivitySettingsUtilsBinding> {
private static final String TAG = "SettingsActivity";
@@ -97,15 +109,15 @@ public class SettingsUtilsActivity extends BaseMvvmActivity<SettingsUtilsViewMod
protected void onResume() {
super.onResume();
int enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
Log.e(TAG, "initView: enableFloatWindow = " + enableFloatWindow);
Logger.e(TAG, "initView: enableFloatWindow = " + enableFloatWindow);
mViewDataBinding.siFloatWindow.setToggleStatu(enableFloatWindow == 1);
int floatWindowKillApp = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
Log.e(TAG, "initView: floatWindowKillApp = " + floatWindowKillApp);
Logger.e(TAG, "initView: floatWindowKillApp = " + floatWindowKillApp);
mViewDataBinding.siFloatWindowKill.setToggleStatu(floatWindowKillApp == 1);
boolean defaultLauncher = SystemUtils.isDefaultLauncher(SettingsUtilsActivity.this, MainActivity.class);
Log.e(TAG, "initView: defaultLauncher = " + defaultLauncher);
Logger.e(TAG, "initView: defaultLauncher = " + defaultLauncher);
mViewDataBinding.siDefaultLauncher.setToggleStatu(defaultLauncher);
}
@@ -116,10 +128,71 @@ public class SettingsUtilsActivity extends BaseMvvmActivity<SettingsUtilsViewMod
public void screenshotSnap(View view) {
// Bitmap bitmap = SystemUtils.takeFullScreenshot(SettingsUtilsActivity.this);
Bitmap bitmap = SystemUtils.takeScreenshotHighVersion();
if (bitmap != null) {
mViewDataBinding.ivSnap.setImageBitmap(bitmap);
}
// Bitmap bitmap = SystemUtils.takeScreenshotHighVersion();
// if (bitmap != null) {
// mViewDataBinding.ivSnap.setImageBitmap(bitmap);
// }
String filepath = getExternalCacheDir().getAbsolutePath() + File.separator + "screenshot_" + System.currentTimeMillis() + ".png";
SystemUtils.screenshotCmd(filepath, new BlockingBaseObserver<Integer>() {
@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<String, String> params = new HashMap<>();
params.put("sn", SystemUtils.getSerial());
params.put("createtime", String.valueOf(createTime));
// Call<BaseResponse> call = NetInterfaceManager.getInstance().getScreenshotCall().sendScreenshot(params, body);
// call.enqueue(new RetryCallback<BaseResponse>(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");
}
}
}

View File

@@ -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<WeatherMainViewModel,
@Override
protected void initData() {
Log.e(TAG, "initData: ");
Logger.e(TAG, "initData: ");
mViewModel.mWeatherNowData.observe(this, new Observer<WeatherNow>() {
@Override
@@ -108,14 +107,14 @@ public class WeatherMainActivity extends BaseMvvmActivity<WeatherMainViewModel,
if (manually) {
} else {
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, BDLocation.class)
.observeSticky(this, new Observer<BDLocation>() {
LiveEventBus.get(CommonConfig.LOCATION_CHANGED_KEY, SnLocationReq.class)
.observeSticky(this, new Observer<SnLocationReq>() {
@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());
}
});

View File

@@ -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<ActivityWeatherMainBindi
public MutableLiveData<WeatherNow> mWeatherNowData = new MutableLiveData<>();
public void getWeatherNow(String adCode) {
Log.e(TAG, "getWeatherNow: " + adCode);
Logger.e(TAG, "getWeatherNow: " + adCode);
WeatherManager.getInstance().getWeatherNow(adCode, new Callback<WeatherNowResponse>() {
@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<ActivityWeatherMainBindi
public MutableLiveData<List<WeatherHourly>> mWeather24hData = new MutableLiveData<>();
public void getWeather24h(String adCode) {
Log.e(TAG, "getWeather24h: " + adCode);
Logger.e(TAG, "getWeather24h: " + adCode);
WeatherManager.getInstance().getWeather24h(adCode, new Callback<WeatherHourlyResponse>() {
@Override
public void onSuccess(WeatherHourlyResponse weatherHourlyResponse) {
Log.e("getWeather24h", "onSuccess: " + weatherHourlyResponse);
Logger.e("getWeather24h", "onSuccess: " + weatherHourlyResponse);
List<WeatherHourly> 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<ActivityWeatherMainBindi
public MutableLiveData<List<WeatherDaily>> mWeatherDailyListData = new MutableLiveData<>();
public void getWeather10D(String adCode) {
Log.e(TAG, "getWeather10D: " + adCode);
Logger.e(TAG, "getWeather10D: " + adCode);
WeatherManager.getInstance().getWeather10D(adCode, new Callback<WeatherDailyResponse>() {
@Override
public void onSuccess(WeatherDailyResponse weatherDailyResponse) {
Log.e("getWeather10D", "onSuccess: ");
Logger.e("getWeather10D", "onSuccess: ");
List<WeatherDaily> 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());
}
});
}

View File

@@ -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<AppAdapter.AppHolder> imple
@Override
public void onLoaderReset(@NonNull @NotNull Loader<Drawable> loader) {
Log.e(TAG, "onLoaderReset: ");
Logger.e(TAG, "onLoaderReset: ");
}
public class AppHolder extends RecyclerView.ViewHolder {

View File

@@ -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<ContactInfoAdapter.
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: " + contactInfo);
Logger.e(TAG, "onClick: " + contactInfo);
if (mOnClickListener != null) {
mOnClickListener.onClick(contactInfo);
}
@@ -85,8 +85,8 @@ public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.
// 处理拖动交换位置
public void onItemMove(int fromPosition, int toPosition) {
Log.e(TAG, "onItemMove: fromPosition = " + fromPosition);
Log.e(TAG, "onItemMove: toPosition = " + toPosition);
Logger.e(TAG, "onItemMove: fromPosition = " + fromPosition);
Logger.e(TAG, "onItemMove: toPosition = " + toPosition);
if (fromPosition == toPosition) {
return; // 直接返回,不执行后续操作
}

View File

@@ -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 HomeContactAdapter extends RecyclerView.Adapter<HomeContactAdapter.
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: " + contactInfo);
Logger.e(TAG, "onClick: " + contactInfo);
if (mOnClickListener != null) {
mOnClickListener.onClick(contactInfo);
}
@@ -85,8 +85,8 @@ public class HomeContactAdapter extends RecyclerView.Adapter<HomeContactAdapter.
// 处理拖动交换位置
public void onItemMove(int fromPosition, int toPosition) {
Log.e(TAG, "onItemMove: fromPosition = " + fromPosition);
Log.e(TAG, "onItemMove: toPosition = " + toPosition);
Logger.e(TAG, "onItemMove: fromPosition = " + fromPosition);
Logger.e(TAG, "onItemMove: toPosition = " + toPosition);
if (fromPosition == toPosition) {
return; // 直接返回,不执行后续操作
}

View File

@@ -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;
@@ -9,17 +8,15 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.qweather.sdk.response.weather.WeatherHourly;
import com.ttstd.dialer.R;
import com.ttstd.dialer.glide.GlideApp;
import com.ttstd.dialer.glide.svg.SvgSoftwareLayerSetter;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.TimeUtils;
import java.util.List;
@@ -71,7 +68,7 @@ public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdap
requestBuilder.load(resId).into(holder.iv_icon);
} else {
// 处理错误
Log.e("GlideLoad", "Raw resource not found: " + fileName);
Logger.e("GlideLoad", "Raw resource not found: " + fileName);
}
holder.tv_temp.setText(weatherHourly.getTemp() + "°");

View File

@@ -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;
@@ -137,7 +137,7 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
@Override
public void onLoaderReset(@NonNull @NotNull Loader<Drawable> loader) {
Log.e(TAG, "onLoaderReset: ");
Logger.e(TAG, "onLoaderReset: ");
}
public class AppHolder extends RecyclerView.ViewHolder {

View File

@@ -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<WeatherAdapter.WeatherH
requestBuilder.load(resId).into(holder.iv_icon);
} else {
// 处理错误
Log.e("GlideLoad", "Raw resource not found: " + fileName);
Logger.e("GlideLoad", "Raw resource not found: " + fileName);
}
}
}

View File

@@ -6,7 +6,8 @@ import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.multidex.MultiDex;
import com.alibaba.android.arouter.launcher.ARouter;
import com.arialyy.aria.core.Aria;
@@ -18,7 +19,11 @@ import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.manager.AppManager;
import com.ttstd.dialer.manager.MapManager;
import com.ttstd.dialer.manager.WeatherManager;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.push.PushExecutor;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.NativeUtils;
import com.ttstd.dialer.utils.SystemUtils;
import com.ttstd.iconloader.IconCacheManager;
@@ -30,21 +35,30 @@ import cn.jpush.android.api.JPushInterface;
public class BaseApplication extends Application {
private static final String TAG = "BaseApplication";
private MMKV mMMKV;
/**
* ViewModel中因为经常旋转导致弱引用为空
*/
@SuppressLint("StaticFieldLeak")
private static Context context;
private static Context mAppContext;
public static Context getContext() {
return context;
return mAppContext;
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ");
context = getApplicationContext();
Logger.e(TAG, "onCreate: ");
mAppContext = getApplicationContext();
if (!BuildConfig.DEBUG) {
catchException();
}
@@ -56,34 +70,20 @@ public class BaseApplication extends Application {
}
private void init() {
Log.e(TAG, "init: ");
Logger.e(TAG, "init: ");
Logger.e(TAG, "init: getNonce = " + NativeUtils.getNonce());
if (SystemUtils.isMainProcessName(this, android.os.Process.myPid())) {
Logger.initialize(this, BuildConfig.DEBUG);
Logger.setLogLevel(Logger.LogLevel.DEBUG); // 开发阶段记录所有日志
String rootDir = MMKV.initialize(this);
Log.e(TAG, "mmkv root: " + rootDir);
MMKV mmkv = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
Logger.e(TAG, "mmkv root: " + rootDir);
mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
/*jpush start*/
JPushInterface.setDebugMode(true);
// 调整点一调用启用推送业务功能代码前增加setAuth调用
boolean isPrivacyReady = true; // app根据是否已弹窗获取隐私授权来赋值
if (!isPrivacyReady) {
// JCore 5.0.4之前版本需要显式设置false
if (JCoreInterface.getJCoreSDKVersionInt() < 504) { // 5.0.4版本号对应504
JCollectionAuth.setAuth(context, false);
}
// 所有版本在未授权时都不应初始化SDK
return;
}
JPushInterface.init(this);
JPushInterface.setAlias(this, 0, SystemUtils.getSerial());
// 调整点二App用户同意了隐私政策授权并且开发者确定要开启推送服务后调用
// JCore 5.0.4+会自动处理授权状态可不需要显式设置true
JCollectionAuth.setAuth(context, true);
/*jpush end*/
DeviceManagerService.init(this);
OkHttpManager.init(this);
PushExecutor.init(this);
initJPush();
if (BuildConfig.DEBUG) { // 这两行必须写在init之前否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
@@ -94,7 +94,7 @@ public class BaseApplication extends Application {
// 初始化 Toast 框架
Toaster.init(this);
Log.e(TAG, "slowInit: ");
Logger.e(TAG, "slowInit: ");
Aria.init(this);
CrashReport.initCrashReport(getApplicationContext(), "845e3ed68c", false);
CrashReport.setDeviceId(this, Build.MODEL);
@@ -103,8 +103,8 @@ public class BaseApplication extends Application {
AppManager.init(this);
MapManager.init(this);
MapManager.getInstance().initMap();
boolean manually = mmkv.decodeBool(CommonConfig.MANUALLY_SELECT_LOCATION, false);
Log.e(TAG, "init: manually location " + manually);
boolean manually = mMMKV.decodeBool(CommonConfig.MANUALLY_SELECT_LOCATION, false);
Logger.e(TAG, "init: manually location " + manually);
if (manually) {
} else {
@@ -115,12 +115,36 @@ public class BaseApplication extends Application {
}
}
private void initJPush() {
/*jpush start*/
JPushInterface.setDebugMode(true);
// 调整点一调用启用推送业务功能代码前增加setAuth调用
boolean isPrivacyReady = true; // app根据是否已弹窗获取隐私授权来赋值
if (!isPrivacyReady) {
// JCore 5.0.4之前版本需要显式设置false
if (JCoreInterface.getJCoreSDKVersionInt() < 504) { // 5.0.4版本号对应504
JCollectionAuth.setAuth(this, false);
}
// 所有版本在未授权时都不应初始化SDK
return;
}
JPushInterface.init(this);
JPushInterface.setAlias(this, 0, SystemUtils.getSerial());
// 调整点二App用户同意了隐私政策授权并且开发者确定要开启推送服务后调用
// JCore 5.0.4+会自动处理授权状态可不需要显式设置true
JCollectionAuth.setAuth(this, true);
/*jpush end*/
Logger.e(TAG, "initJPush: inited JPush");
}
private void catchException() {
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
Log.e("捕获异常子线程:", Thread.currentThread().getName() +
Logger.e("捕获异常子线程:", Thread.currentThread().getName() +
"在:" + e.getStackTrace()[0].getClassName());
}
}
@@ -133,7 +157,7 @@ public class BaseApplication extends Application {
try {
Looper.loop(); //会先执行这个方法,然后在执行下面的异常捕获方法!
} catch (Exception e) {
Log.e("捕获异常主线程:", Thread.currentThread().getName() + "在:" + e.getStackTrace()[0].getClassName());
Logger.e("捕获异常主线程:", Thread.currentThread().getName() + "在:" + e.getStackTrace()[0].getClassName());
e.printStackTrace();
}
}

View File

@@ -0,0 +1,43 @@
package com.ttstd.dialer.base;
import android.content.Intent;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.ttstd.dialer.base.rx.BaseRxService;
import org.jetbrains.annotations.NotNull;
public abstract class BaseService extends BaseRxService implements LifecycleOwner {
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@NotNull
@Override
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
@Override
public void onCreate() {
super.onCreate();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
//修补在Service中LiveData不能触发
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
}

View File

@@ -1,7 +1,6 @@
package com.ttstd.dialer.base.mvvm;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
@@ -10,6 +9,7 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.ttstd.dialer.base.BaseTransparentActivity;
import com.ttstd.dialer.utils.Logger;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -31,7 +31,7 @@ public abstract class BaseMvvmActivity<VM extends ViewModel, VDB extends ViewDat
//ViewModel
vmClass = (Class<VM>) ((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);
}

View File

@@ -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<VM extends ViewModel, VDB extends V
*/
public boolean isAttached() {
boolean flag = getCtx() != null && isAdded();
Log.e(" >> isAttached >>", "flag = " + flag);
Logger.e(" >> isAttached >>", "flag = " + flag);
return flag;
}
@@ -234,7 +233,7 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
//
// public void updateLoadingTip(@StringRes int messageID, int percent) {
// try {
// if (mWaitDialog != null && mWaitDialog.isShow()) {
// if (mWaitDialog != null && mWaitDiaLogger.isShow()) {
// TextView tvTip = mWaitDialog.getCustomView().findViewById(R.id.tv_load_tip);
// if (tvTip != null)
// tvTip.setText(getResources().getString(messageID) + (percent == -1 ? "" : percent + "%"));
@@ -245,7 +244,7 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
// }
//
// public boolean isShowLoading() {
// return mWaitDialog != null && mWaitDialog.isShow();
// return mWaitDialog != null && mWaitDiaLogger.isShow();
// }
//
// public void hideLoading() {
@@ -253,7 +252,7 @@ public abstract class BaseMvvmDialogFragment<VM extends ViewModel, VDB extends V
// boolean isShow = isShowLoading();
// L.d(" >> hideLoading :: isShow: %s", isShow);
// if (isShow)
// mWaitDialog.dismiss();
// mWaitDiaLogger.dismiss();
// } catch (Exception e) {
// e.printStackTrace();
// }

View File

@@ -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<VM extends ViewModel, VDB extends ViewDat
*/
public boolean isAttached() {
boolean flag = getCtx() != null && isAdded();
Log.e(" >> isAttached >>", "flag = " + flag);
Logger.e(" >> isAttached >>", "flag = " + flag);
return flag;
}
@@ -233,7 +233,7 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
//
// public void updateLoadingTip(@StringRes int messageID, int percent) {
// try {
// if (mWaitDialog != null && mWaitDialog.isShow()) {
// if (mWaitDialog != null && mWaitDiaLogger.isShow()) {
// TextView tvTip = mWaitDialog.getCustomView().findViewById(R.id.tv_load_tip);
// if (tvTip != null)
// tvTip.setText(getResources().getString(messageID) + (percent == -1 ? "" : percent + "%"));
@@ -244,7 +244,7 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
// }
//
// public boolean isShowLoading() {
// return mWaitDialog != null && mWaitDialog.isShow();
// return mWaitDialog != null && mWaitDiaLogger.isShow();
// }
//
// public void hideLoading() {
@@ -252,7 +252,7 @@ public abstract class BaseMvvmFragment<VM extends ViewModel, VDB extends ViewDat
// boolean isShow = isShowLoading();
// L.d(" >> hideLoading :: isShow: %s", isShow);
// if (isShow)
// mWaitDialog.dismiss();
// mWaitDiaLogger.dismiss();
// } catch (Exception e) {
// e.printStackTrace();
// }

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,52 @@
package com.ttstd.dialer.bean;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
/**
* 通用响应实体基类
*
* @param <T> 具体数据实体
*/
public class BaseResponse<T> {
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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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<AppViewModel, FragmentAppBinding> {
@@ -30,7 +29,7 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
// public static AppFragment newInstance(List<AppInfo> 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<AppViewModel, FragmentAppBindi
public AppFragment(List<AppInfo> 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<AppViewModel, FragmentAppBindi
@Override
protected void initView(Bundle bundle) {
Log.e(TAG, "initView: " + this.hashCode());
Logger.e(TAG, "initView: " + this.hashCode());
mAppAdapter = new AppAdapter(LoaderManager.getInstance(this));
mAppAdapter.setShortcutCallback(new AppAdapter.ShortcutCallback() {
@@ -87,8 +86,8 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
mViewDataBinding.recyclerView.setAdapter(mAppAdapter);
if (mAppInfos != null) {
Log.e(TAG, "initView: mAppInfos size = " + mAppInfos.size());
Log.e(TAG, "initView: mAppInfos = " + mAppInfos.hashCode());
Logger.e(TAG, "initView: mAppInfos size = " + mAppInfos.size());
Logger.e(TAG, "initView: mAppInfos = " + mAppInfos.hashCode());
mAppAdapter.setAppInfos(mAppInfos);
}
@@ -96,8 +95,8 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
@Override
protected void initData(Bundle savedInstanceState) {
Log.e(TAG, "initData: ");
Log.e(TAG, "initData: " + mViewModel.mAppUpdateData);
Logger.e(TAG, "initData: ");
Logger.e(TAG, "initData: " + mViewModel.mAppUpdateData);
mViewModel.mAppUpdateData.observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
@@ -117,14 +116,14 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
@Override
public void fetchData() {
Log.e(TAG, "fetchData: ");
Logger.e(TAG, "fetchData: ");
}
@Override
public void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
Logger.e(TAG, "onResume: ");
if (mAppInfos != null) {
mAppAdapter.setAppInfos(mAppInfos);
}
@@ -133,7 +132,7 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
@Override
public void onDestroyView() {
super.onDestroyView();
Log.e(TAG, "onDestroyView: ");
Logger.e(TAG, "onDestroyView: ");
}
public class BtnClick {

View File

@@ -1,18 +1,15 @@
package com.ttstd.dialer.fragment.app;
import android.content.Context;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.trello.rxlifecycle4.RxLifecycle;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.trello.rxlifecycle4.android.FragmentEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.FragmentAppBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
import com.ttstd.dialer.livedata.SingleLiveEvent;
import com.ttstd.dialer.utils.Logger;
import java.util.concurrent.Callable;
@@ -52,23 +49,23 @@ public class AppViewModel extends BaseViewModel<FragmentAppBinding, FragmentEven
.subscribe(new Observer<Integer>() {
@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: ");
}
});
}

View File

@@ -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<ContactViewModel, Fragment
mViewModel.mContactListData.observe(this, new Observer<List<ContactInfo>>() {
@Override
public void onChanged(List<ContactInfo> contactInfos) {
Log.e(TAG, "mContactListData: " + contactInfos);
Logger.e(TAG, "mContactListData: " + contactInfos);
mContactInfos = contactInfos;
mHomeContactAdapter.setContactInfos(mContactInfos);
}

View File

@@ -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<FragmentContactBinding, Frag
.subscribe(new Observer<List<ContactInfo>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("getAllContacts", "onSubscribe: ");
Logger.e("getAllContacts", "onSubscribe: ");
}
@Override
public void onNext(@NonNull List<ContactInfo> contactInfos) {
Log.e("getAllContacts", "onNext: ");
Logger.e("getAllContacts", "onNext: ");
mContactListData.setValue(contactInfos);
// List<Contact> sorted = contacts.stream().sorted(new Comparator<Contact>() {
@@ -67,12 +67,12 @@ public class ContactViewModel extends BaseViewModel<FragmentContactBinding, Frag
@Override
public void onError(@NonNull Throwable e) {
Log.e("getAllContacts", "onError: " + e.getMessage());
Logger.e("getAllContacts", "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.e("getAllContacts", "onComplete: ");
Logger.e("getAllContacts", "onComplete: ");
}
});
}

View File

@@ -8,7 +8,6 @@ import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
@@ -29,6 +28,7 @@ import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.service.DialerAccessibilityService;
import com.ttstd.dialer.utils.AccessibilityServiceHelper;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.Logger;
public class CallFragment extends BaseMvvmDialogFragment<CallViewModel, FragmentCallBinding> {
private static final String TAG = "CallFragment";
@@ -99,7 +99,7 @@ public class CallFragment extends BaseMvvmDialogFragment<CallViewModel, Fragment
ft.add(this, tag);
ft.commitAllowingStateLoss();
} catch (Exception e) {
Log.e(TAG, "show: " + e.getMessage());
Logger.e(TAG, "show: " + e.getMessage());
}
}
@@ -126,7 +126,7 @@ public class CallFragment extends BaseMvvmDialogFragment<CallViewModel, Fragment
dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(dialIntent);
} catch (Exception e) {
Log.e(TAG, "callNumber: " + e.getMessage());
Logger.e(TAG, "callNumber: " + e.getMessage());
}

View File

@@ -4,7 +4,6 @@ import android.Manifest;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -24,6 +23,7 @@ import androidx.fragment.app.FragmentTransaction;
import com.ttstd.dialer.R;
import com.ttstd.dialer.base.BaseDialogFragment;
import com.ttstd.dialer.databinding.DialogFragmentPermissionsBinding;
import com.ttstd.dialer.utils.Logger;
public class PermissionDialogFragment extends BaseDialogFragment {
private static final String TAG = "PermissionDialog";
@@ -45,7 +45,7 @@ public class PermissionDialogFragment extends BaseDialogFragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.e(TAG, "onCreateView: ");
Logger.e(TAG, "onCreateView: ");
mBinding = DataBindingUtil.inflate(inflater, R.layout.dialog_fragment_permissions, container, false);
mBinding.setClick(new BtnClick());
rootView = mBinding.getRoot();
@@ -88,7 +88,7 @@ public class PermissionDialogFragment extends BaseDialogFragment {
ft.add(this, tag);
ft.commitAllowingStateLoss();
} catch (Exception e) {
Log.e(TAG, "show: " + e.getMessage());
Logger.e(TAG, "show: " + e.getMessage());
}
}

View File

@@ -8,7 +8,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
@@ -22,6 +21,7 @@ import com.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmDialogFragment;
import com.ttstd.dialer.databinding.FragmentDialogShortcutBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.utils.Logger;
public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewModel, FragmentDialogShortcutBinding> {
private static final String TAG = "ShortcutDialogFagment";
@@ -160,7 +160,7 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewMo
ft.add(this, tag);
ft.commitAllowingStateLoss();
} catch (Exception e) {
Log.e(TAG, "show: " + e.getMessage());
Logger.e(TAG, "show: " + e.getMessage());
}
}

View File

@@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.fragment.app.Fragment;
@@ -21,6 +20,7 @@ import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentHomeBinding;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.DateUtil;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.LunarCalendarFestivalUtils;
/**
@@ -107,14 +107,14 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
@Override
public void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
Logger.e(TAG, "onStart: ");
registerReceivers();
}
@Override
public void onStop() {
super.onStop();
Log.e(TAG, "onStop: ");
Logger.e(TAG, "onStop: ");
unregisterReceivers();
}
@@ -151,7 +151,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e("TimeReceiver", "onReceive: " + action);
Logger.i("TimeReceiver", "onReceive: " + action);
switch (action) {
case Intent.ACTION_DATE_CHANGED:
case Intent.ACTION_TIMEZONE_CHANGED:
@@ -184,7 +184,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
try {
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "openSettings: " + e.getMessage());
Logger.e(TAG, "openSettings: " + e.getMessage());
}
}
@@ -210,7 +210,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
try {
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "launchWeChat: " + e.getMessage());
Logger.e(TAG, "launchWeChat: " + e.getMessage());
Toaster.show("打开微信失败");
ApkUtils.openAppStore(mContext, "com.tencent.mm");
}

View File

@@ -3,7 +3,6 @@ package com.ttstd.dialer.fragment.settings;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.ttstd.dialer.R;
@@ -11,6 +10,7 @@ import com.ttstd.dialer.activity.app.AppListActivity;
import com.ttstd.dialer.activity.settings.home.SettingsActivity;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentSettingsBinding;
import com.ttstd.dialer.utils.Logger;
public class SettingsFragment extends BaseMvvmFragment<SettingsViewModel, FragmentSettingsBinding> {
private static final String TAG = "SettingsFragment";
@@ -55,7 +55,7 @@ public class SettingsFragment extends BaseMvvmFragment<SettingsViewModel, Fragme
try {
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "openSettings: " + e.getMessage());
Logger.e(TAG, "openSettings: " + e.getMessage());
}
}

View File

@@ -1,11 +1,11 @@
package com.ttstd.dialer.livedata;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import com.ttstd.dialer.utils.Logger;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -20,7 +20,7 @@ public class SingleLiveEvent<T> extends MutableLiveData<T> {
public void observe(androidx.lifecycle.LifecycleOwner owner, final Observer<? super T> observer) {
// 警告:多个观察者同时注册时,只有一个会收到通知
if (hasActiveObservers()) {
Log.w(TAG, "多个观察者注册,但仅第一个会收到事件");
Logger.w(TAG, "多个观察者注册,但仅第一个会收到事件");
}
// 包装观察者,仅在事件未处理时触发

View File

@@ -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<ProgressCallback> 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<AppInfo> 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<Void> processFirstTimeApps() {
return CompletableFuture.runAsync(() -> {
notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
Log.i(TAG, "进入首次应用数据初始化流程");
Logger.i(TAG, "进入首次应用数据初始化流程");
try {
// 获取所有可启动应用
List<ResolveInfo> 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<ResolveInfo> 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<AppInfo> 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<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
List<AppInfo> 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<String> WHITE_SYSTEM_PACKAGES = new HashSet<String>() {{
// this.add("com.android.luancher3");
}};
public List<ApkInstalledInfo> getApkInstallInfos() {
StorageStatsManager ssm = mContext.getSystemService(StorageStatsManager.class);
// 获取所有已安装的应用
List<PackageInfo> installedApps = mPackageManager.getInstalledPackages(0);
// 遍历并筛选第三方应用
List<ApkInstalledInfo> apkInstalledInfos = installedApps.stream().filter(new Predicate<PackageInfo>() {
@Override
public boolean test(PackageInfo packageInfo) {
String packageName = packageInfo.packageName;
return !ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName);
}
}).map(new Function<PackageInfo, ApkInstalledInfo>() {
@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;
}
}

View File

@@ -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();
}

View File

@@ -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<String, Object> 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();
}
}

View File

@@ -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<List<CityInfo>> emitter) throws Throwable {
long time = System.currentTimeMillis();
List<CityInfo> 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<WeatherNowResponse> 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<WeatherHourlyResponse> 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<WeatherDailyResponse> 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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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<String> packages) {
}
@Override
public List<String> getInstallPackageTrustList() {
return null;
}
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}

View File

@@ -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) {
}
}

View File

@@ -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<T> implements Observer<T> {
@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);
}

View File

@@ -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 <T> Observable<T> schedule(Observable<T> observable, BehaviorSubject<ActivityEvent> provider) {
return observable
// 子线程执行请求
.subscribeOn(Schedulers.io())
// 主线程回调
.observeOn(AndroidSchedulers.mainThread())
// 生命周期管理:页面销毁自动取消请求,防止内存泄漏
.compose(RxLifecycle.bindUntilEvent(provider, ActivityEvent.DESTROY));
}
public <T> Observable<T> schedule(Observable<T> observable) {
return observable
// 子线程执行请求
.subscribeOn(Schedulers.io())
// 主线程回调
.observeOn(AndroidSchedulers.mainThread());
}
public Observable<BaseResponse> getSnRegisterObservable(BehaviorSubject<ActivityEvent> provider) {
return schedule(mRetrofit.create(SnApi.class).register(SystemUtils.getSerial(), Build.MODEL), provider);
}
public Observable<BaseResponse<Void>> getUpdateHardwareInfoObservable(SnHardwareInfoReq snHardwareInfoReq) {
RequestBody body = convertToRequestBodyjson(snHardwareInfoReq);
return schedule(mRetrofit.create(SnApi.class).updateHardwareInfo(body));
}
public Observable<BaseResponse> getUploadScreenshotObservable(MultipartBody.Part part) {
return schedule(mRetrofit.create(SnApi.class).uploadScreenshot(part));
}
public Observable<BaseResponse> getUploadLocationObservable(SnLocationReq locationReq, BehaviorSubject<ActivityEvent> provider) {
return schedule(mRetrofit.create(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)), provider);
}
public Observable<BaseResponse> getUploadLocationObservable(SnLocationReq locationReq) {
return schedule(mRetrofit.create(SnApi.class).uploadLocation(convertToRequestBodyjson(locationReq)));
}
public Observable<BaseResponse<DeveloperOptions>> getDeveloperOptionsObservable(BehaviorSubject<ActivityEvent> provider) {
return schedule(mRetrofit.create(SnApi.class).getDeveloperOptions(), provider);
}
public Observable<BaseResponse<DeveloperOptions>> getDeveloperOptionsObservable() {
return schedule(mRetrofit.create(SnApi.class).getDeveloperOptions());
}
public Observable<BaseResponse> getUploadInstallApksObservable(RequestBody body) {
return schedule(mRetrofit.create(SnApi.class).uploadInstallApks(body));
}
}

View File

@@ -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<T> implements Callback<T> {
private static final String TAG = "RetryCallback";
private int mRetryCount;
private long mRetryInterval;
private int mCurrentRetryCount;
private boolean isExecuting;
private Call<T> mCall;
private Timer timer = new Timer();
public RetryCallback(Call<T> call, int retryCount, long retryInterval) {
isExecuting = true;
mCall = call;
mRetryCount = retryCount;
mRetryInterval = retryInterval;
}
@Override
public final void onResponse(@NonNull Call<T> call, @NonNull Response<T> 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<T> 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<T> 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();
}

View File

@@ -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<String, String> params) {
StringBuilder sb = new StringBuilder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
params.forEach(new BiConsumer<String, String>() {
@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());
}
}

View File

@@ -1,5 +0,0 @@
package com.ttstd.dialer.network;
public class UrlAddress {
}

View File

@@ -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";
}

View File

@@ -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<BaseResponse> register(
@Field("sn") String sn,
@Field("model") String model
);
@POST(UrlConstants.UPDATE_HARDWARE_INFO)
Observable<BaseResponse<Void>> updateHardwareInfo(
@Body RequestBody body
);
@Multipart
@POST(UrlConstants.UPLOAD_SCREENSHOT)
Observable<BaseResponse> uploadScreenshot(
@Part MultipartBody.Part body
);
@Multipart
@POST(UrlConstants.UPLOAD_SCREENSHOT)
Call<BaseResponse> uploadScreenshotCall(
@Part MultipartBody.Part body
);
@POST(UrlConstants.UPLOAD_LOCATION)
Observable<BaseResponse> uploadLocation(
@Body RequestBody body
);
@GET(UrlConstants.DEVELOPER_OPTIONS)
Observable<BaseResponse<DeveloperOptions>> getDeveloperOptions(
);
@POST(UrlConstants.UPLOAD_INSTALL_APKS)
Observable<BaseResponse> uploadInstallApks(
@Body RequestBody body
);
}

View File

@@ -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<String> 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<String, String> 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);
}
}

View File

@@ -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<BaseResponse<DeveloperOptions>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Logger.e("getDeveloperOptions", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<DeveloperOptions> 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<List<ApkInstalledInfo>>() {
@Override
public List<ApkInstalledInfo> call() throws Exception {
long time = System.currentTimeMillis();
List<ApkInstalledInfo> 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<List<ApkInstalledInfo>, Observable<BaseResponse>>() {
@Override
public Observable<BaseResponse> apply(List<ApkInstalledInfo> apkInstalledInfos) throws Throwable {
RequestBody body = OkHttpManager.convertToRequestBodyjson(apkInstalledInfos);
return OkHttpManager.getInstance().getUploadInstallApksObservable(body);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<BaseResponse>() {
@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<BaseResponse<Void>>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.e("updateHardwareInfo", "onSubscribe: ");
}
@Override
public void onNext(@NonNull BaseResponse<Void> 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<Integer>() {
@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<Integer, Observable<BaseResponse>>() {
@Override
public Observable<BaseResponse> 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>() { // 注意这里应该是BaseResponse不是Observable<BaseResponse>
@Override
public void onSuccess(BaseResponse baseResponse) {
Log.e("screenSnapshot", "onSuccess: " + baseResponse.toString());
}
@Override
public void onFailure(Throwable e) {
Log.e("onFailure", "onFailure: " + e.getMessage());
}
});
}
}

View File

@@ -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: ");
}
}

View File

@@ -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;

View File

@@ -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<SnLocationReq>() {
@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<String> runningPackages = SystemUtils.getRunningTaskPackages(MainService.this);
int i = 0;
for (String pkg : runningPackages) {

View File

@@ -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<ActivityEvent> lifecycleSubject;
// 设置Service生命周期Subject
public void setLifecycleSubject(BehaviorSubject<ActivityEvent> lifecycleSubject) {
this.lifecycleSubject = lifecycleSubject;
}
// 绑定请求到Service销毁事件自动取消请求
private <T> LifecycleTransformer<T> 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<BaseResponse>() {
@Override
public void onSuccess(BaseResponse baseResponse) {
Log.e("uploadLocation", "onSuccess: " + baseResponse);
}
@Override
public void onFailure(Throwable e) {
Log.e("uploadLocation", "onFailure: " + e.getMessage());
}
});
}
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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<Integer> enableCameraList;//可用摄像头列表
private boolean mFlashSupported = false;//是否支持闪光灯
private List<Size> recordSizeList;// 录制尺寸
private Size mPreviewOutputSize;// 预览尺寸
private List<Size> 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 {
// 创建CaptureRequestBuilderTEMPLATE_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_FACINGvalue为对应的摄像头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<Size> 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<Size> sorted = sizes.stream().sorted(new Comparator<Size>() {
@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);
}
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
// ===================== 兼容原生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);
}
}

View File

@@ -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
*

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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); //imsinot 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<NetworkInterface> 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<ActivityManager.RecentTaskInfo> list = getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), getCurrentUserId());
HashMap<String, Integer> 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<Boolean> 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<Integer> observer) {
Observable.fromCallable(new Callable<Integer>() {
@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);
}
}

View File

@@ -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;
}