FileDetailFragment is not reused in double pane view to avoid problems with preview...
[pub/Android/ownCloud.git] / src / eu / alefzero / owncloud / ui / fragment / FileDetailFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
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 as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18 package eu.alefzero.owncloud.ui.fragment;
19
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.content.ActivityNotFoundException;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.graphics.BitmapFactory.Options;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.View.OnClickListener;
38 import android.view.ViewGroup;
39 import android.webkit.MimeTypeMap;
40 import android.widget.Button;
41 import android.widget.ImageView;
42 import android.widget.TextView;
43 import android.widget.Toast;
44
45 import com.actionbarsherlock.app.SherlockFragment;
46
47 import eu.alefzero.owncloud.DisplayUtils;
48 import eu.alefzero.owncloud.R;
49 import eu.alefzero.owncloud.datamodel.OCFile;
50 import eu.alefzero.owncloud.files.services.FileDownloader;
51
52 /**
53 * This Fragment is used to display the details about a file.
54 *
55 * @author Bartek Przybylski
56 *
57 */
58 public class FileDetailFragment extends SherlockFragment implements
59 OnClickListener {
60
61 public static final String EXTRA_FILE = "FILE";
62 public static final String EXTRA_ACCOUNT = "ACCOUNT";
63
64 private int mLayout;
65 private View mView;
66 private OCFile mFile;
67 private Account mAccount;
68
69 private DownloadFinishReceiver mDownloadFinishReceiver;
70
71 private static final String TAG = "FileDetailFragment";
72 public static final String FTAG = "FileDetails";
73
74
75 /**
76 * Creates an empty details fragment.
77 *
78 * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically.
79 */
80 public FileDetailFragment() {
81 mFile = null;
82 mAccount = null;
83 mLayout = R.layout.file_details_empty;
84 }
85
86
87 /**
88 * Creates a details fragment.
89 *
90 * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
91 *
92 * @param fileToDetail An {@link OCFile} to show in the fragment
93 * @param ocAccount An ownCloud account; needed to start downloads
94 */
95 public FileDetailFragment(OCFile fileToDetail, Account ocAccount){
96 mFile = fileToDetail;
97 mAccount = ocAccount;
98 mLayout = R.layout.file_details_empty;
99
100 if(fileToDetail != null && ocAccount != null) {
101 mLayout = R.layout.file_details_fragment;
102 }
103 }
104
105
106 @Override
107 public View onCreateView(LayoutInflater inflater, ViewGroup container,
108 Bundle savedInstanceState) {
109 super.onCreateView(inflater, container, savedInstanceState);
110
111 if (savedInstanceState != null) {
112 mFile = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);
113 mAccount = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_ACCOUNT);
114 }
115
116 View view = null;
117 view = inflater.inflate(mLayout, container, false);
118 mView = view;
119
120 updateFileDetails();
121 return view;
122 }
123
124
125 @Override
126 public void onSaveInstanceState(Bundle outState) {
127 Log.i(getClass().toString(), "onSaveInstanceState() start");
128 super.onSaveInstanceState(outState);
129 outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile);
130 outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
131 Log.i(getClass().toString(), "onSaveInstanceState() end");
132 }
133
134
135 @Override
136 public void onResume() {
137 super.onResume();
138 mDownloadFinishReceiver = new DownloadFinishReceiver();
139 IntentFilter filter = new IntentFilter(
140 FileDownloader.DOWNLOAD_FINISH_MESSAGE);
141 getActivity().registerReceiver(mDownloadFinishReceiver, filter);
142 }
143
144 @Override
145 public void onPause() {
146 super.onPause();
147 getActivity().unregisterReceiver(mDownloadFinishReceiver);
148 mDownloadFinishReceiver = null;
149 }
150
151 @Override
152 public View getView() {
153 return super.getView() == null ? mView : super.getView();
154 }
155
156 @Override
157 public void onClick(View v) {
158 Toast.makeText(getActivity(), "Downloading", Toast.LENGTH_LONG).show();
159 Intent i = new Intent(getActivity(), FileDownloader.class);
160 i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
161 i.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mFile.getRemotePath());
162 i.putExtra(FileDownloader.EXTRA_FILE_PATH, mFile.getURLDecodedRemotePath());
163 i.putExtra(FileDownloader.EXTRA_FILE_SIZE, mFile.getFileLength());
164 v.setEnabled(false);
165 getActivity().startService(i);
166 }
167
168
169 /**
170 * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.
171 *
172 * @return True when the fragment was created with the empty layout.
173 */
174 public boolean isEmpty() {
175 return mLayout == R.layout.file_details_empty;
176 }
177
178
179 /**
180 * Can be used to get the file that is currently being displayed.
181 * @return The file on the screen.
182 */
183 public OCFile getDisplayedFile(){
184 return mFile;
185 }
186
187 /**
188 * Use this method to signal this Activity that it shall update its view.
189 *
190 * @param file : An {@link OCFile}
191 */
192 public void updateFileDetails(OCFile file, Account ocAccount) {
193 mFile = file;
194 mAccount = ocAccount;
195 updateFileDetails();
196 }
197
198
199 /**
200 * Updates the view with all relevant details about that file.
201 */
202 public void updateFileDetails() {
203
204 if (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment) {
205
206 Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
207 // set file details
208 setFilename(mFile.getFileName());
209 setFiletype(DisplayUtils.convertMIMEtoPrettyPrint(mFile
210 .getMimetype()));
211 setFilesize(mFile.getFileLength());
212 if(ocVersionSupportsTimeCreated()){
213 setTimeCreated(mFile.getCreationTimestamp());
214 }
215
216 setTimeModified(mFile.getModificationTimestamp());
217
218 if (mFile.getStoragePath() != null) {
219 // Update preview
220 ImageView preview = (ImageView) getView().findViewById(R.id.fdPreview);
221 boolean previewIsSet = false;
222 try {
223 if (mFile.getMimetype().startsWith("image/")) {
224 BitmapFactory.Options options = new Options();
225 options.inScaled = true;
226 options.inPurgeable = true;
227 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
228 options.inPreferQualityOverSpeed = false;
229 }
230 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
231 options.inMutable = false;
232 }
233
234 Bitmap bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
235
236 if (bmp != null) {
237 int width = options.outWidth;
238 int height = options.outHeight;
239 int scale = 1;
240 if (width >= 2048 || height >= 2048) {
241 scale = (int) (Math.ceil(Math.max(height, width)/2048.));
242 options.inSampleSize = scale;
243 bmp.recycle();
244
245 bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
246 }
247 }
248 if (bmp != null) {
249 //preview.setImageBitmap(bmp);
250 preview.setImageDrawable(new BitmapDrawable(preview.getResources(), bmp));
251 previewIsSet = true;
252 }
253 }
254 } catch (OutOfMemoryError e) {
255 preview.setVisibility(View.INVISIBLE);
256 Log.e(TAG, "Out of memory occured for file with size " + mFile.getFileLength());
257
258 } catch (NoSuchFieldError e) {
259 preview.setVisibility(View.INVISIBLE);
260 Log.e(TAG, "Error from access to unexisting field despite protection " + mFile.getFileLength());
261
262 } catch (Throwable t) {
263 preview.setVisibility(View.INVISIBLE);
264 Log.e(TAG, "Unexpected error while creating image preview " + mFile.getFileLength(), t);
265
266 } finally {
267 if (!previewIsSet) {
268 resetPreview();
269 }
270 }
271 // Change download button to open button
272 downloadButton.setText(R.string.filedetails_open);
273 downloadButton.setOnClickListener(new OnClickListener() {
274 @Override
275 public void onClick(View v) {
276 String storagePath = mFile.getStoragePath();
277 try {
278 Intent i = new Intent(Intent.ACTION_VIEW);
279 i.setDataAndType(Uri.parse("file://"+ storagePath), mFile.getMimetype());
280 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
281 startActivity(i);
282
283 } catch (Throwable t) {
284 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
285 boolean toastIt = true;
286 String mimeType = "";
287 try {
288 Intent i = new Intent(Intent.ACTION_VIEW);
289 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
290 if (mimeType != mFile.getMimetype()) {
291 i.setDataAndType(Uri.parse("file://"+mFile.getStoragePath()), mimeType);
292 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
293 startActivity(i);
294 toastIt = false;
295 }
296
297 } catch (IndexOutOfBoundsException e) {
298 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
299
300 } catch (ActivityNotFoundException e) {
301 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
302
303 } catch (Throwable th) {
304 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
305
306 } finally {
307 if (toastIt) {
308 Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
309 }
310 }
311
312 }
313 }
314 });
315 } else {
316 // Make download button effective
317 downloadButton.setOnClickListener(this);
318 // Be sure that preview image is reset; the fragment is reused when possible, a preview of other file could be there
319 resetPreview();
320 }
321 }
322 }
323
324
325 /**
326 * Updates the filename in view
327 * @param filename to set
328 */
329 private void setFilename(String filename) {
330 TextView tv = (TextView) getView().findViewById(R.id.fdFilename);
331 if (tv != null)
332 tv.setText(filename);
333 }
334
335 /**
336 * Updates the MIME type in view
337 * @param mimetype to set
338 */
339 private void setFiletype(String mimetype) {
340 TextView tv = (TextView) getView().findViewById(R.id.fdType);
341 if (tv != null)
342 tv.setText(mimetype);
343 }
344
345 /**
346 * Updates the file size in view
347 * @param filesize in bytes to set
348 */
349 private void setFilesize(long filesize) {
350 TextView tv = (TextView) getView().findViewById(R.id.fdSize);
351 if (tv != null)
352 tv.setText(DisplayUtils.bytesToHumanReadable(filesize));
353 }
354
355 /**
356 * Updates the time that the file was created in view
357 * @param milliseconds Unix time to set
358 */
359 private void setTimeCreated(long milliseconds){
360 TextView tv = (TextView) getView().findViewById(R.id.fdCreated);
361 TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);
362 if(tv != null){
363 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
364 tv.setVisibility(View.VISIBLE);
365 tvLabel.setVisibility(View.VISIBLE);
366 }
367 }
368
369 /**
370 * Updates the time that the file was last modified
371 * @param milliseconds Unix time to set
372 */
373 private void setTimeModified(long milliseconds){
374 TextView tv = (TextView) getView().findViewById(R.id.fdModified);
375 if(tv != null){
376 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
377 }
378 }
379
380 /**
381 * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return
382 * the time that the file was created. There is a chance that this will
383 * be fixed in future versions. Use this method to check if this version of
384 * ownCloud has this fix.
385 * @return True, if ownCloud the ownCloud version is supporting creation time
386 */
387 private boolean ocVersionSupportsTimeCreated(){
388 /*if(mAccount != null){
389 AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);
390 OwnCloudVersion ocVersion = new OwnCloudVersion(accManager
391 .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));
392 if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {
393 return true;
394 }
395 }*/
396 return false;
397 }
398
399 /**
400 * Once the file download has finished -> update view
401 * @author Bartek Przybylski
402 */
403 private class DownloadFinishReceiver extends BroadcastReceiver {
404 @Override
405 public void onReceive(Context context, Intent intent) {
406 getView().findViewById(R.id.fdDownloadBtn).setEnabled(true);
407 if (intent.getAction().equals(FileDownloader.BAD_DOWNLOAD_MESSAGE)) {
408 Toast.makeText(context, R.string.downloader_download_failed , Toast.LENGTH_SHORT).show();
409
410 } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
411 mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH));
412 updateFileDetails();
413 }
414 }
415
416 }
417
418
419 /**
420 * Make the preview image shows the ownCloud logo.
421 *
422 * To be called when setting a preview image is not possible.
423 */
424 private void resetPreview() {
425 ImageView preview = (ImageView) getView().findViewById(R.id.fdPreview);
426 preview.setImageDrawable(getResources().getDrawable(R.drawable.owncloud_logo));
427 }
428
429
430 }