ImageRecoverViewModel.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. package com.datarecovery.master.module.imgrecover;
  2. import android.app.PendingIntent;
  3. import android.content.ContentResolver;
  4. import android.content.ContentUris;
  5. import android.content.Context;
  6. import android.content.IntentSender;
  7. import android.database.Cursor;
  8. import android.net.Uri;
  9. import android.os.Build;
  10. import android.os.Handler;
  11. import android.os.Looper;
  12. import android.provider.MediaStore;
  13. import android.text.TextUtils;
  14. import android.util.Log;
  15. import android.util.Pair;
  16. import androidx.lifecycle.LiveData;
  17. import androidx.lifecycle.MutableLiveData;
  18. import androidx.lifecycle.Observer;
  19. import androidx.lifecycle.Transformations;
  20. import com.atmob.app.lib.base.BaseViewModel;
  21. import com.atmob.app.lib.livedata.SingleLiveEvent;
  22. import com.atmob.common.runtime.ActivityUtil;
  23. import com.atmob.common.runtime.ContextUtil;
  24. import com.datarecovery.master.R;
  25. import com.datarecovery.master.data.consts.EventId;
  26. import com.datarecovery.master.data.repositories.DeviceFuncRepository;
  27. import com.datarecovery.master.handler.EventHelper;
  28. import com.datarecovery.master.module.member.MemberType;
  29. import com.datarecovery.master.utils.BoxingUtil;
  30. import com.datarecovery.master.utils.FileUtil;
  31. import com.datarecovery.master.utils.ImageDeepDetector;
  32. import com.datarecovery.master.utils.MediaStoreHelper;
  33. import com.datarecovery.master.utils.ToastUtil;
  34. import com.datarecovery.master.utils.xfile.XSAFFile;
  35. import org.reactivestreams.Subscription;
  36. import java.io.File;
  37. import java.io.InputStream;
  38. import java.util.ArrayList;
  39. import java.util.List;
  40. import java.util.Objects;
  41. import java.util.concurrent.TimeUnit;
  42. import javax.inject.Inject;
  43. import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
  44. import atmob.reactivex.rxjava3.annotations.NonNull;
  45. import atmob.reactivex.rxjava3.core.FlowableSubscriber;
  46. import atmob.reactivex.rxjava3.core.Single;
  47. import atmob.reactivex.rxjava3.core.SingleObserver;
  48. import atmob.reactivex.rxjava3.core.SingleSource;
  49. import atmob.reactivex.rxjava3.disposables.Disposable;
  50. import atmob.reactivex.rxjava3.functions.Function;
  51. import atmob.rxjava.utils.RxJavaUtil;
  52. import dagger.hilt.android.lifecycle.HiltViewModel;
  53. @HiltViewModel
  54. public class ImageRecoverViewModel extends BaseViewModel {
  55. private final long SCANNING_COUNTDOWN = 1000 * 60 * 6;
  56. private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedPhotoImg = new MutableLiveData<>(new ArrayList<>());
  57. private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedWxImg = new MutableLiveData<>(new ArrayList<>());
  58. private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedQQImg = new MutableLiveData<>(new ArrayList<>());
  59. private final MutableLiveData<List<ImageDeepDetector.ImageFile>> detectedOtherImg = new MutableLiveData<>(new ArrayList<>());
  60. private final SingleLiveEvent<List<Uri>> deleteUriListSdk11 = new SingleLiveEvent<>();
  61. private final DeviceFuncRepository deviceFuncRepository;
  62. private LiveData<String> detectedPhotoTitle;
  63. private LiveData<String> detectedWxTitle;
  64. private LiveData<String> detectedQQTitle;
  65. private LiveData<String> detectedOtherTitle;
  66. private final SingleLiveEvent<ImageDeepDetector.ImageFile> previewEvent = new SingleLiveEvent<>();
  67. private final SingleLiveEvent<?> detectedFinish = new SingleLiveEvent<>();
  68. private final SingleLiveEvent<?> showClearDialog = new SingleLiveEvent<>();
  69. private final SingleLiveEvent<Boolean> showScanDialogEvent = new SingleLiveEvent<>();
  70. private final SingleLiveEvent<Boolean> showLoadingEvent = new SingleLiveEvent<>();
  71. private final SingleLiveEvent<?> notifyList = new SingleLiveEvent<>();
  72. private final MutableLiveData<String> barTitle = new MutableLiveData<>();
  73. private final MutableLiveData<Boolean> checkAll = new MutableLiveData<>(false);
  74. //总探测到的图片数量
  75. private int totalCount = 0;
  76. private final MutableLiveData<Integer> totalDetectedCount = new MutableLiveData<>();
  77. private final MutableLiveData<List<ImageDeepDetector.ImageFile>> selectedList = new MutableLiveData<>(new ArrayList<>());
  78. private LiveData<String> selectedCountTxt;
  79. private Disposable scanDisposable;
  80. @MemberType
  81. private String type;
  82. private SingleObserver<? super Boolean> dealMediaObserver;
  83. @Inject
  84. public ImageRecoverViewModel(DeviceFuncRepository deviceFuncRepository) {
  85. this.deviceFuncRepository = deviceFuncRepository;
  86. barTitle.setValue(ContextUtil.getContext().getString(R.string.iamge_recover_all));
  87. initLiveData();
  88. }
  89. public LiveData<List<Uri>> getDeleteUriListSdk11() {
  90. return deleteUriListSdk11;
  91. }
  92. public LiveData<?> getShowClearDialog() {
  93. return showClearDialog;
  94. }
  95. public LiveData<?> getNotifyList() {
  96. return notifyList;
  97. }
  98. public LiveData<Boolean> getShowLoadingEvent() {
  99. return showLoadingEvent;
  100. }
  101. public LiveData<ImageDeepDetector.ImageFile> getPreviewEvent() {
  102. return previewEvent;
  103. }
  104. public LiveData<?> getDetectedFinish() {
  105. return detectedFinish;
  106. }
  107. public LiveData<Integer> getTotalDetectedCount() {
  108. return totalDetectedCount;
  109. }
  110. public LiveData<String> getSelectedCountTxt() {
  111. return selectedCountTxt;
  112. }
  113. public LiveData<List<ImageDeepDetector.ImageFile>> getSelectedList() {
  114. return selectedList;
  115. }
  116. public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedPhotoImg() {
  117. return detectedPhotoImg;
  118. }
  119. public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedWxImg() {
  120. return detectedWxImg;
  121. }
  122. public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedQQImg() {
  123. return detectedQQImg;
  124. }
  125. public LiveData<List<ImageDeepDetector.ImageFile>> getDetectedOtherImg() {
  126. return detectedOtherImg;
  127. }
  128. public LiveData<String> getDetectedPhotoTitle() {
  129. return detectedPhotoTitle;
  130. }
  131. public LiveData<String> getDetectedWxTitle() {
  132. return detectedWxTitle;
  133. }
  134. public LiveData<String> getDetectedQQTitle() {
  135. return detectedQQTitle;
  136. }
  137. public LiveData<String> getDetectedOtherTitle() {
  138. return detectedOtherTitle;
  139. }
  140. public LiveData<Boolean> getShowScanDialogEvent() {
  141. return showScanDialogEvent;
  142. }
  143. public LiveData<Boolean> getCheckAll() {
  144. return checkAll;
  145. }
  146. public LiveData<String> getBarTitle() {
  147. return barTitle;
  148. }
  149. public String getType() {
  150. return type;
  151. }
  152. private void initLiveData() {
  153. detectedPhotoTitle = Transformations.map(detectedPhotoImg, list ->
  154. ContextUtil.getContext().getString(R.string.photo_count, (list == null || list.isEmpty()) ? 0 : list.size()));
  155. detectedWxTitle = Transformations.map(detectedWxImg, list ->
  156. ContextUtil.getContext().getString(R.string.wx_count, (list == null || list.isEmpty()) ? 0 : list.size()));
  157. detectedQQTitle = Transformations.map(detectedQQImg, list ->
  158. ContextUtil.getContext().getString(R.string.qq_count, (list == null || list.isEmpty()) ? 0 : list.size()));
  159. detectedOtherTitle = Transformations.map(detectedOtherImg, list ->
  160. ContextUtil.getContext().getString(R.string.other_count, (list == null || list.isEmpty()) ? 0 : list.size()));
  161. }
  162. public void onCheckAllClick(boolean isCheck) {
  163. checkAll.setValue(isCheck);
  164. List<ImageDeepDetector.ImageFile> selectList = getList(selectedList);
  165. setListCheck(detectedPhotoImg.getValue(), isCheck);
  166. setListCheck(detectedWxImg.getValue(), isCheck);
  167. setListCheck(detectedQQImg.getValue(), isCheck);
  168. setListCheck(detectedOtherImg.getValue(), isCheck);
  169. if (isCheck) {
  170. selectList.clear();
  171. selectList.addAll(getList(detectedPhotoImg));
  172. selectList.addAll(getList(detectedWxImg));
  173. selectList.addAll(getList(detectedQQImg));
  174. selectList.addAll(getList(detectedOtherImg));
  175. } else {
  176. selectList.clear();
  177. }
  178. selectedList.setValue(selectList);
  179. }
  180. private void setListCheck(List<ImageDeepDetector.ImageFile> list, boolean isCheck) {
  181. if (list == null || list.isEmpty()) {
  182. return;
  183. }
  184. for (ImageDeepDetector.ImageFile imageFile : list) {
  185. imageFile.setCheck(isCheck);
  186. }
  187. }
  188. private List<ImageDeepDetector.ImageFile> getList(LiveData<List<ImageDeepDetector.ImageFile>> liveData) {
  189. List<ImageDeepDetector.ImageFile> selectList = liveData.getValue();
  190. if (selectList == null) {
  191. selectList = new ArrayList<>();
  192. }
  193. return selectList;
  194. }
  195. public void startImageScanning() {
  196. if (scanDisposable != null && !scanDisposable.isDisposed()) {
  197. return;
  198. }
  199. ImageDeepDetector.detect(ContextUtil.getContext())
  200. .take(SCANNING_COUNTDOWN, TimeUnit.MILLISECONDS)
  201. .observeOn(AndroidSchedulers.mainThread())
  202. .subscribe(new FlowableSubscriber<List<ImageDeepDetector.ImageFile>>() {
  203. @Override
  204. public void onSubscribe(@NonNull Subscription s) {
  205. s.request(Integer.MAX_VALUE);
  206. scanDisposable = Disposable.fromSubscription(s);
  207. addDisposable(scanDisposable);
  208. showScanDialogEvent.setValue(true);
  209. checkAll.setValue(false);
  210. totalCount = 0;
  211. totalDetectedCount.setValue(0);
  212. }
  213. @Override
  214. public void onNext(List<ImageDeepDetector.ImageFile> imageFiles) {
  215. if (imageFiles == null) {
  216. return;
  217. }
  218. totalCount += imageFiles.size();
  219. totalDetectedCount.setValue(totalCount);
  220. for (ImageDeepDetector.ImageFile imageFile : imageFiles) {
  221. MutableLiveData<List<ImageDeepDetector.ImageFile>> liveData;
  222. if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) {
  223. liveData = detectedPhotoImg;
  224. } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) {
  225. liveData = detectedWxImg;
  226. } else if (imageFile.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) {
  227. liveData = detectedQQImg;
  228. } else {
  229. liveData = detectedOtherImg;
  230. }
  231. List<ImageDeepDetector.ImageFile> list = getList(liveData);
  232. list.add(0, imageFile);
  233. liveData.setValue(list);
  234. }
  235. }
  236. @Override
  237. public void onError(Throwable t) {
  238. showScanDialogEvent.setValue(false);
  239. }
  240. @Override
  241. public void onComplete() {
  242. detectedFinish.call();
  243. }
  244. });
  245. }
  246. public void cancelScan() {
  247. if (scanDisposable != null) scanDisposable.dispose();
  248. }
  249. public void setItemCheck(@NonNull ImageDeepDetector.ImageFile imageFile) {
  250. imageFile.setCheck(!imageFile.isCheck());
  251. List<ImageDeepDetector.ImageFile> list = getList(this.selectedList);
  252. if (imageFile.isCheck()) {
  253. list.add(imageFile);
  254. } else {
  255. list.remove(imageFile);
  256. }
  257. selectedList.setValue(list);
  258. }
  259. public void checkPreview(@NonNull ImageDeepDetector.ImageFile imageFile) {
  260. if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN)) {
  261. return;
  262. }
  263. if (Objects.equals(type, MemberType.APP_IMAGE_RECOVER) && !deviceFuncRepository.isHaveAuth(type)) {
  264. return;
  265. } else if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN) && !deviceFuncRepository.isHaveAuth(type)) {
  266. return;
  267. }
  268. previewEvent.setValue(imageFile);
  269. }
  270. public void onOperationClick() {
  271. if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN)) {
  272. showClearDialog.call();
  273. } else {
  274. onExportClick();
  275. }
  276. }
  277. public void executeImageDelete() {
  278. List<ImageDeepDetector.ImageFile> list = getList(selectedList);
  279. if (list.size() == 0) {
  280. return;
  281. }
  282. Single.just(Build.VERSION.SDK_INT)
  283. .map(sdkInt -> sdkInt >= Build.VERSION_CODES.R)
  284. .map(isSdk11 -> {
  285. List<ImageDeepDetector.ImageFile> safList = new ArrayList<>();
  286. List<Uri> uriList = new ArrayList<>();
  287. if (isSdk11) {
  288. for (ImageDeepDetector.ImageFile item : list) {
  289. File tempFile = new File(item.getPath());
  290. long mediaID = getFilePathToMediaID(tempFile.getAbsolutePath(), ContextUtil.getContext());
  291. if (mediaID > 0) {
  292. Uri Uri_one = ContentUris.withAppendedId(MediaStore.Images.Media.getContentUri("external"), mediaID);
  293. uriList.add(Uri_one);
  294. } else {
  295. safList.add(item);
  296. }
  297. }
  298. } else {
  299. safList.addAll(list);
  300. }
  301. return new Pair<>(safList, uriList);
  302. })
  303. .map(pair -> {
  304. if (pair.first != null && !pair.first.isEmpty()) {
  305. for (ImageDeepDetector.ImageFile item : pair.first) {
  306. item.delete();
  307. MutableLiveData<List<ImageDeepDetector.ImageFile>> liveData;
  308. if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) {
  309. liveData = detectedPhotoImg;
  310. } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) {
  311. liveData = detectedWxImg;
  312. } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) {
  313. liveData = detectedQQImg;
  314. } else {
  315. liveData = detectedOtherImg;
  316. }
  317. List<ImageDeepDetector.ImageFile> itemList = getList(liveData);
  318. itemList.remove(item);
  319. }
  320. list.removeAll(pair.first);
  321. selectedList.postValue(list);
  322. notifyList.postValue(null);
  323. }
  324. return pair.second;
  325. })
  326. .map(uriList -> {
  327. if (uriList != null && !uriList.isEmpty()) {
  328. deleteUriListSdk11.postValue(uriList);
  329. return false;
  330. } else {
  331. return true;
  332. }
  333. })
  334. .flatMap(aBoolean -> {
  335. if (BoxingUtil.boxing(aBoolean)) {
  336. return Single.just(true);
  337. } else {
  338. return (SingleSource<Boolean>) observer -> this.dealMediaObserver = observer;
  339. }
  340. })
  341. .compose(RxJavaUtil.SingleSchedule.io2Main())
  342. .subscribe(new SingleObserver<Boolean>() {
  343. @Override
  344. public void onSubscribe(@NonNull Disposable d) {
  345. showLoadingEvent.setValue(true);
  346. }
  347. @Override
  348. public void onSuccess(@NonNull Boolean result) {
  349. showLoadingEvent.setValue(false);
  350. if (result) {
  351. for (ImageDeepDetector.ImageFile item : list) {
  352. try {
  353. item.delete();
  354. } catch (Exception e) {
  355. e.printStackTrace();
  356. }
  357. MutableLiveData<List<ImageDeepDetector.ImageFile>> liveData;
  358. if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_GALLERY) {
  359. liveData = detectedPhotoImg;
  360. } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_WECHAT) {
  361. liveData = detectedWxImg;
  362. } else if (item.getCategory() == ImageDeepDetector.ImageFile.CATEGORY_QQ) {
  363. liveData = detectedQQImg;
  364. } else {
  365. liveData = detectedOtherImg;
  366. }
  367. List<ImageDeepDetector.ImageFile> itemList = getList(liveData);
  368. itemList.remove(item);
  369. }
  370. ToastUtil.show(R.string.delete_success, ToastUtil.LENGTH_SHORT);
  371. list.clear();
  372. selectedList.setValue(list);
  373. notifyList.call();
  374. }
  375. checkAll.setValue(false);
  376. }
  377. @Override
  378. public void onError(@NonNull Throwable e) {
  379. showLoadingEvent.setValue(false);
  380. ToastUtil.show(R.string.delete_fail, ToastUtil.LENGTH_SHORT);
  381. }
  382. });
  383. }
  384. public long getFilePathToMediaID(String songPath, Context context) {
  385. long id = 0;
  386. ContentResolver cr = context.getContentResolver();
  387. Uri uri = MediaStore.Files.getContentUri("external");
  388. String selection = MediaStore.Audio.Media.DATA;
  389. String[] selectionArgs = {songPath};
  390. String[] projection = {MediaStore.Audio.Media._ID};
  391. String sortOrder = MediaStore.Audio.Media.TITLE + " ASC";
  392. Cursor cursor = cr.query(uri, projection, selection + "=?", selectionArgs, null);
  393. if (cursor != null) {
  394. while (cursor.moveToNext()) {
  395. int idIndex = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
  396. id = Long.parseLong(cursor.getString(idIndex));
  397. }
  398. }
  399. return id;
  400. }
  401. public void onExportClick() {
  402. List<ImageDeepDetector.ImageFile> list = getList(selectedList);
  403. if (list.size() == 0) {
  404. return;
  405. }
  406. if (Objects.equals(type, MemberType.APP_IMAGE_RECOVER) && !deviceFuncRepository.isHaveAuth(type)) {
  407. return;
  408. } else if (Objects.equals(type, MemberType.APP_IMAGE_CLEAN) && !deviceFuncRepository.isHaveAuth(type)) {
  409. return;
  410. }
  411. showLoadingEvent.setValue(true);
  412. RxJavaUtil.doInBackground(() -> {
  413. for (ImageDeepDetector.ImageFile item : list) {
  414. MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_IMAGE, item.newInputStream(), FileUtil.getExportImageFileName(item.getName()));
  415. item.setCheck(false);
  416. }
  417. return true;
  418. }, o -> {
  419. checkAll.setValue(false);
  420. showLoadingEvent.setValue(false);
  421. ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT);
  422. list.clear();
  423. selectedList.setValue(list);
  424. }, throwable -> {
  425. showLoadingEvent.setValue(false);
  426. ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
  427. });
  428. }
  429. public List<ImageDeepDetector.ImageFile> getAllDetectedList() {
  430. ArrayList<ImageDeepDetector.ImageFile> allList = new ArrayList<>();
  431. allList.addAll(getList(detectedPhotoImg));
  432. allList.addAll(getList(detectedWxImg));
  433. allList.addAll(getList(detectedQQImg));
  434. allList.addAll(getList(detectedOtherImg));
  435. return allList;
  436. }
  437. public int getPosition(@NonNull List<ImageDeepDetector.ImageFile> list, ImageDeepDetector.ImageFile target) {
  438. return list.indexOf(target);
  439. }
  440. public void setType(@MemberType String type) {
  441. if (!TextUtils.isEmpty(this.type)) {
  442. return;
  443. }
  444. this.type = type;
  445. switch (type) {
  446. case MemberType.APP_IMAGE_CLEAN:
  447. EventHelper.report(EventId.hf1000514);
  448. EventHelper.timeEvent(EventId.hf1000529);
  449. selectedCountTxt = Transformations.map(selectedList, list -> {
  450. if (list == null || list.isEmpty()) {
  451. return ContextUtil.getContext().getString(R.string.delete);
  452. }
  453. return ContextUtil.getContext().getString(R.string.delete_count, list.size());
  454. });
  455. break;
  456. case MemberType.APP_IMAGE_RECOVER:
  457. EventHelper.report(EventId.hf1000506);
  458. EventHelper.timeEvent(EventId.hf1000521);
  459. selectedCountTxt = Transformations.map(selectedList, list -> {
  460. if (list == null || list.isEmpty()) {
  461. return ContextUtil.getContext().getString(R.string.export);
  462. }
  463. return ContextUtil.getContext().getString(R.string.export_count, list.size());
  464. });
  465. break;
  466. }
  467. }
  468. @Override
  469. protected void onCleared() {
  470. super.onCleared();
  471. switch (type) {
  472. case MemberType.APP_IMAGE_CLEAN:
  473. EventHelper.report(EventId.hf1000529);
  474. break;
  475. case MemberType.APP_IMAGE_RECOVER:
  476. EventHelper.report(EventId.hf1000521);
  477. break;
  478. }
  479. }
  480. public void callDealComplete(boolean isDelete) {
  481. if (dealMediaObserver != null) {
  482. dealMediaObserver.onSuccess(isDelete);
  483. }
  484. }
  485. }