PreviewImageFragment.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * @author Chris Narkiewicz
  6. *
  7. * Copyright (C) 2015 ownCloud Inc.
  8. * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.owncloud.android.ui.preview;
  23. import android.accounts.Account;
  24. import android.app.Activity;
  25. import android.content.Context;
  26. import android.content.res.Configuration;
  27. import android.content.res.Resources;
  28. import android.graphics.Bitmap;
  29. import android.graphics.Color;
  30. import android.graphics.Point;
  31. import android.graphics.drawable.BitmapDrawable;
  32. import android.graphics.drawable.Drawable;
  33. import android.graphics.drawable.LayerDrawable;
  34. import android.graphics.drawable.PictureDrawable;
  35. import android.os.AsyncTask;
  36. import android.os.Build;
  37. import android.os.Bundle;
  38. import android.os.Process;
  39. import android.text.SpannableString;
  40. import android.text.style.ForegroundColorSpan;
  41. import android.util.DisplayMetrics;
  42. import android.view.LayoutInflater;
  43. import android.view.Menu;
  44. import android.view.MenuInflater;
  45. import android.view.MenuItem;
  46. import android.view.View;
  47. import android.view.ViewGroup;
  48. import android.widget.ImageView;
  49. import android.widget.LinearLayout;
  50. import android.widget.ProgressBar;
  51. import android.widget.RelativeLayout;
  52. import android.widget.TextView;
  53. import com.caverock.androidsvg.SVG;
  54. import com.caverock.androidsvg.SVGParseException;
  55. import com.github.chrisbanes.photoview.PhotoView;
  56. import com.google.android.material.snackbar.Snackbar;
  57. import com.nextcloud.client.account.UserAccountManager;
  58. import com.nextcloud.client.device.DeviceInfo;
  59. import com.nextcloud.client.di.Injectable;
  60. import com.nextcloud.client.network.ConnectivityService;
  61. import com.owncloud.android.MainApp;
  62. import com.owncloud.android.R;
  63. import com.owncloud.android.datamodel.OCFile;
  64. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  65. import com.owncloud.android.files.FileMenuFilter;
  66. import com.owncloud.android.lib.common.utils.Log_OC;
  67. import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
  68. import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
  69. import com.owncloud.android.ui.fragment.FileFragment;
  70. import com.owncloud.android.utils.BitmapUtils;
  71. import com.owncloud.android.utils.DisplayUtils;
  72. import com.owncloud.android.utils.MimeType;
  73. import com.owncloud.android.utils.MimeTypeUtil;
  74. import org.jetbrains.annotations.NotNull;
  75. import java.io.FileInputStream;
  76. import java.io.FileNotFoundException;
  77. import java.io.IOException;
  78. import java.lang.ref.WeakReference;
  79. import javax.annotation.Nullable;
  80. import javax.inject.Inject;
  81. import androidx.annotation.DrawableRes;
  82. import androidx.annotation.NonNull;
  83. import androidx.annotation.StringRes;
  84. import androidx.fragment.app.FragmentStatePagerAdapter;
  85. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  86. import pl.droidsonroids.gif.GifDrawable;
  87. /**
  88. * This fragment shows a preview of a downloaded image.
  89. *
  90. * Trying to get an instance with a NULL {@link OCFile} will produce an
  91. * {@link IllegalStateException}.
  92. *
  93. * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on
  94. * instantiation too.
  95. */
  96. public class PreviewImageFragment extends FileFragment implements Injectable {
  97. private static final String EXTRA_FILE = "FILE";
  98. private static final String EXTRA_ZOOM = "ZOOM";
  99. private static final String ARG_FILE = "FILE";
  100. private static final String ARG_IGNORE_FIRST = "IGNORE_FIRST";
  101. private static final String ARG_SHOW_RESIZED_IMAGE = "SHOW_RESIZED_IMAGE";
  102. private static final String MIME_TYPE_PNG = "image/png";
  103. private static final String MIME_TYPE_GIF = "image/gif";
  104. private static final String MIME_TYPE_SVG = "image/svg+xml";
  105. private PhotoView mImageView;
  106. private RelativeLayout mMultiView;
  107. private LinearLayout mMultiListContainer;
  108. private TextView mMultiListMessage;
  109. private TextView mMultiListHeadline;
  110. private ImageView mMultiListIcon;
  111. private ProgressBar mMultiListProgress;
  112. private Boolean mShowResizedImage;
  113. private Bitmap mBitmap;
  114. private static final String TAG = PreviewImageFragment.class.getSimpleName();
  115. private boolean mIgnoreFirstSavedState;
  116. private LoadBitmapTask mLoadBitmapTask;
  117. @Inject ConnectivityService connectivityService;
  118. @Inject UserAccountManager accountManager;
  119. @Inject DeviceInfo deviceInfo;
  120. /**
  121. * Public factory method to create a new fragment that previews an image.
  122. *
  123. * Android strongly recommends keep the empty constructor of fragments as the only public
  124. * constructor, and
  125. * use {@link #setArguments(Bundle)} to set the needed arguments.
  126. *
  127. * This method hides to client objects the need of doing the construction in two steps.
  128. *
  129. * @param imageFile An {@link OCFile} to preview as an image in the fragment
  130. * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of
  131. * {@link FragmentStatePagerAdapter}
  132. * ; TODO better solution
  133. */
  134. public static PreviewImageFragment newInstance(@NonNull OCFile imageFile,
  135. boolean ignoreFirstSavedState,
  136. boolean showResizedImage) {
  137. PreviewImageFragment frag = new PreviewImageFragment();
  138. frag.mShowResizedImage = showResizedImage;
  139. Bundle args = new Bundle();
  140. args.putParcelable(ARG_FILE, imageFile);
  141. args.putBoolean(ARG_IGNORE_FIRST, ignoreFirstSavedState);
  142. args.putBoolean(ARG_SHOW_RESIZED_IMAGE, showResizedImage);
  143. frag.setArguments(args);
  144. return frag;
  145. }
  146. /**
  147. * Creates an empty fragment for image previews.
  148. *
  149. * MUST BE KEPT: the system uses it when tries to re-instantiate a fragment automatically
  150. * (for instance, when the device is turned a aside).
  151. *
  152. * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful
  153. * construction
  154. */
  155. public PreviewImageFragment() {
  156. mIgnoreFirstSavedState = false;
  157. }
  158. @Override
  159. public void onCreate(Bundle savedInstanceState) {
  160. super.onCreate(savedInstanceState);
  161. Bundle args = getArguments();
  162. if (args == null) {
  163. throw new IllegalArgumentException("Arguments may not be null!");
  164. }
  165. setFile(args.getParcelable(ARG_FILE));
  166. // TODO better in super, but needs to check ALL the class extending FileFragment;
  167. // not right now
  168. mIgnoreFirstSavedState = args.getBoolean(ARG_IGNORE_FIRST);
  169. mShowResizedImage = args.getBoolean(ARG_SHOW_RESIZED_IMAGE);
  170. setHasOptionsMenu(true);
  171. }
  172. @Override
  173. public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  174. super.onCreateView(inflater, container, savedInstanceState);
  175. View view = inflater.inflate(R.layout.preview_image_fragment, container, false);
  176. mImageView = view.findViewById(R.id.image);
  177. mImageView.setVisibility(View.GONE);
  178. view.setOnClickListener(v -> togglePreviewImageFullScreen());
  179. mImageView.setOnClickListener(v -> togglePreviewImageFullScreen());
  180. mMultiView = view.findViewById(R.id.multi_view);
  181. setupMultiView(view);
  182. setMultiListLoadingMessage();
  183. return view;
  184. }
  185. private void setupMultiView(View view) {
  186. mMultiListContainer = view.findViewById(R.id.empty_list_view);
  187. mMultiListMessage = view.findViewById(R.id.empty_list_view_text);
  188. mMultiListHeadline = view.findViewById(R.id.empty_list_view_headline);
  189. mMultiListIcon = view.findViewById(R.id.empty_list_icon);
  190. mMultiListProgress = view.findViewById(R.id.empty_list_progress);
  191. }
  192. /**
  193. * {@inheritDoc}
  194. */
  195. @Override
  196. public void onActivityCreated(Bundle savedInstanceState) {
  197. super.onActivityCreated(savedInstanceState);
  198. if (savedInstanceState != null) {
  199. if (!mIgnoreFirstSavedState) {
  200. OCFile file = savedInstanceState.getParcelable(EXTRA_FILE);
  201. setFile(file);
  202. mImageView.setScale(savedInstanceState.getFloat(EXTRA_ZOOM));
  203. } else {
  204. mIgnoreFirstSavedState = false;
  205. }
  206. }
  207. }
  208. @Override
  209. public void onSaveInstanceState(@NonNull Bundle outState) {
  210. super.onSaveInstanceState(outState);
  211. outState.putFloat(EXTRA_ZOOM, mImageView.getScale());
  212. outState.putParcelable(EXTRA_FILE, getFile());
  213. }
  214. @Override
  215. public void onStart() {
  216. super.onStart();
  217. if (getFile() != null) {
  218. mImageView.setTag(getFile().getFileId());
  219. Point screenSize = DisplayUtils.getScreenSize(getActivity());
  220. int width = screenSize.x;
  221. int height = screenSize.y;
  222. if (mShowResizedImage) {
  223. Bitmap resizedImage = getResizedBitmap(getFile(), width, height);
  224. if (resizedImage != null && !getFile().isUpdateThumbnailNeeded()) {
  225. mImageView.setImageBitmap(resizedImage);
  226. mImageView.setVisibility(View.VISIBLE);
  227. mBitmap = resizedImage;
  228. } else {
  229. // show thumbnail while loading resized image
  230. Bitmap thumbnail = getResizedBitmap(getFile(), width, height);
  231. if (thumbnail != null) {
  232. mImageView.setImageBitmap(thumbnail);
  233. mImageView.setVisibility(View.VISIBLE);
  234. mBitmap = thumbnail;
  235. } else {
  236. thumbnail = ThumbnailsCacheManager.mDefaultImg;
  237. }
  238. // generate new resized image
  239. if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), mImageView) &&
  240. containerActivity.getStorageManager() != null) {
  241. final ThumbnailsCacheManager.ResizedImageGenerationTask task =
  242. new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
  243. mImageView,
  244. containerActivity.getStorageManager(),
  245. connectivityService,
  246. containerActivity.getStorageManager().getAccount());
  247. if (resizedImage == null) {
  248. resizedImage = thumbnail;
  249. }
  250. final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
  251. new ThumbnailsCacheManager.AsyncResizedImageDrawable(
  252. MainApp.getAppContext().getResources(),
  253. resizedImage,
  254. task
  255. );
  256. mImageView.setImageDrawable(asyncDrawable);
  257. task.execute(getFile());
  258. }
  259. }
  260. mMultiView.setVisibility(View.GONE);
  261. mImageView.setBackgroundColor(getResources().getColor(R.color.background_color_inverse));
  262. mImageView.setVisibility(View.VISIBLE);
  263. } else {
  264. mLoadBitmapTask = new LoadBitmapTask(mImageView);
  265. mLoadBitmapTask.execute(getFile());
  266. }
  267. } else {
  268. showErrorMessage(R.string.preview_image_error_no_local_file);
  269. }
  270. }
  271. private @Nullable
  272. Bitmap getResizedBitmap(OCFile file, int width, int height) {
  273. Bitmap cachedImage = null;
  274. int scaledWidth = width;
  275. int scaledHeight = height;
  276. for (int i = 0; i < 3 && cachedImage == null; i++) {
  277. try {
  278. cachedImage = ThumbnailsCacheManager.getScaledBitmapFromDiskCache(
  279. ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId(),
  280. scaledWidth,
  281. scaledHeight);
  282. } catch (OutOfMemoryError e) {
  283. scaledWidth = scaledWidth / 2;
  284. scaledHeight = scaledHeight / 2;
  285. }
  286. }
  287. return cachedImage;
  288. }
  289. @Override
  290. public void onStop() {
  291. Log_OC.d(TAG, "onStop starts");
  292. if (mLoadBitmapTask != null) {
  293. mLoadBitmapTask.cancel(true);
  294. mLoadBitmapTask = null;
  295. }
  296. super.onStop();
  297. }
  298. /**
  299. * {@inheritDoc}
  300. */
  301. @Override
  302. public void onCreateOptionsMenu(@NotNull Menu menu, @NotNull MenuInflater inflater) {
  303. super.onCreateOptionsMenu(menu, inflater);
  304. inflater.inflate(R.menu.item_file, menu);
  305. int nightModeFlag = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
  306. if (Configuration.UI_MODE_NIGHT_NO == nightModeFlag) {
  307. for (int i = 0; i < menu.size(); i++) {
  308. MenuItem menuItem = menu.getItem(i);
  309. SpannableString spanString = new SpannableString(menuItem.getTitle().toString());
  310. spanString.setSpan(new ForegroundColorSpan(Color.BLACK), 0, spanString.length(), 0);
  311. menuItem.setTitle(spanString);
  312. }
  313. }
  314. }
  315. /**
  316. * {@inheritDoc}
  317. */
  318. @Override
  319. public void onPrepareOptionsMenu(@NotNull Menu menu) {
  320. super.onPrepareOptionsMenu(menu);
  321. if (containerActivity.getStorageManager() != null && getFile() != null) {
  322. // Update the file
  323. setFile(containerActivity.getStorageManager().getFileById(getFile().getFileId()));
  324. Account currentAccount = containerActivity.getStorageManager().getAccount();
  325. FileMenuFilter mf = new FileMenuFilter(
  326. getFile(),
  327. currentAccount,
  328. containerActivity,
  329. getActivity(),
  330. false,
  331. deviceInfo,
  332. accountManager.getUser()
  333. );
  334. mf.filter(menu,
  335. true,
  336. accountManager.isMediaStreamingSupported(currentAccount));
  337. }
  338. // additional restriction for this fragment
  339. // TODO allow renaming in PreviewImageFragment
  340. // TODO allow refresh file in PreviewImageFragment
  341. FileMenuFilter.hideMenuItems(
  342. menu.findItem(R.id.action_rename_file),
  343. menu.findItem(R.id.action_sync_file),
  344. menu.findItem(R.id.action_select_all),
  345. menu.findItem(R.id.action_move),
  346. menu.findItem(R.id.action_copy),
  347. menu.findItem(R.id.action_favorite),
  348. menu.findItem(R.id.action_unset_favorite)
  349. );
  350. if (getFile().isSharedWithMe() && !getFile().canReshare()) {
  351. FileMenuFilter.hideMenuItem(menu.findItem(R.id.action_send_share_file));
  352. }
  353. }
  354. /**
  355. * {@inheritDoc}
  356. */
  357. @Override
  358. public boolean onOptionsItemSelected(MenuItem item) {
  359. switch (item.getItemId()) {
  360. case R.id.action_send_share_file:
  361. if (getFile().isSharedWithMe() && !getFile().canReshare()) {
  362. Snackbar.make(getView(),
  363. R.string.resharing_is_not_allowed,
  364. Snackbar.LENGTH_LONG
  365. )
  366. .show();
  367. } else {
  368. containerActivity.getFileOperationsHelper().sendShareFile(getFile());
  369. }
  370. return true;
  371. case R.id.action_open_file_with:
  372. openFile();
  373. return true;
  374. case R.id.action_remove_file:
  375. RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(getFile());
  376. dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
  377. return true;
  378. case R.id.action_see_details:
  379. seeDetails();
  380. return true;
  381. case R.id.action_download_file:
  382. case R.id.action_sync_file:
  383. containerActivity.getFileOperationsHelper().syncFile(getFile());
  384. return true;
  385. case R.id.action_set_as_wallpaper:
  386. containerActivity.getFileOperationsHelper().setPictureAs(getFile(), getImageView());
  387. return true;
  388. default:
  389. return super.onOptionsItemSelected(item);
  390. }
  391. }
  392. private void seeDetails() {
  393. containerActivity.showDetails(getFile());
  394. }
  395. @SuppressFBWarnings("Dm")
  396. @Override
  397. public void onDestroy() {
  398. if (mBitmap != null) {
  399. mBitmap.recycle();
  400. // putting this in onStop() is just the same; the fragment is always destroyed by
  401. // {@link FragmentStatePagerAdapter} when the fragment in swiped further than the
  402. // valid offscreen distance, and onStop() is never called before than that
  403. }
  404. super.onDestroy();
  405. }
  406. /**
  407. * Opens the previewed image with an external application.
  408. */
  409. private void openFile() {
  410. containerActivity.getFileOperationsHelper().openFile(getFile());
  411. finish();
  412. }
  413. private class LoadBitmapTask extends AsyncTask<OCFile, Void, LoadImage> {
  414. private static final int PARAMS_LENGTH = 1;
  415. /**
  416. * Weak reference to the target {@link ImageView} where the bitmap will be loaded into.
  417. *
  418. * Using a weak reference will avoid memory leaks if the target ImageView is retired from
  419. * memory before the load finishes.
  420. */
  421. private final WeakReference<PhotoView> mImageViewRef;
  422. /**
  423. * Error message to show when a load fails.
  424. */
  425. private int mErrorMessageId;
  426. /**
  427. * Constructor.
  428. *
  429. * @param imageView Target {@link ImageView} where the bitmap will be loaded into.
  430. */
  431. LoadBitmapTask(PhotoView imageView) {
  432. mImageViewRef = new WeakReference<>(imageView);
  433. }
  434. @Override
  435. protected LoadImage doInBackground(OCFile... params) {
  436. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
  437. if (params.length != PARAMS_LENGTH) {
  438. return null;
  439. }
  440. Bitmap bitmapResult = null;
  441. Drawable drawableResult = null;
  442. OCFile ocFile = params[0];
  443. String storagePath = ocFile.getStoragePath();
  444. try {
  445. int maxDownScale = 3; // could be a parameter passed to doInBackground(...)
  446. Point screenSize = DisplayUtils.getScreenSize(getActivity());
  447. int minWidth = screenSize.x;
  448. int minHeight = screenSize.y;
  449. for (int i = 0; i < maxDownScale && bitmapResult == null && drawableResult == null; i++) {
  450. if (MIME_TYPE_SVG.equalsIgnoreCase(ocFile.getMimeType())) {
  451. if (isCancelled()) {
  452. return null;
  453. }
  454. try {
  455. SVG svg = SVG.getFromInputStream(new FileInputStream(storagePath));
  456. drawableResult = new PictureDrawable(svg.renderToPicture());
  457. if (isCancelled()) {
  458. return new LoadImage(null, drawableResult, ocFile);
  459. }
  460. } catch (FileNotFoundException e) {
  461. mErrorMessageId = R.string.common_error_unknown;
  462. Log_OC.e(TAG, "File not found trying to load " + getFile().getStoragePath(), e);
  463. } catch (SVGParseException e) {
  464. mErrorMessageId = R.string.common_error_unknown;
  465. Log_OC.e(TAG, "Couldn't parse SVG " + getFile().getStoragePath(), e);
  466. }
  467. } else {
  468. if (isCancelled()) {
  469. return null;
  470. }
  471. try {
  472. bitmapResult = BitmapUtils.decodeSampledBitmapFromFile(storagePath, minWidth,
  473. minHeight);
  474. if (isCancelled()) {
  475. return new LoadImage(bitmapResult, null, ocFile);
  476. }
  477. if (bitmapResult == null) {
  478. mErrorMessageId = R.string.preview_image_error_unknown_format;
  479. Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
  480. break;
  481. } else {
  482. if (MimeType.JPEG.equalsIgnoreCase(ocFile.getMimeType())) {
  483. // Rotate image, obeying exif tag.
  484. bitmapResult = BitmapUtils.rotateImage(bitmapResult, storagePath);
  485. }
  486. }
  487. } catch (OutOfMemoryError e) {
  488. mErrorMessageId = R.string.common_error_out_memory;
  489. if (i < maxDownScale - 1) {
  490. Log_OC.w(TAG, "Out of memory rendering file " + storagePath + " ; scaling down");
  491. minWidth = minWidth / 2;
  492. minHeight = minHeight / 2;
  493. } else {
  494. Log_OC.w(TAG, "Out of memory rendering file " + storagePath + " ; failing");
  495. }
  496. if (bitmapResult != null) {
  497. bitmapResult.recycle();
  498. }
  499. bitmapResult = null;
  500. }
  501. }
  502. }
  503. } catch (NoSuchFieldError e) {
  504. mErrorMessageId = R.string.common_error_unknown;
  505. Log_OC.e(TAG, "Error from access to non-existing field despite protection; file "
  506. + storagePath, e);
  507. } catch (Throwable t) {
  508. mErrorMessageId = R.string.common_error_unknown;
  509. Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t);
  510. }
  511. return new LoadImage(bitmapResult, drawableResult, ocFile);
  512. }
  513. @Override
  514. protected void onCancelled(LoadImage result) {
  515. if (result != null && result.bitmap != null) {
  516. result.bitmap.recycle();
  517. }
  518. }
  519. @Override
  520. protected void onPostExecute(LoadImage result) {
  521. if (result.bitmap != null || result.drawable != null) {
  522. showLoadedImage(result);
  523. } else {
  524. showErrorMessage(mErrorMessageId);
  525. }
  526. if (result.bitmap != null && mBitmap != result.bitmap) {
  527. // unused bitmap, release it! (just in case)
  528. result.bitmap.recycle();
  529. }
  530. }
  531. private void showLoadedImage(LoadImage result) {
  532. final PhotoView imageView = mImageViewRef.get();
  533. Bitmap bitmap = result.bitmap;
  534. Drawable drawable = result.drawable;
  535. if (imageView != null) {
  536. if (bitmap != null) {
  537. Log_OC.d(TAG, "Showing image with resolution " + bitmap.getWidth() + "x" +
  538. bitmap.getHeight());
  539. if (MIME_TYPE_PNG.equalsIgnoreCase(result.ocFile.getMimeType()) ||
  540. MIME_TYPE_GIF.equalsIgnoreCase(result.ocFile.getMimeType())) {
  541. if (getResources() != null) {
  542. imageView.setImageDrawable(generateCheckerboardLayeredDrawable(result, bitmap));
  543. } else {
  544. imageView.setImageBitmap(bitmap);
  545. }
  546. } else {
  547. imageView.setImageBitmap(bitmap);
  548. }
  549. imageView.setVisibility(View.VISIBLE);
  550. mBitmap = bitmap; // needs to be kept for recycling when not useful
  551. } else if (drawable != null
  552. && MIME_TYPE_SVG.equalsIgnoreCase(result.ocFile.getMimeType())
  553. && getResources() != null) {
  554. imageView.setImageDrawable(generateCheckerboardLayeredDrawable(result, null));
  555. }
  556. }
  557. mMultiView.setVisibility(View.GONE);
  558. if (getResources() != null) {
  559. mImageView.setBackgroundColor(getResources().getColor(R.color.background_color_inverse));
  560. }
  561. mImageView.setVisibility(View.VISIBLE);
  562. }
  563. }
  564. private LayerDrawable generateCheckerboardLayeredDrawable(LoadImage result, Bitmap bitmap) {
  565. Resources r = getResources();
  566. Drawable[] layers = new Drawable[2];
  567. layers[0] = r.getDrawable(R.color.bg_default);
  568. Drawable bitmapDrawable;
  569. if (MIME_TYPE_PNG.equalsIgnoreCase(result.ocFile.getMimeType())) {
  570. bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
  571. } else if (MIME_TYPE_SVG.equalsIgnoreCase(result.ocFile.getMimeType())) {
  572. bitmapDrawable = result.drawable;
  573. } else if (MIME_TYPE_GIF.equalsIgnoreCase(result.ocFile.getMimeType())) {
  574. try {
  575. bitmapDrawable = new GifDrawable(result.ocFile.getStoragePath());
  576. } catch (IOException exception) {
  577. bitmapDrawable = result.drawable;
  578. }
  579. } else {
  580. bitmapDrawable = new BitmapDrawable(getResources(), bitmap);
  581. }
  582. layers[1] = bitmapDrawable;
  583. LayerDrawable layerDrawable = new LayerDrawable(layers);
  584. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  585. Activity activity = getActivity();
  586. if (activity != null) {
  587. int bitmapWidth;
  588. int bitmapHeight;
  589. if (MIME_TYPE_PNG.equalsIgnoreCase(result.ocFile.getMimeType())) {
  590. bitmapWidth = convertDpToPixel(bitmap.getWidth(), getActivity());
  591. bitmapHeight = convertDpToPixel(bitmap.getHeight(), getActivity());
  592. layerDrawable.setLayerSize(0, bitmapWidth, bitmapHeight);
  593. layerDrawable.setLayerSize(1, bitmapWidth, bitmapHeight);
  594. } else {
  595. bitmapWidth = convertDpToPixel(bitmapDrawable.getIntrinsicWidth(), getActivity());
  596. bitmapHeight = convertDpToPixel(bitmapDrawable.getIntrinsicHeight(), getActivity());
  597. layerDrawable.setLayerSize(0, bitmapWidth, bitmapHeight);
  598. layerDrawable.setLayerSize(1, bitmapWidth, bitmapHeight);
  599. }
  600. }
  601. }
  602. return layerDrawable;
  603. }
  604. private void showErrorMessage(@StringRes int errorMessageId) {
  605. mImageView.setBackgroundColor(Color.TRANSPARENT);
  606. setMessageForMultiList(R.string.preview_sorry, errorMessageId, R.drawable.file_image);
  607. }
  608. private void setMultiListLoadingMessage() {
  609. if (mMultiView != null) {
  610. mMultiListHeadline.setText(R.string.file_list_loading);
  611. mMultiListMessage.setText("");
  612. mMultiListIcon.setVisibility(View.GONE);
  613. mMultiListProgress.setVisibility(View.VISIBLE);
  614. }
  615. }
  616. private void setMessageForMultiList(@StringRes int headline, @StringRes int message, @DrawableRes int icon) {
  617. if (mMultiListContainer != null && mMultiListMessage != null) {
  618. mMultiListHeadline.setText(headline);
  619. mMultiListMessage.setText(message);
  620. mMultiListIcon.setImageResource(icon);
  621. mMultiView.setBackgroundColor(getResources().getColor(R.color.background_color_inverse));
  622. mMultiListHeadline.setTextColor(getResources().getColor(R.color.standard_grey));
  623. mMultiListMessage.setTextColor(getResources().getColor(R.color.standard_grey));
  624. mMultiListMessage.setVisibility(View.VISIBLE);
  625. mMultiListIcon.setVisibility(View.VISIBLE);
  626. mMultiListProgress.setVisibility(View.GONE);
  627. }
  628. }
  629. public void setErrorPreviewMessage() {
  630. try {
  631. if (getActivity() != null) {
  632. Snackbar.make(mMultiView,
  633. R.string.resized_image_not_possible_download,
  634. Snackbar.LENGTH_INDEFINITE)
  635. .setAction(R.string.common_yes, v -> {
  636. PreviewImageActivity activity = (PreviewImageActivity) getActivity();
  637. if (activity != null) {
  638. activity.requestForDownload(getFile());
  639. } else {
  640. Snackbar.make(mMultiView,
  641. getResources().getString(R.string.could_not_download_image),
  642. Snackbar.LENGTH_INDEFINITE).show();
  643. }
  644. }
  645. ).show();
  646. } else {
  647. Snackbar.make(mMultiView, R.string.resized_image_not_possible, Snackbar.LENGTH_INDEFINITE).show();
  648. }
  649. } catch (IllegalArgumentException e) {
  650. Log_OC.d(TAG, e.getMessage());
  651. }
  652. }
  653. public void setNoConnectionErrorMessage() {
  654. try {
  655. Snackbar.make(mMultiView, R.string.auth_no_net_conn_title, Snackbar.LENGTH_LONG).show();
  656. } catch (IllegalArgumentException e) {
  657. Log_OC.d(TAG, e.getMessage());
  658. }
  659. }
  660. /**
  661. * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment}
  662. * to be previewed.
  663. *
  664. * @param file File to test if can be previewed.
  665. * @return 'True' if the file can be handled by the fragment.
  666. */
  667. public static boolean canBePreviewed(OCFile file) {
  668. return file != null && MimeTypeUtil.isImage(file);
  669. }
  670. /**
  671. * Finishes the preview
  672. */
  673. private void finish() {
  674. Activity container = getActivity();
  675. if (container != null) {
  676. container.finish();
  677. }
  678. }
  679. private void togglePreviewImageFullScreen() {
  680. Activity activity = getActivity();
  681. if (activity != null) {
  682. ((PreviewImageActivity) activity).toggleFullScreen();
  683. }
  684. toggleImageBackground();
  685. }
  686. private void toggleImageBackground() {
  687. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && getFile() != null
  688. && (MIME_TYPE_PNG.equalsIgnoreCase(getFile().getMimeType()) ||
  689. MIME_TYPE_SVG.equalsIgnoreCase(getFile().getMimeType())) && getActivity() != null
  690. && getActivity() instanceof PreviewImageActivity) {
  691. PreviewImageActivity previewImageActivity = (PreviewImageActivity) getActivity();
  692. if (mImageView.getDrawable() instanceof LayerDrawable) {
  693. LayerDrawable layerDrawable = (LayerDrawable) mImageView.getDrawable();
  694. Drawable layerOne;
  695. if (previewImageActivity.isSystemUIVisible()) {
  696. layerOne = getResources().getDrawable(R.color.bg_default);
  697. } else {
  698. layerOne = getResources().getDrawable(R.drawable.backrepeat);
  699. }
  700. layerDrawable.setDrawableByLayerId(layerDrawable.getId(0), layerOne);
  701. mImageView.setImageDrawable(layerDrawable);
  702. mImageView.invalidate();
  703. }
  704. }
  705. }
  706. private static int convertDpToPixel(float dp, Context context) {
  707. Resources resources = context.getResources();
  708. DisplayMetrics metrics = resources.getDisplayMetrics();
  709. return (int) (dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
  710. }
  711. public PhotoView getImageView() {
  712. return mImageView;
  713. }
  714. private class LoadImage {
  715. private final Bitmap bitmap;
  716. private final Drawable drawable;
  717. private final OCFile ocFile;
  718. LoadImage(Bitmap bitmap, Drawable drawable, OCFile ocFile) {
  719. this.bitmap = bitmap;
  720. this.drawable = drawable;
  721. this.ocFile = ocFile;
  722. }
  723. }
  724. }