MediaGalleryActivity.java 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. /* _____ _
  2. * |_ _| |_ _ _ ___ ___ _ __ __ _
  3. * | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. * |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. *
  6. * Threema for Android
  7. * Copyright (c) 2014-2021 Threema GmbH
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. package ch.threema.app.activities;
  22. import android.Manifest;
  23. import android.annotation.SuppressLint;
  24. import android.annotation.TargetApi;
  25. import android.content.DialogInterface;
  26. import android.content.Intent;
  27. import android.content.pm.PackageManager;
  28. import android.content.res.Configuration;
  29. import android.content.res.TypedArray;
  30. import android.os.AsyncTask;
  31. import android.os.Bundle;
  32. import android.os.Handler;
  33. import android.util.SparseBooleanArray;
  34. import android.view.ActionMode;
  35. import android.view.Menu;
  36. import android.view.MenuItem;
  37. import android.view.View;
  38. import android.widget.AbsListView;
  39. import android.widget.AdapterView;
  40. import android.widget.FrameLayout;
  41. import android.widget.ProgressBar;
  42. import android.widget.TextView;
  43. import com.google.android.material.snackbar.Snackbar;
  44. import org.slf4j.Logger;
  45. import org.slf4j.LoggerFactory;
  46. import java.io.File;
  47. import java.sql.SQLException;
  48. import java.util.ArrayList;
  49. import java.util.Date;
  50. import java.util.Iterator;
  51. import java.util.List;
  52. import java.util.concurrent.CopyOnWriteArrayList;
  53. import androidx.annotation.NonNull;
  54. import androidx.appcompat.app.ActionBar;
  55. import ch.threema.app.R;
  56. import ch.threema.app.ThreemaApplication;
  57. import ch.threema.app.adapters.MediaGalleryAdapter;
  58. import ch.threema.app.adapters.MediaGallerySpinnerAdapter;
  59. import ch.threema.app.cache.ThumbnailCache;
  60. import ch.threema.app.dialogs.CancelableHorizontalProgressDialog;
  61. import ch.threema.app.dialogs.GenericAlertDialog;
  62. import ch.threema.app.managers.ServiceManager;
  63. import ch.threema.app.messagereceiver.MessageReceiver;
  64. import ch.threema.app.services.ContactService;
  65. import ch.threema.app.services.DistributionListService;
  66. import ch.threema.app.services.FileService;
  67. import ch.threema.app.services.GroupService;
  68. import ch.threema.app.services.MessageService;
  69. import ch.threema.app.ui.EmptyView;
  70. import ch.threema.app.ui.FastScrollGridView;
  71. import ch.threema.app.utils.AnimationUtil;
  72. import ch.threema.app.utils.AppRestrictionUtil;
  73. import ch.threema.app.utils.ConfigUtils;
  74. import ch.threema.app.utils.DialogUtil;
  75. import ch.threema.app.utils.FileUtil;
  76. import ch.threema.app.utils.IntentDataUtil;
  77. import ch.threema.app.utils.LocaleUtil;
  78. import ch.threema.app.utils.LogUtil;
  79. import ch.threema.app.utils.NameUtil;
  80. import ch.threema.app.utils.RuntimeUtil;
  81. import ch.threema.app.utils.TestUtil;
  82. import ch.threema.storage.models.AbstractMessageModel;
  83. import ch.threema.storage.models.ContactModel;
  84. import ch.threema.storage.models.DistributionListModel;
  85. import ch.threema.storage.models.GroupModel;
  86. import ch.threema.storage.models.MessageType;
  87. import ch.threema.storage.models.data.MessageContentsType;
  88. import static ch.threema.app.fragments.ComposeMessageFragment.SCROLLBUTTON_VIEW_TIMEOUT;
  89. public class MediaGalleryActivity extends ThreemaToolbarActivity implements AdapterView.OnItemClickListener, ActionBar.OnNavigationListener, GenericAlertDialog.DialogClickListener, FastScrollGridView.ScrollListener {
  90. private static final Logger logger = LoggerFactory.getLogger(MediaGalleryActivity.class);
  91. private ThumbnailCache<?> thumbnailCache = null;
  92. private MediaGalleryAdapter mediaGalleryAdapter;
  93. private MessageReceiver messageReceiver;
  94. private String actionBarTitle;
  95. private SpinnerMessageFilter spinnerMessageFilter;
  96. private MediaGallerySpinnerAdapter spinnerAdapter;
  97. private List<AbstractMessageModel> values;
  98. private FastScrollGridView gridView;
  99. private EmptyView emptyView;
  100. private TypedArray mediaTypeArray;
  101. private int currentType;
  102. private ActionMode actionMode = null;
  103. private AbstractMessageModel initialMessageModel = null;
  104. private TextView dateTextView;
  105. private FrameLayout dateView;
  106. public FileService fileService;
  107. public MessageService messageService;
  108. public ContactService contactService;
  109. public GroupService groupService;
  110. public DistributionListService distributionListService;
  111. private final Handler dateViewHandler = new Handler();
  112. private final Runnable dateViewTask = () -> RuntimeUtil.runOnUiThread(() -> {
  113. if (dateView != null && dateView.getVisibility() == View.VISIBLE) {
  114. AnimationUtil.slideOutAnimation(dateView, false, 1f, null);
  115. }
  116. });
  117. private static final int TYPE_ALL = 0;
  118. private static final int TYPE_IMAGE = 1;
  119. private static final int TYPE_VIDEO = 2;
  120. private static final int TYPE_AUDIO = 3;
  121. private static final int TYPE_FILE = 4;
  122. private static final String DELETE_MESSAGES_CONFIRM_TAG = "reallydelete";
  123. private static final String DIALOG_TAG_DELETING_MEDIA = "dmm";
  124. private static final int PERMISSION_REQUEST_SAVE_MESSAGE = 88;
  125. private static class SpinnerMessageFilter implements MessageService.MessageFilter {
  126. private @MessageContentsType int[] filter = null;
  127. public void setFilterByType(int spinnerMessageType) {
  128. switch (spinnerMessageType) {
  129. case TYPE_ALL:
  130. this.filter = new int[]{MessageContentsType.IMAGE, MessageContentsType.VIDEO, MessageContentsType.AUDIO, MessageContentsType.FILE, MessageContentsType.GIF, MessageContentsType.VOICE_MESSAGE};
  131. break;
  132. case TYPE_IMAGE:
  133. this.filter = new int[]{MessageContentsType.IMAGE};
  134. break;
  135. case TYPE_VIDEO:
  136. this.filter = new int[]{MessageContentsType.VIDEO, MessageContentsType.GIF};
  137. break;
  138. case TYPE_AUDIO:
  139. this.filter = new int[]{MessageContentsType.AUDIO, MessageContentsType.VOICE_MESSAGE};
  140. break;
  141. case TYPE_FILE:
  142. this.filter = new int[]{MessageContentsType.FILE};
  143. break;
  144. default:
  145. break;
  146. }
  147. }
  148. @Override
  149. public long getPageSize() {
  150. return 0;
  151. }
  152. @Override
  153. public Integer getPageReferenceId() {
  154. return null;
  155. }
  156. @Override
  157. public boolean withStatusMessages() {
  158. return false;
  159. }
  160. @Override
  161. public boolean withUnsaved() {
  162. return false;
  163. }
  164. @Override
  165. public boolean onlyUnread() {
  166. return false;
  167. }
  168. @Override
  169. public boolean onlyDownloaded() {
  170. return true;
  171. }
  172. @Override
  173. public MessageType[] types() { return null; }
  174. @Override
  175. @MessageContentsType
  176. public int[] contentTypes() {
  177. return this.filter;
  178. }
  179. }
  180. @Override
  181. public int getLayoutResource() {
  182. return R.layout.activity_media_gallery;
  183. }
  184. @Override
  185. public void onCreate(Bundle savedInstanceState) {
  186. super.onCreate(savedInstanceState);
  187. }
  188. @Override
  189. protected boolean initActivity(Bundle savedInstanceState) {
  190. logger.debug("initActivity");
  191. // set font size according to user preferences
  192. getTheme().applyStyle(preferenceService.getFontStyle(), true);
  193. if (!super.initActivity(savedInstanceState)) {
  194. return false;
  195. }
  196. if (!this.requiredInstances()) {
  197. this.finish();
  198. return false;
  199. }
  200. currentType = TYPE_ALL;
  201. this.gridView = findViewById(R.id.gridview);
  202. this.gridView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE_MODAL);
  203. this.gridView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() {
  204. @Override
  205. public void onItemCheckedStateChanged(android.view.ActionMode mode, int position, long id, boolean checked) {
  206. final int count = gridView.getCheckedItemCount();
  207. if (count > 0) {
  208. mode.setTitle(Integer.toString(count));
  209. }
  210. if (actionMode != null) {
  211. actionMode.getMenu().findItem(R.id.menu_show_in_chat).setVisible(count == 1);
  212. }
  213. }
  214. @Override
  215. public boolean onCreateActionMode(android.view.ActionMode mode, Menu menu) {
  216. mode.getMenuInflater().inflate(R.menu.action_media_gallery, menu);
  217. actionMode = mode;
  218. ConfigUtils.themeMenu(menu, ConfigUtils.getColorFromAttribute(MediaGalleryActivity.this, R.attr.colorAccent));
  219. if (AppRestrictionUtil.isShareMediaDisabled(MediaGalleryActivity.this)) {
  220. menu.findItem(R.id.menu_message_save).setVisible(false);
  221. }
  222. return true;
  223. }
  224. @Override
  225. public boolean onPrepareActionMode(android.view.ActionMode mode, Menu menu) {
  226. mode.setTitle(Integer.toString(gridView.getCheckedItemCount()));
  227. return false;
  228. }
  229. @Override
  230. public boolean onActionItemClicked(android.view.ActionMode mode, MenuItem item) {
  231. switch (item.getItemId()) {
  232. case R.id.menu_message_discard:
  233. discardMessages();
  234. return true;
  235. case R.id.menu_message_save:
  236. saveMessages();
  237. return true;
  238. case R.id.menu_show_in_chat:
  239. showInChat();
  240. return true;
  241. default:
  242. return false;
  243. }
  244. }
  245. @Override
  246. public void onDestroyActionMode(android.view.ActionMode mode) {
  247. actionMode = null;
  248. }
  249. });
  250. this.gridView.setOnItemClickListener(this);
  251. this.gridView.setNumColumns(ConfigUtils.isLandscape(this) ? 5 : 3);
  252. this.gridView.setScrollListener(this);
  253. processIntent(getIntent());
  254. ActionBar actionBar = getSupportActionBar();
  255. if (actionBar == null) {
  256. logger.debug("no action bar");
  257. finish();
  258. return false;
  259. }
  260. actionBar.setDisplayHomeAsUpEnabled(true);
  261. actionBar.setDisplayShowTitleEnabled(false);
  262. // add text view if contact list is empty
  263. this.mediaTypeArray = getResources().obtainTypedArray(R.array.media_gallery_spinner);
  264. this.spinnerAdapter = new MediaGallerySpinnerAdapter(
  265. actionBar.getThemedContext(), getResources().getStringArray(R.array.media_gallery_spinner),
  266. this.actionBarTitle);
  267. actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
  268. actionBar.setListNavigationCallbacks(spinnerAdapter, this);
  269. actionBar.setSelectedNavigationItem(this.currentType);
  270. this.spinnerMessageFilter = new SpinnerMessageFilter();
  271. this.spinnerMessageFilter.setFilterByType(this.currentType);
  272. this.thumbnailCache = new ThumbnailCache<Integer>(null);
  273. FrameLayout frameLayout = findViewById(R.id.frame_parent);
  274. this.emptyView = new EmptyView(this);
  275. this.emptyView.setColorsInt(ConfigUtils.getColorFromAttribute(this, android.R.attr.windowBackground), ConfigUtils.getColorFromAttribute(this, R.attr.textColorPrimary));
  276. this.emptyView.setup(getString(R.string.no_media_found_generic));
  277. frameLayout.addView(this.emptyView);
  278. this.gridView.setEmptyView(this.emptyView);
  279. this.dateView = findViewById(R.id.date_separator_container);
  280. this.dateTextView = findViewById(R.id.text_view);
  281. if (savedInstanceState == null || mediaGalleryAdapter == null) {
  282. setupAdapters(this.currentType, true);
  283. }
  284. return true;
  285. }
  286. private void showInChat() {
  287. if (getSelectedMessages().size() != 1) {
  288. return;
  289. }
  290. AnimationUtil.startActivityForResult(this, null, IntentDataUtil.getJumpToMessageIntent(this, getSelectedMessages().get(0)), ThreemaActivity.ACTIVITY_ID_COMPOSE_MESSAGE);
  291. finish();
  292. }
  293. @Override
  294. protected void onDestroy() {
  295. if (this.thumbnailCache != null) {
  296. this.thumbnailCache.flush();
  297. }
  298. super.onDestroy();
  299. }
  300. @Override
  301. public boolean onCreateOptionsMenu(Menu menu) {
  302. super.onCreateOptionsMenu(menu);
  303. // Inflate the menu; this adds items to the action bar if it is present.
  304. getMenuInflater().inflate(R.menu.activity_media_gallery, menu);
  305. return true;
  306. }
  307. @Override
  308. public boolean onOptionsItemSelected(MenuItem item) {
  309. switch (item.getItemId()) {
  310. case android.R.id.home:
  311. finish();
  312. break;
  313. case R.id.menu_message_select_all:
  314. selectAllMessages();
  315. break;
  316. }
  317. return true;
  318. }
  319. private void processIntent(Intent intent) {
  320. if (intent.hasExtra(ThreemaApplication.INTENT_DATA_GROUP)) {
  321. int groupId = intent.getIntExtra(ThreemaApplication.INTENT_DATA_GROUP, 0);
  322. GroupModel groupModel = this.groupService.getById(groupId);
  323. messageReceiver = this.groupService.createReceiver(groupModel);
  324. actionBarTitle = groupModel.getName();
  325. } else if (intent.hasExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST)) {
  326. DistributionListModel distributionListModel = distributionListService.getById(intent.getIntExtra(ThreemaApplication.INTENT_DATA_DISTRIBUTION_LIST, 0));
  327. try {
  328. messageReceiver = distributionListService.createReceiver(distributionListModel);
  329. } catch (Exception e) {
  330. logger.error("Exception", e);
  331. }
  332. actionBarTitle = distributionListModel.getName();
  333. } else {
  334. String identity = intent.getStringExtra(ThreemaApplication.INTENT_DATA_CONTACT);
  335. if (identity == null) {
  336. finish();
  337. }
  338. ContactModel contactModel = this.contactService.getByIdentity(identity);
  339. messageReceiver = this.contactService.createReceiver(contactModel);
  340. actionBarTitle = NameUtil.getDisplayNameOrNickname(contactModel, true);
  341. }
  342. String type = IntentDataUtil.getAbstractMessageType(intent);
  343. int id = IntentDataUtil.getAbstractMessageId(intent);
  344. if (type != null && id != 0) {
  345. initialMessageModel = messageService.getMessageModelFromId(id, type);
  346. }
  347. }
  348. private void setupAdapters(int newType, boolean force) {
  349. if (this.currentType != newType || force) {
  350. this.values = this.getMessages(this.messageReceiver);
  351. if (this.values == null || this.values.isEmpty()) {
  352. if (this.emptyView != null) {
  353. if (newType == TYPE_ALL) {
  354. this.emptyView.setup(getString(R.string.no_media_found_generic));
  355. } else {
  356. this.emptyView.setup(String.format(getString(R.string.no_media_found), getString(this.mediaTypeArray.getResourceId(newType, -1))));
  357. }
  358. }
  359. }
  360. this.mediaGalleryAdapter = new MediaGalleryAdapter(
  361. this,
  362. values,
  363. this.fileService,
  364. this.thumbnailCache
  365. );
  366. this.gridView.setAdapter(this.mediaGalleryAdapter);
  367. if (initialMessageModel != null) {
  368. this.gridView.post(new Runnable() {
  369. @Override
  370. public void run() {
  371. for(int position = 0; position < values.size(); position++) {
  372. if (values.get(position).getId() == initialMessageModel.getId()) {
  373. gridView.setSelection(position);
  374. break;
  375. }
  376. }
  377. initialMessageModel = null;
  378. }
  379. });
  380. }
  381. }
  382. this.currentType = newType;
  383. resetSpinnerAdapter(newType);
  384. }
  385. private void resetSpinnerAdapter(int type) {
  386. if (this.spinnerAdapter != null && this.mediaTypeArray != null && this.values != null) {
  387. this.spinnerAdapter.setSubtitle(getString(this.mediaTypeArray.getResourceId(type, -1)) + " (" + this.values.size() + ")");
  388. this.spinnerAdapter.notifyDataSetChanged();
  389. }
  390. }
  391. private List<AbstractMessageModel> getMessages(MessageReceiver<AbstractMessageModel> receiver) {
  392. List<AbstractMessageModel> values = null;
  393. try {
  394. values = receiver.loadMessages(this.spinnerMessageFilter);
  395. } catch (SQLException e) {
  396. logger.error("Exception", e);
  397. }
  398. return values;
  399. }
  400. @Override
  401. public void onItemClick(AdapterView<?> parent, final View view, int position, long id) {
  402. final AbstractMessageModel m = this.mediaGalleryAdapter.getItem(position);
  403. ProgressBar progressBar = view.findViewById(R.id.progress_decoding);
  404. switch (mediaGalleryAdapter.getItemViewType(position)) {
  405. case MediaGalleryAdapter.TYPE_IMAGE:
  406. // internal viewer
  407. showInMediaFragment(m, view);
  408. break;
  409. case MediaGalleryAdapter.TYPE_VIDEO:
  410. showInMediaFragment(m, view);
  411. break;
  412. case MediaGalleryAdapter.TYPE_AUDIO:
  413. showInMediaFragment(m, view);
  414. break;
  415. case MediaGalleryAdapter.TYPE_FILE:
  416. if (m!= null && (FileUtil.isImageFile(m.getFileData()) || FileUtil.isVideoFile(m.getFileData()) || FileUtil.isAudioFile(m.getFileData()))) {
  417. showInMediaFragment(m, view);
  418. } else {
  419. decodeAndShowFile(m, view, progressBar);
  420. }
  421. break;
  422. }
  423. }
  424. @Override
  425. public boolean onNavigationItemSelected(int itemPosition, long itemId) {
  426. this.spinnerMessageFilter.setFilterByType(itemPosition);
  427. setupAdapters(itemPosition, false);
  428. return true;
  429. }
  430. @Override
  431. public void onScroll(int firstVisibleItem) {
  432. if (this.mediaGalleryAdapter != null) {
  433. if (dateView.getVisibility() != View.VISIBLE && mediaGalleryAdapter != null && mediaGalleryAdapter.getCount() > 0) {
  434. AnimationUtil.slideInAnimation(dateView, false, 200);
  435. }
  436. dateViewHandler.removeCallbacks(dateViewTask);
  437. dateViewHandler.postDelayed(dateViewTask, SCROLLBUTTON_VIEW_TIMEOUT);
  438. try {
  439. final AbstractMessageModel messageModel = this.mediaGalleryAdapter.getItem(firstVisibleItem);
  440. if (messageModel != null) {
  441. final Date createdAt = messageModel.getCreatedAt();
  442. if (createdAt != null) {
  443. dateView.post(() -> {
  444. dateTextView.setText(LocaleUtil.formatDateRelative(this, createdAt.getTime()));
  445. });
  446. }
  447. }
  448. } catch (IndexOutOfBoundsException ignore) {}
  449. }
  450. }
  451. private void selectAllMessages() {
  452. if (gridView != null) {
  453. if (gridView.getCount() == gridView.getCheckedItemCount()) {
  454. if (actionMode != null) {
  455. actionMode.finish();
  456. }
  457. } else {
  458. for (int i = 0; i < gridView.getCount(); i++) {
  459. if (currentType == TYPE_ALL || mediaGalleryAdapter.getItemViewType(i) == currentType) {
  460. gridView.setItemChecked(i, true);
  461. }
  462. }
  463. if (actionMode != null) {
  464. actionMode.invalidate();
  465. }
  466. }
  467. }
  468. }
  469. private void discardMessages() {
  470. List<AbstractMessageModel> selectedMessages = getSelectedMessages();
  471. GenericAlertDialog dialog = GenericAlertDialog.newInstance(R.string.really_delete_message_title, String.format(getString(R.string.really_delete_media), selectedMessages.size()), R.string.delete_message, R.string.cancel);
  472. dialog.setData(selectedMessages);
  473. dialog.show(getSupportFragmentManager(), DELETE_MESSAGES_CONFIRM_TAG);
  474. }
  475. private void saveMessages() {
  476. if (ConfigUtils.requestWriteStoragePermissions(this, null, PERMISSION_REQUEST_SAVE_MESSAGE)) {
  477. fileService.saveMedia(this, gridView, new CopyOnWriteArrayList<>(getSelectedMessages()), true);
  478. actionMode.finish();
  479. }
  480. }
  481. private List<AbstractMessageModel> getSelectedMessages() {
  482. List<AbstractMessageModel> selectedMessages = new ArrayList<>();
  483. SparseBooleanArray checkedItems = gridView.getCheckedItemPositions();
  484. final int size = checkedItems.size();
  485. for (int i = 0; i < size; i++) {
  486. final int index = checkedItems.keyAt(i);
  487. if (checkedItems.valueAt(i)) {
  488. selectedMessages.add(mediaGalleryAdapter.getItem(index));
  489. }
  490. }
  491. return selectedMessages;
  492. }
  493. @SuppressLint("StaticFieldLeak")
  494. private void reallyDiscardMessages(final CopyOnWriteArrayList<AbstractMessageModel> selectedMessages) {
  495. new AsyncTask<Void, Integer, Void>() {
  496. boolean cancelled = false;
  497. @Override
  498. protected void onPreExecute() {
  499. if (selectedMessages.size() > 10) {
  500. CancelableHorizontalProgressDialog dialog = CancelableHorizontalProgressDialog.newInstance(R.string.deleting_messages, 0, R.string.cancel, selectedMessages.size());
  501. dialog.setOnCancelListener(new DialogInterface.OnClickListener() {
  502. @Override
  503. public void onClick(DialogInterface dialog, int which) {
  504. cancelled = true;
  505. }
  506. });
  507. dialog.show(getSupportFragmentManager(), DIALOG_TAG_DELETING_MEDIA);
  508. }
  509. }
  510. @Override
  511. protected Void doInBackground(Void... params) {
  512. int i = 0;
  513. Iterator<AbstractMessageModel> checkedItemsIterator = selectedMessages.iterator();
  514. while (checkedItemsIterator.hasNext() && !cancelled) {
  515. publishProgress(i++);
  516. try {
  517. final AbstractMessageModel messageModel = checkedItemsIterator.next();
  518. if (messageModel != null) {
  519. messageService.remove(messageModel);
  520. RuntimeUtil.runOnUiThread(new Runnable() {
  521. @Override
  522. public void run() {
  523. mediaGalleryAdapter.remove(messageModel);
  524. }
  525. });
  526. }
  527. } catch (Exception e) {
  528. logger.error("Exception", e);
  529. }
  530. }
  531. return null;
  532. }
  533. @Override
  534. protected void onPostExecute(Void result) {
  535. DialogUtil.dismissDialog(getSupportFragmentManager(), DIALOG_TAG_DELETING_MEDIA, true);
  536. Snackbar.make(gridView, R.string.message_deleted, Snackbar.LENGTH_LONG).show();
  537. if (actionMode != null) {
  538. actionMode.finish();
  539. }
  540. resetSpinnerAdapter(currentType);
  541. }
  542. @Override
  543. protected void onProgressUpdate(Integer... index) {
  544. DialogUtil.updateProgress(getSupportFragmentManager(), DIALOG_TAG_DELETING_MEDIA, index[0] + 1);
  545. }
  546. }.execute();
  547. }
  548. @Override
  549. public void onYes(String tag, Object data) {
  550. reallyDiscardMessages(new CopyOnWriteArrayList<>((ArrayList< AbstractMessageModel>) data));
  551. }
  552. @Override
  553. public void onNo(String tag, Object data) {
  554. }
  555. @Override
  556. public void onConfigurationChanged(@NonNull Configuration newConfig) {
  557. final int topmost;
  558. if (this.gridView != null) {
  559. View topChild = this.gridView.getChildAt(0);
  560. if (topChild != null) {
  561. if (topChild.getTop() < 0) {
  562. topmost = this.gridView.getFirstVisiblePosition() + 1;
  563. } else {
  564. topmost = this.gridView.getFirstVisiblePosition();
  565. }
  566. } else {
  567. topmost = 0;
  568. }
  569. } else {
  570. topmost = 0;
  571. }
  572. super.onConfigurationChanged(newConfig);
  573. if (this.gridView != null) {
  574. this.gridView.post(() -> {
  575. gridView.setNumColumns(ConfigUtils.isLandscape(MediaGalleryActivity.this) ? 5 : 3);
  576. gridView.setSelection(topmost);
  577. });
  578. }
  579. }
  580. private void hideProgressBar(final ProgressBar progressBar) {
  581. if (progressBar != null) {
  582. RuntimeUtil.runOnUiThread(() -> progressBar.setVisibility(View.GONE));
  583. }
  584. }
  585. private void showProgressBar(final ProgressBar progressBar) {
  586. if (progressBar != null) {
  587. RuntimeUtil.runOnUiThread(() -> progressBar.setVisibility(View.VISIBLE));
  588. }
  589. }
  590. public void decodeAndShowFile(final AbstractMessageModel m, final View v, final ProgressBar progressBar) {
  591. showProgressBar(progressBar);
  592. fileService.loadDecryptedMessageFile(m, new FileService.OnDecryptedFileComplete() {
  593. @Override
  594. public void complete(File decodedFile) {
  595. hideProgressBar(progressBar);
  596. messageService.viewMediaMessage(getApplicationContext(), m, fileService.getShareFileUri(decodedFile, null));
  597. }
  598. @Override
  599. public void error(String message) {
  600. hideProgressBar(progressBar);
  601. if (!TestUtil.empty(message)) {
  602. logger.error(message, MediaGalleryActivity.this);
  603. }
  604. }
  605. });
  606. }
  607. public void showInMediaFragment(final AbstractMessageModel m, final View v) {
  608. Intent intent = new Intent(this, MediaViewerActivity.class);
  609. IntentDataUtil.append(m, intent);
  610. intent.putExtra(MediaViewerActivity.EXTRA_ID_IMMEDIATE_PLAY, true);
  611. intent.putExtra(MediaViewerActivity.EXTRA_ID_REVERSE_ORDER, false);
  612. AnimationUtil.startActivityForResult(this, v, intent, ACTIVITY_ID_MEDIA_VIEWER);
  613. }
  614. @Override
  615. protected boolean checkInstances() {
  616. return TestUtil.required(
  617. this.fileService,
  618. this.messageService,
  619. this.groupService,
  620. this.distributionListService,
  621. this.contactService
  622. ) && super.checkInstances();
  623. }
  624. @Override
  625. protected void instantiate() {
  626. super.instantiate();
  627. ServiceManager serviceManager = ThreemaApplication.getServiceManager();
  628. if (serviceManager != null) {
  629. try {
  630. this.fileService = serviceManager.getFileService();
  631. this.messageService = serviceManager.getMessageService();
  632. this.groupService = serviceManager.getGroupService();
  633. this.distributionListService = serviceManager.getDistributionListService();
  634. this.contactService = serviceManager.getContactService();
  635. } catch (Exception e) {
  636. LogUtil.exception(e, this);
  637. }
  638. }
  639. }
  640. @Override
  641. @TargetApi(23)
  642. public void onRequestPermissionsResult(int requestCode,
  643. @NonNull String[] permissions, @NonNull int[] grantResults) {
  644. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  645. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  646. switch (requestCode) {
  647. case PERMISSION_REQUEST_SAVE_MESSAGE:
  648. fileService.saveMedia(this, gridView, new CopyOnWriteArrayList<>(getSelectedMessages()), true);
  649. break;
  650. }
  651. } else {
  652. switch (requestCode) {
  653. case PERMISSION_REQUEST_SAVE_MESSAGE:
  654. if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
  655. ConfigUtils.showPermissionRationale(this, gridView, R.string.permission_storage_required);
  656. }
  657. break;
  658. }
  659. }
  660. actionMode.finish();
  661. }
  662. }