build: 电脑损坏,不知道改了啥
This commit is contained in:
@@ -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<AppListViewModel, Activity
|
||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||
|
||||
private MoreAppAdapter mMoreAppAdapter;
|
||||
private AppGridAdapter mAppGridAdapter;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -49,27 +53,95 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
mAppGridAdapter = new AppGridAdapter(this, LoaderManager.getInstance(this));
|
||||
mViewDataBinding.gvApp.setAdapter(mAppGridAdapter);
|
||||
|
||||
|
||||
mMoreAppAdapter = new MoreAppAdapter(LoaderManager.getInstance(this));
|
||||
mMoreAppAdapter.setShortcutCallback(new MoreAppAdapter.ShortcutCallback() {
|
||||
@Override
|
||||
public void setAppOutside(AppInfo appInfo) {
|
||||
appInfo.setOutside(1);
|
||||
mViewModel.updateAppInfo(appInfo);
|
||||
}
|
||||
});
|
||||
mViewDataBinding.recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
|
||||
mViewDataBinding.recyclerView.setAdapter(mMoreAppAdapter);
|
||||
|
||||
mViewDataBinding.recyclerView.setOnDragListener(new View.OnDragListener() {
|
||||
@Override
|
||||
public boolean onDrag(View v, DragEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
return true;
|
||||
case DragEvent.ACTION_DRAG_ENTERED:
|
||||
v.setAlpha(0.7f);
|
||||
break;
|
||||
case DragEvent.ACTION_DRAG_EXITED:
|
||||
case DragEvent.ACTION_DRAG_ENDED:
|
||||
v.setAlpha(1.0f);
|
||||
break;
|
||||
case DragEvent.ACTION_DROP:
|
||||
AppInfo appInfo = (AppInfo) event.getLocalState();
|
||||
if (appInfo != null && appInfo.getHotseat() == 1) {
|
||||
appInfo.setHotseat(0);
|
||||
appInfo.setOutside(0);
|
||||
appInfo.setPosition(mMoreAppAdapter.getItemCount());
|
||||
mViewModel.updateAppInfo(appInfo);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mViewDataBinding.clHotseat.setOnDragListener(new View.OnDragListener() {
|
||||
@Override
|
||||
public boolean onDrag(View v, DragEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case DragEvent.ACTION_DRAG_STARTED:
|
||||
return true;
|
||||
case DragEvent.ACTION_DRAG_ENTERED:
|
||||
v.setAlpha(0.7f);
|
||||
break;
|
||||
case DragEvent.ACTION_DRAG_EXITED:
|
||||
case DragEvent.ACTION_DRAG_ENDED:
|
||||
v.setAlpha(1.0f);
|
||||
break;
|
||||
case DragEvent.ACTION_DROP:
|
||||
AppInfo appInfo = (AppInfo) event.getLocalState();
|
||||
if (appInfo != null && appInfo.getHotseat() == 0) {
|
||||
// 检查数量限制
|
||||
if (mAppGridAdapter.getCount() >= 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().<String>on(LiveDataAction.ACTION_UPDATE_APPS)
|
||||
.observe(this, event -> {
|
||||
mViewModel.getDbAppList();
|
||||
mViewModel.getHotseatApp();
|
||||
});
|
||||
mViewModel.mHotseatAppData.observe(this, new Observer<List<AppInfo>>() {
|
||||
@Override
|
||||
public void onChanged(List<AppInfo> appInfos) {
|
||||
mViewDataBinding.gvApp.setNumColumns(appInfos.size());
|
||||
mAppGridAdapter.setAppInfos(appInfos);
|
||||
}
|
||||
});
|
||||
mViewModel.getHotseatApp();
|
||||
|
||||
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<AppInfo>>() {
|
||||
@Override
|
||||
@@ -84,7 +156,7 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
|
||||
@Override
|
||||
public void onChanged(Integer integer) {
|
||||
if (integer > 0) {
|
||||
|
||||
mViewModel.getHotseatApp();
|
||||
}
|
||||
mViewModel.getDbAppList();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,41 @@ public class AppListViewModel extends BaseViewModel<ActivityAppListBinding, Acti
|
||||
mAppRepository = new AppRepository(context);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<AppInfo>> mHotseatAppData = new MutableLiveData<>();
|
||||
|
||||
public void getHotseatApp() {
|
||||
Observable.fromCallable(new Callable<List<AppInfo>>() {
|
||||
@Override
|
||||
public List<AppInfo> call() throws Exception {
|
||||
return mAppRepository.getHotseatApp();
|
||||
}
|
||||
}).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<List<AppInfo>>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
Logger.e("getHotseatApp", "onSubscribe: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(@NonNull List<AppInfo> 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<List<AppInfo>> mDesktopSortAppData = new MutableLiveData<>();
|
||||
|
||||
public void getDbAppList() {
|
||||
|
||||
@@ -77,15 +77,7 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
|
||||
mContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
|
||||
@Override
|
||||
public void onItemMove(int fromPosition, int toPosition) {
|
||||
Logger.e(TAG, "onItemMove: ");
|
||||
ContactInfo fromContactInfo = mContactInfos.get(fromPosition);
|
||||
int fromContactPosition = fromContactInfo.getPosition();
|
||||
ContactInfo toContactInfo = mContactInfos.get(toPosition);
|
||||
int toContactPosition = toContactInfo.getPosition();
|
||||
fromContactInfo.setPosition(toContactPosition);
|
||||
mViewModel.updateItemPosition(fromContactInfo);
|
||||
toContactInfo.setPosition(fromContactPosition);
|
||||
mViewModel.updateItemPosition(toContactInfo);
|
||||
Logger.e(TAG, "onItemMove: from " + fromPosition + " to " + toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,6 +85,17 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
|
||||
Logger.e(TAG, "onItemRemoved: ");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMoveFinished() {
|
||||
Logger.e(TAG, "onItemMoveFinished: ");
|
||||
if (mContactInfos != null) {
|
||||
for (int i = 0; i < mContactInfos.size(); i++) {
|
||||
mContactInfos.get(i).setPosition(i + 1);
|
||||
}
|
||||
mViewModel.updateContacts(mContactInfos);
|
||||
}
|
||||
}
|
||||
});
|
||||
mContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||
@Override
|
||||
|
||||
@@ -131,6 +131,39 @@ public class ContactListViewModel extends BaseViewModel<ActivityContactListBindi
|
||||
});
|
||||
}
|
||||
|
||||
public void updateContacts(List<ContactInfo> contactInfos) {
|
||||
Logger.e(TAG, "updateContacts: " + contactInfos.size());
|
||||
Observable.create(new ObservableOnSubscribe<Boolean>() {
|
||||
@Override
|
||||
public void subscribe(@NonNull ObservableEmitter<Boolean> 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<Boolean>() {
|
||||
@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<Boolean> mDeleteLiveData = new MutableLiveData<>();
|
||||
|
||||
public void deleteContact(long id) {
|
||||
|
||||
@@ -79,6 +79,11 @@ public class ContactTestActivity extends BaseMvvmActivity<ContactTestViewModel,
|
||||
Logger.e(TAG, "mOnlineContactInfoAdapter onItemRemoved: ");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMoveFinished() {
|
||||
|
||||
}
|
||||
});
|
||||
mOnlineContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||
@Override
|
||||
@@ -126,6 +131,11 @@ public class ContactTestActivity extends BaseMvvmActivity<ContactTestViewModel,
|
||||
Logger.e(TAG, "mLocalContactInfoAdapter onItemRemoved: ");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemMoveFinished() {
|
||||
|
||||
}
|
||||
});
|
||||
mLocalContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||
@Override
|
||||
|
||||
@@ -188,12 +188,6 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
|
||||
mAppGridAdapter = new AppGridAdapter(this, LoaderManager.getInstance(this));
|
||||
mViewDataBinding.gvApp.setAdapter(mAppGridAdapter);
|
||||
mAppGridAdapter.setShortcutCallback(new AppGridAdapter.ShortcutCallback() {
|
||||
@Override
|
||||
public void setAppInside(AppInfo appInfo) {
|
||||
// 处理回调
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -266,6 +260,8 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
//修补autozie fragment item大小不一致
|
||||
Logger.e(TAG, "onResume: ");
|
||||
mViewModel.getOutsideApp();
|
||||
mViewModel.getHotseatApp();
|
||||
|
||||
if (wallpaperScrollHelper != null) {
|
||||
wallpaperScrollHelper.onResume();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.ttstd.dialer.adapter;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.DragEvent;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -21,7 +27,6 @@ import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.R;
|
||||
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;
|
||||
@@ -51,16 +56,6 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public interface ShortcutCallback {
|
||||
void setAppInside(AppInfo appInfo);
|
||||
}
|
||||
|
||||
private ShortcutCallback mShortcutCallback;
|
||||
|
||||
public void setShortcutCallback(ShortcutCallback shortcutCallback) {
|
||||
mShortcutCallback = shortcutCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mAppInfos == null ? 0 : mAppInfos.size();
|
||||
@@ -109,23 +104,54 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
|
||||
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
|
||||
shortcutDialogFagment.setTitile("温馨提示");
|
||||
shortcutDialogFagment.setTips("是否把这个应用移到“更多应用”里?");
|
||||
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
if (mShortcutCallback != null)
|
||||
mShortcutCallback.setAppInside(appInfo);
|
||||
shortcutDialogFagment.dismiss();
|
||||
if (!v.isAttachedToWindow()) {
|
||||
Logger.e(TAG, "View is not attached to window");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加触感反馈
|
||||
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
|
||||
ClipData data = ClipData.newPlainText("appInfo", appInfo.getPackageName());
|
||||
View.DragShadowBuilder shadowBuilder = new LauncherDragShadowBuilder(v);
|
||||
|
||||
// 使用 post 确保在当前事件处理完成后再开始拖拽,提高稳定性并减少崩溃
|
||||
v.post(() -> {
|
||||
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
|
||||
|
||||
@@ -42,6 +42,8 @@ public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.
|
||||
void onItemMove(int fromPosition, int toPosition);
|
||||
|
||||
void onItemRemoved(int position);
|
||||
|
||||
void onItemMoveFinished();
|
||||
}
|
||||
|
||||
public interface ClickListener {
|
||||
@@ -145,6 +147,12 @@ public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public void onItemMoveFinished() {
|
||||
if (mItemMoveCallback != null) {
|
||||
mItemMoveCallback.onItemMoveFinished();
|
||||
}
|
||||
}
|
||||
|
||||
public class ContactInfoHolder extends RecyclerView.ViewHolder {
|
||||
ConstraintLayout root, clOperation;
|
||||
NiceImageView nv_avatar;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package com.ttstd.dialer.adapter;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.DragEvent;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -21,7 +27,6 @@ import com.tencent.mmkv.MMKV;
|
||||
import com.ttstd.dialer.R;
|
||||
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;
|
||||
@@ -55,16 +60,6 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public interface ShortcutCallback {
|
||||
void setAppOutside(AppInfo appInfo);
|
||||
}
|
||||
|
||||
private ShortcutCallback mShortcutCallback;
|
||||
|
||||
public void setShortcutCallback(ShortcutCallback shortcutCallback) {
|
||||
mShortcutCallback = shortcutCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -73,9 +68,11 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
||||
return new AppHolder(LayoutInflater.from(mContext).inflate(R.layout.item_more_app, parent, false));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull @NotNull AppHolder holder, int position) {
|
||||
AppInfo appInfo = mAppInfos.get(position);
|
||||
holder.root.setAlpha(1.0f);
|
||||
Drawable drawable = mIconCacheManager.getIcon(appInfo.getComponentName().flattenToShortString());
|
||||
if (drawable != null) {
|
||||
holder.iv_icon.setImageDrawable(drawable);
|
||||
@@ -92,29 +89,89 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
||||
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
|
||||
shortcutDialogFagment.setTitile("温馨提示");
|
||||
shortcutDialogFagment.setTips("是否将应用放在桌面显示");
|
||||
if (!v.isAttachedToWindow()) {
|
||||
Logger.e(TAG, "View is not attached to window");
|
||||
return false;
|
||||
}
|
||||
|
||||
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
if (mShortcutCallback != null)
|
||||
mShortcutCallback.setAppOutside(appInfo);
|
||||
shortcutDialogFagment.dismiss();
|
||||
// 添加触感反馈
|
||||
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
|
||||
ClipData data = ClipData.newPlainText("appInfo", appInfo.getPackageName());
|
||||
View.DragShadowBuilder shadowBuilder = new LauncherDragShadowBuilder(v);
|
||||
|
||||
// 使用 post 确保在当前事件处理完成后再开始拖拽,提高稳定性并减少崩溃
|
||||
v.post(() -> {
|
||||
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();
|
||||
|
||||
@@ -24,6 +24,9 @@ public interface ContactDao {
|
||||
@Update
|
||||
int update(ContactInfo contactInfo);
|
||||
|
||||
@Update
|
||||
void update(List<ContactInfo> contactInfos);
|
||||
|
||||
@Delete
|
||||
int delete(ContactInfo contactInfo);
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ public class ContactRepository {
|
||||
return mContactDao.update(contactInfo);
|
||||
}
|
||||
|
||||
public void update(List<ContactInfo> contactInfos) {
|
||||
mContactDao.update(contactInfos);
|
||||
}
|
||||
|
||||
// 删除联系人
|
||||
public int delete(ContactInfo contactInfo) {
|
||||
return mContactDao.delete(contactInfo);
|
||||
|
||||
@@ -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<ProgressCallback> 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<AppInfo> 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<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||
if (allLauncherApps.isEmpty()) {
|
||||
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AppInfo> 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<AppInfo>() {
|
||||
@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<AppInfo> 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<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||
if (resolveInfos.isEmpty()) {
|
||||
Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AppInfo> 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<AppInfo>() {
|
||||
@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<AppInfo> appInfos = getAllDesktopSortApps();
|
||||
if (appInfos.isEmpty()) return;
|
||||
|
||||
Collections.sort(appInfos, new Comparator<AppInfo>() {
|
||||
@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<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) {
|
||||
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<AppInfo> getAppComparator() {
|
||||
return Comparator.comparing(AppInfo::getLabel, Collator.getInstance(Locale.CHINESE));
|
||||
Comparator<AppInfo> getAppComparator() {
|
||||
return new Comparator<AppInfo>() {
|
||||
@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<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());
|
||||
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<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());
|
||||
List<Integer> 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<AppInfo> 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<AppInfo> allApps = getAllDesktopSortApps();
|
||||
if (allApps.isEmpty()) {
|
||||
processFirstTimeAppsSync();
|
||||
} else {
|
||||
processUninstalledAppsSync(allApps);
|
||||
processNewAppsSync();
|
||||
}
|
||||
updatePositionSync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
List<AppInfo> 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<AppInfo>() {
|
||||
@Override
|
||||
public int compare(AppInfo o1, AppInfo o2) {
|
||||
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||
}
|
||||
});
|
||||
|
||||
Collections.sort(defaultApps, new Comparator<AppInfo>() {
|
||||
@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<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) {
|
||||
List<ApkInstalledInfo> 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;
|
||||
}
|
||||
}
|
||||
|
||||
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<List<AppInfo>>() {
|
||||
@Override
|
||||
public List<AppInfo> get() {
|
||||
return manager.getAllDesktopSortApps();
|
||||
}
|
||||
}, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function<List<AppInfo>, CompletableFuture<Void>>() {
|
||||
@Override
|
||||
public CompletableFuture<Void> apply(final List<AppInfo> desktopApps) {
|
||||
if (desktopApps.isEmpty()) {
|
||||
return processFirstTimeApps(manager).thenComposeAsync(new java.util.function.Function<Void, CompletableFuture<Void>>() {
|
||||
@Override
|
||||
public CompletableFuture<Void> 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<Void, CompletableFuture<Void>>() {
|
||||
@Override
|
||||
public CompletableFuture<Void> apply(Void unused) {
|
||||
manager.notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL);
|
||||
return processNewApps(manager);
|
||||
}
|
||||
}, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function<Void, CompletableFuture<Void>>() {
|
||||
@Override
|
||||
public CompletableFuture<Void> 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<Throwable, Void>() {
|
||||
@Override
|
||||
public Void apply(Throwable throwable) {
|
||||
Logger.e(TAG, "异步处理失败", throwable);
|
||||
manager.notifyCompleted(false, "处理失败: " + throwable.getMessage());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
static CompletableFuture<Void> 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<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(manager.mContext);
|
||||
if (allLauncherApps.isEmpty()) {
|
||||
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AppInfo> 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<Void> processUninstalledApps(final AppManager manager, final List<AppInfo> appInfos) {
|
||||
return CompletableFuture.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
manager.notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
|
||||
if (appInfos.isEmpty()) return;
|
||||
|
||||
List<Integer> 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<Void> processNewApps(final AppManager manager) {
|
||||
return CompletableFuture.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(manager.mContext);
|
||||
if (resolveInfos.isEmpty()) return;
|
||||
|
||||
List<AppInfo> 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<Void> updatePosition(final AppManager manager) {
|
||||
return CompletableFuture.runAsync(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
List<AppInfo> appInfos = manager.getAllDesktopSortApps();
|
||||
if (appInfos.isEmpty()) return;
|
||||
|
||||
List<AppInfo> 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<AppInfo> allApps = manager.getAllDesktopSortApps();
|
||||
if (allApps.isEmpty()) {
|
||||
processFirstTimeApps(manager).join();
|
||||
} else {
|
||||
processUninstalledApps(manager, allApps).join();
|
||||
processNewApps(manager).join();
|
||||
}
|
||||
updatePosition(manager).join();
|
||||
}
|
||||
}, ASYNC_EXECUTOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,31 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/cl_hotseat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/dp_100"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginHorizontal="@dimen/dp_8"
|
||||
android:background="@drawable/main_hotseat_background"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bar">
|
||||
|
||||
<GridView
|
||||
android:id="@+id/gv_app"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:numColumns="auto_fit"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bar" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/cl_hotseat" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user