627c26aba782c74d5e82fc26873d331d8c6e8bfb
[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.net.Uri;
31 import android.os.Bundle;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.View.OnClickListener;
36 import android.view.ViewGroup;
37 import android.webkit.MimeTypeMap;
38 import android.widget.Button;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 import android.widget.Toast;
42
43 import com.actionbarsherlock.app.SherlockFragment;
44
45 import eu.alefzero.owncloud.DisplayUtils;
46 import eu.alefzero.owncloud.R;
47 import eu.alefzero.owncloud.authenticator.AccountAuthenticator;
48 import eu.alefzero.owncloud.datamodel.OCFile;
49 import eu.alefzero.owncloud.files.services.FileDownloader;
50 import eu.alefzero.owncloud.utils.OwnCloudVersion;
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 * Can be used to get the file that is currently being displayed.
170 * @return The file on the screen.
171 */
172 public OCFile getDisplayedFile(){
173 return mFile;
174 }
175
176 /**
177 * Use this method to signal this Activity that it shall update its view.
178 *
179 * @param file : An {@link OCFile}
180 */
181 public void updateFileDetails(OCFile file, Account ocAccount) {
182 mFile = file;
183 mAccount = ocAccount;
184 updateFileDetails();
185 }
186
187
188 /**
189 * Updates the view with all relevant details about that file.
190 */
191 public void updateFileDetails() {
192
193 if (mFile != null && mLayout == R.layout.file_details_fragment) {
194 Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
195 // set file details
196 setFilename(mFile.getFileName());
197 setFiletype(DisplayUtils.convertMIMEtoPrettyPrint(mFile
198 .getMimetype()));
199 setFilesize(mFile.getFileLength());
200 if(ocVersionSupportsTimeCreated()){
201 setTimeCreated(mFile.getCreationTimestamp());
202 }
203
204 setTimeModified(mFile.getModificationTimestamp());
205
206 // Update preview
207 if (mFile.getStoragePath() != null) {
208 ImageView preview = (ImageView) getView().findViewById(R.id.fdPreview);
209 try {
210 if (mFile.getMimetype().startsWith("image/")) {
211 BitmapFactory.Options options = new Options();
212 options.inScaled = true;
213 options.inPurgeable = true;
214 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
215 options.inPreferQualityOverSpeed = false;
216 }
217 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
218 options.inMutable = false;
219 }
220
221 Bitmap bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
222
223 int width = options.outWidth;
224 int height = options.outHeight;
225 int scale = 1;
226 if (width >= 2048 || height >= 2048) {
227 scale = (int) (Math.ceil(Math.max(height, width)/2048.));
228 options.inSampleSize = scale;
229 bmp.recycle();
230
231 bmp = BitmapFactory.decodeFile(mFile.getStoragePath(), options);
232 }
233 preview.setImageBitmap(bmp);
234 }
235 } catch (OutOfMemoryError e) {
236 preview.setVisibility(View.INVISIBLE);
237 Log.e(TAG, "Out of memory occured for file with size " + mFile.getFileLength());
238
239 } catch (NoSuchFieldError e) {
240 preview.setVisibility(View.INVISIBLE);
241 Log.e(TAG, "Error from access to unexisting field despite protection " + mFile.getFileLength());
242
243 } catch (Throwable t) {
244 preview.setVisibility(View.INVISIBLE);
245 Log.e(TAG, "Unexpected error while creating image preview " + mFile.getFileLength(), t);
246 }
247 downloadButton.setText(R.string.filedetails_open);
248 downloadButton.setOnClickListener(new OnClickListener() {
249 @Override
250 public void onClick(View v) {
251 String storagePath = mFile.getStoragePath();
252 try {
253 Intent i = new Intent(Intent.ACTION_VIEW);
254 i.setDataAndType(Uri.parse("file://"+ storagePath), mFile.getMimetype());
255 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
256 startActivity(i);
257
258 } catch (Throwable t) {
259 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
260 boolean toastIt = true;
261 String mimeType = "";
262 try {
263 Intent i = new Intent(Intent.ACTION_VIEW);
264 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
265 if (mimeType != mFile.getMimetype()) {
266 i.setDataAndType(Uri.parse("file://"+mFile.getStoragePath()), mimeType);
267 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
268 startActivity(i);
269 toastIt = false;
270 }
271
272 } catch (IndexOutOfBoundsException e) {
273 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
274
275 } catch (ActivityNotFoundException e) {
276 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
277
278 } catch (Throwable th) {
279 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
280
281 } finally {
282 if (toastIt) {
283 Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
284 }
285 }
286
287 }
288 }
289 });
290 } else {
291 // Make download button effective
292 downloadButton.setOnClickListener(this);
293 }
294 }
295 }
296
297 /**
298 * Updates the filename in view
299 * @param filename to set
300 */
301 private void setFilename(String filename) {
302 TextView tv = (TextView) getView().findViewById(R.id.fdFilename);
303 if (tv != null)
304 tv.setText(filename);
305 }
306
307 /**
308 * Updates the MIME type in view
309 * @param mimetype to set
310 */
311 private void setFiletype(String mimetype) {
312 TextView tv = (TextView) getView().findViewById(R.id.fdType);
313 if (tv != null)
314 tv.setText(mimetype);
315 }
316
317 /**
318 * Updates the file size in view
319 * @param filesize in bytes to set
320 */
321 private void setFilesize(long filesize) {
322 TextView tv = (TextView) getView().findViewById(R.id.fdSize);
323 if (tv != null)
324 tv.setText(DisplayUtils.bytesToHumanReadable(filesize));
325 }
326
327 /**
328 * Updates the time that the file was created in view
329 * @param milliseconds Unix time to set
330 */
331 private void setTimeCreated(long milliseconds){
332 TextView tv = (TextView) getView().findViewById(R.id.fdCreated);
333 TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);
334 if(tv != null){
335 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
336 tv.setVisibility(View.VISIBLE);
337 tvLabel.setVisibility(View.VISIBLE);
338 }
339 }
340
341 /**
342 * Updates the time that the file was last modified
343 * @param milliseconds Unix time to set
344 */
345 private void setTimeModified(long milliseconds){
346 TextView tv = (TextView) getView().findViewById(R.id.fdModified);
347 if(tv != null){
348 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
349 }
350 }
351
352 /**
353 * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return
354 * the time that the file was created. There is a chance that this will
355 * be fixed in future versions. Use this method to check if this version of
356 * ownCloud has this fix.
357 * @return True, if ownCloud the ownCloud version is supporting creation time
358 */
359 private boolean ocVersionSupportsTimeCreated(){
360 /*if(mAccount != null){
361 AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);
362 OwnCloudVersion ocVersion = new OwnCloudVersion(accManager
363 .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));
364 if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {
365 return true;
366 }
367 }*/
368 return false;
369 }
370
371 /**
372 * Once the file download has finished -> update view
373 * @author Bartek Przybylski
374 */
375 private class DownloadFinishReceiver extends BroadcastReceiver {
376 @Override
377 public void onReceive(Context context, Intent intent) {
378 getView().findViewById(R.id.fdDownloadBtn).setEnabled(true);
379 if (intent.getAction().equals(FileDownloader.BAD_DOWNLOAD_MESSAGE)) {
380 Toast.makeText(context, R.string.downloader_download_failed , Toast.LENGTH_SHORT).show();
381
382 } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) {
383 mFile.setStoragePath(intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH));
384 updateFileDetails();
385 }
386 }
387
388 }
389
390
391 }