diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index b3cbb988c4f1..571d15e69bd1 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -32,6 +32,7 @@ import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.services.OperationsService.OperationsServiceBinder; import com.owncloud.android.ui.activity.ComponentsGetter; +import com.owncloud.android.utils.MimeTypeUtil; import java.util.ArrayList; import java.util.Arrays; @@ -257,7 +258,12 @@ private void filter(List toShow, List toHide) { toShow.add(R.id.action_unset_favorite); } - + // STREAM + if (isSingleFile() && !anyFileDown() && (anyFileAudio() || anyFileVideo())){ + toShow.add(R.id.action_stream_file); + } else { + toHide.add(R.id.action_stream_file); + } } private boolean anyFileSynchronizing() { @@ -335,6 +341,24 @@ private boolean anyFileDown() { return false; } + private boolean anyFileAudio() { + for(OCFile file: mFiles) { + if(MimeTypeUtil.isAudio(file)) { + return true; + } + } + return false; + } + + private boolean anyFileVideo() { + for(OCFile file: mFiles) { + if(MimeTypeUtil.isVideo(file)) { + return true; + } + } + return false; + } + private boolean allKeptAvailableOffline() { for(OCFile file: mFiles) { if(!file.isAvailableOffline()) { diff --git a/src/main/java/com/owncloud/android/media/MediaService.java b/src/main/java/com/owncloud/android/media/MediaService.java index e91a4fd4f48c..baf2e98e8bf3 100644 --- a/src/main/java/com/owncloud/android/media/MediaService.java +++ b/src/main/java/com/owncloud/android/media/MediaService.java @@ -21,25 +21,30 @@ package com.owncloud.android.media; import android.accounts.Account; +import android.app.Activity; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.media.AudioManager; 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.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.IBinder; import android.os.PowerManager; +import android.support.v7.app.AlertDialog; import android.support.v7.app.NotificationCompat; import android.widget.Toast; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; @@ -214,6 +219,25 @@ public static String getMessageForMediaError(Context context, int what, int extr return context.getString(messageId); } + public static AlertDialog.Builder streamWithExternalApp(final Uri uri, final Activity activity){ + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setMessage(activity.getString(R.string.stream_expose_password)) + .setPositiveButton(activity.getString(R.string.common_yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(uri); + activity.startActivity(i); + } + }) + .setNegativeButton(activity.getString(R.string.common_no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); + return builder; + } + /** @@ -435,12 +459,7 @@ protected void playMedia() { releaseResources(false); // release everything except MediaPlayer try { - if (mFile == null) { - Toast.makeText(this, R.string.media_err_nothing_to_play, Toast.LENGTH_LONG).show(); - processStopRequest(true); - return; - - } else if (mAccount == null) { + if (mAccount == null) { Toast.makeText(this, R.string.media_err_not_in_owncloud, Toast.LENGTH_LONG).show(); processStopRequest(true); return; @@ -449,12 +468,12 @@ protected void playMedia() { createMediaPlayerIfNeeded(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); String url = mFile.getStoragePath(); - /* Streaming is not possible right now + if (url == null || url.length() <= 0) { url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath(); } mIsStreaming = url.startsWith("http:") || url.startsWith("https:"); - */ + mIsStreaming = false; mPlayer.setDataSource(url); @@ -495,6 +514,8 @@ protected void playMedia() { Toast.makeText(this, String.format(getString(R.string.media_err_unexpected), mFile.getFileName()), Toast.LENGTH_LONG).show(); processStopRequest(true); + } catch (AccountUtils.AccountNotFoundException e) { + Log_OC.e(TAG, "playMedia - Account missing",e); } } diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 00738fbca37c..bd90a76c4cbe 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -67,6 +68,7 @@ import com.owncloud.android.lib.resources.files.ToggleFavoriteOperation; import com.owncloud.android.lib.resources.shares.GetRemoteSharesOperation; import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.media.MediaService; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.FolderPickerActivity; @@ -801,15 +803,13 @@ public void onItemClick(AdapterView l, View v, int position, long id) { ((FileDisplayActivity) mContainerActivity).startImagePreview(file); } else if (PreviewTextFragment.canBePreviewed(file)) { ((FileDisplayActivity) mContainerActivity).startTextPreview(file); - } else if (file.isDown()) { - if (PreviewMediaFragment.canBePreviewed(file)) { + } else if (PreviewMediaFragment.canBePreviewed(file)) { // media preview ((FileDisplayActivity) mContainerActivity).startMediaPreview(file, 0, true); - } else { + } else if (file.isDown()) { mContainerActivity.getFileOperationsHelper().openFile(file); } - - } else { + else { // automatic download, preview on finish ((FileDisplayActivity) mContainerActivity).startDownloadForPreview(file); } @@ -869,6 +869,14 @@ public boolean onFileActionChosen(int menuId) { } return true; } + case R.id.action_stream_file: { + Account account = ((FileActivity)mContainerActivity).getAccount(); + Context context = MainApp.getAppContext(); + Uri uri = PreviewMediaFragment.generateUrlWithCredentials(account, context, singleFile); + MediaService.streamWithExternalApp(uri, getActivity()).show(); + + return true; + } } } @@ -1029,7 +1037,7 @@ private void updateLayout() { updateFooter(); // decide grid vs list view OwnCloudVersion version = AccountUtils.getServerVersion( - ((FileActivity) mContainerActivity).getAccount()); + ((FileActivity)mContainerActivity).getAccount()); if (version != null && version.supportsRemoteThumbnails() && isGridViewPreferred(mFile)) { switchToGridView(); diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 77786561a50d..5b701adc1195 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -20,6 +20,8 @@ package com.owncloud.android.ui.preview; import android.accounts.Account; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; import android.app.Activity; import android.content.ComponentName; import android.content.Context; @@ -35,6 +37,8 @@ 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.IBinder; import android.support.v7.app.AlertDialog; @@ -50,9 +54,15 @@ import android.widget.Toast; import android.widget.VideoView; +import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.FileMenuFilter; +import com.owncloud.android.lib.common.OwnCloudAccount; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.OwnCloudCredentials; +import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.media.MediaControlView; import com.owncloud.android.media.MediaService; @@ -63,6 +73,8 @@ import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.utils.MimeTypeUtil; +import java.io.IOException; + /** * This fragment shows a preview of a downloaded media file (audio or video). @@ -73,8 +85,7 @@ * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is * generated on instantiation too. */ -public class PreviewMediaFragment extends FileFragment implements - OnTouchListener { +public class PreviewMediaFragment extends FileFragment implements OnTouchListener { public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_ACCOUNT = "ACCOUNT"; @@ -134,7 +145,6 @@ public PreviewMediaFragment() { mAutoplay = true; } - /** * {@inheritDoc} */ @@ -144,7 +154,6 @@ public void onCreate(Bundle savedInstanceState) { setHasOptionsMenu(true); } - /** * {@inheritDoc} */ @@ -166,7 +175,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return mView; } - /** * {@inheritDoc} */ @@ -183,10 +191,6 @@ public void onActivityCreated(Bundle savedInstanceState) { if (mAccount == null) { throw new IllegalStateException("Instanced with a NULL ownCloud Account"); } - if (!file.isDown()) { - throw new IllegalStateException("There is no local file to preview"); - } - } else { file = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE); @@ -197,7 +201,7 @@ public void onActivityCreated(Bundle savedInstanceState) { mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING); } - if (file != null && file.isDown()) { + if (file != null) { if (MimeTypeUtil.isVideo(file)) { mVideoPreview.setVisibility(View.VISIBLE); mImagePreview.setVisibility(View.GONE); @@ -236,7 +240,6 @@ private void extractAndSetCoverArt(OCFile file) { } } - /** * {@inheritDoc} */ @@ -267,14 +270,13 @@ public void onSaveInstanceState(Bundle outState) { } } - @Override public void onStart() { super.onStart(); Log_OC.v(TAG, "onStart"); OCFile file = getFile(); - if (file != null && file.isDown()) { + if (file != null) { if (MimeTypeUtil.isAudio(file)) { bindMediaService(); @@ -288,14 +290,12 @@ public void onStart() { } } - private void stopAudio() { Intent i = new Intent(getActivity(), MediaService.class); i.setAction(MediaService.ACTION_STOP_ALL); getActivity().startService(i); } - /** * {@inheritDoc} */ @@ -305,7 +305,6 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.file_actions_menu, menu); } - /** * {@inheritDoc} */ @@ -361,7 +360,6 @@ public void onPrepareOptionsMenu(Menu menu) { } - /** * {@inheritDoc} */ @@ -398,7 +396,6 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - /** * Update the file of the fragment with file value * @@ -437,11 +434,67 @@ private void playVideo() { // create and prepare control panel for the user mMediaController.setMediaPlayer(mVideoPreview); + Uri videoUri; + // load the video file in the video player ; // when done, VideoHelper#onPrepared() will be called - mVideoPreview.setVideoURI(getFile().getStorageUri()); + if (getFile().isDown()) { + videoUri = getFile().getStorageUri(); + } else { + Context context = MainApp.getAppContext(); + videoUri = generateUrlWithCredentials(mAccount, context, getFile()); + } + + mVideoPreview.setVideoURI(videoUri); + } + + public static Uri generateUrlWithCredentials(Account account, Context context, OCFile file){ + OwnCloudAccount ocAccount = null; + try { + ocAccount = new OwnCloudAccount(account, context); + + final ClientGenerationTask task = new ClientGenerationTask(); + task.execute(ocAccount); + + OwnCloudClient mClient = task.get(); + String url = AccountUtils.constructFullURLForAccount(context, account) + + Uri.encode(file.getRemotePath(), "/"); + OwnCloudCredentials credentials = mClient.getCredentials(); + + return Uri.parse(url.replace("//", "//" + credentials.getUsername() + ":" + credentials.getAuthToken() + + "@")); + } catch (AccountUtils.AccountNotFoundException e) { + Log_OC.e(TAG,"Couldn't create streaming URI - Account missing", e); + } catch (Exception e) { + Log_OC.e(TAG,"Couldn't create streaming URI", e); + } + + return null; } + public static class ClientGenerationTask extends AsyncTask { + @Override + protected OwnCloudClient doInBackground(Object... params) { + Object account = params[0]; + if (account instanceof OwnCloudAccount){ + try { + OwnCloudAccount ocAccount = (OwnCloudAccount) account; + return OwnCloudClientManagerFactory.getDefaultSingleton(). + getClientFor(ocAccount, MainApp.getAppContext()); + } catch (AccountUtils.AccountNotFoundException e) { + Log_OC.e(TAG,"Couldn't create Nc client - Account missing", e); + } catch (OperationCanceledException e) { + Log_OC.e(TAG,"Couldn't create Nc client - Operation canceled", e); + } catch (AuthenticatorException e) { + Log_OC.e(TAG,"Couldn't create Nc client - Authentication error", e); + } catch (IOException e) { + Log_OC.e(TAG,"Couldn't create Nc client - I/O error", e); + } + } + + return null; + } + } private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener { @@ -464,7 +517,6 @@ public void onPrepared(MediaPlayer vp) { mPrepared = true; } - /** * Called when the file is finished playing. *

@@ -481,7 +533,6 @@ public void onCompletion(MediaPlayer mp) { mMediaController.updatePausePlay(); } - /** * Called when an error in playback occurs. * @@ -491,7 +542,6 @@ public void onCompletion(MediaPlayer mp) { */ @Override public boolean onError(MediaPlayer mp, int what, int extra) { - Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); if (mVideoPreview.getWindowToken() != null) { String message = MediaService.getMessageForMediaError( getActivity(), what, extra); @@ -509,10 +559,8 @@ public void onClick(DialogInterface dialog, int whichButton) { } return true; } - } - @Override public void onPause() { Log_OC.v(TAG, "onPause"); @@ -561,7 +609,6 @@ public boolean onTouch(View v, MotionEvent event) { return false; } - private void startFullScreenVideo() { Intent i = new Intent(getActivity(), PreviewVideoActivity.class); i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); @@ -594,7 +641,6 @@ private void playAudio() { if (!mMediaServiceBinder.isPlaying(file)) { Log_OC.d(TAG, "starting playback of " + file.getStoragePath()); mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition); - } else { if (!mMediaServiceBinder.isPlaying() && mAutoplay) { @@ -610,11 +656,12 @@ private void bindMediaService() { if (mMediaServiceConnection == null) { mMediaServiceConnection = new MediaServiceConnection(); } - getActivity().bindService( new Intent(getActivity(), - MediaService.class), - mMediaServiceConnection, - Context.BIND_AUTO_CREATE); - // follow the flow in MediaServiceConnection#onServiceConnected(...) + 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() */ @@ -666,7 +713,6 @@ public void onServiceDisconnected(ComponentName component) { } } - /** * Opens the previewed file with an external application. */ @@ -687,7 +733,6 @@ public static boolean canBePreviewed(OCFile file) { return (file != null && (MimeTypeUtil.isAudio(file) || MimeTypeUtil.isVideo(file))); } - public void stopPreview(boolean stopAudio) { OCFile file = getFile(); if (MimeTypeUtil.isAudio(file) && stopAudio) { @@ -701,7 +746,6 @@ public void stopPreview(boolean stopAudio) { } } - /** * Finishes the preview */ @@ -709,7 +753,6 @@ private void finish() { getActivity().onBackPressed(); } - public int getPosition() { if (mPrepared) { mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); @@ -724,5 +767,4 @@ public boolean isPlaying() { } return mAutoplay; } - } diff --git a/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java b/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java index 1ef2cc62d34b..f8a8994fec05 100644 --- a/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@ -208,14 +208,9 @@ protected void onAccountSet(boolean stateWasRecovered) { mVideoPlayer.setVideoURI(file.getStorageUri()); } else { - // not working yet - String url; - try { - url = AccountUtils.constructFullURLForAccount(this, getAccount()) + file.getRemotePath(); - mVideoPlayer.setVideoURI(Uri.parse(url)); - } catch (AccountNotFoundException e) { - onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account); - } + Uri uri = PreviewMediaFragment.generateUrlWithCredentials(getAccount(), getApplicationContext(), + getFile()); + mVideoPlayer.setVideoURI(uri); } // create and prepare control panel for the user diff --git a/src/main/res/menu/file_actions_menu.xml b/src/main/res/menu/file_actions_menu.xml index 0b0b5dff5343..0103cdc704da 100644 --- a/src/main/res/menu/file_actions_menu.xml +++ b/src/main/res/menu/file_actions_menu.xml @@ -29,6 +29,12 @@ app:showAsAction="never" android:showAsAction="never" android:orderInCategory="1" /> + Move file Select all + Stream file with external player + Do you want to stream this file with an external app?\n\nCAUTION: This may expose your password locally on the display! + kept in original folder moved to app folder deleted