From aac6b98c9248c788b8f7fb3007ddd8dbe4b3168d Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Thu, 11 Jun 2026 01:20:13 +0800 Subject: [PATCH] =?UTF-8?q?build:=20=E7=94=B5=E8=84=91=E6=8D=9F=E5=9D=8F?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E7=9F=A5=E9=81=93=E6=94=B9=E4=BA=86=E5=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dialer/activity/app/AppListActivity.java | 88 +- .../dialer/activity/app/AppListViewModel.java | 35 + .../contact/list/ContactListActivity.java | 21 +- .../contact/list/ContactListViewModel.java | 33 + .../contact/test/ContactTestActivity.java | 10 + .../dialer/activity/main/MainActivity.java | 8 +- .../ttstd/dialer/adapter/AppGridAdapter.java | 102 ++- .../dialer/adapter/ContactInfoAdapter.java | 8 + .../ttstd/dialer/adapter/MoreAppAdapter.java | 105 ++- .../ttstd/dialer/db/contact/ContactDao.java | 3 + .../dialer/db/contact/ContactRepository.java | 4 + .../com/ttstd/dialer/manager/AppManager.java | 841 +++++++++++------- .../dialer/view/ItemTouchHelperCallback.java | 6 + app/src/main/res/layout/activity_app_list.xml | 20 +- app/src/main/res/layout/activity_main.xml | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 16 files changed, 914 insertions(+), 376 deletions(-) diff --git a/app/src/main/java/com/ttstd/dialer/activity/app/AppListActivity.java b/app/src/main/java/com/ttstd/dialer/activity/app/AppListActivity.java index 968a66c..0e3c33a 100644 --- a/app/src/main/java/com/ttstd/dialer/activity/app/AppListActivity.java +++ b/app/src/main/java/com/ttstd/dialer/activity/app/AppListActivity.java @@ -1,5 +1,6 @@ package com.ttstd.dialer.activity.app; +import android.view.DragEvent; import android.view.View; import androidx.lifecycle.Observer; @@ -8,6 +9,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.tencent.mmkv.MMKV; import com.ttstd.dialer.R; +import com.ttstd.dialer.adapter.AppGridAdapter; import com.ttstd.dialer.adapter.MoreAppAdapter; import com.ttstd.dialer.base.mvvm.BaseMvvmActivity; import com.ttstd.dialer.config.CommonConfig; @@ -15,6 +17,7 @@ import com.ttstd.dialer.config.LiveDataAction; import com.ttstd.dialer.databinding.ActivityAppListBinding; import com.ttstd.dialer.db.app.AppInfo; import com.ttstd.dialer.livedata.LiveDataBus; +import com.ttstd.dialer.utils.Logger; import java.util.List; @@ -24,6 +27,7 @@ public class AppListActivity extends BaseMvvmActivity= 5) { + android.widget.Toast.makeText(AppListActivity.this, "快捷栏最多只能添加5个应用", android.widget.Toast.LENGTH_SHORT).show(); + return true; + } + appInfo.setHotseat(1); + appInfo.setOutside(1); + appInfo.setHotseatPosition(mAppGridAdapter.getCount()); + mViewModel.updateAppInfo(appInfo); + } + return true; + } + return true; + } + }); + } + + @Override protected void initData() { LiveDataBus.get().on(LiveDataAction.ACTION_UPDATE_APPS) .observe(this, event -> { mViewModel.getDbAppList(); + mViewModel.getHotseatApp(); }); + mViewModel.mHotseatAppData.observe(this, new Observer>() { + @Override + public void onChanged(List appInfos) { + mViewDataBinding.gvApp.setNumColumns(appInfos.size()); + mAppGridAdapter.setAppInfos(appInfos); + } + }); + mViewModel.getHotseatApp(); mViewModel.mDesktopSortAppData.observe(this, new Observer>() { @Override @@ -84,7 +156,7 @@ public class AppListActivity extends BaseMvvmActivity 0) { - + mViewModel.getHotseatApp(); } mViewModel.getDbAppList(); } diff --git a/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java index 980ca60..db0d5e2 100644 --- a/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java +++ b/app/src/main/java/com/ttstd/dialer/activity/app/AppListViewModel.java @@ -33,6 +33,41 @@ public class AppListViewModel extends BaseViewModel> mHotseatAppData = new MutableLiveData<>(); + + public void getHotseatApp() { + Observable.fromCallable(new Callable>() { + @Override + public List call() throws Exception { + return mAppRepository.getHotseatApp(); + } + }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Logger.e("getHotseatApp", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull List appInfos) { + Logger.e("getHotseatApp", "onNext: " + appInfos); + mHotseatAppData.setValue(appInfos); + } + + @Override + public void onError(@NonNull Throwable e) { + Logger.e("getHotseatApp", "onError: " + e.getMessage()); + } + + @Override + public void onComplete() { + Logger.e("getHotseatApp", "onComplete: "); + } + }); + } + public MutableLiveData> mDesktopSortAppData = new MutableLiveData<>(); public void getDbAppList() { diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java index bfd92ec..bc356b3 100644 --- a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java +++ b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java @@ -77,15 +77,7 @@ public class ContactListActivity extends BaseMvvmActivity contactInfos) { + Logger.e(TAG, "updateContacts: " + contactInfos.size()); + Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable { + mRepository.update(contactInfos); + emitter.onNext(true); + emitter.onComplete(); + } + }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + } + + @Override + public void onNext(@NonNull Boolean aBoolean) { + } + + @Override + public void onError(@NonNull Throwable e) { + Logger.e("updateContacts", "onError: " + e.getMessage()); + } + + @Override + public void onComplete() { + Logger.e("updateContacts", "onComplete: "); + } + }); + } + public MutableLiveData mDeleteLiveData = new MutableLiveData<>(); public void deleteContact(long id) { diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java index cea1a39..f6c8f86 100644 --- a/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java +++ b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java @@ -79,6 +79,11 @@ public class ContactTestActivity extends BaseMvvmActivity { + boolean started = false; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + started = v.startDragAndDrop(data, shadowBuilder, appInfo, View.DRAG_FLAG_OPAQUE); + } else { + started = v.startDrag(data, shadowBuilder, appInfo, 0); + } + } catch (Exception e) { + Logger.e(TAG, "startDragAndDrop error: " + e.getMessage()); } - @Override - public void onNegativeClick() { - shortcutDialogFagment.dismiss(); + Logger.e(TAG, "startDragAndDrop result: " + started); + if (started) { + v.setAlpha(0.5f); } }); - shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment"); + + return true; + } + }); + + holder.root.setOnDragListener(new View.OnDragListener() { + @Override + public boolean onDrag(View v, DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + if (event.getLocalState() == appInfo) { + v.setAlpha(0.5f); + return true; + } + return false; + case DragEvent.ACTION_DRAG_ENDED: + v.setAlpha(1.0f); + return true; + } return false; } }); @@ -133,6 +159,34 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC return convertView; } + private static class LauncherDragShadowBuilder extends View.DragShadowBuilder { + public LauncherDragShadowBuilder(View view) { + super(view); + } + + @Override + public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { + float scale = 1.2f; + int width = (int) (getView().getWidth() * scale); + int height = (int) (getView().getHeight() * scale); + outShadowSize.set(width, height); + outShadowTouchPoint.set(width / 2, height / 2); + } + + @Override + public void onDrawShadow(Canvas canvas) { + float scale = 1.2f; + canvas.save(); + canvas.scale(scale, scale); + // 确保在绘制阴影时视图是不透明的 + float originalAlpha = getView().getAlpha(); + getView().setAlpha(1.0f); + getView().draw(canvas); + getView().setAlpha(originalAlpha); + canvas.restore(); + } + } + @NonNull @NotNull @Override diff --git a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java index 1b29803..07cdbfc 100644 --- a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java +++ b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java @@ -42,6 +42,8 @@ public class ContactInfoAdapter extends RecyclerView.Adapter { + boolean started = false; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // 修复:移除 DRAG_FLAG_GLOBAL 以解决某些设备上的 IllegalStateException + // 增加 DRAG_FLAG_OPAQUE 提升性能 + started = v.startDragAndDrop(data, shadowBuilder, appInfo, View.DRAG_FLAG_OPAQUE); + } else { + started = v.startDrag(data, shadowBuilder, appInfo, 0); + } + } catch (Exception e) { + Logger.e(TAG, "startDragAndDrop error: " + e.getMessage()); } - @Override - public void onNegativeClick() { - shortcutDialogFagment.dismiss(); + Logger.e(TAG, "startDragAndDrop result: " + started); + if (started) { + v.setAlpha(0.5f); } }); - shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment"); + + return true; + } + }); + + holder.root.setOnDragListener(new View.OnDragListener() { + @Override + public boolean onDrag(View v, DragEvent event) { + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + if (event.getLocalState() == appInfo) { + v.setAlpha(0.5f); + return true; + } + return false; + case DragEvent.ACTION_DRAG_ENDED: + v.setAlpha(1.0f); + return true; + } return false; } }); } + private static class LauncherDragShadowBuilder extends View.DragShadowBuilder { + public LauncherDragShadowBuilder(View view) { + super(view); + } + + @Override + public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { + float scale = 1.2f; + int width = (int) (getView().getWidth() * scale); + int height = (int) (getView().getHeight() * scale); + outShadowSize.set(width, height); + outShadowTouchPoint.set(width / 2, height / 2); + } + + @Override + public void onDrawShadow(Canvas canvas) { + float scale = 1.2f; + canvas.save(); + canvas.scale(scale, scale); + // 确保在绘制阴影时视图是不透明的 + float originalAlpha = getView().getAlpha(); + getView().setAlpha(1.0f); + getView().draw(canvas); + getView().setAlpha(originalAlpha); + canvas.restore(); + } + } + @Override public int getItemCount() { return mAppInfos == null ? 0 : mAppInfos.size(); diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactDao.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactDao.java index ade434e..9dc6de4 100644 --- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactDao.java +++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactDao.java @@ -24,6 +24,9 @@ public interface ContactDao { @Update int update(ContactInfo contactInfo); + @Update + void update(List contactInfos); + @Delete int delete(ContactInfo contactInfo); diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java index 877196a..aaea0be 100644 --- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java +++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java @@ -46,6 +46,10 @@ public class ContactRepository { return mContactDao.update(contactInfo); } + public void update(List contactInfos) { + mContactDao.update(contactInfos); + } + // 删除联系人 public int delete(ContactInfo contactInfo) { return mContactDao.delete(contactInfo); diff --git a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java index 2a14b6f..aaf7577 100644 --- a/app/src/main/java/com/ttstd/dialer/manager/AppManager.java +++ b/app/src/main/java/com/ttstd/dialer/manager/AppManager.java @@ -56,14 +56,14 @@ public class AppManager { 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); + MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE); @SuppressLint("StaticFieldLeak") private static volatile AppManager INSTANCE; - private Context mContext; - private PackageManager mPackageManager; + Context mContext; + PackageManager mPackageManager; - private AppRepository mAppRepository; + AppRepository mAppRepository; private Set mProgressCallbacks = new CopyOnWriteArraySet<>(); @@ -142,46 +142,216 @@ public class AppManager { // 通知进度回调 private void notifyProgress(String step, int current, int total) { - mProgressCallbacks.forEach(callback -> callback.onProgress(step, current, total)); + for (ProgressCallback callback : mProgressCallbacks) { + callback.onProgress(step, current, total); + } } // 通知完成回调 private void notifyCompleted(boolean success, String message) { - mProgressCallbacks.forEach(callback -> callback.onCompleted(success, message)); + for (ProgressCallback callback : mProgressCallbacks) { + callback.onCompleted(success, message); + } } // 异步执行应用列表处理流程 public void executeAppListProcessing() { - CompletableFuture - .supplyAsync(this::getAllDesktopSortApps, ASYNC_EXECUTOR) - .thenComposeAsync(desktopApps -> { - // 根据应用列表是否为空,选择不同处理流程 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + V26Helper.executeAppListProcessing(this); + } else { + executeAppListProcessingLegacy(); + } + } + + private void executeAppListProcessingLegacy() { + ASYNC_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + try { + List desktopApps = getAllDesktopSortApps(); if (desktopApps.isEmpty()) { - // 首次初始化流程:首次数据加载 -> 更新位置 - return processFirstTimeApps() - .thenComposeAsync(unused -> { - notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST); - return updatePosition(); - }, ASYNC_EXECUTOR); + processFirstTimeAppsSync(); + notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST); + updatePositionSync(); } 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); + processUninstalledAppsSync(desktopApps); + notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL); + processNewAppsSync(); + notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL); + updatePositionSync(); } - }, ASYNC_EXECUTOR) - .thenRun(() -> notifyCompleted(true, "应用列表处理完成")) - .exceptionally(throwable -> { - Logger.e(TAG, "异步处理失败", throwable); - notifyCompleted(false, "处理失败: " + throwable.getMessage()); - return null; - }); + notifyCompleted(true, "应用列表处理完成"); + } catch (Exception e) { + Logger.e(TAG, "异步处理失败", e); + notifyCompleted(false, "处理失败: " + e.getMessage()); + } + } + }); + } + + // 同步版本的处理逻辑,用于低版本适配 + private void processFirstTimeAppsSync() { + notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST); + Logger.i(TAG, "进入首次应用数据初始化流程(Legacy)"); + + try { + List allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext); + if (allLauncherApps.isEmpty()) { + Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化"); + return; + } + + List allFirstApps = new ArrayList<>(); + for (ResolveInfo ri : allLauncherApps) { + if (ri != null) { + AppInfo appInfo = resolveInfoToDesktopApp(ri); + if (appInfo != null) { + allFirstApps.add(appInfo); + } + } + } + + Collections.sort(allFirstApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + boolean isSystem1 = ApkUtils.isSystemApp(mContext, o1.getPackageName()); + boolean isSystem2 = ApkUtils.isSystemApp(mContext, o2.getPackageName()); + if (isSystem1 != isSystem2) { + return isSystem2 ? 1 : -1; + } + return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel()); + } + }); + + int hotseatCounter = 0; + for (int i = 0; i < allFirstApps.size(); i++) { + AppInfo appInfo = allFirstApps.get(i); + 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++); + } else if (DEFAULT_CONTACT_PACKAGE.contains(appInfo.getPackageName())) { + appInfo.setHotseat(1); + appInfo.setHotseatPosition(hotseatCounter++); + } else if (DEFAULT_MESSAGE_PACKAGE.contains(appInfo.getPackageName())) { + appInfo.setHotseat(1); + appInfo.setHotseatPosition(hotseatCounter++); + } else if (DEFAULT_SETTINGS_PACKAGE.contains(appInfo.getPackageName())) { + appInfo.setHotseat(1); + appInfo.setHotseatPosition(hotseatCounter++); + } else { + appInfo.setHotseat(0); + appInfo.setHotseatPosition(0); + } + appInfo.setPosition(i); + } + + for (AppInfo app : allFirstApps) { + try { + mAppRepository.insert(app); + } catch (Exception e) { + Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e); + } + } + Logger.i(TAG, "首次初始化完成(Legacy),插入默认应用: " + allFirstApps.size() + " 个"); + } catch (Exception e) { + Logger.e(TAG, "首次应用数据初始化失败", e); + throw new RuntimeException("首次初始化失败", e); + } + } + + private void processUninstalledAppsSync(List appInfos) { + notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL); + if (appInfos.isEmpty()) { + Logger.w(TAG, "桌面应用列表为空,跳过删除处理"); + return; + } + + for (AppInfo app : appInfos) { + if (app != null && !ApkUtils.isInstalled(mContext, app.getPackageName())) { + try { + int id = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName()); + if (id > 0) { + mAppRepository.deleteById(id); + } + } catch (Exception e) { + Logger.e(TAG, "处理未安装应用失败: " + app.getPackageName(), e); + } + } + } + } + + private void processNewAppsSync() { + try { + List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext); + if (resolveInfos.isEmpty()) { + Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理"); + return; + } + + List newApps = new ArrayList<>(); + for (ResolveInfo ri : resolveInfos) { + if (ri != null) { + AppInfo app = resolveInfoToDesktopApp(ri); + if (app != null && ApkUtils.isInstalled(mContext, app.getPackageName()) && !isAppExists(app)) { + newApps.add(app); + } + } + } + + Collections.sort(newApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel()); + } + }); + + if (!newApps.isEmpty()) { + int totalCount = mAppRepository.getTotalCount(); + for (int i = 0; i < newApps.size(); i++) { + AppInfo app = newApps.get(i); + app.setPosition(totalCount + i); + try { + mAppRepository.insert(app); + } catch (Exception e) { + Logger.e(TAG, "插入新应用失败: " + app.getPackageName(), e); + } + } + } + } catch (Exception e) { + Logger.e(TAG, "处理新应用时发生异常", e); + } + } + + private void updatePositionSync() { + try { + List appInfos = getAllDesktopSortApps(); + if (appInfos.isEmpty()) return; + + Collections.sort(appInfos, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Integer.compare(o1.getPosition(), o2.getPosition()); + } + }); + + for (int i = 0; i < appInfos.size(); i++) { + AppInfo app = appInfos.get(i); + app.setPosition(i); + try { + mAppRepository.update(app); + } catch (Exception e) { + Logger.e(TAG, "更新应用位置失败: " + app.getPackageName(), e); + } + } + } catch (Exception e) { + Logger.e(TAG, "更新应用位置时发生异常", e); + } } // 第一步:获取所有桌面应用(空值安全处理) @@ -195,188 +365,8 @@ public class AppManager { } } - // 首次初始化应用数据(针对首次获取为空的场景) - 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) { + AppInfo resolveInfoToDesktopApp(ResolveInfo resolveInfo) { ComponentName component = new ComponentName( resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name @@ -385,7 +375,7 @@ public class AppManager { } // 工具方法:检查应用是否已存在 - private boolean isAppExists(AppInfo app) { + boolean isAppExists(AppInfo app) { try { return mAppRepository.checkAppInfoExists(app.getPackageName(), app.getClassName()) > 0; } catch (Exception e) { @@ -395,8 +385,13 @@ public class AppManager { } // 工具方法:获取应用排序器(统一排序逻辑) - private Comparator getAppComparator() { - return Comparator.comparing(AppInfo::getLabel, Collator.getInstance(Locale.CHINESE)); + Comparator getAppComparator() { + return new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel()); + } + }; } public void updateApp(String packageName) { @@ -408,27 +403,23 @@ public class AppManager { 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()); + for (ResolveInfo ri : resolveInfos) { + AppInfo app = resolveInfoToDesktopApp(ri); + if (app != null) { + 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); + if (existingId <= 0) { + 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"); } @@ -438,21 +429,25 @@ public class AppManager { 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()); + List idsToDelete = new ArrayList<>(); + for (AppInfo app : allApps) { + if (packageName.equals(app.getPackageName())) { + int id = app.getId(); + if (id > 0) { + idsToDelete.add(id); + } + } + } if (!idsToDelete.isEmpty()) { - idsToDelete.forEach(id -> { + for (Integer id : idsToDelete) { 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, "数据库中未找到该应用的记录"); @@ -469,34 +464,58 @@ public class AppManager { // 重构getAllApp方法,复用现有处理逻辑 public void refreshAllApps() { - CompletableFuture.runAsync(() -> { - List allApps = getAllDesktopSortApps(); - if (allApps.isEmpty()) { - processFirstTimeApps().join(); - } else { - processUninstalledApps(allApps).join(); - processNewApps().join(); - } - updatePosition().join(); - }, ASYNC_EXECUTOR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + V26Helper.refreshAllApps(this); + } else { + ASYNC_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + List allApps = getAllDesktopSortApps(); + if (allApps.isEmpty()) { + processFirstTimeAppsSync(); + } else { + processUninstalledAppsSync(allApps); + processNewAppsSync(); + } + updatePositionSync(); + } + }); + } } 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)); + List defaultApps = new LinkedList<>(); + for (ResolveInfo ri : resolveInfos) { + if (DEFAULT_APP_PACKAGES.contains(ri.activityInfo.packageName)) { + ComponentName component = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name); + defaultApps.add(new AppInfo(mContext, component)); + } + } - IntStream.range(0, defaultApps.size()) - .forEach(i -> defaultApps.get(i).setPosition(i)); + Collections.sort(defaultApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel()); + } + }); + + Collections.sort(defaultApps, new Comparator() { + @Override + public int compare(AppInfo a, AppInfo b) { + boolean isSystemA = ApkUtils.isSystemApp(mContext, a.getPackageName()); + boolean isSystemB = ApkUtils.isSystemApp(mContext, b.getPackageName()); + if (isSystemA != isSystemB) { + return isSystemB ? 1 : -1; + } + return 0; + } + }); + + for (int i = 0; i < defaultApps.size(); i++) { + defaultApps.get(i).setPosition(i); + } return GsonUtils.toJSONString(defaultApps); } @@ -506,19 +525,12 @@ public class AppManager { }}; 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) { + List apkInstalledInfos = new ArrayList<>(); + + for (PackageInfo packageInfo : installedApps) { + String packageName = packageInfo.packageName; + if (!ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName)) { ApkInstalledInfo apkInstalledInfo = new ApkInstalledInfo(); apkInstalledInfo.setPackageName(packageInfo.packageName); apkInstalledInfo.setAppName(packageInfo.applicationInfo.loadLabel(mPackageManager).toString()); @@ -531,33 +543,260 @@ public class AppManager { 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()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + StorageStatsHelper.fillStats(mContext, packageInfo, apkInstalledInfo); } - 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))); + File publicSourceFile = new File(packageInfo.applicationInfo.publicSourceDir); + if (publicSourceFile.exists()) { + apkInstalledInfo.setMd5(HashUtils.getFileMd5(publicSourceFile)); + } apkInstalledInfo.setSystemApp(ApkUtils.isSystemApp(mContext, packageInfo.packageName)); - return apkInstalledInfo; + apkInstalledInfos.add(apkInstalledInfo); } - }).collect(Collectors.toList()); + } return apkInstalledInfos; } -} \ No newline at end of file + + private static class StorageStatsHelper { + @SuppressLint("NewApi") + static void fillStats(Context context, PackageInfo packageInfo, ApkInstalledInfo apkInstalledInfo) { + try { + StorageStatsManager ssm = (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE); + if (ssm != null) { + StorageStats stats = ssm.queryStatsForPackage( + packageInfo.applicationInfo.storageUuid, + packageInfo.packageName, + android.os.Process.myUserHandle() + ); + apkInstalledInfo.setApkSize(stats.getAppBytes()); + apkInstalledInfo.setDataSize(stats.getDataBytes()); + apkInstalledInfo.setCacheSize(stats.getCacheBytes()); + } + } catch (Exception e) { + Log.e(TAG, "getApkInstallInfos stats error: " + e.getMessage()); + } + } + } + + private static class V26Helper { + @SuppressLint("NewApi") + static void executeAppListProcessing(final AppManager manager) { + CompletableFuture.supplyAsync(new java.util.function.Supplier>() { + @Override + public List get() { + return manager.getAllDesktopSortApps(); + } + }, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function, CompletableFuture>() { + @Override + public CompletableFuture apply(final List desktopApps) { + if (desktopApps.isEmpty()) { + return processFirstTimeApps(manager).thenComposeAsync(new java.util.function.Function>() { + @Override + public CompletableFuture apply(Void unused) { + manager.notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST); + return updatePosition(manager); + } + }, ASYNC_EXECUTOR); + } else { + return processUninstalledApps(manager, desktopApps).thenComposeAsync(new java.util.function.Function>() { + @Override + public CompletableFuture apply(Void unused) { + manager.notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL); + return processNewApps(manager); + } + }, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function>() { + @Override + public CompletableFuture apply(Void unused) { + manager.notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL); + return updatePosition(manager); + } + }, ASYNC_EXECUTOR); + } + } + }, ASYNC_EXECUTOR).thenRun(new Runnable() { + @Override + public void run() { + manager.notifyCompleted(true, "应用列表处理完成"); + } + }).exceptionally(new java.util.function.Function() { + @Override + public Void apply(Throwable throwable) { + Logger.e(TAG, "异步处理失败", throwable); + manager.notifyCompleted(false, "处理失败: " + throwable.getMessage()); + return null; + } + }); + } + + @SuppressLint("NewApi") + static CompletableFuture processFirstTimeApps(final AppManager manager) { + return CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + manager.notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST); + Logger.i(TAG, "进入首次应用数据初始化流程"); + try { + List allLauncherApps = ApkUtils.getAllLauncherResolveInfo(manager.mContext); + if (allLauncherApps.isEmpty()) { + Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化"); + return; + } + + List allFirstApps = allLauncherApps.stream() + .filter(Objects::nonNull) + .map(manager::resolveInfoToDesktopApp) + .filter(Objects::nonNull) + .sorted((o1, o2) -> Boolean.compare(ApkUtils.isSystemApp(manager.mContext, o2.getPackageName()), ApkUtils.isSystemApp(manager.mContext, o1.getPackageName()))) + .sorted(manager.getAppComparator()) + .collect(Collectors.toList()); + + final int[] hotseatCounter = {0}; + IntStream.range(0, allFirstApps.size()).forEach(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 { + manager.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); + } + + @SuppressLint("NewApi") + static CompletableFuture processUninstalledApps(final AppManager manager, final List appInfos) { + return CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + manager.notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL); + if (appInfos.isEmpty()) return; + + List ids = appInfos.stream() + .filter(Objects::nonNull) + .filter(app -> !ApkUtils.isInstalled(manager.mContext, app.getPackageName())) + .map(app -> { + try { + return manager.mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName()); + } catch (Exception e) { + return -1; + } + }) + .filter(id -> id > 0) + .collect(Collectors.toList()); + + if (!ids.isEmpty()) { + ids.forEach(id -> { + try { + manager.mAppRepository.deleteById(id); + } catch (Exception e) {} + }); + } + } + }, ASYNC_EXECUTOR); + } + + @SuppressLint("NewApi") + static CompletableFuture processNewApps(final AppManager manager) { + return CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + try { + List resolveInfos = ApkUtils.getAllLauncherResolveInfo(manager.mContext); + if (resolveInfos.isEmpty()) return; + + List newApps = resolveInfos.stream() + .filter(Objects::nonNull) + .map(manager::resolveInfoToDesktopApp) + .filter(Objects::nonNull) + .filter(app -> ApkUtils.isInstalled(manager.mContext, app.getPackageName())) + .filter(manager::isAppExists) + .sorted(manager.getAppComparator()) + .collect(Collectors.toList()); + + if (!newApps.isEmpty()) { + int count = manager.mAppRepository.getTotalCount(); + for (int i = 0; i < newApps.size(); i++) { + AppInfo app = newApps.get(i); + app.setPosition(count + i); + manager.mAppRepository.insert(app); + } + } + } catch (Exception e) {} + } + }, ASYNC_EXECUTOR); + } + + @SuppressLint("NewApi") + static CompletableFuture updatePosition(final AppManager manager) { + return CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + try { + List appInfos = manager.getAllDesktopSortApps(); + if (appInfos.isEmpty()) return; + + List sortedApps = appInfos.stream() + .filter(Objects::nonNull) + .sorted(Comparator.comparingInt(AppInfo::getPosition)) + .collect(Collectors.toList()); + + for (int i = 0; i < sortedApps.size(); i++) { + AppInfo app = sortedApps.get(i); + app.setPosition(i); + manager.mAppRepository.update(app); + } + } catch (Exception e) {} + } + }, ASYNC_EXECUTOR); + } + + @SuppressLint("NewApi") + static void refreshAllApps(final AppManager manager) { + CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + List allApps = manager.getAllDesktopSortApps(); + if (allApps.isEmpty()) { + processFirstTimeApps(manager).join(); + } else { + processUninstalledApps(manager, allApps).join(); + processNewApps(manager).join(); + } + updatePosition(manager).join(); + } + }, ASYNC_EXECUTOR); + } + } +} diff --git a/app/src/main/java/com/ttstd/dialer/view/ItemTouchHelperCallback.java b/app/src/main/java/com/ttstd/dialer/view/ItemTouchHelperCallback.java index b9bfb47..f8dad62 100644 --- a/app/src/main/java/com/ttstd/dialer/view/ItemTouchHelperCallback.java +++ b/app/src/main/java/com/ttstd/dialer/view/ItemTouchHelperCallback.java @@ -51,4 +51,10 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback { public boolean isItemViewSwipeEnabled() { return false; } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + mAdapter.onItemMoveFinished(); + } } diff --git a/app/src/main/res/layout/activity_app_list.xml b/app/src/main/res/layout/activity_app_list.xml index d9ade39..6a1c248 100644 --- a/app/src/main/res/layout/activity_app_list.xml +++ b/app/src/main/res/layout/activity_app_list.xml @@ -48,13 +48,31 @@ + + + + + + app:layout_constraintTop_toBottomOf="@+id/cl_hotseat" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index aa303f9..5225a24 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -38,6 +38,8 @@ android:id="@+id/cl_hotseat" android:layout_width="match_parent" android:layout_height="@dimen/dp_100" + android:layout_marginHorizontal="@dimen/dp_8" + android:background="@drawable/main_hotseat_background" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> @@ -46,8 +48,6 @@ android:id="@+id/gv_app" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginHorizontal="@dimen/dp_8" - android:background="@drawable/main_hotseat_background" android:numColumns="auto_fit" android:orientation="horizontal" /> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c46977f..c09bdf5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip