394 lines
18 KiB
Java
394 lines
18 KiB
Java
package com.ttstd.dialer.manager;
|
||
|
||
import android.annotation.SuppressLint;
|
||
import android.content.ComponentName;
|
||
import android.content.Context;
|
||
import android.content.pm.PackageManager;
|
||
import android.content.pm.ResolveInfo;
|
||
import android.util.Log;
|
||
|
||
import com.tencent.mmkv.MMKV;
|
||
import com.ttstd.dialer.config.CommonConfig;
|
||
import com.ttstd.dialer.db.app.AppRepository;
|
||
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||
import com.ttstd.dialer.gson.GsonUtils;
|
||
import com.ttstd.dialer.utils.ApkUtils;
|
||
|
||
import java.text.Collator;
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.Comparator;
|
||
import java.util.LinkedList;
|
||
import java.util.List;
|
||
import java.util.Locale;
|
||
import java.util.Objects;
|
||
import java.util.Optional;
|
||
import java.util.Set;
|
||
import java.util.concurrent.CompletableFuture;
|
||
import java.util.concurrent.CopyOnWriteArraySet;
|
||
import java.util.concurrent.ExecutorService;
|
||
import java.util.concurrent.Executors;
|
||
import java.util.function.Consumer;
|
||
import java.util.function.Function;
|
||
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 MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||
|
||
@SuppressLint("StaticFieldLeak")
|
||
private static AppManager INSTANCE;
|
||
private Context mContext;
|
||
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) {
|
||
mProgressCallbacks.add(progressCallback);
|
||
}
|
||
|
||
public static final List<String> DEFAULT_APP_PACKAGES = new ArrayList<String>() {{
|
||
this.add("com.android.settings");
|
||
this.add("com.android.dialer");
|
||
this.add("com.android.camera2");
|
||
this.add("com.tencent.mm");
|
||
this.add("com.jiangjia.gif");
|
||
this.add("com.ss.android.ugc.aweme");
|
||
}};
|
||
|
||
public static void init(Context context) {
|
||
if (INSTANCE == null) {
|
||
INSTANCE = new AppManager(context);
|
||
}
|
||
}
|
||
|
||
public static AppManager getInstance() {
|
||
if (INSTANCE == null) {
|
||
throw new IllegalStateException("You must be init AppManager first");
|
||
}
|
||
return INSTANCE;
|
||
}
|
||
|
||
private AppManager(Context context) {
|
||
this.mContext = context.getApplicationContext();
|
||
this.mAppRepository = new AppRepository(context);
|
||
executeAppListProcessing();
|
||
}
|
||
|
||
|
||
// 异步执行方法
|
||
public void executeAppListProcessing() {
|
||
CompletableFuture
|
||
.supplyAsync(this::getAllDesktopSortApps, ASYNC_EXECUTOR)
|
||
.thenComposeAsync(desktopSortApps -> {
|
||
// 判断获取的应用列表是否为空
|
||
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||
Log.i("AppListProcessor", "检测到桌面应用列表为空,直接插入新应用数据");
|
||
return processNewApps()
|
||
.thenComposeAsync(unused -> updatePosition(), ASYNC_EXECUTOR);
|
||
} else {
|
||
return processUninstalledApps(desktopSortApps)
|
||
.thenComposeAsync(unused -> processNewApps(), ASYNC_EXECUTOR)
|
||
.thenComposeAsync(unused -> updatePosition(), ASYNC_EXECUTOR);
|
||
}
|
||
}, ASYNC_EXECUTOR)
|
||
.thenComposeAsync(unused -> processNewApps(), ASYNC_EXECUTOR)
|
||
.exceptionally(throwable -> {
|
||
// 统一异常处理
|
||
Log.e("AppListProcessor", "异步处理失败", throwable);
|
||
return null;
|
||
});
|
||
}
|
||
|
||
// 第一步:获取所有桌面应用(增加空值安全处理)
|
||
private List<DesktopSortApp> getAllDesktopSortApps() {
|
||
try {
|
||
List<DesktopSortApp> result = mAppRepository.getAllApp();
|
||
return result != null ? result : Collections.emptyList();
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "获取桌面应用列表失败", e);
|
||
return Collections.emptyList();
|
||
}
|
||
}
|
||
|
||
private Optional<List<DesktopSortApp>> getAllDesktopSortAppsSafe() {
|
||
try {
|
||
return Optional.ofNullable(mAppRepository.getAllApp())
|
||
.filter(list -> !list.isEmpty());
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "获取应用列表失败", e);
|
||
return Optional.empty();
|
||
}
|
||
}
|
||
|
||
// 第二步:处理未安装的应用(删除操作) - 增加空值检查
|
||
private CompletableFuture<Void> processUninstalledApps(List<DesktopSortApp> desktopSortApps) {
|
||
return CompletableFuture.runAsync(() -> {
|
||
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||
Log.w("AppListProcessor", "桌面应用列表为空,跳过删除处理");
|
||
return;
|
||
}
|
||
|
||
List<Integer> ids = desktopSortApps.stream()
|
||
.filter(desktopSortApp -> desktopSortApp != null &&
|
||
!ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName()))
|
||
.map(desktopSortApp -> {
|
||
try {
|
||
return mAppRepository.getIdByPackageAndClass(
|
||
desktopSortApp.getPackageName(), desktopSortApp.getClassName());
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "获取应用ID失败: " + desktopSortApp.getPackageName(), e);
|
||
return -1; // 返回无效ID,后续过滤掉
|
||
}
|
||
})
|
||
.filter(id -> id > 0) // 过滤掉无效ID
|
||
.collect(Collectors.toList());
|
||
|
||
if (!ids.isEmpty()) {
|
||
ids.forEach(id -> {
|
||
try {
|
||
mAppRepository.deleteById(id);
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "删除应用失败, ID: " + id, e);
|
||
}
|
||
});
|
||
Log.i("AppListProcessor", "成功删除 " + ids.size() + " 个未安装应用");
|
||
}
|
||
}, ASYNC_EXECUTOR);
|
||
}
|
||
|
||
// 第三步:处理新应用(插入操作) - 增加空值和异常处理
|
||
private CompletableFuture<Void> processNewApps() {
|
||
return CompletableFuture.runAsync(() -> {
|
||
try {
|
||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||
if (resolveInfos.isEmpty()) {
|
||
Log.w("AppListProcessor", "未获取到启动器应用列表");
|
||
return;
|
||
}
|
||
|
||
List<DesktopSortApp> appList = resolveInfos.stream()
|
||
.filter(Objects::nonNull)
|
||
.map(resolveInfo -> {
|
||
try {
|
||
String packageName = resolveInfo.activityInfo.packageName;
|
||
String className = resolveInfo.activityInfo.name;
|
||
ComponentName componentName = new ComponentName(packageName, className);
|
||
return new DesktopSortApp(mContext, componentName);
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "创建DesktopSortApp失败", e);
|
||
return null;
|
||
}
|
||
})
|
||
.filter(Objects::nonNull)
|
||
.sorted(new Comparator<DesktopSortApp>() {
|
||
@Override
|
||
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||
}
|
||
})
|
||
.collect(Collectors.toList());
|
||
|
||
List<DesktopSortApp> filterApp = appList.stream()
|
||
.filter(desktopSortApp -> ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName()))
|
||
.filter(desktopSortApp -> {
|
||
try {
|
||
return mAppRepository.checkAppInfoExists(
|
||
desktopSortApp.getPackageName(), desktopSortApp.getClassName()) <= 0;
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "检查应用存在性失败", e);
|
||
return false;
|
||
}
|
||
})
|
||
.collect(Collectors.toList());
|
||
|
||
if (!filterApp.isEmpty()) {
|
||
filterApp.forEach(desktopSortApp -> {
|
||
desktopSortApp.setPosition(mAppRepository.getTotalCount());
|
||
try {
|
||
mAppRepository.insert(desktopSortApp);
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "插入应用失败: " + desktopSortApp.getPackageName(), e);
|
||
}
|
||
});
|
||
Log.i("AppListProcessor", "成功插入 " + filterApp.size() + " 个新应用");
|
||
} else {
|
||
Log.i("AppListProcessor", "没有需要插入的新应用");
|
||
}
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "处理新应用时发生异常", e);
|
||
}
|
||
}, ASYNC_EXECUTOR);
|
||
}
|
||
|
||
// 第四步:更新应用位置(您提供的方法优化)
|
||
private CompletableFuture<Void> updatePosition() {
|
||
return CompletableFuture.runAsync(() -> {
|
||
try {
|
||
List<DesktopSortApp> desktopSortApps = getAllDesktopSortApps();
|
||
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||
Log.w("AppListProcessor", "无应用数据,跳过位置更新");
|
||
return;
|
||
}
|
||
|
||
// 使用Lambda简化代码
|
||
List<DesktopSortApp> sortedApps = desktopSortApps.stream()
|
||
.filter(Objects::nonNull)
|
||
.sorted((o1, o2) -> Integer.compare(o1.getPosition(), o2.getPosition()))
|
||
.collect(Collectors.toList());
|
||
|
||
// 批量更新位置
|
||
IntStream.range(0, sortedApps.size())
|
||
.forEach(index -> {
|
||
DesktopSortApp desktopSortApp = sortedApps.get(index);
|
||
desktopSortApp.setPosition(index);
|
||
try {
|
||
mAppRepository.update(desktopSortApp);
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "更新应用位置失败: " + desktopSortApp.getPackageName(), e);
|
||
}
|
||
});
|
||
Log.i("AppListProcessor", "成功更新 " + sortedApps.size() + " 个应用的位置");
|
||
} catch (Exception e) {
|
||
Log.e("AppListProcessor", "更新应用位置时发生异常", e);
|
||
}
|
||
}, ASYNC_EXECUTOR);
|
||
}
|
||
|
||
public void updateApp(String packageName) {
|
||
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
|
||
if (!resolveInfos.isEmpty()) {
|
||
resolveInfos.stream().map(new Function<ResolveInfo, DesktopSortApp>() {
|
||
@Override
|
||
public DesktopSortApp apply(ResolveInfo resolveInfo) {
|
||
String packageName = resolveInfo.activityInfo.packageName;
|
||
String className = resolveInfo.activityInfo.name;
|
||
ComponentName componentName = new ComponentName(packageName, className);
|
||
return new DesktopSortApp(mContext, componentName);
|
||
}
|
||
}).filter(new Predicate<DesktopSortApp>() {
|
||
@Override
|
||
public boolean test(DesktopSortApp desktopSortApp) {
|
||
return mAppRepository.checkAppInfoExists(desktopSortApp.getPackageName(), desktopSortApp.getClassName()) <= 0;
|
||
}
|
||
}).forEach(new Consumer<DesktopSortApp>() {
|
||
@Override
|
||
public void accept(DesktopSortApp desktopSortApp) {
|
||
desktopSortApp.setPosition(mAppRepository.getTotalCount());
|
||
mAppRepository.insert(desktopSortApp);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
public void getAllApp() {
|
||
List<DesktopSortApp> desktopSortApps = mAppRepository.getAllApp();
|
||
List<Integer> ids = desktopSortApps.stream().filter(new Predicate<DesktopSortApp>() {
|
||
@Override
|
||
public boolean test(DesktopSortApp desktopSortApp) {
|
||
return !ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName());
|
||
}
|
||
}).map(new java.util.function.Function<DesktopSortApp, Integer>() {
|
||
@Override
|
||
public Integer apply(DesktopSortApp desktopSortApp) {
|
||
return mAppRepository.getIdByPackageAndClass(desktopSortApp.getPackageName(), desktopSortApp.getClassName());
|
||
}
|
||
}).collect(Collectors.toList());
|
||
ids.stream().forEach(new Consumer<Integer>() {
|
||
@Override
|
||
public void accept(Integer integer) {
|
||
mAppRepository.deleteById(integer);
|
||
}
|
||
});
|
||
|
||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||
List<DesktopSortApp> appList = resolveInfos.stream().map(new Function<ResolveInfo, ComponentName>() {
|
||
@Override
|
||
public ComponentName apply(ResolveInfo resolveInfo) {
|
||
String packageName = resolveInfo.activityInfo.packageName;
|
||
String className = resolveInfo.activityInfo.name;
|
||
ComponentName componentName = new ComponentName(packageName, className);
|
||
return componentName;
|
||
}
|
||
}).map(new Function<ComponentName, DesktopSortApp>() {
|
||
@Override
|
||
public DesktopSortApp apply(ComponentName componentName) {
|
||
DesktopSortApp desktopSortApp = new DesktopSortApp(mContext, componentName);
|
||
return desktopSortApp;
|
||
}
|
||
}).collect(Collectors.toList());
|
||
List<DesktopSortApp> filterApp = appList.stream().filter(new Predicate<DesktopSortApp>() {
|
||
@Override
|
||
public boolean test(DesktopSortApp desktopSortApp) {
|
||
return ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName());
|
||
}
|
||
}).filter(new Predicate<DesktopSortApp>() {
|
||
@Override
|
||
public boolean test(DesktopSortApp desktopSortApp) {
|
||
return mAppRepository.checkAppInfoExists(desktopSortApp.getPackageName(), desktopSortApp.getClassName()) > 0;
|
||
}
|
||
}).collect(Collectors.toList());
|
||
|
||
filterApp.stream().forEach(new Consumer<DesktopSortApp>() {
|
||
@Override
|
||
public void accept(DesktopSortApp desktopSortApp) {
|
||
mAppRepository.insert(desktopSortApp);
|
||
}
|
||
});
|
||
|
||
|
||
}
|
||
|
||
public String getDefaultAppList() {
|
||
PackageManager packageManager = mContext.getPackageManager();
|
||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||
List<ResolveInfo> filter = resolveInfos.stream().filter(new Predicate<ResolveInfo>() {
|
||
@Override
|
||
public boolean test(ResolveInfo resolveInfo) {
|
||
return DEFAULT_APP_PACKAGES.contains(resolveInfo.activityInfo.packageName);
|
||
}
|
||
}).sorted(new Comparator<ResolveInfo>() {
|
||
@Override
|
||
public int compare(ResolveInfo o1, ResolveInfo o2) {
|
||
return Collator.getInstance(Locale.CHINESE).compare(o1.activityInfo.loadLabel(packageManager), o2.activityInfo.loadLabel(packageManager));
|
||
}
|
||
}).collect(Collectors.toList());
|
||
|
||
List<DesktopSortApp> desktopSortApps = filter.stream().map(new Function<ResolveInfo, DesktopSortApp>() {
|
||
@Override
|
||
public DesktopSortApp apply(ResolveInfo resolveInfo) {
|
||
ComponentName componentName = new ComponentName(resolveInfo.activityInfo.packageName,
|
||
resolveInfo.activityInfo.name);
|
||
return new DesktopSortApp(mContext, componentName);
|
||
}
|
||
}).sorted(new Comparator<DesktopSortApp>() {
|
||
@Override
|
||
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||
return Boolean.compare(ApkUtils.isSystemApp(mContext, o2.getPackageName()), ApkUtils.isSystemApp(mContext, o1.getPackageName()));
|
||
}
|
||
}).collect(Collectors.toCollection(LinkedList::new));
|
||
|
||
for (int i = 0; i < desktopSortApps.size(); i++) {
|
||
DesktopSortApp desktopSortApp = desktopSortApps.get(i);
|
||
desktopSortApp.setPosition(i);
|
||
}
|
||
|
||
return GsonUtils.toJSONString(desktopSortApps);
|
||
}
|
||
|
||
|
||
}
|