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 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 DEFAULT_APP_PACKAGES = new ArrayList() {{ 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 DEFAULT_DIALER_PACKAGE = new HashSet() {{ this.add("com.android.dialer"); }}; public static final Set DEFAULT_CONTACT_PACKAGE = new HashSet() {{ this.add("com.android.contacts"); }}; public static final Set DEFAULT_MESSAGE_PACKAGE = new HashSet() {{ this.add("com.android.messaging"); }}; public static final Set DEFAULT_SETTINGS_PACKAGE = new HashSet() {{ 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 getAllDesktopSortApps() { try { List result = mAppRepository.getAllApp(); return result != null ? result : Collections.emptyList(); } catch (Exception e) { Logger.e(TAG, "获取桌面应用列表失败", e); return Collections.emptyList(); } } // 首次初始化应用数据(针对首次获取为空的场景) private CompletableFuture processFirstTimeApps() { return CompletableFuture.runAsync(() -> { notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST); Logger.i(TAG, "进入首次应用数据初始化流程"); try { // 获取所有可启动应用 List allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext); if (allLauncherApps.isEmpty()) { Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化"); return; } List 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 processUninstalledApps(List appInfos) { return CompletableFuture.runAsync(() -> { notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL); if (appInfos.isEmpty()) { Logger.w(TAG, "桌面应用列表为空,跳过删除处理"); return; } List 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 processNewApps() { return CompletableFuture.runAsync(() -> { try { List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext); if (resolveInfos.isEmpty()) { Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理"); return; } List 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 updatePosition() { return CompletableFuture.runAsync(() -> { try { List appInfos = getAllDesktopSortApps(); if (appInfos.isEmpty()) { Logger.w(TAG, "无应用数据,跳过位置更新"); return; } List 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 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 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 allApps = mAppRepository.getAllApp(); if (allApps != null) { List 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 allApps = getAllDesktopSortApps(); if (allApps.isEmpty()) { processFirstTimeApps().join(); } else { processUninstalledApps(allApps).join(); processNewApps().join(); } updatePosition().join(); }, ASYNC_EXECUTOR); } public String getDefaultAppList() { List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext); List defaultApps = resolveInfos.stream() .filter(ri -> DEFAULT_APP_PACKAGES.contains(ri.activityInfo.packageName)) .sorted((o1, o2) -> Collator.getInstance(Locale.CHINESE) .compare(o1.activityInfo.loadLabel(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 WHITE_SYSTEM_PACKAGES = new HashSet() {{ // this.add("com.android.luancher3"); }}; public List getApkInstallInfos() { StorageStatsManager ssm = mContext.getSystemService(StorageStatsManager.class); // 获取所有已安装的应用 List installedApps = mPackageManager.getInstalledPackages(0); // 遍历并筛选第三方应用 List apkInstalledInfos = installedApps.stream().filter(new Predicate() { @Override public boolean test(PackageInfo packageInfo) { String packageName = packageInfo.packageName; return !ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName); } }).map(new Function() { @Override public ApkInstalledInfo apply(PackageInfo packageInfo) { ApkInstalledInfo apkInstalledInfo = new ApkInstalledInfo(); apkInstalledInfo.setPackageName(packageInfo.packageName); apkInstalledInfo.setAppName(packageInfo.applicationInfo.loadLabel(mPackageManager).toString()); apkInstalledInfo.setVersionName(packageInfo.versionName); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { apkInstalledInfo.setVersionCode(packageInfo.getLongVersionCode()); } else { apkInstalledInfo.setVersionCode(packageInfo.versionCode); } apkInstalledInfo.setInstallTime(packageInfo.firstInstallTime); apkInstalledInfo.setLastUpdateTime(packageInfo.lastUpdateTime); try { StorageStats stats = ssm.queryStatsForPackage( UUID.fromString(packageInfo.applicationInfo.storageUuid.toString()), mContext.getPackageName(), UserHandle.of(UserHandle.myUserId()) ); long appSize = stats.getAppBytes(); // 应用大小 Log.e(TAG, "apply: appSize = " + appSize); long dataSize = stats.getDataBytes(); // 用户数据 long cacheSize = stats.getCacheBytes(); // 缓存 apkInstalledInfo.setApkSize(appSize); apkInstalledInfo.setDataSize(dataSize); apkInstalledInfo.setCacheSize(cacheSize); } catch (Exception e) { Log.e(TAG, "getApkInstallInfos: " + e.getMessage()); } Log.e(TAG, "apply: " + packageInfo.applicationInfo.publicSourceDir); Log.e(TAG, "apply: publicSourceDir = " + new File(packageInfo.applicationInfo.publicSourceDir).length()); Log.e(TAG, "apply: " + packageInfo.applicationInfo.dataDir); apkInstalledInfo.setMd5(HashUtils.getFileMd5(new File(packageInfo.applicationInfo.publicSourceDir))); apkInstalledInfo.setSystemApp(ApkUtils.isSystemApp(mContext, packageInfo.packageName)); return apkInstalledInfo; } }).collect(Collectors.toList()); return apkInstalledInfos; } }