package com.datarecovery.master.module.imgrecover; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Pair; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; import com.atmob.app.lib.base.BaseViewModel; import com.atmob.app.lib.livedata.SingleLiveEvent; import com.atmob.common.logging.AtmobLog; import com.atmob.common.runtime.ActivityUtil; import com.atmob.common.runtime.ContextUtil; import com.datarecovery.master.R; import com.datarecovery.master.data.consts.AdFuncId; import com.datarecovery.master.module.member.MemberActivity; import com.datarecovery.master.module.member.MemberType; import com.datarecovery.master.sdk.ad.AtmobAdHelper; import com.datarecovery.master.sdk.bugly.BuglyHelper; import com.datarecovery.master.utils.BoxingUtil; import com.datarecovery.master.utils.FileUtil; import com.datarecovery.master.utils.ImageDeepDetector; import com.datarecovery.master.utils.MediaStoreHelper; import com.datarecovery.master.utils.ReverseArrayList; import com.datarecovery.master.utils.ToastUtil; import org.reactivestreams.Subscription; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import atmob.reactivex.rxjava3.annotations.NonNull; import atmob.reactivex.rxjava3.core.FlowableSubscriber; import atmob.reactivex.rxjava3.core.Single; import atmob.reactivex.rxjava3.core.SingleObserver; import atmob.reactivex.rxjava3.core.SingleOnSubscribe; import atmob.reactivex.rxjava3.core.SingleSource; import atmob.reactivex.rxjava3.disposables.Disposable; import atmob.reactivex.rxjava3.schedulers.Schedulers; import atmob.rxjava.utils.RxJavaUtil; import dagger.hilt.android.lifecycle.HiltViewModel; @HiltViewModel public class ImageRecoverViewModel extends BaseViewModel { private final long SCANNING_COUNTDOWN = 1000 * 60 * 8; private final long SCANNING_IS_TRIAL_COUNTDOWN = 1000 * 5; private final List detectedPhotoList = new ReverseArrayList<>(); private final List detectedWxList = new ReverseArrayList<>(); private final List detectedQQList = new ReverseArrayList<>(); private final List detectedOtherList = new ReverseArrayList<>(); private final List selectList = new ReverseArrayList<>(); private final MutableLiveData> detectedPhotoImg = new MutableLiveData<>(detectedPhotoList); private final MutableLiveData> detectedWxImg = new MutableLiveData<>(detectedWxList); private final MutableLiveData> detectedQQImg = new MutableLiveData<>(detectedQQList); private final MutableLiveData> detectedOtherImg = new MutableLiveData<>(detectedOtherList); private final MutableLiveData> selectedList = new MutableLiveData<>(selectList); private final SingleLiveEvent> deleteUriListSdk11 = new SingleLiveEvent<>(); private final SingleLiveEvent showTrialFinishDialog = new SingleLiveEvent<>(); private final SingleLiveEvent showTrialExportFailDialog = new SingleLiveEvent<>(); private final SingleLiveEvent failScrollPosition = new SingleLiveEvent<>(); private final MutableLiveData showTrialView = new MutableLiveData<>(); private final SingleLiveEvent, Integer>> previewEvent = new SingleLiveEvent<>(); private final SingleLiveEvent showScanDialogEvent = new SingleLiveEvent<>(); private final SingleLiveEvent showLoadingEvent = new SingleLiveEvent<>(); private final SingleLiveEvent scrollTop = new SingleLiveEvent<>(); private final SingleLiveEvent notifyList = new SingleLiveEvent<>(); private final MutableLiveData barTitle = new MutableLiveData<>(); private final MutableLiveData checkAll = new MutableLiveData<>(false); private final MutableLiveData detectedLastFileName = new MutableLiveData<>(); private final SingleLiveEvent onExportClickEvent = new SingleLiveEvent<>(); private LiveData detectedPhotoTitle; private LiveData detectedWxTitle; private LiveData detectedQQTitle; private LiveData detectedOtherTitle; private LiveData selectedCountTxt; private Disposable scanDisposable; @MemberType private String type; private SingleObserver dealMediaObserver; private long detectTime; @Inject public ImageRecoverViewModel() { barTitle.setValue(ContextUtil.getContext().getString(R.string.iamge_recover_all)); initLiveData(); } public LiveData getFailScrollPosition() { return failScrollPosition; } public LiveData getScrollTop() { return scrollTop; } public LiveData getShowTrialFinishDialog() { return showTrialFinishDialog; } public LiveData getShowTrialExportFailDialog() { return showTrialExportFailDialog; } public LiveData getDetectedLastFileName() { return detectedLastFileName; } public LiveData getShowTrialView() { return showTrialView; } public LiveData> getDeleteUriListSdk11() { return deleteUriListSdk11; } public LiveData getNotifyList() { return notifyList; } public LiveData getShowLoadingEvent() { return showLoadingEvent; } public LiveData, Integer>> getPreviewEvent() { return previewEvent; } public LiveData getSelectedCountTxt() { return selectedCountTxt; } public LiveData> getSelectedList() { return selectedList; } public LiveData> getDetectedPhotoImg() { return detectedPhotoImg; } public LiveData> getDetectedWxImg() { return detectedWxImg; } public LiveData> getDetectedQQImg() { return detectedQQImg; } public LiveData> getDetectedOtherImg() { return detectedOtherImg; } public LiveData getDetectedPhotoTitle() { return detectedPhotoTitle; } public LiveData getDetectedWxTitle() { return detectedWxTitle; } public LiveData getDetectedQQTitle() { return detectedQQTitle; } public LiveData getDetectedOtherTitle() { return detectedOtherTitle; } public LiveData getShowScanDialogEvent() { return showScanDialogEvent; } public LiveData getCheckAll() { return checkAll; } public LiveData getBarTitle() { return barTitle; } public LiveData getOnExportClickEvent() { return onExportClickEvent; } public String getType() { return type; } public void setType(@MemberType String type) { if (!TextUtils.isEmpty(this.type)) { return; } this.type = type; switch (type) { case MemberType.APP_IMAGE_CLEAN: selectedCountTxt = Transformations.map(selectedList, list -> { if (list == null || list.isEmpty()) { return ContextUtil.getContext().getString(R.string.delete); } return ContextUtil.getContext().getString(R.string.delete_count, list.size()); }); break; case MemberType.APP_IMAGE_RECOVER: selectedCountTxt = Transformations.map(selectedList, list -> { if (list == null || list.isEmpty()) { return ContextUtil.getContext().getString(R.string.export); } return ContextUtil.getContext().getString(R.string.export_count, list.size()); }); break; } } private void initLiveData() { detectedPhotoTitle = Transformations.map(detectedPhotoImg, list -> ContextUtil.getContext().getString(R.string.photo_count, (list == null || list.isEmpty()) ? 0 : list.size())); detectedWxTitle = Transformations.map(detectedWxImg, list -> ContextUtil.getContext().getString(R.string.wx_count, (list == null || list.isEmpty()) ? 0 : list.size())); detectedQQTitle = Transformations.map(detectedQQImg, list -> ContextUtil.getContext().getString(R.string.qq_count, (list == null || list.isEmpty()) ? 0 : list.size())); detectedOtherTitle = Transformations.map(detectedOtherImg, list -> ContextUtil.getContext().getString(R.string.other_count, (list == null || list.isEmpty()) ? 0 : list.size())); } public void onCheckAllClick(boolean isCheck) { checkAll.setValue(isCheck); setListCheck(detectedPhotoList, isCheck); setListCheck(detectedWxList, isCheck); setListCheck(detectedQQList, isCheck); setListCheck(detectedOtherList, isCheck); if (isCheck) { selectList.clear(); selectList.addAll(detectedPhotoList); selectList.addAll(detectedWxList); selectList.addAll(detectedQQList); selectList.addAll(detectedOtherList); } else { selectList.clear(); } selectedList.setValue(selectList); } private void setListCheck(List list, boolean isCheck) { if (list == null || list.isEmpty()) { return; } for (ImageDeepDetector.ImageFile imageFile : list) { imageFile.setCheck(isCheck); } } public void startImageScanning() { if (scanDisposable != null && !scanDisposable.isDisposed()) { return; } ImageDeepDetector.detect(ContextUtil.getContext(), type) .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new FlowableSubscriber>() { @Override public void onSubscribe(@NonNull Subscription s) { s.request(Integer.MAX_VALUE); scanDisposable = Disposable.fromSubscription(s); addDisposable(scanDisposable); showScanDialogEvent.setValue(true); checkAll.setValue(false); detectTime = System.currentTimeMillis(); detectedPhotoList.clear(); detectedWxList.clear(); detectedQQList.clear(); detectedOtherList.clear(); selectList.clear(); } @Override public void onNext(List imageFiles) { if (imageFiles == null) { return; } int photoCount = 0; int wxCount = 0; int qqCount = 0; int otherCount = 0; for (ImageDeepDetector.ImageFile imageFile : imageFiles) { detectedLastFileName.setValue(imageFile.getName()); if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) { detectedPhotoList.add(imageFile); photoCount++; } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) { detectedWxList.add(imageFile); wxCount++; } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) { detectedQQList.add(imageFile); qqCount++; } else { detectedOtherList.add(imageFile); otherCount++; } } if (photoCount > 0) { detectedPhotoImg.setValue(detectedPhotoList); } if (wxCount > 0) { detectedWxImg.setValue(detectedWxList); } if (qqCount > 0) { detectedQQImg.setValue(detectedQQList); } if (otherCount > 0) { detectedOtherImg.setValue(detectedOtherList); } scrollTop.call(); } @Override public void onError(Throwable t) { showScanDialogEvent.setValue(false); } @Override public void onComplete() { AtmobLog.d("ImageRecoverViewModel", System.currentTimeMillis() - detectTime + "ms"); showScanDialogEvent.setValue(false); } }); } public void cancelScan() { if (scanDisposable != null && !scanDisposable.isDisposed()) scanDisposable.dispose(); showScanDialogEvent.setValue(false); } public void setItemCheck(@NonNull ImageDeepDetector.ImageFile imageFile) { imageFile.setCheck(!imageFile.isCheck()); if (imageFile.isCheck()) { selectList.add(imageFile); } else { selectList.remove(imageFile); } selectedList.setValue(selectList); } public void checkPreview(@NonNull ImageDeepDetector.ImageFile imageFile) { if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN)) { return; } sendPreviewEvent(imageFile); } public void sendPreviewEvent(@NonNull ImageDeepDetector.ImageFile imageFile) { List allDetectedList = getAllDetectedList(); int position = getPosition(allDetectedList, imageFile); previewEvent.setValue(new Pair<>(allDetectedList, position)); } public void onOperationClick() { if (selectList.isEmpty()) { return; } onExportClickEvent.call(); } public void executeImageDelete() { if (selectList.isEmpty()) { return; } Single.just(Build.VERSION.SDK_INT) .map(sdkInt -> sdkInt >= Build.VERSION_CODES.R) .map(isSdk11 -> { List safList = new ArrayList<>(); List uriList = new ArrayList<>(); if (isSdk11) { for (ImageDeepDetector.ImageFile item : selectList) { File tempFile = new File(item.getPath()); long mediaID = getFilePathToMediaID(tempFile.getAbsolutePath(), ContextUtil.getContext()); if (mediaID > 0) { Uri externalUri = ContentUris.withAppendedId(MediaStore.Images.Media.getContentUri("external"), mediaID); item.setExternalUri(externalUri); uriList.add(item); } else { safList.add(item); } } } else { safList.addAll(selectList); } return new Pair<>(safList, uriList); }) .flatMap(pair -> Single.zip( imageDelete(pair.first).subscribeOn(Schedulers.io()), externalDelete(pair.second).subscribeOn(Schedulers.io()), Pair::new )) .compose(RxJavaUtil.SingleSchedule.io2Main()) .subscribe(new SingleObserver, Pair>>() { @Override public void onSubscribe(@NonNull Disposable d) { addDisposable(d); showLoadingEvent.setValue(true); } @Override public void onSuccess(@NonNull Pair, Pair> pairPairPair) { showLoadingEvent.setValue(false); if (pairPairPair.first.second != null) { ToastUtil.show("部分图片清除失败:" + pairPairPair.first.second.getMessage(), ToastUtil.LENGTH_SHORT); failScrollPosition.setValue(pairPairPair.first.first); return; } if (pairPairPair.second.second instanceof CancelDeleteException) { return; } if (pairPairPair.second.second != null) { ToastUtil.show("部分图片清除失败:" + pairPairPair.second.second.getMessage(), ToastUtil.LENGTH_SHORT); failScrollPosition.setValue(pairPairPair.second.first); return; } if (pairPairPair.first.first == null && pairPairPair.second.first == null) { ToastUtil.show(R.string.delete_success, ToastUtil.LENGTH_SHORT); } AtmobAdHelper.showInterstitial(AdFuncId.INTERSTITIAL_CLEAN_SUCCESS, null); } @Override public void onError(@NonNull Throwable e) { showLoadingEvent.setValue(false); ToastUtil.show(R.string.delete_fail, ToastUtil.LENGTH_SHORT); } }); } private @NonNull Single> externalDelete(List externalList) { return Single.create((SingleOnSubscribe>) emitter -> { List uriDeleteList = new ArrayList<>(); if (externalList.isEmpty()) { emitter.onSuccess(uriDeleteList); return; } for (ImageDeepDetector.ImageFile item : externalList) { uriDeleteList.add(item.getExternalUri()); } emitter.onSuccess(uriDeleteList); }) .flatMap(uriDeleteList -> { if (!uriDeleteList.isEmpty()) { deleteUriListSdk11.postValue(uriDeleteList); return (SingleSource) observer -> this.dealMediaObserver = observer; } else { return Single.just(true); } }) .flatMap(state -> { if (BoxingUtil.boxing(state)) { Exception exception = null; ImageDeepDetector.ImageFile deleteError = null; for (ImageDeepDetector.ImageFile item : externalList) { try { item.delete(); selectList.remove(item); if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) { detectedPhotoList.remove(item); } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) { detectedWxList.remove(item); } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) { detectedQQList.remove(item); } else { detectedOtherList.remove(item); } } catch (Exception throwable) { exception = throwable; deleteError = item; BuglyHelper.postCatchedException(new Exception("图片删除External:", throwable)); } } selectedList.postValue(selectList); notifyList.postValue(null); return Single.just(new Pair<>(deleteError, exception)); } else { return Single.just(new Pair<>(null, new CancelDeleteException())); } }); } private Single> imageDelete(List safList) { return Single.create(emitter -> { Exception exception = null; ImageDeepDetector.ImageFile deleteError = null; if (safList.isEmpty()) { emitter.onSuccess(new Pair<>(deleteError, exception)); return; } for (ImageDeepDetector.ImageFile item : safList) { try { item.delete(); selectList.remove(item); if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) { detectedPhotoList.remove(item); } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) { detectedWxList.remove(item); } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) { detectedQQList.remove(item); } else { detectedOtherList.remove(item); } } catch (Exception throwable) { exception = throwable; deleteError = item; BuglyHelper.postCatchedException(new Exception("图片删除:", throwable)); } } selectedList.postValue(selectList); notifyList.postValue(null); emitter.onSuccess(new Pair<>(deleteError, exception)); }); } public long getFilePathToMediaID(String songPath, Context context) { long id = 0; ContentResolver cr = context.getContentResolver(); Uri uri = MediaStore.Files.getContentUri("external"); String selection = MediaStore.Audio.Media.DATA; String[] selectionArgs = {songPath}; String[] projection = {MediaStore.Audio.Media._ID}; String sortOrder = MediaStore.Audio.Media.TITLE + " ASC"; Cursor cursor = cr.query(uri, projection, selection + "=?", selectionArgs, null); if (cursor != null) { while (cursor.moveToNext()) { int idIndex = cursor.getColumnIndex(MediaStore.Audio.Media._ID); id = Long.parseLong(cursor.getString(idIndex)); } } return id; } public void exportImage() { if (selectList.isEmpty()) { return; } Single.just(selectList.size()) .map(oldSize -> { Iterator iterator = selectList.iterator(); int errorCount = 0; Exception exception = null; while (iterator.hasNext()) { ImageDeepDetector.ImageFile item = iterator.next(); try { MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, item.newInputStream(), FileUtil.getCreateFileName(MediaStoreHelper.TYPE_IMAGE, item.getName())); item.setCheck(false); iterator.remove(); } catch (Exception throwable) { errorCount++; exception = throwable; BuglyHelper.postCatchedException(new Exception("图片导出:", throwable)); } } if (errorCount == oldSize) { throw new Exception("导出失败" + (exception == null ? "" : ":" + exception.getMessage())); } else if (errorCount > 0) { throw new Exception("部分导出失败" + ":" + exception.getMessage()); } return errorCount; }) .compose(RxJavaUtil.SingleSchedule.io2Main()) .subscribe(new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { addDisposable(d); showLoadingEvent.setValue(true); } @Override public void onSuccess(@NonNull Integer integer) { showLoadingEvent.setValue(false); selectedList.setValue(selectList); checkAll.setValue(false); ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT); AtmobAdHelper.showInterstitial(AdFuncId.INTERSTITIAL_RECOVER_SUCCESS, null); } @Override public void onError(@NonNull Throwable e) { showLoadingEvent.setValue(false); selectedList.setValue(selectList); ToastUtil.show(e.getMessage(), ToastUtil.LENGTH_SHORT); if (!selectList.isEmpty()) { failScrollPosition.setValue(selectList.get(0)); } } }); } public List getAllDetectedList() { List allList = new ReverseArrayList<>(); allList.addAll(detectedOtherList); allList.addAll(detectedQQList); allList.addAll(detectedWxList); allList.addAll(detectedPhotoList); return allList; } public int getPosition(@NonNull List list, ImageDeepDetector.ImageFile target) { return list.indexOf(target); } public void callDealComplete(boolean isDelete) { if (dealMediaObserver != null) { dealMediaObserver.onSuccess(isDelete); } } public void onTrialRecoverClick() { MemberActivity.start(ActivityUtil.getTopActivity(), type); } public void onViewTrialRecoverClick() { MemberActivity.start(ActivityUtil.getTopActivity(), type); } public void scrollPosition(int lastCompletelyItemPosition, int itemCount) { if (scanDisposable == null || !scanDisposable.isDisposed()) { return; } if (lastCompletelyItemPosition == itemCount - 1 && !BoxingUtil.boxing(showScanDialogEvent.getValue())) { showTrialFinishDialog.call(); } } private static class CancelDeleteException extends Exception { } }