增加app列表页面,app数据存入数据库,优化icon cache
This commit is contained in:
@@ -43,6 +43,10 @@
|
|||||||
android:name=".activity.contact.list.ContactListActivity"
|
android:name=".activity.contact.list.ContactListActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:screenOrientation="portrait" />
|
android:screenOrientation="portrait" />
|
||||||
|
<activity
|
||||||
|
android:name=".activity.app.AppListActivity"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.ttstd.dialer.activity.app;
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
|
||||||
|
import com.tencent.mmkv.MMKV;
|
||||||
|
import com.ttstd.dialer.R;
|
||||||
|
import com.ttstd.dialer.adapter.MoreAppAdapter;
|
||||||
|
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||||
|
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||||||
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
|
import com.ttstd.dialer.databinding.ActivityAppListBinding;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AppListActivity extends BaseMvvmActivity<AppListViewModel, ActivityAppListBinding> {
|
||||||
|
private static final String TAG = "AppListActivity";
|
||||||
|
|
||||||
|
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
|
private MoreAppAdapter mMoreAppAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setNightMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setfitWindow() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getLayoutId() {
|
||||||
|
return R.layout.activity_app_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initDataBinding() {
|
||||||
|
mViewModel.setContext(this);
|
||||||
|
mViewModel.setVDBinding(mViewDataBinding);
|
||||||
|
mViewModel.setLifecycle(getLifecycleSubject());
|
||||||
|
mViewDataBinding.setClick(new BtnClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initView() {
|
||||||
|
mMoreAppAdapter = new MoreAppAdapter(LoaderManager.getInstance(this));
|
||||||
|
mViewDataBinding.recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
|
||||||
|
mViewDataBinding.recyclerView.setAdapter(mMoreAppAdapter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initData() {
|
||||||
|
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<DesktopSortApp> desktopSortApps) {
|
||||||
|
mMoreAppAdapter.setDesktopSortApps(desktopSortApps);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// mViewModel.getLauncherAppList();
|
||||||
|
mViewModel.getDbAppList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class BtnClick {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.ttstd.dialer.activity.app;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.trello.rxlifecycle4.RxLifecycle;
|
||||||
|
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||||
|
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||||
|
import com.ttstd.dialer.db.app.AppRepository;
|
||||||
|
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||||||
|
import com.ttstd.dialer.databinding.ActivityAppListBinding;
|
||||||
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
|
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.annotations.NonNull;
|
||||||
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
import io.reactivex.rxjava3.core.Observer;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.functions.Function;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class AppListViewModel extends BaseViewModel<ActivityAppListBinding, ActivityEvent> {
|
||||||
|
private static final String TAG = "AppListViewModel";
|
||||||
|
|
||||||
|
private AppRepository mAppRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(Context context) {
|
||||||
|
super.setContext(context);
|
||||||
|
mAppRepository = new AppRepository(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<List<DesktopSortApp>> mDesktopSortAppData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
public void getDbAppList(){
|
||||||
|
Observable.fromCallable(new Callable<List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public List<DesktopSortApp> call() throws Exception {
|
||||||
|
return mAppRepository.getAllContacts();
|
||||||
|
}
|
||||||
|
}) .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
Log.e("getDbAppList", "onSubscribe: " );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull List<DesktopSortApp> desktopSortApps) {
|
||||||
|
Log.e("getDbAppList", "onNext: "+desktopSortApps );
|
||||||
|
mDesktopSortAppData.setValue(desktopSortApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
Log.e("getDbAppList", "onError: " +e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Log.e("getDbAppList", "onComplete: " );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getLauncherAppList() {
|
||||||
|
Observable.fromCallable(new Callable<List<ResolveInfo>>() {
|
||||||
|
@Override
|
||||||
|
public List<ResolveInfo> call() throws Exception {
|
||||||
|
return ApkUtils.getAllLauncherResolveInfo(getSafeContext());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map(new Function<List<ResolveInfo>, List<ComponentName>>() {
|
||||||
|
@Override
|
||||||
|
public List<ComponentName> apply(List<ResolveInfo> resolveInfos) throws Throwable {
|
||||||
|
List<ComponentName> componentNames = resolveInfos.stream().map(new java.util.function.Function<ResolveInfo, ComponentName>() {
|
||||||
|
@Override
|
||||||
|
public ComponentName apply(ResolveInfo resolveInfo) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
String className = resolveInfo.activityInfo.name;
|
||||||
|
ComponentName componentName = new ComponentName(packageName, className);
|
||||||
|
return componentName;
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
return componentNames;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map(new Function<List<ComponentName>, List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public List<DesktopSortApp> apply(List<ComponentName> componentNames) throws Throwable {
|
||||||
|
List<DesktopSortApp> desktopSortApps = componentNames.stream().map(new java.util.function.Function<ComponentName, DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public DesktopSortApp apply(ComponentName componentName) {
|
||||||
|
DesktopSortApp desktopSortApp = new DesktopSortApp(getSafeContext(), componentName);
|
||||||
|
return desktopSortApp;
|
||||||
|
}
|
||||||
|
}).sorted(new Comparator<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
return desktopSortApps;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull List<DesktopSortApp> desktopSortApps) {
|
||||||
|
Log.e(TAG, "getLauncherAppList" + "onNext: " + desktopSortApps);
|
||||||
|
mDesktopSortAppData.setValue(desktopSortApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initData() {
|
protected void initData() {
|
||||||
|
mViewModel.updateAppList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,9 +1,112 @@
|
|||||||
package com.ttstd.dialer.activity.main;
|
package com.ttstd.dialer.activity.main;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.trello.rxlifecycle4.RxLifecycle;
|
||||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||||
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
import com.ttstd.dialer.base.mvvm.BaseViewModel;
|
||||||
import com.ttstd.dialer.databinding.ActivityMainBinding;
|
import com.ttstd.dialer.databinding.ActivityMainBinding;
|
||||||
|
import com.ttstd.dialer.db.app.AppRepository;
|
||||||
|
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||||||
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
|
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.annotations.NonNull;
|
||||||
|
import io.reactivex.rxjava3.core.Observable;
|
||||||
|
import io.reactivex.rxjava3.core.Observer;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
import io.reactivex.rxjava3.functions.Function;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEvent> {
|
public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEvent> {
|
||||||
|
private static final String TAG = "MainViewModel";
|
||||||
|
|
||||||
|
|
||||||
|
private AppRepository mAppRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setContext(Context context) {
|
||||||
|
super.setContext(context);
|
||||||
|
mAppRepository = new AppRepository(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateAppList() {
|
||||||
|
Observable.fromCallable(new Callable<List<ResolveInfo>>() {
|
||||||
|
@Override
|
||||||
|
public List<ResolveInfo> call() throws Exception {
|
||||||
|
return ApkUtils.getAllLauncherResolveInfo(getSafeContext());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map(new Function<List<ResolveInfo>, List<ComponentName>>() {
|
||||||
|
@Override
|
||||||
|
public List<ComponentName> apply(List<ResolveInfo> resolveInfos) throws Throwable {
|
||||||
|
List<ComponentName> componentNames = resolveInfos.stream().map(new java.util.function.Function<ResolveInfo, ComponentName>() {
|
||||||
|
@Override
|
||||||
|
public ComponentName apply(ResolveInfo resolveInfo) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
String className = resolveInfo.activityInfo.name;
|
||||||
|
ComponentName componentName = new ComponentName(packageName, className);
|
||||||
|
return componentName;
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
return componentNames;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map(new Function<List<ComponentName>, List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public List<DesktopSortApp> apply(List<ComponentName> componentNames) throws Throwable {
|
||||||
|
List<DesktopSortApp> desktopSortApps = componentNames.stream().map(new java.util.function.Function<ComponentName, DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public DesktopSortApp apply(ComponentName componentName) {
|
||||||
|
DesktopSortApp desktopSortApp = new DesktopSortApp(getSafeContext(), componentName);
|
||||||
|
return desktopSortApp;
|
||||||
|
}
|
||||||
|
}).sorted(new Comparator<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
return desktopSortApps;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<List<DesktopSortApp>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull List<DesktopSortApp> desktopSortApps) {
|
||||||
|
Log.e(TAG, "getLauncherAppList" + "onNext: " + desktopSortApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java
Normal file
122
app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package com.ttstd.dialer.adapter;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.loader.app.LoaderManager;
|
||||||
|
import androidx.loader.content.Loader;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.tencent.mmkv.MMKV;
|
||||||
|
import com.ttstd.dialer.R;
|
||||||
|
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||||||
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
|
import com.ttstd.iconloader.IconCacheManager;
|
||||||
|
import com.ttstd.iconloader.IconLoader;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolder> implements LoaderManager.LoaderCallbacks<Drawable> {
|
||||||
|
private static final String TAG = "MoreAppAdapter";
|
||||||
|
|
||||||
|
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
|
private FragmentActivity mContext;
|
||||||
|
private LoaderManager mLoaderManager;
|
||||||
|
private IconCacheManager mIconCacheManager = IconCacheManager.getInstance();
|
||||||
|
|
||||||
|
public MoreAppAdapter(LoaderManager loaderManager) {
|
||||||
|
mLoaderManager = loaderManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectApps(List<DesktopSortApp> selectApps) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DesktopSortApp> mDesktopSortApps;
|
||||||
|
|
||||||
|
public void setDesktopSortApps(List<DesktopSortApp> desktopSortApps) {
|
||||||
|
mDesktopSortApps = desktopSortApps;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public AppHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
|
||||||
|
mContext = (FragmentActivity) parent.getContext();
|
||||||
|
return new AppHolder(LayoutInflater.from(mContext).inflate(R.layout.item_app, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull @NotNull AppHolder holder, int position) {
|
||||||
|
DesktopSortApp desktopSortApp = mDesktopSortApps.get(position);
|
||||||
|
Drawable drawable = mIconCacheManager.getIcon(desktopSortApp.getComponentName().flattenToShortString());
|
||||||
|
if (drawable != null) {
|
||||||
|
holder.iv_icon.setImageDrawable(drawable);
|
||||||
|
} else {
|
||||||
|
mLoaderManager.restartLoader(position, null, this).forceLoad();
|
||||||
|
}
|
||||||
|
holder.tv_app_name.setText(desktopSortApp.getLabel());
|
||||||
|
holder.root.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
ApkUtils.openApp(mContext, desktopSortApp.getComponentName());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mDesktopSortApps == null ? 0 : mDesktopSortApps.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Loader<Drawable> onCreateLoader(int id, @Nullable @org.jetbrains.annotations.Nullable Bundle args) {
|
||||||
|
DesktopSortApp appInfo = mDesktopSortApps.get(id);
|
||||||
|
ComponentName componentName = appInfo.getComponentName(); // 从数据项中获取 ComponentName
|
||||||
|
return new IconLoader(mContext, componentName, mIconCacheManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(@NonNull @NotNull Loader<Drawable> loader, Drawable data) {
|
||||||
|
int position = loader.getId();
|
||||||
|
notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(@NonNull @NotNull Loader<Drawable> loader) {
|
||||||
|
Log.e(TAG, "onLoaderReset: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AppHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
ConstraintLayout root;
|
||||||
|
ImageView iv_icon;
|
||||||
|
TextView tv_app_name;
|
||||||
|
|
||||||
|
public AppHolder(@NonNull @NotNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
root = itemView.findViewById(R.id.root);
|
||||||
|
iv_icon = itemView.findViewById(R.id.iv_icon);
|
||||||
|
tv_app_name = itemView.findViewById(R.id.tv_app_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,9 @@ import com.tencent.bugly.crashreport.CrashReport;
|
|||||||
import com.tencent.mmkv.MMKV;
|
import com.tencent.mmkv.MMKV;
|
||||||
import com.ttstd.dialer.BuildConfig;
|
import com.ttstd.dialer.BuildConfig;
|
||||||
import com.ttstd.dialer.config.CommonConfig;
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
|
import com.ttstd.dialer.manager.AppManager;
|
||||||
import com.ttstd.dialer.utils.SystemUtils;
|
import com.ttstd.dialer.utils.SystemUtils;
|
||||||
|
import com.ttstd.iconloader.IconCacheManager;
|
||||||
|
|
||||||
|
|
||||||
public class BaseApplication extends Application {
|
public class BaseApplication extends Application {
|
||||||
@@ -67,6 +69,9 @@ public class BaseApplication extends Application {
|
|||||||
CrashReport.initCrashReport(getApplicationContext(), "845e3ed68c", false);
|
CrashReport.initCrashReport(getApplicationContext(), "845e3ed68c", false);
|
||||||
CrashReport.setDeviceId(this, Build.MODEL);
|
CrashReport.setDeviceId(this, Build.MODEL);
|
||||||
xcrash.XCrash.init(this);
|
xcrash.XCrash.init(this);
|
||||||
|
|
||||||
|
AppManager.init(this);
|
||||||
|
IconCacheManager.init(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
app/src/main/java/com/ttstd/dialer/db/app/AppDao.java
Normal file
57
app/src/main/java/com/ttstd/dialer/db/app/AppDao.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package com.ttstd.dialer.db.app;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Update;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface AppDao {
|
||||||
|
// 基本查询:获取总行数
|
||||||
|
@Query("SELECT COUNT(*) FROM app_list")
|
||||||
|
Integer getTotalCount();
|
||||||
|
|
||||||
|
// 查询:根据特定列的非空值计数
|
||||||
|
@Query("SELECT COUNT(id) FROM app_list")
|
||||||
|
Integer getCountById();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM app_list WHERE package_name = :packageName AND class_name = :className")
|
||||||
|
DesktopSortApp getAppInfo(String packageName, String className);
|
||||||
|
|
||||||
|
@Query("SELECT id FROM app_list WHERE package_name = :packageName AND class_name = :className")
|
||||||
|
Integer getIdByPackageAndClass(String packageName, String className);
|
||||||
|
|
||||||
|
// 检查数据是否存在(返回布尔值)
|
||||||
|
@Query("SELECT COUNT(*) FROM app_list WHERE package_name = :pkgName AND class_name = :clsName")
|
||||||
|
Integer checkAppInfoExists(String pkgName, String clsName);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
long insert(DesktopSortApp desktopSortApp);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
long[] insert(List<DesktopSortApp> contacts);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
Integer update(DesktopSortApp desktopSortApp);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
Integer delete(DesktopSortApp desktopSortApp);
|
||||||
|
|
||||||
|
@Query("DELETE FROM app_list WHERE id = :id")
|
||||||
|
Integer deleteById(Integer id);
|
||||||
|
|
||||||
|
@Query("DELETE FROM app_list")
|
||||||
|
Integer deleteAll();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM app_list ORDER BY position ASC")
|
||||||
|
List<DesktopSortApp> getAllApp();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM app_list WHERE id = :id")
|
||||||
|
DesktopSortApp getAppById(Integer id);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM app_list WHERE label LIKE :searchQuery OR package_name LIKE :searchQuery")
|
||||||
|
List<DesktopSortApp> searchApp(String searchQuery);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.ttstd.dialer.db.contact;
|
package com.ttstd.dialer.db.app;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
@@ -8,9 +8,9 @@ import androidx.room.RoomDatabase;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@Database(entities = {Contact.class}, version = 1, exportSchema = false)
|
@Database(entities = {DesktopSortApp.class}, version = 2, exportSchema = false)
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
public abstract ContactDao contactDao();
|
public abstract AppDao appDao();
|
||||||
|
|
||||||
private static volatile AppDatabase INSTANCE;
|
private static volatile AppDatabase INSTANCE;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
synchronized (AppDatabase.class) {
|
synchronized (AppDatabase.class) {
|
||||||
if (INSTANCE == null) {
|
if (INSTANCE == null) {
|
||||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||||
AppDatabase.class, context.getExternalFilesDir("db") + File.separator + "contact_database")
|
AppDatabase.class, context.getExternalFilesDir("db") + File.separator + "app" + File.separator + "app_db")
|
||||||
// .allowMainThreadQueries() // 为了简化示例,允许主线程查询
|
// .allowMainThreadQueries() // 为了简化示例,允许主线程查询
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
78
app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java
Normal file
78
app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package com.ttstd.dialer.db.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AppRepository {
|
||||||
|
AppDao mAppDao;
|
||||||
|
|
||||||
|
public AppRepository(Context context) {
|
||||||
|
AppDatabase db = AppDatabase.getDatabase(context);
|
||||||
|
mAppDao = db.appDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalCount() {
|
||||||
|
return mAppDao.getTotalCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCountById() {
|
||||||
|
return mAppDao.getCountById();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DesktopSortApp getAppInfo(String packageName, String className) {
|
||||||
|
return mAppDao.getAppInfo(packageName, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIdByPackageAndClass(String packageName, String className) {
|
||||||
|
return mAppDao.getIdByPackageAndClass(packageName, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int checkAppInfoExists(String packageName, String className) {
|
||||||
|
return mAppDao.checkAppInfoExists(packageName, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有APP
|
||||||
|
public List<DesktopSortApp> getAllContacts() {
|
||||||
|
return mAppDao.getAllApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID获取APP
|
||||||
|
public DesktopSortApp getContactById(int id) {
|
||||||
|
return mAppDao.getAppById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索APP
|
||||||
|
public List<DesktopSortApp> searchContacts(String query) {
|
||||||
|
return mAppDao.searchApp("%" + query + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加APP
|
||||||
|
public long insert(DesktopSortApp contact) {
|
||||||
|
return mAppDao.insert(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] insert(List<DesktopSortApp> contacts) {
|
||||||
|
return mAppDao.insert(contacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新APP
|
||||||
|
public int update(DesktopSortApp contact) {
|
||||||
|
return mAppDao.update(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除APP
|
||||||
|
public int delete(DesktopSortApp contact) {
|
||||||
|
return mAppDao.delete(contact);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除APP
|
||||||
|
public int deleteById(int id) {
|
||||||
|
return mAppDao.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除所有APP
|
||||||
|
public int deleteAll() {
|
||||||
|
return mAppDao.deleteAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.ttstd.dialer.db.app;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
|
public class ComponentNameConverter {
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromComponentName(ComponentName componentName) {
|
||||||
|
// 如果 componentName 为 null,则返回 null
|
||||||
|
if (componentName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 将 ComponentName 转换为其字符串形式(例如:"com.example.app/.MyService")
|
||||||
|
return componentName.flattenToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static ComponentName toComponentName(String componentNameString) {
|
||||||
|
// 如果字符串为 null 或空,则返回 null
|
||||||
|
if (componentNameString == null || componentNameString.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 从字符串形式解包为 ComponentName 对象
|
||||||
|
return ComponentName.unflattenFromString(componentNameString);
|
||||||
|
}
|
||||||
|
}
|
||||||
118
app/src/main/java/com/ttstd/dialer/db/app/DesktopSortApp.java
Normal file
118
app/src/main/java/com/ttstd/dialer/db/app/DesktopSortApp.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package com.ttstd.dialer.db.app;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.room.ColumnInfo;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
import androidx.room.TypeConverters;
|
||||||
|
|
||||||
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity(tableName = "app_list", indices = {@Index(value = "component_name", unique = true)})
|
||||||
|
@TypeConverters({ComponentNameConverter.class})
|
||||||
|
public class DesktopSortApp implements Serializable {
|
||||||
|
private static final long serialVersionUID = 9113517079637096245L;
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
private int id;
|
||||||
|
@ColumnInfo(name = "component_name")
|
||||||
|
private ComponentName componentName;
|
||||||
|
@ColumnInfo(name = "label")
|
||||||
|
private String mLabel;
|
||||||
|
@ColumnInfo(name = "package_name")
|
||||||
|
private String mPackageName;
|
||||||
|
@ColumnInfo(name = "class_name")
|
||||||
|
private String mClassName;
|
||||||
|
@ColumnInfo(name = "position")
|
||||||
|
private int mPosition;
|
||||||
|
|
||||||
|
public DesktopSortApp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public DesktopSortApp(Context context, ComponentName componentName) {
|
||||||
|
this.componentName = componentName;
|
||||||
|
mPackageName = componentName.getPackageName();
|
||||||
|
mClassName = componentName.getClassName();
|
||||||
|
mPosition = 0;
|
||||||
|
mLabel = ApkUtils.getAppName(context, componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DesktopSortApp(Context context, ComponentName componentName, int pos) {
|
||||||
|
this.componentName = componentName;
|
||||||
|
mPackageName = componentName.getPackageName();
|
||||||
|
mClassName = componentName.getClassName();
|
||||||
|
mPosition = pos;
|
||||||
|
mLabel = ApkUtils.getAppName(context, componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentName getComponentName() {
|
||||||
|
return componentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComponentName(ComponentName componentName) {
|
||||||
|
this.componentName = componentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
this.mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return mPackageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.mPackageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClassName() {
|
||||||
|
return mClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClassName(String className) {
|
||||||
|
this.mClassName = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return mPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.mPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable @org.jetbrains.annotations.Nullable Object obj) {
|
||||||
|
if (obj instanceof DesktopSortApp) {
|
||||||
|
return Objects.equals(componentName, ((DesktopSortApp) obj).componentName);
|
||||||
|
} else if (obj instanceof ComponentName) {
|
||||||
|
return Objects.equals(componentName, obj);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return componentName.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.ttstd.dialer.db.contact;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.room.Database;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.RoomDatabase;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Database(entities = {Contact.class}, version = 1, exportSchema = false)
|
||||||
|
public abstract class ContactDatabase extends RoomDatabase {
|
||||||
|
public abstract ContactDao contactDao();
|
||||||
|
|
||||||
|
private static volatile ContactDatabase INSTANCE;
|
||||||
|
|
||||||
|
// 单例模式获取数据库实例
|
||||||
|
public static ContactDatabase getDatabase(final Context context) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
synchronized (ContactDatabase.class) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
|
||||||
|
ContactDatabase.class, context.getExternalFilesDir("db") + File.separator + "contact" + File.separator + "contact_database")
|
||||||
|
// .allowMainThreadQueries() // 为了简化示例,允许主线程查询
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ public class ContactRepository {
|
|||||||
|
|
||||||
// 构造函数,获取数据库访问对象
|
// 构造函数,获取数据库访问对象
|
||||||
public ContactRepository(Context context) {
|
public ContactRepository(Context context) {
|
||||||
AppDatabase db = AppDatabase.getDatabase(context);
|
ContactDatabase db = ContactDatabase.getDatabase(context);
|
||||||
mContactDao = db.contactDao();
|
mContactDao = db.contactDao();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.fragment.app.Fragment;
|
|||||||
|
|
||||||
import com.hjq.toast.Toaster;
|
import com.hjq.toast.Toaster;
|
||||||
import com.ttstd.dialer.R;
|
import com.ttstd.dialer.R;
|
||||||
|
import com.ttstd.dialer.activity.app.AppListActivity;
|
||||||
import com.ttstd.dialer.activity.contact.list.ContactListActivity;
|
import com.ttstd.dialer.activity.contact.list.ContactListActivity;
|
||||||
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
|
||||||
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
import com.ttstd.dialer.databinding.FragmentHomeBinding;
|
||||||
@@ -186,12 +187,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
|
|||||||
|
|
||||||
public class BtnClick {
|
public class BtnClick {
|
||||||
public void openContact(View view) {
|
public void openContact(View view) {
|
||||||
Intent intent = new Intent(mContext, ContactListActivity.class);
|
startActivity(new Intent(mContext, ContactListActivity.class));
|
||||||
try {
|
|
||||||
startActivity(intent);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "openSettings: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openSettings(View view) {
|
public void openSettings(View view) {
|
||||||
@@ -234,6 +230,11 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
|
|||||||
openAppStore("com.tencent.mm");
|
openAppStore("com.tencent.mm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void openAppList(View view) {
|
||||||
|
startActivity(new Intent(mContext, AppListActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
app/src/main/java/com/ttstd/dialer/gson/GetJsonDataUtil.java
Normal file
38
app/src/main/java/com/ttstd/dialer/gson/GetJsonDataUtil.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package com.ttstd.dialer.gson;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <读取Json文件的工具类>
|
||||||
|
*
|
||||||
|
* @author: 小嵩
|
||||||
|
* @date: 2017/3/16 16:22
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GetJsonDataUtil {
|
||||||
|
|
||||||
|
|
||||||
|
public String getJson(Context context, String fileName) {
|
||||||
|
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
try {
|
||||||
|
AssetManager assetManager = context.getAssets();
|
||||||
|
BufferedReader bf = new BufferedReader(new InputStreamReader(
|
||||||
|
assetManager.open(fileName)));
|
||||||
|
String line;
|
||||||
|
while ((line = bf.readLine()) != null) {
|
||||||
|
stringBuilder.append(line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
153
app/src/main/java/com/ttstd/dialer/gson/GsonUtils.java
Normal file
153
app/src/main/java/com/ttstd/dialer/gson/GsonUtils.java
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package com.ttstd.dialer.gson;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
public class GsonUtils {
|
||||||
|
//https://blog.csdn.net/zte1055889498/article/details/122400299
|
||||||
|
|
||||||
|
public static JsonObject getJsonObject(String jsonString) {
|
||||||
|
JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Gson gson;
|
||||||
|
private static final Gson exposeGson;
|
||||||
|
|
||||||
|
static {
|
||||||
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
builder.registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory());
|
||||||
|
builder.registerTypeAdapter(Integer.class, new IntegerDefault0Adapter());
|
||||||
|
builder.registerTypeAdapter(int.class, new IntegerDefault0Adapter());
|
||||||
|
builder.disableHtmlEscaping();
|
||||||
|
builder.enableComplexMapKeySerialization();
|
||||||
|
// builder.excludeFieldsWithoutExposeAnnotation();
|
||||||
|
builder.setDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
gson = builder.create();
|
||||||
|
exposeGson = new GsonBuilder()
|
||||||
|
.excludeFieldsWithoutExposeAnnotation()
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type makeJavaType(Type rawType, Type... typeArguments) {
|
||||||
|
return TypeToken.getParameterized(rawType, typeArguments).getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(Object value) {
|
||||||
|
if (Objects.isNull(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
return toJSONString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJSONString(Object value) {
|
||||||
|
return gson.toJson(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toExposeJSONString(Object value) {
|
||||||
|
return gson.toJson(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String toPrettyString(Object value) {
|
||||||
|
return gson.newBuilder().setPrettyPrinting().create().toJson(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonElement fromJavaObject(Object value) {
|
||||||
|
JsonElement result = null;
|
||||||
|
if (Objects.nonNull(value) && (value instanceof String)) {
|
||||||
|
result = parseObject((String) value);
|
||||||
|
} else {
|
||||||
|
result = gson.toJsonTree(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonElement parseObject(String content) {
|
||||||
|
return JsonParser.parseString(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonElement getJsonElement(JsonObject node, String name) {
|
||||||
|
return node.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonElement getJsonElement(JsonArray node, int index) {
|
||||||
|
return node.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(JsonElement node, Class<T> clazz) {
|
||||||
|
return gson.fromJson(node, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(JsonElement node, Type type) {
|
||||||
|
return gson.fromJson(node, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(JsonElement node, TypeToken<?> typeToken) {
|
||||||
|
return toJavaObject(node, typeToken.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E> List<E> toJavaList(JsonElement node, Class<E> clazz) {
|
||||||
|
return toJavaObject(node, makeJavaType(List.class, clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Object> toJavaList(JsonElement node) {
|
||||||
|
return toJavaObject(node, new TypeToken<List<Object>>() {
|
||||||
|
}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> Map<String, V> toJavaMap(JsonElement node, Class<V> clazz) {
|
||||||
|
return toJavaObject(node, makeJavaType(Map.class, String.class, clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> toJavaMap(JsonElement node) {
|
||||||
|
return toJavaObject(node, new TypeToken<Map<String, Object>>() {
|
||||||
|
}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(String content, Class<T> clazz) {
|
||||||
|
JsonObject jsonObject = getJsonObject(content);
|
||||||
|
String jsonString = jsonObject.toString();
|
||||||
|
return gson.fromJson(jsonString, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(String content, Type type) {
|
||||||
|
return gson.fromJson(content, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T toJavaObject(String content, TypeToken<?> typeToken) {
|
||||||
|
return toJavaObject(content, typeToken.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E> List<E> toJavaList(String content, Class<E> clazz) {
|
||||||
|
return toJavaObject(content, makeJavaType(List.class, clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Object> toJavaList(String content) {
|
||||||
|
return toJavaObject(content, new TypeToken<List<Object>>() {
|
||||||
|
}.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <V> Map<String, V> toJavaMap(String content, Class<V> clazz) {
|
||||||
|
return toJavaObject(content, makeJavaType(Map.class, String.class, clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> toJavaMap(String content) {
|
||||||
|
return toJavaObject(content, new TypeToken<Map<String, Object>>() {
|
||||||
|
}.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ttstd.dialer.gson;
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class IntegerDefault0Adapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
|
||||||
|
@Override
|
||||||
|
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||||
|
throws JsonParseException {
|
||||||
|
try {
|
||||||
|
if (json.getAsString().equals("")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return json.getAsInt();
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new JsonSyntaxException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.ttstd.dialer.gson;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonToken;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory {
|
||||||
|
@Override
|
||||||
|
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||||
|
|
||||||
|
Class<T> rawType = (Class<T>) type.getRawType();
|
||||||
|
if (rawType != String.class) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (TypeAdapter<T>) new StringAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StringAdapter extends TypeAdapter<String> {
|
||||||
|
@Override
|
||||||
|
public String read(JsonReader reader) throws IOException {
|
||||||
|
if (reader.peek() == JsonToken.NULL) {
|
||||||
|
reader.nextNull();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return reader.nextString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter writer, String value) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
writer.nullValue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writer.value(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
384
app/src/main/java/com/ttstd/dialer/manager/AppManager.java
Normal file
384
app/src/main/java/com/ttstd/dialer/manager/AppManager.java
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
package com.ttstd.dialer.manager;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.tencent.mmkv.MMKV;
|
||||||
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
|
import com.ttstd.dialer.db.app.AppRepository;
|
||||||
|
import com.ttstd.dialer.db.app.DesktopSortApp;
|
||||||
|
import com.ttstd.dialer.gson.GsonUtils;
|
||||||
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
|
|
||||||
|
import java.text.Collator;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
|
||||||
|
public class AppManager {
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private static AppManager INSTANCE;
|
||||||
|
private Context mContext;
|
||||||
|
private AppRepository mAppRepository;
|
||||||
|
|
||||||
|
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
|
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||||
|
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
|
||||||
|
private static final ExecutorService ASYNC_EXECUTOR = Executors.newFixedThreadPool(CORE_POOL_SIZE);
|
||||||
|
|
||||||
|
|
||||||
|
public static final List<String> DEFAULT_APP_PACKAGES = new ArrayList<String>() {{
|
||||||
|
this.add("com.android.settings");
|
||||||
|
this.add("com.android.dialer");
|
||||||
|
this.add("com.android.camera2");
|
||||||
|
this.add("com.tencent.mm");
|
||||||
|
this.add("com.jiangjia.gif");
|
||||||
|
this.add("com.ss.android.ugc.aweme");
|
||||||
|
}};
|
||||||
|
|
||||||
|
public static void init(Context context) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new AppManager(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppManager getInstance() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
throw new IllegalStateException("You must be init AppManager first");
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppManager(Context context) {
|
||||||
|
this.mContext = context.getApplicationContext();
|
||||||
|
this.mAppRepository = new AppRepository(context);
|
||||||
|
executeAppListProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ProgressCallback {
|
||||||
|
void onProgress(String step, int current, int total);
|
||||||
|
|
||||||
|
void onCompleted(boolean success, String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异步执行方法
|
||||||
|
public void executeAppListProcessing() {
|
||||||
|
CompletableFuture
|
||||||
|
.supplyAsync(this::getAllDesktopSortApps, ASYNC_EXECUTOR)
|
||||||
|
.thenComposeAsync(desktopSortApps -> {
|
||||||
|
// 判断获取的应用列表是否为空
|
||||||
|
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||||||
|
Log.i("AppListProcessor", "检测到桌面应用列表为空,直接插入新应用数据");
|
||||||
|
return processNewApps()
|
||||||
|
.thenComposeAsync(unused -> updatePosition(), ASYNC_EXECUTOR);
|
||||||
|
} else {
|
||||||
|
return processUninstalledApps(desktopSortApps)
|
||||||
|
.thenComposeAsync(unused -> processNewApps(), ASYNC_EXECUTOR)
|
||||||
|
.thenComposeAsync(unused -> updatePosition(), ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR)
|
||||||
|
.thenComposeAsync(unused -> processNewApps(), ASYNC_EXECUTOR)
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
// 统一异常处理
|
||||||
|
Log.e("AppListProcessor", "异步处理失败", throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第一步:获取所有桌面应用(增加空值安全处理)
|
||||||
|
private List<DesktopSortApp> getAllDesktopSortApps() {
|
||||||
|
try {
|
||||||
|
List<DesktopSortApp> result = mAppRepository.getAllContacts();
|
||||||
|
return result != null ? result : Collections.emptyList();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "获取桌面应用列表失败", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<List<DesktopSortApp>> getAllDesktopSortAppsSafe() {
|
||||||
|
try {
|
||||||
|
return Optional.ofNullable(mAppRepository.getAllContacts())
|
||||||
|
.filter(list -> !list.isEmpty());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "获取应用列表失败", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:处理未安装的应用(删除操作) - 增加空值检查
|
||||||
|
private CompletableFuture<Void> processUninstalledApps(List<DesktopSortApp> desktopSortApps) {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||||||
|
Log.w("AppListProcessor", "桌面应用列表为空,跳过删除处理");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> ids = desktopSortApps.stream()
|
||||||
|
.filter(desktopSortApp -> desktopSortApp != null &&
|
||||||
|
!ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName()))
|
||||||
|
.map(desktopSortApp -> {
|
||||||
|
try {
|
||||||
|
return mAppRepository.getIdByPackageAndClass(
|
||||||
|
desktopSortApp.getPackageName(), desktopSortApp.getClassName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "获取应用ID失败: " + desktopSortApp.getPackageName(), e);
|
||||||
|
return -1; // 返回无效ID,后续过滤掉
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(id -> id > 0) // 过滤掉无效ID
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!ids.isEmpty()) {
|
||||||
|
ids.forEach(id -> {
|
||||||
|
try {
|
||||||
|
mAppRepository.deleteById(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "删除应用失败, ID: " + id, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Log.i("AppListProcessor", "成功删除 " + ids.size() + " 个未安装应用");
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三步:处理新应用(插入操作) - 增加空值和异常处理
|
||||||
|
private CompletableFuture<Void> processNewApps() {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
if (resolveInfos.isEmpty()) {
|
||||||
|
Log.w("AppListProcessor", "未获取到启动器应用列表");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DesktopSortApp> appList = resolveInfos.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(resolveInfo -> {
|
||||||
|
try {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
String className = resolveInfo.activityInfo.name;
|
||||||
|
ComponentName componentName = new ComponentName(packageName, className);
|
||||||
|
return new DesktopSortApp(mContext, componentName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "创建DesktopSortApp失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(new Comparator<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DesktopSortApp> filterApp = appList.stream()
|
||||||
|
.filter(desktopSortApp -> ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName()))
|
||||||
|
.filter(desktopSortApp -> {
|
||||||
|
try {
|
||||||
|
return mAppRepository.checkAppInfoExists(
|
||||||
|
desktopSortApp.getPackageName(), desktopSortApp.getClassName()) <= 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "检查应用存在性失败", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!filterApp.isEmpty()) {
|
||||||
|
filterApp.forEach(desktopSortApp -> {
|
||||||
|
desktopSortApp.setPosition(mAppRepository.getTotalCount());
|
||||||
|
try {
|
||||||
|
mAppRepository.insert(desktopSortApp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "插入应用失败: " + desktopSortApp.getPackageName(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Log.i("AppListProcessor", "成功插入 " + filterApp.size() + " 个新应用");
|
||||||
|
} else {
|
||||||
|
Log.i("AppListProcessor", "没有需要插入的新应用");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "处理新应用时发生异常", e);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第四步:更新应用位置(您提供的方法优化)
|
||||||
|
private CompletableFuture<Void> updatePosition() {
|
||||||
|
return CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
List<DesktopSortApp> desktopSortApps = getAllDesktopSortApps();
|
||||||
|
if (desktopSortApps == null || desktopSortApps.isEmpty()) {
|
||||||
|
Log.w("AppListProcessor", "无应用数据,跳过位置更新");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Lambda简化代码
|
||||||
|
List<DesktopSortApp> sortedApps = desktopSortApps.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted((o1, o2) -> Integer.compare(o1.getPosition(), o2.getPosition()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 批量更新位置
|
||||||
|
IntStream.range(0, sortedApps.size())
|
||||||
|
.forEach(index -> {
|
||||||
|
DesktopSortApp desktopSortApp = sortedApps.get(index);
|
||||||
|
desktopSortApp.setPosition(index);
|
||||||
|
try {
|
||||||
|
mAppRepository.update(desktopSortApp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "更新应用位置失败: " + desktopSortApp.getPackageName(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Log.i("AppListProcessor", "成功更新 " + sortedApps.size() + " 个应用的位置");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("AppListProcessor", "更新应用位置时发生异常", e);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateApp(String packageName) {
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
|
||||||
|
if (!resolveInfos.isEmpty()) {
|
||||||
|
resolveInfos.stream().map(new Function<ResolveInfo, DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public DesktopSortApp apply(ResolveInfo resolveInfo) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
String className = resolveInfo.activityInfo.name;
|
||||||
|
ComponentName componentName = new ComponentName(packageName, className);
|
||||||
|
return new DesktopSortApp(mContext, componentName);
|
||||||
|
}
|
||||||
|
}).filter(new Predicate<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(DesktopSortApp desktopSortApp) {
|
||||||
|
return mAppRepository.checkAppInfoExists(desktopSortApp.getPackageName(), desktopSortApp.getClassName()) <= 0;
|
||||||
|
}
|
||||||
|
}).forEach(new Consumer<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public void accept(DesktopSortApp desktopSortApp) {
|
||||||
|
desktopSortApp.setPosition(mAppRepository.getTotalCount());
|
||||||
|
mAppRepository.insert(desktopSortApp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAllApp() {
|
||||||
|
List<DesktopSortApp> desktopSortApps = mAppRepository.getAllContacts();
|
||||||
|
List<Integer> ids = desktopSortApps.stream().filter(new Predicate<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(DesktopSortApp desktopSortApp) {
|
||||||
|
return !ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName());
|
||||||
|
}
|
||||||
|
}).map(new java.util.function.Function<DesktopSortApp, Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer apply(DesktopSortApp desktopSortApp) {
|
||||||
|
return mAppRepository.getIdByPackageAndClass(desktopSortApp.getPackageName(), desktopSortApp.getClassName());
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
ids.stream().forEach(new Consumer<Integer>() {
|
||||||
|
@Override
|
||||||
|
public void accept(Integer integer) {
|
||||||
|
mAppRepository.deleteById(integer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
List<DesktopSortApp> appList = resolveInfos.stream().map(new Function<ResolveInfo, ComponentName>() {
|
||||||
|
@Override
|
||||||
|
public ComponentName apply(ResolveInfo resolveInfo) {
|
||||||
|
String packageName = resolveInfo.activityInfo.packageName;
|
||||||
|
String className = resolveInfo.activityInfo.name;
|
||||||
|
ComponentName componentName = new ComponentName(packageName, className);
|
||||||
|
return componentName;
|
||||||
|
}
|
||||||
|
}).map(new Function<ComponentName, DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public DesktopSortApp apply(ComponentName componentName) {
|
||||||
|
DesktopSortApp desktopSortApp = new DesktopSortApp(mContext, componentName);
|
||||||
|
return desktopSortApp;
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
List<DesktopSortApp> filterApp = appList.stream().filter(new Predicate<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(DesktopSortApp desktopSortApp) {
|
||||||
|
return ApkUtils.isInstalled(mContext, desktopSortApp.getPackageName());
|
||||||
|
}
|
||||||
|
}).filter(new Predicate<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(DesktopSortApp desktopSortApp) {
|
||||||
|
return mAppRepository.checkAppInfoExists(desktopSortApp.getPackageName(), desktopSortApp.getClassName()) > 0;
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
filterApp.stream().forEach(new Consumer<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public void accept(DesktopSortApp desktopSortApp) {
|
||||||
|
mAppRepository.insert(desktopSortApp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultAppList() {
|
||||||
|
PackageManager packageManager = mContext.getPackageManager();
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
List<ResolveInfo> filter = resolveInfos.stream().filter(new Predicate<ResolveInfo>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(ResolveInfo resolveInfo) {
|
||||||
|
return DEFAULT_APP_PACKAGES.contains(resolveInfo.activityInfo.packageName);
|
||||||
|
}
|
||||||
|
}).sorted(new Comparator<ResolveInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(ResolveInfo o1, ResolveInfo o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.activityInfo.loadLabel(packageManager), o2.activityInfo.loadLabel(packageManager));
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<DesktopSortApp> desktopSortApps = filter.stream().map(new Function<ResolveInfo, DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public DesktopSortApp apply(ResolveInfo resolveInfo) {
|
||||||
|
ComponentName componentName = new ComponentName(resolveInfo.activityInfo.packageName,
|
||||||
|
resolveInfo.activityInfo.name);
|
||||||
|
return new DesktopSortApp(mContext, componentName);
|
||||||
|
}
|
||||||
|
}).sorted(new Comparator<DesktopSortApp>() {
|
||||||
|
@Override
|
||||||
|
public int compare(DesktopSortApp o1, DesktopSortApp o2) {
|
||||||
|
return Boolean.compare(ApkUtils.isSystemApp(mContext, o2.getPackageName()), ApkUtils.isSystemApp(mContext, o1.getPackageName()));
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toCollection(LinkedList::new));
|
||||||
|
|
||||||
|
for (int i = 0; i < desktopSortApps.size(); i++) {
|
||||||
|
DesktopSortApp desktopSortApp = desktopSortApps.get(i);
|
||||||
|
desktopSortApp.setPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GsonUtils.toJSONString(desktopSortApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,18 +3,21 @@ package com.ttstd.dialer.utils;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.hjq.toast.Toaster;
|
import com.hjq.toast.Toaster;
|
||||||
|
import com.ttstd.dialer.R;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ApkUtils {
|
public class ApkUtils {
|
||||||
|
|
||||||
private static final String TAG = "ApkUtils";
|
private static final String TAG = "ApkUtils";
|
||||||
|
|
||||||
public static boolean isInstalled(Context context, String packageName) {
|
public static boolean isInstalled(Context context, String packageName) {
|
||||||
@@ -83,4 +86,94 @@ public class ApkUtils {
|
|||||||
intent.setComponent(new ComponentName(packageName, mainAct));
|
intent.setComponent(new ComponentName(packageName, mainAct));
|
||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean openApp(Context context, ComponentName componentName) {
|
||||||
|
Intent intent = new Intent()
|
||||||
|
.setComponent(componentName)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
try {
|
||||||
|
context.startActivity(intent);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "openApp: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getDrawableFromComponentName(Context context, ComponentName componentName) {
|
||||||
|
try {
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
ActivityInfo activityInfo = packageManager.getActivityInfo(componentName, 0);
|
||||||
|
ApplicationInfo applicationInfo = activityInfo.applicationInfo;
|
||||||
|
Drawable activityIcon = applicationInfo.loadIcon(packageManager);
|
||||||
|
return activityIcon;
|
||||||
|
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
// 处理未找到组件的情况
|
||||||
|
e.printStackTrace();
|
||||||
|
// 可以根据需要设置一个默认图标或提示用户
|
||||||
|
}
|
||||||
|
return context.getDrawable(R.mipmap.ic_launcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAppName(Context context, ComponentName componentName) {
|
||||||
|
try {
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
ActivityInfo activityInfo = packageManager.getActivityInfo(componentName, 0);
|
||||||
|
ApplicationInfo applicationInfo = activityInfo.applicationInfo;
|
||||||
|
String name = applicationInfo.loadLabel(packageManager).toString();
|
||||||
|
return name;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ResolveInfo> getAllLauncherResolveInfo(Context context) {
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
// 查询所有包含CATEGORY_LAUNCHER的Activity
|
||||||
|
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||||
|
return resolveInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ResolveInfo> getResolveInfoByPackageName(Context context, String packageName) {
|
||||||
|
PackageManager packageManager = context.getPackageManager(); // 在Activity中使用
|
||||||
|
|
||||||
|
// 构建查询Intent
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN, null);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
intent.setPackage(packageName); // 设置包名过滤
|
||||||
|
|
||||||
|
// 查询匹配的Activity列表
|
||||||
|
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
|
||||||
|
return resolveInfoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为系统应用
|
||||||
|
*
|
||||||
|
* @param context 上下文
|
||||||
|
* @param pkgName 包名
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isSystemApp(Context context, String pkgName) {
|
||||||
|
boolean isSystemApp = false;
|
||||||
|
PackageInfo pi = null;
|
||||||
|
try {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
pi = pm.getPackageInfo(pkgName, 0);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
Log.e("isSystemApp: ", "NameNotFoundException:" + e.getMessage());
|
||||||
|
}
|
||||||
|
// 是系统中已安装的应用
|
||||||
|
if (pi != null) {
|
||||||
|
boolean isSysApp = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1;
|
||||||
|
boolean isSysUpd = (pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 1;
|
||||||
|
isSystemApp = isSysApp || isSysUpd;
|
||||||
|
}
|
||||||
|
return isSystemApp;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
9
app/src/main/res/drawable/ic_app.xml
Normal file
9
app/src/main/res/drawable/ic_app.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="1024"
|
||||||
|
android:viewportHeight="1024">
|
||||||
|
<path
|
||||||
|
android:pathData="M271.96,64c-115.28,0 -208.87,93.67 -208.87,208.95C63.08,388.34 156.63,481.9 272.02,481.9h144.89c35.35,0 63.92,-28.76 63.92,-64.11L480.83,272.89c-0.01,-115.37 -93.5,-208.89 -208.87,-208.89zM416.88,417.79v0.03a0.93,0.93 0,0 1,0.04 0.14L272.02,417.96a145.24,145.24 0,1 1,144.86 -145.06v144.89zM416.9,542.23L272.02,542.23c-115.37,0 -208.9,93.52 -208.9,208.89 0,115.28 93.56,208.89 208.86,208.89 115.37,0 208.86,-93.53 208.86,-208.9v-144.89c-0.02,-35.35 -28.59,-63.98 -63.94,-63.98zM416.88,751.1a144.72,144.72 0,0 1,-88.48 133.5C310.56,892.15 291.6,895.88 272.02,895.88c-19.56,0 -38.52,-3.74 -56.35,-11.29A144.88,144.88 0,0 1,272.02 606.17h144.92a0.09,0.09 0,0 0,-0.04 0v144.92zM751.1,542.23h-144.89A63.97,63.97 0,0 0,542.23 606.21v144.89c0,115.37 93.52,208.9 208.89,208.9 115.28,0 208.89,-93.61 208.89,-208.89 -0,-115.37 -93.53,-208.89 -208.9,-208.89zM884.69,807.45a144.93,144.93 0,0 1,-278.52 -56.35L606.17,606.17h144.92A145,145 0,0 1,884.69 807.45zM606.21,481.9h144.89c115.37,0 208.9,-93.56 208.9,-208.95 0,-115.28 -93.61,-208.92 -208.89,-208.92 -115.37,0 -208.89,93.5 -208.89,208.87v144.89c0,35.35 28.63,64.11 63.98,64.11zM606.17,272.89A144.95,144.95 0,1 1,751.1 417.96L606.17,417.96a0.52,0.52 0,0 1,0 -0.14L606.17,272.89z"
|
||||||
|
android:fillColor="#8BC34A"/>
|
||||||
|
</vector>
|
||||||
25
app/src/main/res/layout/activity_app_list.xml
Normal file
25
app/src/main/res/layout/activity_app_list.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".activity.app.AppListActivity">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="click"
|
||||||
|
type="com.ttstd.dialer.activity.app.AppListActivity.BtnClick" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</layout>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="3"
|
android:layout_weight="2"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -181,6 +181,7 @@
|
|||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/ic_douyin"
|
android:src="@drawable/ic_douyin"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
@@ -250,6 +251,101 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.shehuan.niv.NiceImageView
|
||||||
|
android:id="@+id/niceImageView5"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="@dimen/home_item_text_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/niceImageView5"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/niceImageView5"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/niceImageView5" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:onClick="@{click::openAppList}"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.shehuan.niv.NiceImageView
|
||||||
|
android:id="@+id/niceImageView6"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/ic_app"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="应用"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="@dimen/home_item_text_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/niceImageView6"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/niceImageView6"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/niceImageView6" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
49
app/src/main/res/layout/item_app.xml
Normal file
49
app/src/main/res/layout/item_app.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_icon"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@mipmap/ic_launcher"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_app_name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="appname"
|
||||||
|
android:textColor="@color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/iv_icon" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.uiui.iconloader;
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
@@ -1 +1 @@
|
|||||||
<manifest package="com.uiui.iconloader" />
|
<manifest package="com.ttstd.iconloader" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.uiui.iconloader;
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.collection.LruCache;
|
||||||
|
|
||||||
|
import com.jakewharton.disklrucache.DiskLruCache;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
public class IconCacheManager {
|
||||||
|
private static IconCacheManager INSTANCE;
|
||||||
|
|
||||||
|
private static final String TAG = "IconCacheManager";
|
||||||
|
private LruCache<String, Drawable> mMemoryCache;
|
||||||
|
private DiskLruCache mDiskCache;
|
||||||
|
|
||||||
|
public static void init(Context context) {
|
||||||
|
if (context == null) {
|
||||||
|
throw new RuntimeException("Context is NULL");
|
||||||
|
}
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new IconCacheManager(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IconCacheManager getInstance() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
throw new IllegalStateException("You must be init IconCacheManager first");
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IconCacheManager(Context context) {
|
||||||
|
// 内存缓存(最大8MB)
|
||||||
|
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
|
int cacheSize = maxMemory / 8;
|
||||||
|
mMemoryCache = new LruCache<String, Drawable>(cacheSize) {
|
||||||
|
@Override
|
||||||
|
protected int sizeOf(String key, Drawable icon) {
|
||||||
|
if (icon instanceof BitmapDrawable) {
|
||||||
|
return ((BitmapDrawable) icon).getBitmap().getByteCount() / 1024;
|
||||||
|
}
|
||||||
|
return 1; // 非Bitmap对象按1KB计算
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 磁盘缓存(50MB)
|
||||||
|
File cacheDir = new File(context.getExternalCacheDir(), "icon_cache");
|
||||||
|
try {
|
||||||
|
mDiskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "IconCacheManager: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
final PackageManager packageManager = context.getPackageManager();
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(10);
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
// 查询所有包含CATEGORY_LAUNCHER的Activity
|
||||||
|
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||||
|
for (ResolveInfo resolveInfo : resolveInfoList) {
|
||||||
|
ComponentName componentName = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||||
|
String key = componentName.flattenToShortString();
|
||||||
|
try {
|
||||||
|
ActivityInfo info = packageManager.getActivityInfo(componentName, 0);
|
||||||
|
Drawable rawIcon = info.loadIcon(packageManager);
|
||||||
|
putToMemory(key, rawIcon);
|
||||||
|
putToDisk(key, rawIcon);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "run: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getFromMemory(String key) {
|
||||||
|
return mMemoryCache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getFromDisk(String key) {
|
||||||
|
String md5 = MD5Util.getMD5(key);
|
||||||
|
try {
|
||||||
|
DiskLruCache.Snapshot snapshot = mDiskCache.get(md5);
|
||||||
|
if (snapshot != null) {
|
||||||
|
InputStream in = snapshot.getInputStream(0);
|
||||||
|
return Drawable.createFromStream(in, null);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "getFromDisk Disk read error", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putToMemory(String key, Drawable icon) {
|
||||||
|
mMemoryCache.put(key, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putToDisk(String key, Drawable icon) {
|
||||||
|
String md5 = MD5Util.getMD5(key);
|
||||||
|
try {
|
||||||
|
DiskLruCache.Editor editor = mDiskCache.edit(md5);
|
||||||
|
if (icon instanceof BitmapDrawable) {
|
||||||
|
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, editor.newOutputStream(0));
|
||||||
|
editor.commit();
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
if (icon instanceof AdaptiveIconDrawable) {
|
||||||
|
// 分别获取背景和前景Drawable
|
||||||
|
Drawable[] layers = new Drawable[2];
|
||||||
|
layers[0] = ((AdaptiveIconDrawable) icon).getBackground();
|
||||||
|
layers[1] = ((AdaptiveIconDrawable) icon).getForeground();
|
||||||
|
LayerDrawable layerDrawable = new LayerDrawable(layers);
|
||||||
|
|
||||||
|
int width = icon.getIntrinsicWidth();
|
||||||
|
int height = icon.getIntrinsicHeight();
|
||||||
|
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
|
||||||
|
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||||
|
layerDrawable.draw(canvas);
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, editor.newOutputStream(0));
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "putToDisk Disk write error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getIcon(String key) {
|
||||||
|
Drawable memDrawable = getFromMemory(key);
|
||||||
|
if (memDrawable != null) {
|
||||||
|
Log.i(TAG, "getIcon: fromMemory " + key);
|
||||||
|
return memDrawable;
|
||||||
|
}
|
||||||
|
Drawable diskDrawable = getFromDisk(key);
|
||||||
|
if (diskDrawable != null) {
|
||||||
|
putToMemory(key, diskDrawable);
|
||||||
|
Log.i(TAG, "getIcon: fromDisk " + key);
|
||||||
|
return diskDrawable;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.uiui.iconloader;
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -12,29 +12,24 @@ import android.graphics.drawable.AdaptiveIconDrawable;
|
|||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.loader.content.AsyncTaskLoader;
|
import androidx.loader.content.AsyncTaskLoader;
|
||||||
|
|
||||||
public class IconLoader extends AsyncTaskLoader<Drawable> {
|
public class IconLoader extends AsyncTaskLoader<Drawable> {
|
||||||
private final ComponentName mComponentName; // 关键:使用ComponentName标识组件
|
private static final String TAG = "IconLoader";
|
||||||
private final IconCacheManager mCache;
|
private final IconCacheManager mCache;
|
||||||
|
private ComponentName mComponentName;
|
||||||
|
|
||||||
public IconLoader(Context context, ComponentName componentName, IconCacheManager cache) {
|
public IconLoader(Context context, ComponentName componentName, IconCacheManager iconCacheManager) {
|
||||||
super(context);
|
super(context);
|
||||||
mComponentName = componentName;
|
this.mComponentName = componentName;
|
||||||
mCache = cache;
|
this.mCache = iconCacheManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStartLoading() {
|
protected void onStartLoading() {
|
||||||
String key = CacheKeyGenerator.generateKey(mComponentName);
|
|
||||||
// 1. 检查内存缓存
|
|
||||||
Drawable cachedIcon = mCache.getFromMemory(key);
|
|
||||||
if (cachedIcon != null) {
|
|
||||||
deliverResult(cachedIcon); // 直接返回缓存
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
forceLoad(); // 触发后台加载
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -43,12 +38,15 @@ public class IconLoader extends AsyncTaskLoader<Drawable> {
|
|||||||
|
|
||||||
// 1. 检查内存缓存
|
// 1. 检查内存缓存
|
||||||
Drawable cachedIcon = mCache.getFromMemory(key);
|
Drawable cachedIcon = mCache.getFromMemory(key);
|
||||||
if (cachedIcon != null) return cachedIcon;
|
if (cachedIcon != null) {
|
||||||
|
return cachedIcon;
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 检查磁盘缓存
|
// 2. 检查磁盘缓存
|
||||||
Drawable diskCached = mCache.getFromDisk(key);
|
Drawable diskCached = mCache.getFromDisk(key);
|
||||||
if (diskCached != null) {
|
if (diskCached != null) {
|
||||||
mCache.putToMemory(key, diskCached);
|
mCache.putToMemory(key, diskCached);
|
||||||
|
Log.e(TAG, "loadInBackground: putToMemory key = " + key);
|
||||||
return diskCached;
|
return diskCached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,12 +57,12 @@ public class IconLoader extends AsyncTaskLoader<Drawable> {
|
|||||||
Drawable rawIcon = info.loadIcon(pm); // 加载该组件的图标
|
Drawable rawIcon = info.loadIcon(pm); // 加载该组件的图标
|
||||||
|
|
||||||
// 4. 图标处理(压缩/主题适配)
|
// 4. 图标处理(压缩/主题适配)
|
||||||
Drawable processedIcon = processIcon(rawIcon);
|
// Drawable processedIcon = processIcon(rawIcon);
|
||||||
|
|
||||||
// 5. 更新缓存
|
// 5. 更新缓存
|
||||||
mCache.putToMemory(key, processedIcon);
|
mCache.putToMemory(key, rawIcon);
|
||||||
mCache.putToDisk(key, processedIcon);
|
mCache.putToDisk(key, rawIcon);
|
||||||
return processedIcon;
|
return rawIcon;
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
29
iconloader/src/main/java/com/ttstd/iconloader/MD5Util.java
Normal file
29
iconloader/src/main/java/com/ttstd/iconloader/MD5Util.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class MD5Util {
|
||||||
|
public static String getMD5(String input) {
|
||||||
|
try {
|
||||||
|
// 1. 获取 MD5 消息摘要实例
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
// 2. 将输入字符串转换为字节数组。建议指定编码(如UTF-8),但使用默认平台编码时通常可省略。
|
||||||
|
byte[] inputBytes = input.getBytes(); // 如需指定编码,可使用 input.getBytes("UTF-8")
|
||||||
|
// 3. 计算消息摘要,得到128位(16字节)的哈希值
|
||||||
|
byte[] digest = md.digest(inputBytes);
|
||||||
|
// 4. 将字节数组转换为十六进制字符串表示
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : digest) {
|
||||||
|
// 每个字节转换为两位十六进制数,不足两位前面补零
|
||||||
|
sb.append(String.format("%02x", b & 0xff));
|
||||||
|
}
|
||||||
|
// 5. 返回最终的32位十六进制字符串
|
||||||
|
return sb.toString();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// MD5算法是所有Java标准平台必须支持的,此异常通常不会抛出
|
||||||
|
throw new RuntimeException("MD5 algorithm not available", e);
|
||||||
|
}
|
||||||
|
// 若指定了编码(如UTF-8),需捕获 UnsupportedEncodingException,但UTF-8通常也受支持
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package com.uiui.iconloader;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.collection.LruCache;
|
|
||||||
|
|
||||||
import com.jakewharton.disklrucache.DiskLruCache;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class IconCacheManager {
|
|
||||||
private static final String TAG = "IconCacheManager";
|
|
||||||
private LruCache<String, Drawable> mMemoryCache;
|
|
||||||
private DiskLruCache mDiskCache;
|
|
||||||
|
|
||||||
public IconCacheManager(Context context) {
|
|
||||||
// 内存缓存(最大8MB)
|
|
||||||
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
|
||||||
int cacheSize = maxMemory / 8;
|
|
||||||
mMemoryCache = new LruCache<String, Drawable>(cacheSize) {
|
|
||||||
@Override
|
|
||||||
protected int sizeOf(String key, Drawable icon) {
|
|
||||||
if (icon instanceof BitmapDrawable) {
|
|
||||||
return ((BitmapDrawable) icon).getBitmap().getByteCount() / 1024;
|
|
||||||
}
|
|
||||||
return 1; // 非Bitmap对象按1KB计算
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 磁盘缓存(50MB)
|
|
||||||
File cacheDir = new File(context.getCacheDir(), "icon_cache");
|
|
||||||
try {
|
|
||||||
mDiskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "IconCacheManager: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getFromMemory(String key) {
|
|
||||||
return mMemoryCache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getFromDisk(String key) {
|
|
||||||
try {
|
|
||||||
DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
|
|
||||||
if (snapshot != null) {
|
|
||||||
InputStream in = snapshot.getInputStream(0);
|
|
||||||
return Drawable.createFromStream(in, null);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e("IconCache", "Disk read error", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putToMemory(String key, Drawable icon) {
|
|
||||||
mMemoryCache.put(key, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putToDisk(String key, Drawable icon) {
|
|
||||||
try {
|
|
||||||
DiskLruCache.Editor editor = mDiskCache.edit(key);
|
|
||||||
if (icon instanceof BitmapDrawable) {
|
|
||||||
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, editor.newOutputStream(0));
|
|
||||||
editor.commit();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e("IconCache", "Disk write error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.uiui.iconloader;
|
package com.ttstd.iconloader;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
Reference in New Issue
Block a user