51585c6cfde2f0d139cbb8ca8b89dd0fb7f0e761
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / preview / PreviewImageFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17 package com.owncloud.android.ui.preview;
18
19 import java.lang.ref.WeakReference;
20
21 import android.accounts.Account;
22 import android.app.Activity;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.support.v4.app.FragmentStatePagerAdapter;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.ViewGroup;
32 import android.widget.ImageView;
33 import android.widget.ProgressBar;
34 import android.widget.TextView;
35
36 import com.actionbarsherlock.view.Menu;
37 import com.actionbarsherlock.view.MenuInflater;
38 import com.actionbarsherlock.view.MenuItem;
39 import com.ortiz.touch.TouchImageView;
40 import com.owncloud.android.R;
41 import com.owncloud.android.datamodel.OCFile;
42 import com.owncloud.android.files.FileMenuFilter;
43 import com.owncloud.android.lib.common.utils.Log_OC;
44 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
45 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
46 import com.owncloud.android.ui.fragment.FileFragment;
47
48
49 /**
50 * This fragment shows a preview of a downloaded image.
51 *
52 * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}.
53 *
54 * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too.
55 *
56 * @author David A. Velasco
57 */
58 public class PreviewImageFragment extends FileFragment {
59 public static final String EXTRA_FILE = "FILE";
60 public static final String EXTRA_ACCOUNT = "ACCOUNT";
61
62 private View mView;
63 private Account mAccount;
64 private TouchImageView mImageView;
65 private TextView mMessageView;
66 private ProgressBar mProgressWheel;
67
68 public Bitmap mBitmap = null;
69
70 private static final String TAG = PreviewImageFragment.class.getSimpleName();
71
72 private boolean mIgnoreFirstSavedState;
73
74
75 /**
76 * Creates a fragment to preview an image.
77 *
78 * When 'imageFile' or 'ocAccount' are null
79 *
80 * @param imageFile An {@link OCFile} to preview as an image in the fragment
81 * @param ocAccount An ownCloud account; needed to start downloads
82 * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution
83 */
84 public PreviewImageFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) {
85 super(fileToDetail);
86 mAccount = ocAccount;
87 mIgnoreFirstSavedState = ignoreFirstSavedState;
88 }
89
90
91 /**
92 * Creates an empty fragment for image previews.
93 *
94 * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
95 *
96 * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
97 */
98 public PreviewImageFragment() {
99 super();
100 mAccount = null;
101 mIgnoreFirstSavedState = false;
102 }
103
104
105 /**
106 * {@inheritDoc}
107 */
108 @Override
109 public void onCreate(Bundle savedInstanceState) {
110 super.onCreate(savedInstanceState);
111 setHasOptionsMenu(true);
112 }
113
114
115 /**
116 * {@inheritDoc}
117 */
118 @Override
119 public View onCreateView(LayoutInflater inflater, ViewGroup container,
120 Bundle savedInstanceState) {
121 super.onCreateView(inflater, container, savedInstanceState);
122 mView = inflater.inflate(R.layout.preview_image_fragment, container, false);
123 mImageView = (TouchImageView) mView.findViewById(R.id.image);
124 mImageView.setVisibility(View.GONE);
125 mImageView.setOnClickListener(new OnClickListener() {
126 @Override
127 public void onClick(View v) {
128 ((PreviewImageActivity) getActivity()).toggleFullScreen();
129 }
130
131 });
132 mMessageView = (TextView)mView.findViewById(R.id.message);
133 mMessageView.setVisibility(View.GONE);
134 mProgressWheel = (ProgressBar)mView.findViewById(R.id.progressWheel);
135 mProgressWheel.setVisibility(View.VISIBLE);
136 return mView;
137 }
138
139 /**
140 * {@inheritDoc}
141 */
142 @Override
143 public void onActivityCreated(Bundle savedInstanceState) {
144 super.onActivityCreated(savedInstanceState);
145 if (savedInstanceState != null) {
146 if (!mIgnoreFirstSavedState) {
147 OCFile file = (OCFile)savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE);
148 setFile(file);
149 mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT);
150 } else {
151 mIgnoreFirstSavedState = false;
152 }
153 }
154 if (getFile() == null) {
155 throw new IllegalStateException("Instanced with a NULL OCFile");
156 }
157 if (mAccount == null) {
158 throw new IllegalStateException("Instanced with a NULL ownCloud Account");
159 }
160 if (!getFile().isDown()) {
161 throw new IllegalStateException("There is no local file to preview");
162 }
163 }
164
165
166 /**
167 * {@inheritDoc}
168 */
169 @Override
170 public void onSaveInstanceState(Bundle outState) {
171 super.onSaveInstanceState(outState);
172 outState.putParcelable(PreviewImageFragment.EXTRA_FILE, getFile());
173 outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount);
174 }
175
176
177 @Override
178 public void onStart() {
179 super.onStart();
180 if (getFile() != null) {
181 BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel);
182 bl.execute(new String[]{getFile().getStoragePath()});
183 }
184 }
185
186
187 /**
188 * {@inheritDoc}
189 */
190 @Override
191 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
192 super.onCreateOptionsMenu(menu, inflater);
193 inflater.inflate(R.menu.file_actions_menu, menu);
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 @Override
200 public void onPrepareOptionsMenu(Menu menu) {
201 super.onPrepareOptionsMenu(menu);
202
203 if (mContainerActivity.getStorageManager() != null) {
204 // Update the file
205 setFile(mContainerActivity.getStorageManager().getFileById(getFile().getFileId()));
206
207 FileMenuFilter mf = new FileMenuFilter(
208 getFile(),
209 mContainerActivity.getStorageManager().getAccount(),
210 mContainerActivity,
211 getSherlockActivity()
212 );
213 mf.filter(menu);
214 }
215
216 // additional restriction for this fragment
217 // TODO allow renaming in PreviewImageFragment
218 MenuItem item = menu.findItem(R.id.action_rename_file);
219 if (item != null) {
220 item.setVisible(false);
221 item.setEnabled(false);
222 }
223
224 // additional restriction for this fragment
225 // TODO allow refresh file in PreviewImageFragment
226 item = menu.findItem(R.id.action_sync_file);
227 if (item != null) {
228 item.setVisible(false);
229 item.setEnabled(false);
230 }
231
232 // additional restriction for this fragment
233 item = menu.findItem(R.id.action_move);
234 if (item != null) {
235 item.setVisible(false);
236 item.setEnabled(false);
237 }
238
239 }
240
241
242
243 /**
244 * {@inheritDoc}
245 */
246 @Override
247 public boolean onOptionsItemSelected(MenuItem item) {
248 switch (item.getItemId()) {
249 case R.id.action_share_file: {
250 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
251 return true;
252 }
253 case R.id.action_unshare_file: {
254 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
255 return true;
256 }
257 case R.id.action_open_file_with: {
258 openFile();
259 return true;
260 }
261 case R.id.action_remove_file: {
262 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(getFile());
263 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
264 return true;
265 }
266 case R.id.action_see_details: {
267 seeDetails();
268 return true;
269 }
270 case R.id.action_send_file: {
271 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
272 return true;
273 }
274 case R.id.action_sync_file: {
275 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
276 return true;
277 }
278
279 default:
280 return false;
281 }
282 }
283
284
285 private void seeDetails() {
286 mContainerActivity.showDetails(getFile());
287 }
288
289
290 @Override
291 public void onResume() {
292 super.onResume();
293 }
294
295
296 @Override
297 public void onPause() {
298 super.onPause();
299 }
300
301 @Override
302 public void onDestroy() {
303 if (mBitmap != null) {
304 mBitmap.recycle();
305 }
306 super.onDestroy();
307 }
308
309
310 /**
311 * Opens the previewed image with an external application.
312 */
313 private void openFile() {
314 mContainerActivity.getFileOperationsHelper().openFile(getFile());
315 finish();
316 }
317
318
319 private class BitmapLoader extends AsyncTask<String, Void, Bitmap> {
320
321 /**
322 * Weak reference to the target {@link ImageView} where the bitmap will be loaded into.
323 *
324 * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
325 */
326 private final WeakReference<ImageView> mImageViewRef;
327
328 /**
329 * Weak reference to the target {@link TextView} where error messages will be written.
330 *
331 * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
332 */
333 private final WeakReference<TextView> mMessageViewRef;
334
335
336 /**
337 * Weak reference to the target {@link Progressbar} shown while the load is in progress.
338 *
339 * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes.
340 */
341 private final WeakReference<ProgressBar> mProgressWheelRef;
342
343
344 /**
345 * Error message to show when a load fails
346 */
347 private int mErrorMessageId;
348
349
350 /**
351 * Constructor.
352 *
353 * @param imageView Target {@link ImageView} where the bitmap will be loaded into.
354 */
355 public BitmapLoader(ImageView imageView, TextView messageView, ProgressBar progressWheel) {
356 mImageViewRef = new WeakReference<ImageView>(imageView);
357 mMessageViewRef = new WeakReference<TextView>(messageView);
358 mProgressWheelRef = new WeakReference<ProgressBar>(progressWheel);
359 }
360
361
362 @Override
363 protected Bitmap doInBackground(String... params) {
364 Bitmap result = null;
365 if (params.length != 1) return result;
366 String storagePath = params[0];
367 try {
368 //Decode file into a bitmap in real size for being able to make zoom on the image
369 result = BitmapFactory.decodeFile(storagePath);
370
371 if (result == null) {
372 mErrorMessageId = R.string.preview_image_error_unknown_format;
373 Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
374 }
375
376 } catch (OutOfMemoryError e) {
377 mErrorMessageId = R.string.preview_image_error_unknown_format;
378 Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e);
379
380 } catch (NoSuchFieldError e) {
381 mErrorMessageId = R.string.common_error_unknown;
382 Log_OC.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e);
383
384 } catch (Throwable t) {
385 mErrorMessageId = R.string.common_error_unknown;
386 Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t);
387
388 }
389 return result;
390 }
391
392 @Override
393 protected void onPostExecute(Bitmap result) {
394 hideProgressWheel();
395 if (result != null) {
396 showLoadedImage(result);
397 } else {
398 showErrorMessage();
399 }
400 }
401
402 private void showLoadedImage(Bitmap result) {
403 if (mImageViewRef != null) {
404 final ImageView imageView = mImageViewRef.get();
405 if (imageView != null) {
406 imageView.setImageBitmap(result);
407 imageView.setVisibility(View.VISIBLE);
408 mBitmap = result;
409 } // else , silently finish, the fragment was destroyed
410 }
411 if (mMessageViewRef != null) {
412 final TextView messageView = mMessageViewRef.get();
413 if (messageView != null) {
414 messageView.setVisibility(View.GONE);
415 } // else , silently finish, the fragment was destroyed
416 }
417 }
418
419 private void showErrorMessage() {
420 if (mImageViewRef != null) {
421 final ImageView imageView = mImageViewRef.get();
422 if (imageView != null) {
423 // shows the default error icon
424 imageView.setVisibility(View.VISIBLE);
425 } // else , silently finish, the fragment was destroyed
426 }
427 if (mMessageViewRef != null) {
428 final TextView messageView = mMessageViewRef.get();
429 if (messageView != null) {
430 messageView.setText(mErrorMessageId);
431 messageView.setVisibility(View.VISIBLE);
432 } // else , silently finish, the fragment was destroyed
433 }
434 }
435
436 private void hideProgressWheel() {
437 if (mProgressWheelRef != null) {
438 final ProgressBar progressWheel = mProgressWheelRef.get();
439 if (progressWheel != null) {
440 progressWheel.setVisibility(View.GONE);
441 }
442 }
443 }
444
445 }
446
447 /**
448 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed.
449 *
450 * @param file File to test if can be previewed.
451 * @return 'True' if the file can be handled by the fragment.
452 */
453 public static boolean canBePreviewed(OCFile file) {
454 return (file != null && file.isImage());
455 }
456
457
458 /**
459 * Finishes the preview
460 */
461 private void finish() {
462 Activity container = getActivity();
463 container.finish();
464 }
465
466 public TouchImageView getImageView() {
467 return mImageView;
468 }
469
470 }