From: David A. Velasco Date: Thu, 14 Feb 2013 18:21:09 +0000 (+0100) Subject: Created preview fragment to show previews for audio, video and images; shown when... X-Git-Tag: oc-android-1.4.3~39^2~60 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/7101a04247be24d9eb3c1aa49b88ec2496c7d2d6 Created preview fragment to show previews for audio, video and images; shown when a downloaded file in these categories is downloaded --- diff --git a/res/layout/file_preview.xml b/res/layout/file_preview.xml new file mode 100644 index 00000000..3b2a3921 --- /dev/null +++ b/res/layout/file_preview.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index feeeb488..8466b0cf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -269,6 +269,8 @@ Overwrite Don\'t upload + Image preview + %1$s could not be copied to %2$s local directory diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index fb89f574..c9e2b17a 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -454,12 +454,19 @@ public class OCFile implements Parcelable, Comparable { return 0; } + /** @return 'True' if the file contains audio */ public boolean isAudio() { return (mMimeType != null && mMimeType.startsWith("audio/")); } + /** @return 'True' if the file contains video */ public boolean isVideo() { return (mMimeType != null && mMimeType.startsWith("video/")); } + /** @return 'True' if the file contains an image */ + public boolean isImage() { + return (mMimeType != null && mMimeType.startsWith("image/")); + } + } diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java index 30558cd4..5f4e1ac4 100644 --- a/src/com/owncloud/android/media/MediaService.java +++ b/src/com/owncloud/android/media/MediaService.java @@ -72,7 +72,10 @@ public class MediaService extends Service implements OnCompletionListener, OnPre public static final int OC_MEDIA_ERROR = 0; /** Time To keep the control panel visible when the user does not use it */ - public static final int MEDIA_CONTROL_LIFE = 5000; + public static final int MEDIA_CONTROL_SHORT_LIFE = 5000; + + /** Time To keep the control panel visible when the user does not use it */ + public static final int MEDIA_CONTROL_PERMANENT = 0; /** Volume to set when audio focus is lost and ducking is allowed */ private static final float DUCK_VOLUME = 0.1f; @@ -496,7 +499,7 @@ public class MediaService extends Service implements OnCompletionListener, OnPre } configAndStartMediaPlayer(); if (mMediaController != null) { - mMediaController.show(MEDIA_CONTROL_LIFE); + mMediaController.show(MEDIA_CONTROL_PERMANENT); } } diff --git a/src/com/owncloud/android/media/MediaServiceBinder.java b/src/com/owncloud/android/media/MediaServiceBinder.java index 39628c7c..514f65d4 100644 --- a/src/com/owncloud/android/media/MediaServiceBinder.java +++ b/src/com/owncloud/android/media/MediaServiceBinder.java @@ -175,7 +175,7 @@ public class MediaServiceBinder extends Binder implements MediaController.MediaP } public void unregisterMediaController(MediaController mediaController) { - if (mService.getMediaController() == mediaController) { + if (mediaController != null && mediaController == mService.getMediaController()) { mService.setMediaContoller(null); } diff --git a/src/com/owncloud/android/ui/activity/FileDetailActivity.java b/src/com/owncloud/android/ui/activity/FileDetailActivity.java index ee23ca3d..5bf6bd2b 100644 --- a/src/com/owncloud/android/ui/activity/FileDetailActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDetailActivity.java @@ -27,6 +27,7 @@ import android.content.ServiceConnection; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.util.Log; @@ -39,6 +40,7 @@ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.ui.fragment.FilePreviewFragment; import com.owncloud.android.R; @@ -59,7 +61,7 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File private FileDownloaderBinder mDownloaderBinder = null; private ServiceConnection mDownloadConnection, mUploadConnection = null; private FileUploaderBinder mUploaderBinder = null; - + @Override protected void onCreate(Bundle savedInstanceState) { @@ -82,13 +84,9 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); - OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); - Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); - FileDetailFragment mFileDetail = new FileDetailFragment(file, account); - - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.fragment, mFileDetail, FileDetailFragment.FTAG); - ft.commit(); + if (savedInstanceState == null) { + createChildFragment(); + } } else { backToDisplayActivity(); // the 'back' won't be effective until this.onStart() and this.onResume() are completed; @@ -98,6 +96,23 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File } + private void createChildFragment() { + OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); + Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); + Fragment newFragment = null; + if (FilePreviewFragment.canBePreviewed(file)) { + newFragment = new FilePreviewFragment(file, account); + + } else { + newFragment = new FileDetailFragment(file, account); + } + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.fragment, newFragment, FileDetailFragment.FTAG); + ft.commit(); + } + + + /** Defines callbacks for service binding, passed to bindService() */ private class DetailsServiceConnection implements ServiceConnection { @@ -112,9 +127,10 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File } else { return; } - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) - fragment.updateFileDetails(false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais()) + Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (fragment != null && fragment instanceof FileDetailFragment) { + ((FileDetailFragment) fragment).updateFileDetails(false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais()) + } } @Override @@ -166,9 +182,11 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File protected void onResume() { super.onResume(); - if (!mConfigurationChangedToLandscape) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - fragment.updateFileDetails(false); + if (!mConfigurationChangedToLandscape) { + Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (fragment != null && fragment instanceof FileDetailFragment) { + ((FileDetailFragment) fragment).updateFileDetails(false); + } } } diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 8eafbf79..fbce7cb8 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -48,6 +48,7 @@ import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.provider.MediaStore; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.util.Log; import android.view.View; @@ -87,6 +88,8 @@ import com.owncloud.android.ui.dialog.ChangelogDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog; import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.ui.fragment.FilePreviewFragment; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.R; @@ -289,8 +292,13 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); if (mCurrentFile != null) { - transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); // empty FileDetailFragment + if (FilePreviewFragment.canBePreviewed(mCurrentFile)) { + transaction.replace(R.id.file_details_container, new FilePreviewFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); + } else { + transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); + } mCurrentFile = null; + } else { transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment } @@ -504,11 +512,10 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mDualPane) { // Resets the FileDetailsFragment on Tablets so that it always displays - FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileDetails != null && !fileDetails.isEmpty()) { + Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (fileFragment != null && (fileFragment instanceof FilePreviewFragment || !((FileDetailFragment) fileFragment).isEmpty())) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.remove(fileDetails); - transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); + transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment transaction.commit(); } } @@ -526,9 +533,9 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements super.onSaveInstanceState(outState); outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir); if (mDualPane) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + FileFragment fragment = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); if (fragment != null) { - OCFile file = fragment.getDisplayedFile(); + OCFile file = fragment.getFile(); if (file != null) { outState.putParcelable(FileDetailFragment.EXTRA_FILE, file); } @@ -999,11 +1006,10 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mDualPane) { // Resets the FileDetailsFragment on Tablets so that it always displays - FileDetailFragment fileDetails = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileDetails != null && !fileDetails.isEmpty()) { + Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (fileFragment != null && (fileFragment instanceof FilePreviewFragment || !((FileDetailFragment) fileFragment).isEmpty())) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.remove(fileDetails); - transaction.add(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); + transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment transaction.commit(); } } @@ -1020,8 +1026,12 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mDualPane) { // buttons in the details view are problematic when trying to reuse an existing fragment; create always a new one solves some of them, BUT no all; downloads are 'dangerous' FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + if (FilePreviewFragment.canBePreviewed(file)) { + transaction.replace(R.id.file_details_container, new FilePreviewFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); + } else { + transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); + } + //transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); transaction.commit(); } else { // small or medium screen device -> new Activity @@ -1090,9 +1100,10 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mFileList != null) mFileList.listDirectory(); if (mDualPane) { - FileDetailFragment fragment = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) - fragment.updateFileDetails(false); + Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (fragment != null && fragment instanceof FileDetailFragment) { + ((FileDetailFragment)fragment).updateFileDetails(false); + } } } @@ -1172,8 +1183,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements msg.show(); OCFile removedFile = operation.getFile(); if (mDualPane) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && removedFile.equals(details.getDisplayedFile()) ) { + FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (details != null && removedFile.equals(details.getFile())) { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment transaction.commit(); @@ -1205,9 +1216,9 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements OCFile renamedFile = operation.getFile(); if (result.isSuccess()) { if (mDualPane) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && renamedFile.equals(details.getDisplayedFile()) ) { - details.updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this)); + FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) { + ((FileDetailFragment) details).updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this)); } } if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) { @@ -1269,12 +1280,12 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements fileListFragment.listDirectory(); }*/ if (mDualPane) { - FileDetailFragment details = (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && file.equals(details.getDisplayedFile()) ) { + FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) { if (downloading || uploading) { - details.updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this)); + ((FileDetailFragment)details).updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this)); } else { - details.updateFileDetails(downloading || uploading); + ((FileDetailFragment)details).updateFileDetails(downloading || uploading); } } } diff --git a/src/com/owncloud/android/ui/activity/VideoActivity.java b/src/com/owncloud/android/ui/activity/VideoActivity.java index bb610a2e..be0f8621 100644 --- a/src/com/owncloud/android/ui/activity/VideoActivity.java +++ b/src/com/owncloud/android/ui/activity/VideoActivity.java @@ -183,7 +183,7 @@ public class VideoActivity extends Activity implements OnCompletionListener, OnP @Override public boolean onTouchEvent (MotionEvent ev){ if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mMediaController.show(MediaService.MEDIA_CONTROL_LIFE); + mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE); return true; } else { return false; diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index f37c6094..e90dbf71 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -116,7 +116,8 @@ import eu.alefzero.webdav.WebdavUtils; */ public class FileDetailFragment extends SherlockFragment implements OnClickListener, OnTouchListener, - ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener { + ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener, + FileFragment { public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_ACCOUNT = "ACCOUNT"; @@ -425,28 +426,7 @@ public class FileDetailFragment extends SherlockFragment implements @Override public boolean onTouch(View v, MotionEvent event) { if (v == mPreview && event.getAction() == MotionEvent.ACTION_DOWN && mFile != null && mFile.isDown()) { - if (mFile.isAudio()) { - if (!mMediaServiceBinder.isPlaying(mFile)) { - Log.d(TAG, "starting playback of " + mFile.getStoragePath()); - mMediaServiceBinder.start(mAccount, mFile); - // this is a patch; need to synchronize this with the onPrepared() coming from MediaPlayer in the MediaService - /* - mMediaController.postDelayed(new Runnable() { - @Override - public void run() { - mMediaController.show(0); - } - } , 300); - */ - } else { - if (mMediaController.isShowing()) { - mMediaController.hide(); - } else { - mMediaController.show(MediaService.MEDIA_CONTROL_LIFE); - } - } - - } else if (mFile.isVideo()) { + if (mFile.isVideo()) { startVideoActivity(); } } @@ -620,10 +600,9 @@ public class FileDetailFragment extends SherlockFragment implements /** - * Can be used to get the file that is currently being displayed. - * @return The file on the screen. + * {@inheritDoc} */ - public OCFile getDisplayedFile(){ + public OCFile getFile(){ return mFile; } diff --git a/src/com/owncloud/android/ui/fragment/FileFragment.java b/src/com/owncloud/android/ui/fragment/FileFragment.java new file mode 100644 index 00000000..5537d62e --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/FileFragment.java @@ -0,0 +1,39 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.owncloud.android.ui.fragment; + +import android.support.v4.app.Fragment; + +import com.owncloud.android.datamodel.OCFile; + +/** + * Common methods for {@link Fragment}s containing {@link OCFile}s + * + * @author David A. Velasco + * + */ +public interface FileFragment { + + /** + * Getter for the hold {@link OCFile} + * + * @return The {@link OCFile} hold + */ + public OCFile getFile(); +} diff --git a/src/com/owncloud/android/ui/fragment/FilePreviewFragment.java b/src/com/owncloud/android/ui/fragment/FilePreviewFragment.java new file mode 100644 index 00000000..f00ee1ff --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/FilePreviewFragment.java @@ -0,0 +1,1042 @@ +/* ownCloud Android client application + * Copyright (C) 2012-2013 ownCloud Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.owncloud.android.ui.fragment; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.params.HttpConnectionManagerParams; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.FileEntity; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.HTTP; +import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +import org.json.JSONObject; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.graphics.Point; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.webkit.MimeTypeMap; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.MediaController; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.VideoView; + +import com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.AccountUtils; +import com.owncloud.android.DisplayUtils; +import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader; +import com.owncloud.android.files.services.FileObserverService; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.media.MediaService; +import com.owncloud.android.media.MediaServiceBinder; +import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.operations.OnRemoteOperationListener; +import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.operations.RenameFileOperation; +import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.ui.activity.ConflictsResolveActivity; +import com.owncloud.android.ui.activity.FileDetailActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.OnSwipeTouchListener; +import com.owncloud.android.ui.activity.TransferServiceGetter; +import com.owncloud.android.ui.activity.VideoActivity; +import com.owncloud.android.ui.dialog.EditNameDialog; +import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; +import com.owncloud.android.utils.OwnCloudVersion; + +import com.owncloud.android.R; +import eu.alefzero.webdav.WebdavClient; +import eu.alefzero.webdav.WebdavUtils; + +/** + * This fragment shows a preview of a downloaded file. + * + * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. + * + * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. + * + * @author David A. Velasco + */ +public class FilePreviewFragment extends SherlockFragment implements + /*OnClickListener,*/ OnTouchListener , FileFragment + /*ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener*/ { + + public static final String EXTRA_FILE = "FILE"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION"; + + //private FilePreviewFragment.ContainerActivity mContainerActivity; + + private View mView; + private OCFile mFile; + private Account mAccount; + //private FileDataStorageManager mStorageManager; + private ImageView mImagePreview; + public Bitmap mBitmap = null; + private VideoView mVideoPreview; + private int mSavedPlaybackPosition; + + //private DownloadFinishReceiver mDownloadFinishReceiver; + //private UploadFinishReceiver mUploadFinishReceiver; + + //private Handler mHandler; + //private RemoteOperation mLastRemoteOperation; + //private DialogFragment mCurrentDialog; + + private MediaServiceBinder mMediaServiceBinder = null; + private MediaController mMediaController = null; + private MediaServiceConnection mMediaServiceConnection = null; + private VideoHelper mVideoHelper; + + private static final String TAG = FilePreviewFragment.class.getSimpleName(); + public static final String FTAG = FilePreviewFragment.class.getSimpleName();; + //public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT"; + + + /** + * Creates a fragment to preview a file. + * + * When 'fileToDetail' or 'ocAccount' are null + * + * @param fileToDetail An {@link OCFile} to preview in the fragment + * @param ocAccount An ownCloud account; needed to start downloads + */ + public FilePreviewFragment(OCFile fileToDetail, Account ocAccount) { + mFile = fileToDetail; + mAccount = ocAccount; + mSavedPlaybackPosition = 0; + //mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment + } + + + /** + * Creates an empty fragment for previews. + * + * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). + * + * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction + */ + public FilePreviewFragment() { + mFile = null; + mAccount = null; + mSavedPlaybackPosition = 0; + //mStorageManager = null; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //mHandler = new Handler(); + } + + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + mView = inflater.inflate(R.layout.file_preview, container, false); + + //mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this); + //mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this); + //mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this); + //mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this); + //mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this); + mImagePreview = (ImageView)mView.findViewById(R.id.image_preview); + mImagePreview.setOnTouchListener(this); + mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview); + + //updateFileDetails(false); + return mView; + } + + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + } + + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + //mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + if (savedInstanceState != null) { + mFile = savedInstanceState.getParcelable(FilePreviewFragment.EXTRA_FILE); + mAccount = savedInstanceState.getParcelable(FilePreviewFragment.EXTRA_ACCOUNT); + mSavedPlaybackPosition = savedInstanceState.getInt(FilePreviewFragment.EXTRA_PLAY_POSITION); + + } + if (mFile == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (mAccount == null) { + throw new IllegalStateException("Instanced with a NULL ownCloud Account"); + } + if (!mFile.isDown()) { + throw new IllegalStateException("There is no local file to preview"); + } + if (mFile.isVideo()) { + mVideoPreview.setVisibility(View.VISIBLE); + mImagePreview.setVisibility(View.GONE); + prepareVideo(); + + } else { + mVideoPreview.setVisibility(View.GONE); + mImagePreview.setVisibility(View.VISIBLE); + } + + } + + + /** + * {@inheritDoc} + */ + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(FilePreviewFragment.EXTRA_FILE, mFile); + outState.putParcelable(FilePreviewFragment.EXTRA_ACCOUNT, mAccount); + + if (mVideoPreview.isPlaying()) { + outState.putInt(FilePreviewFragment.EXTRA_PLAY_POSITION , mVideoPreview.getCurrentPosition()); + } + } + + + @Override + public void onStart() { + super.onStart(); + + if (mFile != null) { + if (mFile.isAudio()) { + bindMediaService(); + + } else if (mFile.isImage()) { + BitmapLoader bl = new BitmapLoader(mImagePreview); + bl.execute(new String[]{mFile.getStoragePath()}); + + } else if (mFile.isVideo()) { + playVideo(); + } + } + } + + + private void prepareVideo() { + // create helper to get more control on the playback + mVideoHelper = new VideoHelper(mSavedPlaybackPosition); + mVideoPreview.setOnPreparedListener(mVideoHelper); + mVideoPreview.setOnCompletionListener(mVideoHelper); + mVideoPreview.setOnErrorListener(mVideoHelper); + } + + private void playVideo() { + // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called + mVideoPreview.setVideoPath(mFile.getStoragePath()); + + // create and prepare control panel for the user + mMediaController = new MediaController(getActivity()); + mMediaController.setMediaPlayer(mVideoPreview); + mMediaController.setAnchorView(mVideoPreview); + mVideoPreview.setMediaController(mMediaController); + } + + + private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener { + + /** + * Called when the file is ready to be played. + * + * Just starts the playback. + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onPrepared(MediaPlayer vp) { + mVideoPreview.seekTo(mSavedPlaybackPosition); + mVideoPreview.start(); + mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE); + } + + + /** + * Called when the file is finished playing. + * + * Finishes the activity. + * + * @param mp {@link MediaPlayer} instance performing the playback. + */ + @Override + public void onCompletion(MediaPlayer mp) { + // nothing, right now + } + + + /** + * Called when an error in playback occurs. + * + * @param mp {@link MediaPlayer} instance performing the playback. + * @param what Type of error + * @param extra Extra code specific to the error + */ + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); + + if (mMediaController != null) { + mMediaController.hide(); + } + + if (mVideoPreview.getWindowToken() != null) { + String message = MediaService.getMessageForMediaError(getActivity(), what, extra); + new AlertDialog.Builder(getActivity()) + .setMessage(message) + .setPositiveButton(android.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dialog.dismiss(); + VideoHelper.this.onCompletion(null); + } + }) + .setCancelable(false) + .show(); + } + return true; + } + + } + + + @Override + public void onResume() { + super.onResume(); + /* + mDownloadFinishReceiver = new DownloadFinishReceiver(); + IntentFilter filter = new IntentFilter( + FileDownloader.DOWNLOAD_FINISH_MESSAGE); + getActivity().registerReceiver(mDownloadFinishReceiver, filter); + + mUploadFinishReceiver = new UploadFinishReceiver(); + filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + getActivity().registerReceiver(mUploadFinishReceiver, filter); + */ + + } + + + @Override + public void onPause() { + super.onPause(); + /* + if (mVideoPreview.getVisibility() == View.VISIBLE) { + mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); + }*/ + /* + getActivity().unregisterReceiver(mDownloadFinishReceiver); + mDownloadFinishReceiver = null; + + getActivity().unregisterReceiver(mUploadFinishReceiver); + mUploadFinishReceiver = null; + */ + } + + + @Override + public void onStop() { + super.onStop(); + if (mMediaServiceConnection != null) { + Log.d(TAG, "Unbinding from MediaService ..."); + if (mMediaServiceBinder != null && mMediaController != null) { + mMediaServiceBinder.unregisterMediaController(mMediaController); + } + getActivity().unbindService(mMediaServiceConnection); + mMediaServiceConnection = null; + mMediaServiceBinder = null; + if (mMediaController != null) { + mMediaController.hide(); + mMediaController = null; + } + } + if (mBitmap != null) { + mBitmap.recycle(); + } + } + + /* + @Override + public View getView() { + return super.getView() == null ? mView : super.getView(); + } + */ + + /* + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.fdDownloadBtn: { + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { + downloaderBinder.cancel(mAccount, mFile); + if (mFile.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) { + uploaderBinder.cancel(mAccount, mFile); + if (!mFile.fileExists()) { + // TODO make something better + if (getActivity() instanceof FileDisplayActivity) { + // double pane + FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.file_details_container, new FilePreviewFragment(null, null), FTAG); // empty FileDetailFragment + transaction.commit(); + mContainerActivity.onFileStateChanged(); + } else { + getActivity().finish(); + } + + } else if (mFile.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + + } else { + mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity()); + WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); + mLastRemoteOperation.execute(wc, this, mHandler); + + // update ui + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + setButtonsForTransferring(); // disable button immediately, although the synchronization does not result in a file transference + + } + break; + } + case R.id.fdKeepInSync: { + CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); + mFile.setKeepInSync(cb.isChecked()); + mStorageManager.saveFile(mFile); + + /// register the OCFile instance in the observer service to monitor local updates; + /// if necessary, the file is download + Intent intent = new Intent(getActivity().getApplicationContext(), + FileObserverService.class); + intent.putExtra(FileObserverService.KEY_FILE_CMD, + (cb.isChecked()? + FileObserverService.CMD_ADD_OBSERVED_FILE: + FileObserverService.CMD_DEL_OBSERVED_FILE)); + intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile); + intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); + Log.e(TAG, "starting observer service"); + getActivity().startService(intent); + + if (mFile.keepInSync()) { + onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization + } + break; + } + case R.id.fdRenameBtn: { + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this); + dialog.show(getFragmentManager(), "nameeditdialog"); + break; + } + case R.id.fdRemoveBtn: { + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + R.string.confirmation_remove_alert, + new String[]{mFile.getFileName()}, + mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, + mFile.isDown() ? R.string.confirmation_remove_local : -1, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + mCurrentDialog = confDialog; + mCurrentDialog.show(getFragmentManager(), FTAG_CONFIRMATION); + break; + } + case R.id.fdOpenBtn: { + openFile(); + break; + } + default: + Log.e(TAG, "Incorrect view clicked!"); + } + + } + */ + + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (v == mImagePreview && + mMediaServiceBinder != null && mFile.isAudio() && mMediaServiceBinder.isPlaying(mFile)) { + toggleMediaController(MediaService.MEDIA_CONTROL_PERMANENT); + return true; + + } else if (v == mVideoPreview) { + toggleMediaController(MediaService.MEDIA_CONTROL_SHORT_LIFE); + return true; + } + } + return false; + } + + + private void toggleMediaController(int time) { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(time); + } + } + + + private void playAudio() { + if (!mMediaServiceBinder.isPlaying(mFile)) { + Log.d(TAG, "starting playback of " + mFile.getStoragePath()); + mMediaServiceBinder.start(mAccount, mFile); + + } else { + if (!mMediaServiceBinder.isPlaying()) { + mMediaServiceBinder.start(); + } + if (!mMediaController.isShowing() && isVisible()) { + mMediaController.show(MediaService.MEDIA_CONTROL_PERMANENT); + // TODO - fix strange bug; steps to trigger : + // 1. remove the "isVisible()" control + // 2. start the app and preview an audio file + // 3. exit from the app (home button, for instance) while the audio file is still being played + // 4. go to notification bar and click on the "ownCloud music app" notification + // PUM! + } + } + } + + + private void bindMediaService() { + Log.d(TAG, "Binding to MediaService..."); + if (mMediaServiceConnection == null) { + mMediaServiceConnection = new MediaServiceConnection(); + } + getActivity().bindService( new Intent(getActivity(), + MediaService.class), + mMediaServiceConnection, + Context.BIND_AUTO_CREATE); + // follow the flow in MediaServiceConnection#onServiceConnected(...) + } + + /** Defines callbacks for service binding, passed to bindService() */ + private class MediaServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (component.equals(new ComponentName(getActivity(), MediaService.class))) { + Log.d(TAG, "Media service connected"); + mMediaServiceBinder = (MediaServiceBinder) service; + if (mMediaServiceBinder != null) { + if (mMediaController == null) { + mMediaController = new MediaController(getSherlockActivity()); + } + prepareMediaController(); + playAudio(); // do not wait for the touch of nobody to play audio + + Log.d(TAG, "Successfully bound to MediaService, MediaController ready"); + + } else { + Log.e(TAG, "Unexpected response from MediaService while binding"); + } + } + } + + private void prepareMediaController() { + mMediaServiceBinder.registerMediaController(mMediaController); + mMediaController.setMediaPlayer(mMediaServiceBinder); + mMediaController.setAnchorView(getView()); + mMediaController.setEnabled(mMediaServiceBinder.isInPlaybackState()); + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(getActivity(), MediaService.class))) { + Log.e(TAG, "Media service suddenly disconnected"); + if (mMediaController != null) { + mMediaController.hide(); + mMediaController.setMediaPlayer(null); + mMediaController = null; + } + mMediaServiceBinder = null; + mMediaServiceConnection = null; + } + } + } + + + + /*-* + * Opens mFile. + *-/ + private void openFile() { + + String storagePath = mFile.getStoragePath(); + String encodedStoragePath = WebdavUtils.encodePath(storagePath); + try { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype()); + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + + } catch (Throwable t) { + Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype()); + boolean toastIt = true; + String mimeType = ""; + try { + Intent i = new Intent(Intent.ACTION_VIEW); + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); + if (mimeType == null || !mimeType.equals(mFile.getMimetype())) { + if (mimeType != null) { + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); + } else { + // desperate try + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*"); + } + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivity(i); + toastIt = false; + } + + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); + + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); + + } catch (Throwable th) { + Log.e(TAG, "Unexpected problem when opening: " + storagePath, th); + + } finally { + if (toastIt) { + Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show(); + } + } + + } + } + */ + + /* + @Override + public void onConfirmation(String callerTag) { + if (callerTag.equals(FTAG_CONFIRMATION)) { + if (mStorageManager.getFileById(mFile.getFileId()) != null) { + mLastRemoteOperation = new RemoveFileOperation( mFile, + true, + mStorageManager); + WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); + mLastRemoteOperation.execute(wc, this, mHandler); + + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + } + } + mCurrentDialog.dismiss(); + mCurrentDialog = null; + } + + @Override + public void onNeutral(String callerTag) { + File f = null; + if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) { + f.delete(); + mFile.setStoragePath(null); + mStorageManager.saveFile(mFile); + updateFileDetails(mFile, mAccount); + } + mCurrentDialog.dismiss(); + mCurrentDialog = null; + } + + @Override + public void onCancel(String callerTag) { + Log.d(TAG, "REMOVAL CANCELED"); + mCurrentDialog.dismiss(); + mCurrentDialog = null; + } + */ + + /** + * {@inheritDoc} + */ + public OCFile getFile(){ + return mFile; + } + + /* + /** + * Use this method to signal this Activity that it shall update its view. + * + * @param file : An {@link OCFile} + *-/ + public void updateFileDetails(OCFile file, Account ocAccount) { + mFile = file; + if (ocAccount != null && ( + mStorageManager == null || + (mAccount != null && !mAccount.equals(ocAccount)) + )) { + mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); + } + mAccount = ocAccount; + updateFileDetails(false); + } + */ + + + /** + * Interface to implement by any Activity that includes some instance of FileDetailFragment + * + * @author David A. Velasco + */ + public interface ContainerActivity extends TransferServiceGetter { + + /** + * Callback method invoked when the detail fragment wants to notice its container + * activity about a relevant state the file shown by the fragment. + * + * Added to notify to FileDisplayActivity about the need of refresh the files list. + * + * Currently called when: + * - a download is started; + * - a rename is completed; + * - a deletion is completed; + * - the 'inSync' flag is changed; + */ + public void onFileStateChanged(); + + } + + /* + public void onDismiss(EditNameDialog dialog) { + if (dialog.getResult()) { + String newFilename = dialog.getNewFilename(); + Log.d(TAG, "name edit dialog dismissed with new name " + newFilename); + mLastRemoteOperation = new RenameFileOperation( mFile, + mAccount, + newFilename, + new FileDataStorageManager(mAccount, getActivity().getContentResolver())); + WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); + mLastRemoteOperation.execute(wc, this, mHandler); + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + } + } + */ + + private class BitmapLoader extends AsyncTask { + + /** + * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. + */ + private final WeakReference mImageViewRef; + + + /** + * Constructor. + * + * @param imageView Target {@link ImageView} where the bitmap will be loaded into. + */ + public BitmapLoader(ImageView imageView) { + mImageViewRef = new WeakReference(imageView); + } + + + @SuppressWarnings("deprecation") + @SuppressLint({ "NewApi", "NewApi", "NewApi" }) // to avoid Lint errors since Android SDK r20 + @Override + protected Bitmap doInBackground(String... params) { + Bitmap result = null; + if (params.length != 1) return result; + String storagePath = params[0]; + try { + // set desired options that will affect the size of the bitmap + BitmapFactory.Options options = new Options(); + options.inScaled = true; + options.inPurgeable = true; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + options.inPreferQualityOverSpeed = false; + } + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + options.inMutable = false; + } + // make a false load of the bitmap - just to be able to read outWidth, outHeight and outMimeType + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(storagePath, options); + + int width = options.outWidth; + int height = options.outHeight; + int scale = 1; + if (width >= 2048 || height >= 2048) { + // try to scale down the image to save memory + scale = (int) Math.ceil((Math.ceil(Math.max(height, width) / 2048.))); + options.inSampleSize = scale; + } + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point size = new Point(); + int screenwidth; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR2) { + display.getSize(size); + screenwidth = size.x; + } else { + screenwidth = display.getWidth(); + } + + Log.d(TAG, "image width: " + width + ", screen width: " + screenwidth); + + if (width > screenwidth) { + // second try to scale down the image , this time depending upon the screen size; WTF... + scale = (int) Math.ceil((float)width / screenwidth); + options.inSampleSize = scale; + } + + // really load the bitmap + options.inJustDecodeBounds = false; // the next decodeFile call will be real + result = BitmapFactory.decodeFile(storagePath, options); + Log.e(TAG, "loaded width: " + options.outWidth + ", loaded height: " + options.outHeight); + + } catch (OutOfMemoryError e) { + result = null; + Log.e(TAG, "Out of memory occured for file with size " + storagePath); + + } catch (NoSuchFieldError e) { + result = null; + Log.e(TAG, "Error from access to unexisting field despite protection " + storagePath); + + } catch (Throwable t) { + result = null; + Log.e(TAG, "Unexpected error while creating image preview " + storagePath, t); + } + return result; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null && mImageViewRef != null) { + final ImageView imageView = mImageViewRef.get(); + imageView.setImageBitmap(result); + mBitmap = result; + } + } + + } + + /** + * Helper method to test if an {@link OCFile} can be passed to a {@link FilePreviewFragment} to be previewed. + * + * @param file File to test if can be previewed. + * @return 'True' if the file can be handled by the fragment. + */ + public static boolean canBePreviewed(OCFile file) { + return (file != null && file.isDown() && + (file.isAudio() || file.isVideo() || file.isImage())); + } + + /*-* + * {@inheritDoc} + *-/ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + if (operation.equals(mLastRemoteOperation)) { + if (operation instanceof RemoveFileOperation) { + onRemoveFileOperationFinish((RemoveFileOperation)operation, result); + + } else if (operation instanceof RenameFileOperation) { + onRenameFileOperationFinish((RenameFileOperation)operation, result); + + } else if (operation instanceof SynchronizeFileOperation) { + onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); + } + } + } + */ + + /* + private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + + if (result.isSuccess()) { + Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); + msg.show(); + if (inDisplayActivity) { + // double pane + FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.file_details_container, new FilePreviewFragment(null, null)); // empty FileDetailFragment + transaction.commit(); + mContainerActivity.onFileStateChanged(); + } else { + getActivity().finish(); + } + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + + private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + + if (result.isSuccess()) { + updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount); + mContainerActivity.onFileStateChanged(); + + } else { + if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { + Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG); + msg.show(); + // TODO throw again the new rename dialog + } else { + Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG); + msg.show(); + if (result.isSslRecoverableException()) { + // TODO show the SSL warning dialog + } + } + } + } + + private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { + boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; + getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + + if (!result.isSuccess()) { + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + Intent i = new Intent(getActivity(), ConflictsResolveActivity.class); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); + startActivity(i); + + } else { + Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); + msg.show(); + } + + if (mFile.isDown()) { + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + + } else { + if (operation.transferWasRequested()) { + mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so + // checking the service to see if the file is downloading results in FALSE + } else { + Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); + msg.show(); + if (mFile.isDown()) { + setButtonsForDown(); + + } else { + setButtonsForRemote(); + } + } + } + } + */ + + +}