505ff6c74a9c89b4f40876e685b978aa3ac9639f
[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 try {
222 if (mFile.getMimetype().startsWith("image/")) {
223 BitmapFactory.Options options = new Options();
224 options.inScaled = true;
225 options.inPurgeable = true;
226 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
227 options.inPreferQualityOverSpeed = false;
228 }
229 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
230 options.inMutable = false;
231 }
232
233 Bitmap bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
234
235 if (bmp != null) {
236 int width = options.outWidth;
237 int height = options.outHeight;
238 int scale = 1;
239 if (width >= 2048 || height >= 2048) {
240 scale = (int) (Math.ceil(Math.max(height, width)/2048.));
241 options.inSampleSize = scale;
242 bmp.recycle();
243
244 bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
245 }
246 }
247 if (bmp != null) {
248 preview.setImageBitmap(bmp);
249 }
250 }
251 } catch (OutOfMemoryError e) {
252 preview.setVisibility(View.INVISIBLE);
253 Log.e(TAG, "Out of memory occured for file with size " + mFile.getFileLength());
254
255 } catch (NoSuchFieldError e) {
256 preview.setVisibility(View.INVISIBLE);
257 Log.e(TAG, "Error from access to unexisting field despite protection " + mFile.getFileLength());
258
259 } catch (Throwable t) {
260 preview.setVisibility(View.INVISIBLE);
261 Log.e(TAG, "Unexpected error while creating image preview " + mFile.getFileLength(), t);
262 }
263
264 // Change download button to open button
265 downloadButton.setText(R.string.filedetails_open);
266 downloadButton.setOnClickListener(new OnClickListener() {
267 @Override
268 public void onClick(View v) {
269 String storagePath = mFile.getStoragePath();
270 try {
271 Intent i = new Intent(Intent.ACTION_VIEW);
272 i.setDataAndType(Uri.parse("file://"+ storagePath), mFile.getMimetype());
273 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
274 startActivity(i);
275
276 } catch (Throwable t) {
277 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
278 boolean toastIt = true;
279 String mimeType = "";
280 try {
281 Intent i = new Intent(Intent.ACTION_VIEW);
282 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
283 if (mimeType != null && !mimeType.equals(mFile.getMimetype())) {
284 i.setDataAndType(Uri.parse("file://"+mFile.getStoragePath()), mimeType);
285 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
286 startActivity(i);
287 toastIt = false;
288 }
289
290 } catch (IndexOutOfBoundsException e) {
291 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
292
293 } catch (ActivityNotFoundException e) {
294 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
295
296 } catch (Throwable th) {
297 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
298
299 } finally {
300 if (toastIt) {
301 Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
302 }
303 }
304
305 }
306 }
307 });
308 } else {
309 // Make download button effective
310 downloadButton.setOnClickListener(this);
311 }
312 }
313 }
314
315
316 /**
317 * Updates the filename in view
318 * @param filename to set
319 */
320 private void setFilename(String filename) {
321 TextView tv = (TextView) getView().findViewById(R.id.fdFilename);
322 if (tv != null)
323 tv.setText(filename);
324 }
325
326 /**
327 * Updates the MIME type in view
328 * @param mimetype to set
329 */
330 private void setFiletype(String mimetype) {
331 TextView tv = (TextView) getView().findViewById(R.id.fdType);
332 if (tv != null)
333 tv.setText(mimetype);
334 }
335
336 /**
337 * Updates the file size in view
338 * @param filesize in bytes to set
339 */
340 private void setFilesize(long filesize) {
341 TextView tv = (TextView) getView().findViewById(R.id.fdSize);
342 if (tv != null)
343 tv.setText(DisplayUtils.bytesToHumanReadable(filesize));
344 }
345
346 /**
347 * Updates the time that the file was created in view
348 * @param milliseconds Unix time to set
349 */
350 private void setTimeCreated(long milliseconds){
351 TextView tv = (TextView) getView().findViewById(R.id.fdCreated);
352 TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);
353 if(tv != null){
354 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
355 tv.setVisibility(View.VISIBLE);
356 tvLabel.setVisibility(View.VISIBLE);
357 }
358 }
359
360 /**
361 * Updates the time that the file was last modified
362 * @param milliseconds Unix time to set
363 */
364 private void setTimeModified(long milliseconds){
365 TextView tv = (TextView) getView().findViewById(R.id.fdModified);
366 if(tv != null){
367 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
368 }
369 }
370
371 /**
372 * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return
373 * the time that the file was created. There is a chance that this will
374 * be fixed in future versions. Use this method to check if this version of
375 * ownCloud has this fix.
376 * @return True, if ownCloud the ownCloud version is supporting creation time
377 */
378 private boolean ocVersionSupportsTimeCreated(){
379 /*if(mAccount != null){
380 AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);
381 OwnCloudVersion ocVersion = new OwnCloudVersion(accManager
382 .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));
383 if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {
384 return true;
385 }
386 }*/
387 return false;
388 }
389
390 /**
391 * Once the file download has finished -> update view
392 * @author Bartek Przybylski
393 */
394 private class DownloadFinishReceiver extends BroadcastReceiver {
395 @Override
396 public void onReceive(Context context, Intent intent) {
397 getView().findViewById(R.id.fdDownloadBtn).setEnabled(true);
398 if (intent.getAction().equals(FileDownloader.BAD_DOWNLOAD_MESSAGE)) {
399 Toast.makeText(context, R.string.downloader_download_failed , Toast.LENGTH_SHORT).show();
400
401 } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
402 mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH));
403 updateFileDetails();
404 }
405 }
406
407 }
408
409 }