Files
ElderlyDialer/app/src/main/java/com/ttstd/dialer/manager/AppManager.java

563 lines
25 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.config.LiveDataAction;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.livedata.LiveDataBus;
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;
public class AppManager {
private static final String TAG = "AppManager";
// 步骤常量定义,便于回调和日志统一管理
private static final String STEP_FIRST_INIT = "首次初始化应用数据";
private static final String STEP_REMOVE_UNINSTALLED = "移除未安装应用";
private static final String STEP_ADD_NEW_APPS = "添加新安装应用";
private static final String STEP_UPDATE_POSITION = "更新应用位置";
private static final int TOTAL_STEPS_NORMAL = 3; // 正常流程总步骤
private static final int TOTAL_STEPS_FIRST = 2; // 首次初始化总步骤
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@SuppressLint("StaticFieldLeak")
private static volatile AppManager INSTANCE;
private Context mContext;
private PackageManager mPackageManager;
private AppRepository mAppRepository;
private Set<ProgressCallback> mProgressCallbacks = new CopyOnWriteArraySet<>();
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final ExecutorService ASYNC_EXECUTOR = Executors.newFixedThreadPool(CORE_POOL_SIZE);
public interface ProgressCallback {
void onProgress(String step, int current, int total);
void onCompleted(boolean success, String message);
}
public void addProgressCallback(ProgressCallback progressCallback) {
if (progressCallback != null) {
mProgressCallbacks.add(progressCallback);
}
}
public void removeProgressCallback(ProgressCallback progressCallback) {
mProgressCallbacks.remove(progressCallback);
}
public static final List<String> DEFAULT_APP_PACKAGES = new ArrayList<String>() {{
this.add("com.android.dialer");
this.add("com.android.contacts");
this.add("com.android.messaging");
this.add("com.android.settings");
this.add("com.android.camera2");
this.add("com.tencent.mm");
this.add("com.jiangjia.gif");
this.add("com.ss.android.ugc.aweme");
}};
public static final Set<String> DEFAULT_DIALER_PACKAGE = new HashSet<String>() {{
this.add("com.android.dialer");
}};
public static final Set<String> DEFAULT_CONTACT_PACKAGE = new HashSet<String>() {{
this.add("com.android.contacts");
}};
public static final Set<String> DEFAULT_MESSAGE_PACKAGE = new HashSet<String>() {{
this.add("com.android.messaging");
}};
public static final Set<String> DEFAULT_SETTINGS_PACKAGE = new HashSet<String>() {{
this.add("com.android.settings");
}};
public static void init(Context context) {
if (INSTANCE == null) {
synchronized (AppManager.class) {
if (INSTANCE == null) {
INSTANCE = new AppManager(context);
}
}
}
}
public static AppManager getInstance() {
if (INSTANCE == null) {
throw new IllegalStateException("You must first initialize the AppManager");
}
return INSTANCE;
}
private AppManager(Context context) {
this.mContext = context.getApplicationContext();
this.mAppRepository = new AppRepository(context);
this.mPackageManager = mContext.getPackageManager();
executeAppListProcessing();
}
// 通知进度回调
private void notifyProgress(String step, int current, int total) {
mProgressCallbacks.forEach(callback -> callback.onProgress(step, current, total));
}
// 通知完成回调
private void notifyCompleted(boolean success, String message) {
mProgressCallbacks.forEach(callback -> callback.onCompleted(success, message));
}
// 异步执行应用列表处理流程
public void executeAppListProcessing() {
CompletableFuture
.supplyAsync(this::getAllDesktopSortApps, ASYNC_EXECUTOR)
.thenComposeAsync(desktopApps -> {
// 根据应用列表是否为空,选择不同处理流程
if (desktopApps.isEmpty()) {
// 首次初始化流程:首次数据加载 -> 更新位置
return processFirstTimeApps()
.thenComposeAsync(unused -> {
notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST);
return updatePosition();
}, ASYNC_EXECUTOR);
} else {
// 正常流程:移除未安装 -> 添加新应用 -> 更新位置
return processUninstalledApps(desktopApps)
.thenComposeAsync(unused -> {
notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL);
return processNewApps();
}, ASYNC_EXECUTOR)
.thenComposeAsync(unused -> {
notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL);
return updatePosition();
}, ASYNC_EXECUTOR);
}
}, ASYNC_EXECUTOR)
.thenRun(() -> notifyCompleted(true, "应用列表处理完成"))
.exceptionally(throwable -> {
Logger.e(TAG, "异步处理失败", throwable);
notifyCompleted(false, "处理失败: " + throwable.getMessage());
return null;
});
}
// 第一步:获取所有桌面应用(空值安全处理)
private List<AppInfo> getAllDesktopSortApps() {
try {
List<AppInfo> result = mAppRepository.getAllApp();
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
Logger.e(TAG, "获取桌面应用列表失败", e);
return Collections.emptyList();
}
}
// 首次初始化应用数据(针对首次获取为空的场景)
private CompletableFuture<Void> processFirstTimeApps() {
return CompletableFuture.runAsync(() -> {
notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
Logger.i(TAG, "进入首次应用数据初始化流程");
try {
// 获取所有可启动应用
List<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext);
if (allLauncherApps.isEmpty()) {
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
return;
}
List<AppInfo> allFirstApps = allLauncherApps.stream()
.filter(Objects::nonNull)
.map(this::resolveInfoToDesktopApp)
.filter(Objects::nonNull)
.sorted((o1, o2) -> Boolean.compare(ApkUtils.isSystemApp(mContext, o2.getPackageName()), ApkUtils.isSystemApp(mContext, o1.getPackageName())))
.sorted(getAppComparator())
.collect(Collectors.toList());
final int[] hotseatCounter = {0};
IntStream.range(0, allFirstApps.size())
.forEach(new IntConsumer() {
@Override
public void accept(int index) {
AppInfo appInfo = allFirstApps.get(index);
if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) {
appInfo.setOutside(1);
} else {
appInfo.setOutside(0);
}
if (DEFAULT_DIALER_PACKAGE.contains(appInfo.getPackageName())) {
appInfo.setHotseat(1);
appInfo.setHotseatPosition(hotseatCounter[0]++);
} else if (DEFAULT_CONTACT_PACKAGE.contains(appInfo.getPackageName())) {
appInfo.setHotseat(1);
appInfo.setHotseatPosition(hotseatCounter[0]++);
} else if (DEFAULT_MESSAGE_PACKAGE.contains(appInfo.getPackageName())) {
appInfo.setHotseat(1);
appInfo.setHotseatPosition(hotseatCounter[0]++);
} else if (DEFAULT_SETTINGS_PACKAGE.contains(appInfo.getPackageName())) {
appInfo.setHotseat(1);
appInfo.setHotseatPosition(hotseatCounter[0]++);
} else {
appInfo.setHotseat(0);
appInfo.setHotseatPosition(0);
}
appInfo.setPosition(index);
}
});
// 批量插入首次数据
allFirstApps.forEach(app -> {
try {
mAppRepository.insert(app);
} catch (Exception e) {
Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
}
});
Logger.i(TAG, "首次初始化完成,插入默认应用: " + allFirstApps.size() + "");
} catch (Exception e) {
Logger.e(TAG, "首次应用数据初始化失败", e);
throw new RuntimeException("首次初始化失败", e); // 抛出异常触发回调
}
}, ASYNC_EXECUTOR);
}
// 处理未安装的应用(删除操作)
private CompletableFuture<Void> processUninstalledApps(List<AppInfo> appInfos) {
return CompletableFuture.runAsync(() -> {
notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
if (appInfos.isEmpty()) {
Logger.w(TAG, "桌面应用列表为空,跳过删除处理");
return;
}
List<Integer> ids = appInfos.stream()
.filter(Objects::nonNull)
.filter(app -> !ApkUtils.isInstalled(mContext, app.getPackageName()))
.map(app -> {
try {
return mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
} catch (Exception e) {
Logger.e(TAG, "获取应用ID失败: " + app.getPackageName(), e);
return -1;
}
})
.filter(id -> id > 0)
.collect(Collectors.toList());
if (!ids.isEmpty()) {
ids.forEach(id -> {
try {
mAppRepository.deleteById(id);
} catch (Exception e) {
Logger.e(TAG, "删除应用失败, ID: " + id, e);
}
});
Logger.i(TAG, "成功删除 " + ids.size() + " 个未安装应用");
}
}, ASYNC_EXECUTOR);
}
// 处理新安装的应用(插入操作)- 非首次场景
private CompletableFuture<Void> processNewApps() {
return CompletableFuture.runAsync(() -> {
try {
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
if (resolveInfos.isEmpty()) {
Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
return;
}
List<AppInfo> newApps = resolveInfos.stream()
.filter(Objects::nonNull)
.map(this::resolveInfoToDesktopApp)
.filter(Objects::nonNull)
.filter(app -> ApkUtils.isInstalled(mContext, app.getPackageName()))
.filter(app -> !isAppExists(app)) // 过滤已存在的应用
.sorted(getAppComparator())
.collect(Collectors.toList());
if (!newApps.isEmpty()) {
newApps.forEach(app -> app.setPosition(mAppRepository.getTotalCount()));
newApps.forEach(app -> {
try {
mAppRepository.insert(app);
} catch (Exception e) {
Logger.e(TAG, "插入新应用失败: " + app.getPackageName(), e);
}
});
Logger.i(TAG, "成功插入 " + newApps.size() + " 个新应用");
} else {
Logger.i(TAG, "没有需要插入的新应用");
}
} catch (Exception e) {
Logger.e(TAG, "处理新应用时发生异常", e);
throw new RuntimeException("新应用处理失败", e);
}
}, ASYNC_EXECUTOR);
}
// 更新应用位置
private CompletableFuture<Void> updatePosition() {
return CompletableFuture.runAsync(() -> {
try {
List<AppInfo> appInfos = getAllDesktopSortApps();
if (appInfos.isEmpty()) {
Logger.w(TAG, "无应用数据,跳过位置更新");
return;
}
List<AppInfo> sortedApps = appInfos.stream()
.filter(Objects::nonNull)
.sorted(Comparator.comparingInt(AppInfo::getPosition))
.collect(Collectors.toList());
IntStream.range(0, sortedApps.size())
.forEach(index -> {
AppInfo app = sortedApps.get(index);
app.setPosition(index);
try {
mAppRepository.update(app);
} catch (Exception e) {
Logger.e(TAG, "更新应用位置失败: " + app.getPackageName(), e);
}
});
Logger.i(TAG, "成功更新 " + sortedApps.size() + " 个应用的位置");
} catch (Exception e) {
Logger.e(TAG, "更新应用位置时发生异常", e);
throw new RuntimeException("位置更新失败", e);
}
}, ASYNC_EXECUTOR);
}
// 工具方法ResolveInfo转换为DesktopSortApp
private AppInfo resolveInfoToDesktopApp(ResolveInfo resolveInfo) {
ComponentName component = new ComponentName(
resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name
);
return new AppInfo(mContext, component);
}
// 工具方法:检查应用是否已存在
private boolean isAppExists(AppInfo app) {
try {
return mAppRepository.checkAppInfoExists(app.getPackageName(), app.getClassName()) > 0;
} catch (Exception e) {
Logger.e(TAG, "检查应用存在性失败: " + app.getPackageName(), e);
return false;
}
}
// 工具方法:获取应用排序器(统一排序逻辑)
private Comparator<AppInfo> getAppComparator() {
return Comparator.comparing(AppInfo::getLabel, Collator.getInstance(Locale.CHINESE));
}
public void updateApp(String packageName) {
boolean isInstalled = ApkUtils.isInstalled(mContext, packageName);
if (isInstalled) {
Logger.i(TAG, "应用已安装: " + packageName + ", 开始更新数据库");
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
if (!resolveInfos.isEmpty()) {
resolveInfos.stream()
.map(this::resolveInfoToDesktopApp)
.filter(Objects::nonNull)
.forEach(app -> {
try {
int existingId = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
if (existingId > 0) {
// app.setId(existingId);
// mAppRepository.update(app);
// Logger.i(TAG, "更新应用信息: " + app.getPackageName());
} else {
app.setOutside(1);
app.setPosition(mAppRepository.getTotalCount());
mAppRepository.insert(app);
Logger.i(TAG, "新增应用信息: " + app.getPackageName());
}
} catch (Exception e) {
Logger.e(TAG, "处理应用失败: " + app.getPackageName(), e);
}
});
} else {
Logger.w(TAG, "应用已安装但无可启动的 Launcher Activity");
}
} else {
Logger.i(TAG, "应用未安装或被禁用: " + packageName + ", 开始删除数据库数据");
try {
List<AppInfo> allApps = mAppRepository.getAllApp();
if (allApps != null) {
List<Integer> idsToDelete = allApps.stream()
.filter(app -> packageName.equals(app.getPackageName()))
.map(AppInfo::getId)
.filter(id -> id > 0)
.collect(Collectors.toList());
if (!idsToDelete.isEmpty()) {
idsToDelete.forEach(id -> {
try {
mAppRepository.deleteById(id);
Logger.i(TAG, "删除应用数据库记录, ID: " + id);
} catch (Exception e) {
Logger.e(TAG, "删除应用记录失败, ID: " + id, e);
}
});
Logger.i(TAG, "成功删除 " + idsToDelete.size() + " 条记录");
} else {
Logger.i(TAG, "数据库中未找到该应用的记录");
}
}
} catch (Exception e) {
Logger.e(TAG, "删除应用记录时发生异常", e);
}
}
LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG);
}
// 重构getAllApp方法复用现有处理逻辑
public void refreshAllApps() {
CompletableFuture.runAsync(() -> {
List<AppInfo> allApps = getAllDesktopSortApps();
if (allApps.isEmpty()) {
processFirstTimeApps().join();
} else {
processUninstalledApps(allApps).join();
processNewApps().join();
}
updatePosition().join();
}, ASYNC_EXECUTOR);
}
public String getDefaultAppList() {
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(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(
ApkUtils.isSystemApp(mContext, b.getPackageName()),
ApkUtils.isSystemApp(mContext, a.getPackageName())))
.collect(Collectors.toCollection(LinkedList::new));
IntStream.range(0, defaultApps.size())
.forEach(i -> defaultApps.get(i).setPosition(i));
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;
}
}