diff options
Diffstat (limited to 'News-Android-App/src/main/java')
37 files changed, 2785 insertions, 218 deletions
diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java index 0e0c2fea..5c073094 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/ListView/PodcastArrayAdapter.java @@ -5,40 +5,46 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; import butterknife.ButterKnife; import butterknife.InjectView; import de.greenrobot.event.EventBus; import de.luhmer.owncloudnewsreader.R; import de.luhmer.owncloudnewsreader.events.podcast.AudioPodcastClicked; -import de.luhmer.owncloudnewsreader.model.AudioPodcastItem; +import de.luhmer.owncloudnewsreader.events.podcast.StartDownloadPodcast; +import de.luhmer.owncloudnewsreader.helper.JavaYoutubeDownloader; +import de.luhmer.owncloudnewsreader.model.PodcastItem; /** * Created by David on 21.06.2014. */ -public class PodcastArrayAdapter extends ArrayAdapter<AudioPodcastItem> { +public class PodcastArrayAdapter extends ArrayAdapter<PodcastItem> { LayoutInflater inflater; EventBus eventBus; - public PodcastArrayAdapter(Context context, AudioPodcastItem[] values) { - super(context, R.layout.podcast_audio_row, values); + public PodcastArrayAdapter(Context context, PodcastItem[] values) { + super(context, R.layout.podcast_row, values); inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); eventBus = EventBus.getDefault(); + //eventBus.register(this); } @Override public View getView(final int position, View view, ViewGroup parent) { - ViewHolder holder; + final ViewHolder holder; if (view != null) { holder = (ViewHolder) view.getTag(); } else { - view = inflater.inflate(R.layout.podcast_audio_row, parent, false); + view = inflater.inflate(R.layout.podcast_row, parent, false); holder = new ViewHolder(view); view.setTag(holder); } - AudioPodcastItem podcastItem = getItem(position); + final PodcastItem podcastItem = getItem(position); holder.tvTitle.setText(podcastItem.title); holder.tvBody.setText(podcastItem.mimeType); @@ -46,19 +52,85 @@ public class PodcastArrayAdapter extends ArrayAdapter<AudioPodcastItem> { view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - AudioPodcastClicked audioPodcastClicked = new AudioPodcastClicked(); - audioPodcastClicked.position = position; - eventBus.post(audioPodcastClicked); + playPodcast(position); } }); + + holder.flDownloadPodcast.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + holder.flDownloadPodcast.setVisibility(View.GONE); + + Toast.makeText(getContext(), "Starting download.. Please wait", Toast.LENGTH_SHORT).show(); + + eventBus.post(new StartDownloadPodcast() {{ podcast = podcastItem; }}); + } + }); + + holder.flPlayPodcast.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + playPodcast(position); + } + }); + + + holder.pbDownloadPodcast.setProgress(podcastItem.downloadProgress); + if(podcastItem.downloadProgress >= 0) + holder.tvDownloadPodcastProgress.setText(podcastItem.downloadProgress + "%"); + else + holder.tvDownloadPodcastProgress.setText(null); + + + if(podcastItem.link.contains(JavaYoutubeDownloader.host)) { + if(podcastItem.downloadProgress == PodcastItem.DOWNLOAD_NOT_STARTED) { + holder.flPlayPodcast.setVisibility(View.GONE);//Youtube Videos can't be streamed + holder.flDownloadPodcast.setVisibility(View.VISIBLE); + } else { + holder.flPlayPodcast.setVisibility(View.VISIBLE); + holder.flDownloadPodcast.setVisibility(View.GONE); + } + } else if(podcastItem.downloadProgress == PodcastItem.DOWNLOAD_NOT_STARTED) { + holder.flDownloadPodcast.setVisibility(View.VISIBLE); + } else { + holder.flDownloadPodcast.setVisibility(View.GONE); + } + + /* + File podcastFile = new File(PodcastDownloadService.getUrlToPodcastFile(getContext(), podcastItem.link, true)); + File podcastFileCache = new File(PodcastDownloadService.getUrlToPodcastFile(getContext(), podcastItem.link, true) + ".download"); + if(podcastFile.exists()) { + holder.flDownloadPodcast.setVisibility(View.GONE); + } + else if(podcastFileCache.exists()) { + holder.flDownloadPodcast.setVisibility(View.GONE); + } + else + holder.flDownloadPodcast.setVisibility(View.VISIBLE); + */ + return view; } + private void playPodcast(int position) { + AudioPodcastClicked audioPodcastClicked = new AudioPodcastClicked(); + audioPodcastClicked.position = position; + eventBus.post(audioPodcastClicked); + } + + + static class ViewHolder { @InjectView(R.id.tv_title) TextView tvTitle; @InjectView(R.id.tv_body) TextView tvBody; + @InjectView(R.id.fl_downloadPodcastWrapper) FrameLayout flDownloadPodcast; + @InjectView(R.id.fl_PlayPodcastWrapper) FrameLayout flPlayPodcast; + @InjectView(R.id.pbDownloadPodcast) ProgressBar pbDownloadPodcast; + @InjectView(R.id.tvDownloadPodcastProgress) TextView tvDownloadPodcastProgress; + + public ViewHolder(View view) { ButterKnife.inject(this, view); diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/MenuUtilsSherlockFragmentActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/MenuUtilsSherlockFragmentActivity.java new file mode 100644 index 00000000..cafc0807 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/MenuUtilsSherlockFragmentActivity.java @@ -0,0 +1,220 @@ +/** + * Android ownCloud News + * + * @author David Luhmer + * @copyright 2013 David Luhmer david-dev@live.de + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package de.luhmer.owncloudnewsreader; + +import android.annotation.TargetApi; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.v4.app.FragmentActivity; +import android.util.Log; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; + +import de.luhmer.owncloudnewsreader.database.DatabaseConnection; +import de.luhmer.owncloudnewsreader.reader.IReader; +import de.luhmer.owncloudnewsreader.reader.OnAsyncTaskCompletedListener; +import de.luhmer.owncloudnewsreader.reader.owncloud.API; +import de.luhmer.owncloudnewsreader.reader.owncloud.OwnCloud_Reader; + +public class MenuUtilsSherlockFragmentActivity extends PodcastSherlockFragmentActivity { + + protected static final String TAG = "MenuUtils"; + + static FragmentActivity activity; + + static MenuItem menuItemSettings; + static MenuItem menuItemLogin; + static MenuItem menuItemStartImageCaching; + + + private static MenuItem menuItemUpdater; + private static MenuItem menuItemDownloadMoreItems; + + static IReader _Reader; + + /** + * @return the menuItemUpdater + */ + public static MenuItem getMenuItemUpdater() { + return menuItemUpdater; + } + + + /** + * @return the menuItemDownloadMoreItems + */ + public static MenuItem getMenuItemDownloadMoreItems() { + return menuItemDownloadMoreItems; + } + + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + protected void onResume() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + invalidateOptionsMenu(); + } + super.onResume(); + } + + + public static void onCreateOptionsMenu(Menu menu, MenuInflater inflater, FragmentActivity act) { + inflater.inflate(R.menu.news_reader, menu); + activity = act; + + menuItemSettings = menu.findItem(R.id.action_settings); + menuItemLogin = menu.findItem(R.id.action_login); + menuItemStartImageCaching = menu.findItem(R.id.menu_StartImageCaching); + + menuItemUpdater = menu.findItem(R.id.menu_update); + //menuItemMarkAllAsRead = menu.findItem(R.id.menu_markAllAsRead); + menuItemDownloadMoreItems = menu.findItem(R.id.menu_downloadMoreItems); + + + //menuItemMarkAllAsRead.setEnabled(false); + menuItemDownloadMoreItems.setEnabled(false); + + NewsReaderDetailFragment ndf = ((NewsReaderDetailFragment) activity.getSupportFragmentManager().findFragmentById(R.id.content_frame)); + if(ndf != null) + ndf.UpdateMenuItemsState(); + } + + public static boolean onOptionsItemSelected(MenuItem item, FragmentActivity activity) { + switch (item.getItemId()) { + case R.id.menu_About_Changelog: + SherlockDialogFragment dialog = new VersionInfoDialogFragment(); + dialog.show(activity.getSupportFragmentManager(), "VersionChangelogDialogFragment"); + return true; + + case R.id.menu_markAllAsRead: + NewsReaderDetailFragment ndf = ((NewsReaderDetailFragment) activity.getSupportFragmentManager().findFragmentById(R.id.content_frame)); + if(ndf != null) + { + /* + for(int i = 0; i < ndf.getListView().getChildCount(); i++) + { + View view = ndf.getListView().getChildAt(i); + CheckBox cb = (CheckBox) view.findViewById(R.id.cb_lv_item_read); + if(!cb.isChecked()) + cb.setChecked(true); + } + */ + + DatabaseConnection dbConn = new DatabaseConnection(activity); + try { + /* + //dbConn.markAllItemsAsRead(ndf.getDatabaseIdsOfItems()); + List<Integer> items = new ArrayList<Integer>(); + + + Cursor cursor = ncla.getLvAdapter().getCursor(); + cursor.moveToFirst(); + do { + items.add(Integer.parseInt(cursor.getString(0))); + } while (cursor.moveToNext()); + dbConn.markAllItemsAsRead(items); + */ + dbConn.markAllItemsAsReadForCurrentView(); + } finally { + dbConn.closeDatabase(); + } + ndf.UpdateCursor(); + + //If tablet view is enabled update the listview as well + if(activity instanceof NewsReaderListActivity) + ((NewsReaderListActivity) activity).updateAdapter(); + + } + return true; + + case R.id.menu_downloadMoreItems: + DownloadMoreItems(); + return true; + } + return false; + } + + private static void DownloadMoreItems() + { + /* + DatabaseConnection dbConn = new DatabaseConnection(activity); + int count = dbConn.getCountFeedsForFolder(SubscriptionExpandableListAdapter.ALL_ITEMS, false); + if(count >= Constants.maxItemsCount) + { + String text = activity.getString(R.string.max_items_count_reached); + text = text.replace("XX", "" + Constants.maxItemsCount); + new AlertDialog.Builder(activity) + .setTitle(activity.getString(R.string.empty_view_header)) + .setMessage(text) + .setPositiveButton(activity.getString(android.R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog,int id) { + + } + }) + .create() + .show(); + //Toast.makeText(activity, text, Toast.LENGTH_LONG).show(); + } + else + {*/ + String username = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()).getString("edt_username", ""); + String password = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()).getString("edt_password", ""); + + if(username != null) { + _Reader = new OwnCloud_Reader(); + ((OwnCloud_Reader)_Reader).Start_AsyncTask_GetVersion(Constants.TaskID_GetVersion, activity, onAsyncTaskGetVersionFinished, username, password); + + Toast.makeText(activity, activity.getString(R.string.toast_GettingMoreItems), Toast.LENGTH_SHORT).show(); + } + //} + } + + static OnAsyncTaskCompletedListener onAsyncTaskGetVersionFinished = new OnAsyncTaskCompletedListener() { + + @Override + public void onAsyncTaskCompleted(int task_id, Object task_result) { + if(_Reader != null) { + String appVersion = task_result.toString(); + API api = API.GetRightApiForVersion(appVersion, activity); + ((OwnCloud_Reader) _Reader).setApi(api); + + NewsReaderDetailFragment ndf = ((NewsReaderDetailFragment) activity.getSupportFragmentManager().findFragmentById(R.id.content_frame)); + _Reader.Start_AsyncTask_GetOldItems(Constants.TaskID_GetItems, activity, onAsyncTaskComplete, ndf.getIdFeed(), ndf.getIdFolder()); + } + } + }; + + static OnAsyncTaskCompletedListener onAsyncTaskComplete = new OnAsyncTaskCompletedListener() { + @Override + public void onAsyncTaskCompleted(int task_id, Object task_result) { + NewsReaderDetailFragment ndf = ((NewsReaderDetailFragment) activity.getSupportFragmentManager().findFragmentById(R.id.content_frame)); + if(ndf != null) + ndf.UpdateCursor(); + + Log.d(TAG, "Finished Download extra items.."); + } + }; +}
\ No newline at end of file diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java index 33b8083a..0c00036f 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailActivity.java @@ -38,9 +38,7 @@ import android.support.v4.view.ViewPager.OnPageChangeListener; import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; -import android.view.View; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; @@ -53,10 +51,9 @@ import de.luhmer.owncloudnewsreader.helper.PostDelayHandler; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; import de.luhmer.owncloudnewsreader.reader.IReader; import de.luhmer.owncloudnewsreader.reader.owncloud.OwnCloud_Reader; -import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout; import de.luhmer.owncloudnewsreader.widget.WidgetProvider; -public class NewsDetailActivity extends SherlockFragmentActivity { +public class NewsDetailActivity extends PodcastSherlockFragmentActivity { /** * The {@link android.support.v4.view.PagerAdapter} that will provide @@ -67,7 +64,6 @@ public class NewsDetailActivity extends SherlockFragmentActivity { * {@link android.support.v4.app.FragmentStatePagerAdapter}. */ SectionsPagerAdapter mSectionsPagerAdapter; - public PodcastSlidingUpPanelLayout sliding_layout; /** * The {@link ViewPager} that will host the section contents. @@ -75,8 +71,6 @@ public class NewsDetailActivity extends SherlockFragmentActivity { public ViewPager mViewPager; private int currentPosition; - PodcastFragment podcastFragment; - PostDelayHandler pDelayHandler; MenuItem menuItem_Starred; @@ -100,9 +94,6 @@ public class NewsDetailActivity extends SherlockFragmentActivity { SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - sliding_layout = (PodcastSlidingUpPanelLayout) findViewById(R.id.sliding_layout); - UpdatePodcastView(); - pDelayHandler = new PostDelayHandler(this); _Reader = new OwnCloud_Reader(); @@ -184,20 +175,9 @@ public class NewsDetailActivity extends SherlockFragmentActivity { public void onPageScrollStateChanged(int arg0) { } }); - } - - public void UpdatePodcastView() { - SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - if(mPrefs.getBoolean(SettingsActivity.CB_ENABLE_PODCASTS_STRING, false)) { - podcastFragment = PodcastFragment.newInstance(null, null); - getSupportFragmentManager().beginTransaction() - .replace(R.id.podcast_frame, podcastFragment) - .commit(); - } else { - sliding_layout.getChildAt(1).setVisibility(View.GONE); - } } + @Override protected void onDestroy() { if(dbConn != null) @@ -349,10 +329,8 @@ public class NewsDetailActivity extends SherlockFragmentActivity { @Override public void onBackPressed() { - if(podcastFragment != null && sliding_layout.isPanelExpanded()) { - if (!podcastFragment.onBackPressed()) - sliding_layout.collapsePanel(); - } else + if(handlePodcastBackPressed()); + else super.onBackPressed(); } @@ -383,10 +361,8 @@ public class NewsDetailActivity extends SherlockFragmentActivity { switch (item.getItemId()) { case android.R.id.home: - if(podcastFragment != null && sliding_layout.isPanelExpanded()) { - if (!podcastFragment.onBackPressed()) - sliding_layout.collapsePanel(); - } else { + if(handlePodcastBackPressed()); + else { super.onBackPressed(); } break; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailFragment.java index 89085df6..1fabb950 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsDetailFragment.java @@ -46,6 +46,7 @@ import java.util.Date; import java.util.List; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; +import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.FontHelper; import de.luhmer.owncloudnewsreader.helper.ImageHandler; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; @@ -264,7 +265,7 @@ public class NewsDetailFragment extends SherlockFragment { favIconUrl = favIconCursor.getString(favIconCursor.getColumnIndex(DatabaseConnection.SUBSCRIPTION_FAVICON_URL)); if(favIconUrl != null) { - File file = ImageHandler.getFullPathOfCacheFile(favIconUrl, ImageHandler.getPathFavIcons(context)); + File file = ImageHandler.getFullPathOfCacheFile(favIconUrl, FileUtils.getPathFavIcons(context)); if(file.isFile()) favIconUrl = "file://" + file.getAbsolutePath().toString(); } @@ -356,7 +357,7 @@ public class NewsDetailFragment extends SherlockFragment { link = link.trim(); try { - File file = ImageHandler.getFullPathOfCacheFile(link, ImageHandler.getPathImageCache(context)); + File file = ImageHandler.getFullPathOfCacheFile(link, FileUtils.getPathImageCache(context)); if(file.isFile()) text = text.replace(link, "file://" + file.getAbsolutePath().toString()); } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java index 8fda9002..361abab7 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderDetailFragment.java @@ -52,7 +52,6 @@ import de.luhmer.owncloudnewsreader.cursor.NewsListCursorAdapter; import de.luhmer.owncloudnewsreader.cursor.SimpleCursorLoader; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; import de.luhmer.owncloudnewsreader.database.DatabaseConnection.SORT_DIRECTION; -import de.luhmer.owncloudnewsreader.helper.MenuUtilsSherlockFragmentActivity; /** * A fragment representing a single NewsReader detail screen. This fragment is @@ -140,7 +139,7 @@ public class NewsReaderDetailFragment extends SherlockListFragment implements IO public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - //setRetainInstance(true); + setRetainInstance(true); //dbConn = new DatabaseConnection(getActivity()); @@ -173,7 +172,7 @@ public class NewsReaderDetailFragment extends SherlockListFragment implements IO } - public void UpdateMenuItemsState() + public void UpdateMenuItemsState() { if(MenuUtilsSherlockFragmentActivity.getMenuItemDownloadMoreItems() != null) { @@ -225,6 +224,8 @@ public class NewsReaderDetailFragment extends SherlockListFragment implements IO + + private RobotoCheckBox getCheckBoxAtPosition(int pos, AbsListView viewLV) { ListView lv = (ListView) viewLV; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java index 5d539889..e006a116 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/NewsReaderListActivity.java @@ -49,11 +49,9 @@ import de.luhmer.owncloudnewsreader.cursor.NewsListCursorAdapter; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; import de.luhmer.owncloudnewsreader.helper.DatabaseUtils; import de.luhmer.owncloudnewsreader.helper.ImageHandler; -import de.luhmer.owncloudnewsreader.helper.MenuUtilsSherlockFragmentActivity; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; import de.luhmer.owncloudnewsreader.services.DownloadImagesService; import de.luhmer.owncloudnewsreader.services.IOwnCloudSyncService; -import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout; import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefreshLayout; /** @@ -70,8 +68,8 @@ import uk.co.senab.actionbarpulltorefresh.extras.actionbarsherlock.PullToRefresh public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity implements NewsReaderListFragment.Callbacks { - private SlidingPaneLayout mSlidingLayout; - public PodcastSlidingUpPanelLayout sliding_layout; + SlidingPaneLayout mSlidingLayout; + //static final String TAG = "NewsReaderListActivity"; //ActionBarDrawerToggle drawerToggle; @@ -82,7 +80,6 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im public static final String ITEM_ID = "ITEM_ID"; public static final String TITEL = "TITEL"; - PodcastFragment podcastFragment; //boolean isSlideUpPanelExpanded = false; @TargetApi(Build.VERSION_CODES.HONEYCOMB) @@ -131,14 +128,6 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im - - - - sliding_layout = (PodcastSlidingUpPanelLayout) findViewById(R.id.sliding_layout); - UpdatePodcastView(); - - - mSlidingLayout = (SlidingPaneLayout) findViewById(R.id.sliding_pane); mSlidingLayout.setParallaxDistance(280); @@ -152,6 +141,8 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im @Override public void onPanelOpened(View arg0) { + togglePodcastVideoViewAnimation(); + updateAdapter(); getSupportActionBar().setDisplayHomeAsUpEnabled(false); @@ -162,6 +153,8 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im @Override public void onPanelClosed(View arg0) { + togglePodcastVideoViewAnimation(); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); @@ -222,25 +215,6 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im private static final String OPTIONAL_FOLDER_ID ="OPTIONAL_FOLDER_ID"; - public void UpdatePodcastView() { - - if(podcastFragment != null) { - getSupportFragmentManager().beginTransaction().remove(podcastFragment).commitAllowingStateLoss(); - } - - - SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - if(mPrefs.getBoolean(SettingsActivity.CB_ENABLE_PODCASTS_STRING, false)) { - podcastFragment = PodcastFragment.newInstance(null, null); - getSupportFragmentManager().beginTransaction() - .replace(R.id.podcast_frame, podcastFragment) - .commitAllowingStateLoss(); - } else { - sliding_layout.getChildAt(1).setVisibility(View.GONE); - podcastFragment = null; - } - } - /* (non-Javadoc) * @see com.actionbarsherlock.app.SherlockFragmentActivity#onSaveInstanceState(android.os.Bundle) */ @@ -481,10 +455,8 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im @Override public void onBackPressed() { - if(podcastFragment != null && sliding_layout.isPanelExpanded()) { - if (!podcastFragment.onBackPressed()) - sliding_layout.collapsePanel(); - } else if(mSlidingLayout.isOpen()) + if(handlePodcastBackPressed()); + else if(mSlidingLayout.isOpen()) super.onBackPressed(); else mSlidingLayout.openPane(); @@ -502,10 +474,7 @@ public class NewsReaderListActivity extends MenuUtilsSherlockFragmentActivity im switch (item.getItemId()) { case android.R.id.home: - if(podcastFragment != null && sliding_layout.isPanelExpanded()) { - if (!podcastFragment.onBackPressed()) - sliding_layout.collapsePanel(); - } + if(handlePodcastBackPressed()); else if(!mSlidingLayout.isOpen()) mSlidingLayout.openPane(); return true; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java index dacf89f4..9d5d1c96 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastFragment.java @@ -24,6 +24,8 @@ import android.widget.ViewSwitcher; import com.actionbarsherlock.app.SherlockFragment; import com.sothree.slidinguppanel.SlidingUpPanelLayout; +import java.io.File; +import java.util.Arrays; import java.util.List; import butterknife.ButterKnife; @@ -34,14 +36,16 @@ import de.luhmer.owncloudnewsreader.ListView.PodcastArrayAdapter; import de.luhmer.owncloudnewsreader.ListView.PodcastFeedArrayAdapter; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; import de.luhmer.owncloudnewsreader.events.podcast.AudioPodcastClicked; -import de.luhmer.owncloudnewsreader.events.podcast.OpenAudioPodcastEvent; +import de.luhmer.owncloudnewsreader.events.podcast.OpenPodcastEvent; import de.luhmer.owncloudnewsreader.events.podcast.PodcastFeedClicked; +import de.luhmer.owncloudnewsreader.events.podcast.StartDownloadPodcast; import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent; import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent; import de.luhmer.owncloudnewsreader.events.podcast.WindPodcast; -import de.luhmer.owncloudnewsreader.model.AudioPodcastItem; import de.luhmer.owncloudnewsreader.model.PodcastFeedItem; -import de.luhmer.owncloudnewsreader.services.AudioPodcastService; +import de.luhmer.owncloudnewsreader.model.PodcastItem; +import de.luhmer.owncloudnewsreader.services.PodcastDownloadService; +import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService; import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout; @@ -106,6 +110,9 @@ public class PodcastFragment extends SherlockFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + setRetainInstance(true); + if (getArguments() != null) { mParam1 = getArguments().getString(ARG_PARAM1); mParam2 = getArguments().getString(ARG_PARAM2); @@ -114,7 +121,10 @@ public class PodcastFragment extends SherlockFragment { eventBus = EventBus.getDefault(); - getActivity().startService(new Intent(getActivity(), AudioPodcastService.class)); + // when initialize + //getActivity().registerReceiver(downloadCompleteReceiver, downloadCompleteIntentFilter); + + getActivity().startService(new Intent(getActivity(), PodcastPlaybackService.class)); } @@ -133,29 +143,135 @@ public class PodcastFragment extends SherlockFragment { super.onPause(); } + public void onEventMainThread(StartDownloadPodcast podcast) { + PodcastDownloadService.startPodcastDownload(getActivity(), podcast.podcast);//, new DownloadReceiver(new Handler(), new WeakReference<ProgressBar>(holder.pbDownloadPodcast))); + } + + /* + private class DownloadReceiver extends ResultReceiver { + WeakReference<ProgressBar> progressBar; + + public DownloadReceiver(Handler handler, WeakReference<ProgressBar> progressBar) { + super(handler); + + this.progressBar = progressBar; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + super.onReceiveResult(resultCode, resultData); + if (resultCode == PodcastDownloadService.UPDATE_PROGRESS) { + + if(progressBar.get() != null) { + int progress = resultData.getInt("progress"); + progressBar.get().setIndeterminate(false); + progressBar.get().setProgress(progress); + if (progress == 100) { + progressBar.get().setVisibility(View.GONE); + } + } + } + } + } + */ + + public static String[] VIDEO_FORMATS = { "youtube" }; + public void onEventMainThread(AudioPodcastClicked podcast) { - final AudioPodcastItem audioPodcast = audioPodcasts.get(podcast.position); + final PodcastItem audioPodcast = audioPodcasts.get(podcast.position); tvTitle.setText(audioPodcast.title); - eventBus.post(new OpenAudioPodcastEvent() {{ pathToFile = audioPodcast.link; mediaTitle = audioPodcast.title; }}); + boolean isVideo = Arrays.asList(VIDEO_FORMATS).contains(audioPodcast.mimeType); - Toast.makeText(getActivity(), "Starting podcast.. please wait", Toast.LENGTH_SHORT).show(); + if(audioPodcast.mimeType.equals("youtube") && !audioPodcast.offlineCached) + Toast.makeText(getActivity(), "Cannot stream from youtube. Please download the video first.", Toast.LENGTH_SHORT).show(); + else { + File file = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), audioPodcast.link, true)); + if(file.exists()) + audioPodcast.link = file.getAbsolutePath(); + else + Toast.makeText(getActivity(), "Starting podcast.. please wait", Toast.LENGTH_SHORT).show(); //Only show if we need to stream the file + + eventBus.post(new OpenPodcastEvent(audioPodcast.link, audioPodcast.title, isVideo)); + } } public void onEventMainThread(PodcastFeedClicked podcast) { DatabaseConnection dbConn = new DatabaseConnection(getActivity()); - audioPodcasts = dbConn.getListOfAudioPodcastsForFeed(feedsWithAudioPodcasts.get(podcast.position).itemId); + audioPodcasts = dbConn.getListOfAudioPodcastsForFeed(getActivity(), feedsWithAudioPodcasts.get(podcast.position).itemId); + + + for(int i = 0; i < audioPodcasts.size(); i++) { + PodcastItem podcastItem = audioPodcasts.get(i); + + File podcastFile = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), podcastItem.link, false)); + File podcastFileCache = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), podcastItem.link, false) + ".download"); + if(podcastFile.exists()) + podcastItem.downloadProgress = PodcastItem.DOWNLOAD_COMPLETED; + else if(podcastFileCache.exists()) + podcastItem.downloadProgress = 0; + else + podcastItem.downloadProgress = PodcastItem.DOWNLOAD_NOT_STARTED; + } - PodcastArrayAdapter mArrayAdapter = new PodcastArrayAdapter(getActivity(), audioPodcasts.toArray(new AudioPodcastItem[audioPodcasts.size()])); + PodcastArrayAdapter mArrayAdapter = new PodcastArrayAdapter(getActivity(), audioPodcasts.toArray(new PodcastItem[audioPodcasts.size()])); if (podcastTitleGrid != null) { podcastTitleGrid.setAdapter(mArrayAdapter); } podcastTitleGrid.setVisibility(View.VISIBLE); podcastFeedList.setVisibility(View.GONE); + + + //eventBus.post(new OpenAudioPodcastEvent(FileUtils.getPathPodcasts(getActivity()) + "/Foxes.mp4", "Test Video")); + //eventBus.post(new OpenPodcastEvent(FileUtils.getPathPodcasts(getActivity()) + "/Aneta.mp4", "Test Video", true)); + + //PodcastDownloadService.startPodcastDownload(getActivity(), new PodcastItem("5", "Blaa", "http://www.youtube.com/v/wtLJPvx7-ys?version=3&f=playlists&app=youtube_gdata", "youtube")); + } + + + public void onEventMainThread(PodcastDownloadService.DownloadProgressUpdate downloadProgress) { + PodcastArrayAdapter podcastArrayAdapter = (PodcastArrayAdapter) podcastTitleGrid.getAdapter(); + + for(int i = 0; i < podcastTitleGrid.getCount(); i++) { + if(podcastArrayAdapter.getItem(i).link.equals(downloadProgress.podcast.link)) { + + if(podcastArrayAdapter.getItem(i).downloadProgress != downloadProgress.podcast.downloadProgress) { //If Progress changed + PodcastItem pItem = podcastArrayAdapter.getItem(i); + + if (downloadProgress.podcast.downloadProgress == 100) { + pItem.downloadProgress = PodcastItem.DOWNLOAD_COMPLETED; + File file = new File(PodcastDownloadService.getUrlToPodcastFile(getActivity(), pItem.link, false)); + pItem.offlineCached = file.exists(); + } else + pItem.downloadProgress = downloadProgress.podcast.downloadProgress; + podcastTitleGrid.invalidateViews(); + } + + return; + /* + View v = podcastTitleGrid.getChildAt(i - + podcastTitleGrid.getFirstVisiblePosition()); + ((ProgressBar)v.findViewById(R.id.pbDownloadPodcast)).setProgress(downloadProgress.podcast.downloadProgress); + + //podcastArrayAdapter.notifyDataSetChanged(); + return; + */ + } + } } + /* + private String downloadCompleteIntentName = DownloadManager.ACTION_DOWNLOAD_COMPLETE; + private IntentFilter downloadCompleteIntentFilter = new IntentFilter(downloadCompleteIntentName); + private BroadcastReceiver downloadCompleteReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + ((ArrayAdapter) podcastTitleGrid.getAdapter()).notifyDataSetChanged(); + } + }; + */ int lastDrawableId; @@ -207,7 +323,7 @@ public class PodcastFragment extends SherlockFragment { } } - List<AudioPodcastItem> audioPodcasts; + List<PodcastItem> audioPodcasts; List<PodcastFeedItem> feedsWithAudioPodcasts; @InjectView(R.id.btn_playPausePodcast) ImageButton btnPlayPausePodcast; @@ -274,15 +390,14 @@ public class PodcastFragment extends SherlockFragment { ButterKnife.inject(this, view); - if(getActivity() instanceof NewsReaderListActivity) { - sliding_layout = ((NewsReaderListActivity) getActivity()).sliding_layout; - } else if(getActivity() instanceof NewsDetailActivity) { - sliding_layout = ((NewsDetailActivity) getActivity()).sliding_layout; + if(getActivity() instanceof PodcastSherlockFragmentActivity) { + sliding_layout = ((PodcastSherlockFragmentActivity) getActivity()).getSlidingLayout(); } if(sliding_layout != null) { sliding_layout.setSlideableView(rlPodcast); sliding_layout.setDragView(rlPodcastHeader); + //sliding_layout.setEnableDragViewTouchEvents(true); sliding_layout.setPanelSlideListener(onPanelSlideListener); } @@ -365,6 +480,9 @@ public class PodcastFragment extends SherlockFragment { if(sliding_layout != null) sliding_layout.setDragView(rlPodcastHeader); viewSwitcherProgress.setDisplayedChild(0); + + if(getActivity() instanceof PodcastSherlockFragmentActivity) + ((PodcastSherlockFragmentActivity)getActivity()).togglePodcastVideoViewAnimation(); } @Override @@ -372,6 +490,9 @@ public class PodcastFragment extends SherlockFragment { if(sliding_layout != null) sliding_layout.setDragView(viewSwitcherProgress); viewSwitcherProgress.setDisplayedChild(1); + + if(getActivity() instanceof PodcastSherlockFragmentActivity) + ((PodcastSherlockFragmentActivity)getActivity()).togglePodcastVideoViewAnimation(); } @Override @@ -389,7 +510,7 @@ public class PodcastFragment extends SherlockFragment { private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - Log.d(TAG, "onProgressChanged"); + //Log.d(TAG, "onProgressChanged"); } @Override @@ -400,8 +521,12 @@ public class PodcastFragment extends SherlockFragment { @Override public void onStopTrackingTouch(final SeekBar seekBar) { - eventBus.post(new WindPodcast() {{ toPositionInPercent = seekBar.getProgress(); }}); - blockSeekbarUpdate = false; + if(hasTitleInCache) { + eventBus.post(new WindPodcast() {{ + toPositionInPercent = seekBar.getProgress(); + }}); + blockSeekbarUpdate = false; + } Log.d(TAG, "onStopTrackingTouch"); } }; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastSherlockFragmentActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastSherlockFragmentActivity.java new file mode 100644 index 00000000..4a5c9dcd --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/PodcastSherlockFragmentActivity.java @@ -0,0 +1,467 @@ +package de.luhmer.owncloudnewsreader; + +import android.animation.Animator; +import android.annotation.TargetApi; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; +import android.util.TypedValue; +import android.view.SurfaceView; +import android.view.View; +import android.view.animation.Animation; +import android.widget.LinearLayout; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import de.greenrobot.event.EventBus; +import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter; +import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput; +import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent; +import de.luhmer.owncloudnewsreader.events.podcast.VideoDoubleClicked; +import de.luhmer.owncloudnewsreader.helper.SizeAnimator; +import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout; +import de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout; + +/** + * Created by David on 29.06.2014. + */ +public class PodcastSherlockFragmentActivity extends SherlockFragmentActivity { + + private static final String TAG = "PodcastSherlockFragmentActivity"; + private PodcastFragment podcastFragment; + + private EventBus eventBus; + + @InjectView(R.id.videoPodcastSurfaceWrapper) ZoomableRelativeLayout rlVideoPodcastSurfaceWrapper; + //@InjectView(R.id.videoPodcastSurface) SurfaceView surfaceView; + @InjectView(R.id.sliding_layout) PodcastSlidingUpPanelLayout sliding_layout; + + int appHeight; + int appWidth; + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + eventBus = EventBus.getDefault(); + + ButterKnife.inject(this); + + //surfaceView.getHolder().setFixedSize(surfaceView.getWidth(), 10); + //surfaceView.setVisibility(View.GONE); + //rlVideoPodcastSurfaceWrapper.setVisibility(View.GONE); + + rlVideoPodcastSurfaceWrapper.setVisibility(View.INVISIBLE); + + UpdatePodcastView(); + + /* + new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { + @Override + public void onOrientationChanged(int i) { + sliding_layout.collapsePanel(); + } + }; + */ + + super.onPostCreate(savedInstanceState); + } + + + + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + if(hasWindowFocus) { + int currentOrientation = getResources().getConfiguration().orientation; + if (currentOrientation != lastOrientation) { + sliding_layout.collapsePanel(); + lastOrientation = currentOrientation; + } + } + + //rlVideoPodcastSurfaceWrapper.setVisibility(View.GONE); + //isVideoViewVisible = false; + + super.onWindowFocusChanged(hasWindowFocus); + } + + + + int lastOrientation = -1; + @Override + protected void onResume() { + eventBus.register(this); + + //eventBus.post(new RegisterVideoOutput(surfaceView)); + super.onResume(); + } + + @Override + protected void onPause() { + Log.d(TAG, "onPause"); + eventBus.unregister(this); + eventBus.post(new RegisterVideoOutput(null, null)); + + super.onPause(); + } + + + //TODO sliding_layout.collapsePanel();// --> on Orientation change!!!! + + /* + @Override + public void onConfigurationChanged(Configuration newConfig) { + sliding_layout.collapsePanel(); + super.onConfigurationChanged(newConfig); + } + */ + + public PodcastSlidingUpPanelLayout getSlidingLayout() { + return sliding_layout; + } + + public boolean handlePodcastBackPressed() { + if(podcastFragment != null && sliding_layout.isPanelExpanded()) { + if (!podcastFragment.onBackPressed()) + sliding_layout.collapsePanel(); + return true; + } + return false; + } + + public void UpdatePodcastView() { + + if(podcastFragment != null) { + getSupportFragmentManager().beginTransaction().remove(podcastFragment).commitAllowingStateLoss(); + } + + + SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this); + if(mPrefs.getBoolean(SettingsActivity.CB_ENABLE_PODCASTS_STRING, false)) { + podcastFragment = PodcastFragment.newInstance(null, null); + getSupportFragmentManager().beginTransaction() + .replace(R.id.podcast_frame, podcastFragment) + .commitAllowingStateLoss(); + } else { + sliding_layout.getChildAt(1).setVisibility(View.GONE); + podcastFragment = null; + } + } + + + boolean surfaceInitalized = false; + boolean isVideoViewVisible = true; + public void onEventMainThread(UpdatePodcastStatusEvent podcast) { + + if (podcast.isVideoFile()) { + if((!isVideoViewVisible || !surfaceInitalized) && rlVideoPodcastSurfaceWrapper.isPositionReady()) { + surfaceInitalized = true; + isVideoViewVisible = true; + + rlVideoPodcastSurfaceWrapper.setVisibility(View.VISIBLE); + //AlphaAnimator.AnimateVisibilityChange(rlVideoPodcastSurfaceWrapper, View.VISIBLE); + + + SurfaceView surfaceView = new SurfaceView(this); + surfaceView.setLayoutParams(new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT)); + rlVideoPodcastSurfaceWrapper.addView(surfaceView); + + eventBus.post(new RegisterVideoOutput(surfaceView, rlVideoPodcastSurfaceWrapper)); + togglePodcastVideoViewAnimation(); + } + } else if(isVideoViewVisible) { + isVideoViewVisible = false; + + eventBus.post(new RegisterVideoOutput(null, null)); + + rlVideoPodcastSurfaceWrapper.setVisibility(View.GONE); + //AlphaAnimator.AnimateVisibilityChange(rlVideoPodcastSurfaceWrapper, View.GONE); + + rlVideoPodcastSurfaceWrapper.removeAllViews(); + } + + } + + private static final int animationTime = 300; //Milliseconds + float oldScaleFactor = 1; + boolean isFullScreen = false; + float scaleFactor = 1; + boolean useAnimation = false; + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + public void onEventMainThread(VideoDoubleClicked doubleClicked) { + appHeight = getWindow().getDecorView().findViewById(android.R.id.content).getHeight(); + appWidth = getWindow().getDecorView().findViewById(android.R.id.content).getWidth(); + + if(isFullScreen) { + rlVideoPodcastSurfaceWrapper.setDisableScale(false); + togglePodcastVideoViewAnimation(); + } else { + rlVideoPodcastSurfaceWrapper.setDisableScale(true); + oldScaleFactor = rlVideoPodcastSurfaceWrapper.getScaleFactor(); + + final View view = rlVideoPodcastSurfaceWrapper; + + final float oldHeight = view.getLayoutParams().height; + final float oldWidth = view.getLayoutParams().width; + + + //view.setPivotX(oldWidth/2); + //view.setPivotY(oldHeight/2); + + /* + Display display = getWindowManager().getDefaultDisplay(); + float width = display.getWidth(); // deprecated + float height = display.getHeight(); // deprecated + */ + + + + scaleFactor = appWidth / (float) view.getWidth(); + float newHeightTemp = oldHeight * scaleFactor; + float newWidthTemp = oldWidth * scaleFactor; + + //view.animate().scaleX(scaleFactor).scaleY(scaleFactor).setDuration(100); + //scaleView(view, 1f, scaleFactor, 1f, scaleFactor); + + + if(newHeightTemp > appHeight) { //Could happen on Tablets or in Landscape Mode + scaleFactor = appHeight / (float) view.getHeight(); + newHeightTemp = oldHeight * scaleFactor; + newWidthTemp = oldWidth * scaleFactor; + } + + + final float newHeight = newHeightTemp; + final float newWidth = newWidthTemp; + float newXPosition = rlVideoPodcastSurfaceWrapper.getVideoXPosition() + (int) getResources().getDimension(R.dimen.activity_vertical_margin);// (appWidth / 2) + dipToPx(10); + float newYPosition = (appHeight/2) + ((newHeight/2) - oldHeight); + + useAnimation = true; + + view.animate().x(newXPosition).y(newYPosition).setDuration(animationTime).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + + } + + @Override + public void onAnimationEnd(Animator animator) { + if(useAnimation) { + view.startAnimation(new SizeAnimator(view, newWidth, newHeight, oldWidth, oldHeight, animationTime).sizeAnimator); + } + useAnimation = false; + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }); + + + //rlVideoPodcastSurfaceWrapper.animate().scaleX(scaleFactor).scaleY(scaleFactor).x(newXPosition).y(newYPosition).setDuration(500).setListener(onResizeListener); + //surfaceView.animate().scaleX(scaleFactor).scaleY(scaleFactor).setDuration(500); + + //oldScaleFactor + //scaleFactor = dipToPx(oldWidth) / newWidth; + //scaleFactor = (1/oldScaleFactor) * dipToPx(oldWidth) / newWidth; + //scaleFactor = oldWidth / newWidth; + + scaleFactor = 1/scaleFactor; + } + + isFullScreen = !isFullScreen; + } + + + + Animator.AnimatorListener onResizeListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + + } + + @Override + public void onAnimationEnd(Animator animator) { + View view = rlVideoPodcastSurfaceWrapper; + + + int height = (int) (view.getHeight() * view.getScaleY()); + int width = (int) (view.getWidth() * view.getScaleX()); + + //view.setPivotX(width/2); + //view.setPivotY(height/2); + + view.setScaleX(1); + view.setScaleY(1); + view.getLayoutParams().height = height; + view.getLayoutParams().width = width; + view.setLayoutParams(view.getLayoutParams()); + + //view.setX(0); + + /* + surfaceView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + surfaceView.forceLayout(); + surfaceView.requestLayout(); + surfaceView.invalidate(); + surfaceView.requestLayout(); + surfaceView.forceLayout(); + */ + } + + @Override + public void onAnimationCancel(Animator animator) { + + } + + @Override + public void onAnimationRepeat(Animator animator) { + + } + }; + + + /* + public void scaleView(View v, float startX, float startY, float endX, float endY) { + Animation anim = new ScaleAnimation( + startX, endX, // Start and end values for the X axis scaling + startY, endY, // Start and end values for the Y axis scaling + Animation.RELATIVE_TO_SELF, 0f, // Pivot point of X scaling + Animation.RELATIVE_TO_SELF, 0f); // Pivot point of Y scaling + anim.setFillAfter(true); // Needed to keep the result of the animation + anim.setDuration(500); + v.startAnimation(anim); + } + */ + + /* + public void onEvent(PodcastPlaybackService.PodcastPlaybackServiceStarted serviceStarted) { + + } + */ + + + public void togglePodcastVideoViewAnimation() { + boolean isLeftSliderOpen = false; + + if(this instanceof NewsReaderListActivity) { + isLeftSliderOpen = ((NewsReaderListActivity) this).mSlidingLayout.isOpen(); + } + + boolean isTabletView = SubscriptionExpandableListAdapter.isTwoPane(this); + + int podcastMediaControlHeightDp = pxToDp((int) getResources().getDimension(R.dimen.podcast_media_control_height)); + + if(isTabletView && sliding_layout.isPanelExpanded()) { //On Tablets + animateToPosition(podcastMediaControlHeightDp); + } else if(!isTabletView && isLeftSliderOpen) + animateToPosition(0); + else if(sliding_layout.isPanelExpanded()) { + animateToPosition(podcastMediaControlHeightDp); + } else { + animateToPosition(64); + } + } + + public static int pxToDp(int px) + { + return (int) (px / Resources.getSystem().getDisplayMetrics().density); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) + public void animateToPosition(final int yPosition) { + appHeight = getWindow().getDecorView().findViewById(android.R.id.content).getHeight(); + appWidth = getWindow().getDecorView().findViewById(android.R.id.content).getWidth(); + + final View view = rlVideoPodcastSurfaceWrapper; //surfaceView + + /* + viewToMove.getLayoutParams().height *= scaleFactor; + viewToMove.getLayoutParams().width *= scaleFactor; + viewToMove.setLayoutParams(viewToMove.getLayoutParams()); + */ + //float newHeight = viewToMove.getLayoutParams().height * scaleFactor; + //float newWidth = viewToMove.getLayoutParams().width * scaleFactor; + //viewToMove.animate().scaleX(scaleFactor).scaleY(scaleFactor).setDuration(100); + //viewToMove.startAnimation(new SizeAnimator(viewToMove, newWidth, newHeight, 500).sizeAnimator); + + //scaleView(viewToMove, scaleFactor, 1f, scaleFactor, 1f); + + + + + + + + + + + if(scaleFactor != 1) { + int oldHeight = view.getLayoutParams().height; + int oldWidth = view.getLayoutParams().width; + int newHeight = view.getLayoutParams().height *= scaleFactor; + int newWidth = view.getLayoutParams().width *= scaleFactor; + scaleFactor = 1; + + Animation animator = new SizeAnimator(view, newWidth, newHeight, oldWidth, oldHeight, animationTime).sizeAnimator; + animator.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + animateToPosition(yPosition); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + view.startAnimation(animator); + } else { + int absoluteYPosition = appHeight - view.getHeight() - (int) getResources().getDimension(R.dimen.activity_vertical_margin) - (int) dipToPx(yPosition); + + //int animationpos = 500; + float xPosition = rlVideoPodcastSurfaceWrapper.getVideoXPosition(); + view.animate().x(xPosition).y(absoluteYPosition).setDuration(animationTime); + //scaleX(scaleFactor).scaleY(scaleFactor) + } + + + + + /* + int height = (int)(view.getHeight() * scaleFactor); + int width = (int)(view.getWidth() * scaleFactor); + view.setScaleX(oldScaleFactor); + view.setScaleY(oldScaleFactor); + view.getLayoutParams().height = height; + view.getLayoutParams().width = width; +*/ + oldScaleFactor = 1; + + + } + + + float dipToPx(float dip) { + Resources r = getResources(); + float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, r.getDisplayMetrics()); + return px; + } +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java index 13fd686e..d611fe66 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/SettingsActivity.java @@ -51,6 +51,7 @@ import java.text.DecimalFormat; import java.util.List; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; +import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.ImageHandler; import de.luhmer.owncloudnewsreader.helper.PostDelayHandler; import de.luhmer.owncloudnewsreader.helper.ThemeChooser; @@ -597,7 +598,7 @@ public class SettingsActivity extends SherlockPreferenceActivity { protected Void doInBackground(Void... params) { try { - getFolderSize(new File(ImageHandler.getPath(_mActivity))); + getFolderSize(new File(FileUtils.getPath(_mActivity))); mSize = dcmFormat.format(size / 1024d / 1024d) + "MB"; mCount = String.valueOf(count) + " Files"; } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/async_tasks/AsyncTask_DownloadImages.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/async_tasks/AsyncTask_DownloadImages.java index 9b373731..a4457b48 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/async_tasks/AsyncTask_DownloadImages.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/async_tasks/AsyncTask_DownloadImages.java @@ -26,25 +26,26 @@ import android.os.AsyncTask; import java.util.List; +import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.ImageHandler; public class AsyncTask_DownloadImages extends AsyncTask<Void, Void, Void>{ String text; Context context; - + public AsyncTask_DownloadImages(String text, Context context) { this.text = text; this.context = context; } - + @Override protected Void doInBackground(Void... params) { if(text != null) { List<String> links = ImageHandler.getImageLinksFromText(text); - + for(String link : links) - new GetImageAsyncTask(link, null, 999, ImageHandler.getPathImageCache(context), context, null).execute(); + new GetImageAsyncTask(link, null, 999, FileUtils.getPathImageCache(context), context, null).execute(); } return null; } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/IKeyValuePair.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/IKeyValuePair.java new file mode 100644 index 00000000..6c930681 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/IKeyValuePair.java @@ -0,0 +1,65 @@ +package de.luhmer.owncloudnewsreader.collections; + +/* + + * Copyright (c) 2008, 2009 by Xuggle Incorporated. All rights reserved. + + * + + * This file is part of Xuggler. + + * + + * You can redistribute Xuggler and/or modify it under the terms of the GNU + + * Affero General Public License as published by the Free Software + + * Foundation, either version 3 of the License, or (at your option) any + + * later version. + + * + + * Xuggler 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 Affero General Public + + * License for more details. + + * + + * You should have received a copy of the GNU Affero General Public License + + * along with Xuggler. If not, see <http://www.gnu.org/licenses/>. + + */ + + +/** + * A key-alue pair. + * + * @author aclarke + * + */ +public interface IKeyValuePair + +{ + + /** + * Get the key. + * + * @return the key. + */ + public String getKey(); + + + /** + * Get the value. + * + * @return the value. + */ + public String getValue(); + +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/KeyValuePair.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/KeyValuePair.java new file mode 100644 index 00000000..33397f26 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/KeyValuePair.java @@ -0,0 +1,154 @@ +package de.luhmer.owncloudnewsreader.collections; + +/* + + * Copyright (c) 2008, 2009 by Xuggle Incorporated. All rights reserved. + + * + + * This file is part of Xuggler. + + * + + * You can redistribute Xuggler and/or modify it under the terms of the GNU + + * Affero General Public License as published by the Free Software + + * Foundation, either version 3 of the License, or (at your option) any + + * later version. + + * + + * Xuggler 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 Affero General Public + + * License for more details. + + * + + * You should have received a copy of the GNU Affero General Public License + + * along with Xuggler. If not, see <http://www.gnu.org/licenses/>. + + */ + + +import java.util.Map.Entry; + + +/** + + * An implementation of {@link IKeyValuePair} with immutable keys + + * but resettable values. + + * + + * @author aclarke + + * + + */ + +public class KeyValuePair implements IKeyValuePair, Entry<String, String> + +{ + + private final String mKey; + + private String mValue; + + + + /** + + * Creates a new key-value pair. + + * + + * @param key + + * The key; may not be null. + + * @param value + + * The value; may be null. + + * @throws IllegalArgumentException + + * if <code>name==null</code>. + + */ + + public KeyValuePair(final String key, final String value) + + { + + if (key == null) + + throw new IllegalArgumentException("must have non null name"); + + mKey = key; + + mValue = value; + + } + + + + /** + + * {@inheritDoc} + + */ + + public String getKey() + + { + + return mKey; + + } + + + + /** + + * {@inheritDoc} + + */ + + public String getValue() + + { + + return mValue; + + } + + + + /** + + * {@inheritDoc} + + */ + + public String setValue(String value) + + { + + final String retval = mValue; + + mValue = value; + + return retval; + + } + +} + diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/MapUtils.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/MapUtils.java new file mode 100644 index 00000000..ef05d858 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/MapUtils.java @@ -0,0 +1,145 @@ +package de.luhmer.owncloudnewsreader.collections; + +/* + * Copyright (c) 2008, 2009 by Xuggle Incorporated. All rights reserved. + * + * This file is part of Xuggler. + * + * You can redistribute Xuggler and/or modify it under the terms of the GNU + * Affero General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Xuggler 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 Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xuggler. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Some useful utilities for mapping lists to maps and back. + * @author aclarke + * + */ +public class MapUtils +{ + /** + * How {@link MapUtils#listToMap(List, Map, ListToMapMode)} treats + * duplicate values in the input list. + * @author aclarke + * + */ + public enum ListToMapMode + { + /** + * Last item in list always wins. + */ + LAST_WINS, + /** + * First item in list always wins. + */ + FIRST_WINS, + } + + /** + * Converts a list of key-value pairs into a {@link Map}, with either + * the first duplicate key always being inserted, or the last duplicate key + * always being inserted. + * + * @param list + * The list of name value pairs to convert. + * @param mapToFill + * The map to fill. This method will empty the list first. + * @param mode + * How duplicate values in <code>list</code> should be treated. + */ + public static void listToMap(final List<? extends IKeyValuePair> list, + final Map<String, String> mapToFill, final ListToMapMode mode) + { + if (list == null) + throw new IllegalArgumentException(); + if (mapToFill == null) + throw new IllegalArgumentException(); + mapToFill.clear(); + for (IKeyValuePair pair : list) + { + if (pair != null) + { + if (mode == ListToMapMode.FIRST_WINS) + { + if (mapToFill.containsKey(pair.getKey())) + continue; + } + mapToFill.put(pair.getKey(), pair.getValue()); + } + } + } + + /** + * Converts a list of key-value pairs into a {@link Map}, with either + * the first duplicate key always being inserted, or the last duplicate key + * always being inserted. + * + * @param list + * The list of name value pairs to convert. + * @param mode + * How duplicate values in <code>list</code> should be treated. + * @return a new {@link Map} of key-value pairs. + */ + public static Map<String, String> listToMap( + final List<? extends IKeyValuePair> list, final ListToMapMode mode) + { + final Map<String, String> retval = new HashMap<String, String>(); + listToMap(list, retval, mode); + return retval; + } + + /** + * Converts a map into a {@link List} of {@link IKeyValuePair} objects. + * @param map Map to convert. + * @param listToFill List to fill. The list has {@link List#clear()} called + * before any items are added. + */ + public static void mapToList(final Map<String, String> map, final List<IKeyValuePair> listToFill) + { + if (map == null || listToFill == null) + throw new IllegalArgumentException(); + final Set<Entry<String, String>> entries = map.entrySet(); + for(Entry<String, String> entry : entries) + { + final String name = entry.getKey(); + final String value = entry.getValue(); + if (name == null) + continue; + IKeyValuePair pair = new KeyValuePair(name, value); + listToFill.add(pair); + } + } + /** + * Converts a map into a {@link List} of {@link IKeyValuePair} objects, and + * returns the new list. + * @param map Map to convert. + * @return A new {@link List} containing all key-value pairs in <code>map</code> + * as {@link IKeyValuePair} objects. + */ + public static List<IKeyValuePair> mapToList(final Map<String, String> map) + { + final List<IKeyValuePair> retval = new ArrayList<IKeyValuePair>(); + mapToList(map, retval); + return retval; + } + + +} + + diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/URLParams.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/URLParams.java new file mode 100644 index 00000000..cb681f22 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/collections/URLParams.java @@ -0,0 +1,198 @@ +package de.luhmer.owncloudnewsreader.collections; + +/* + * Copyright (c) 2008, 2009 by Xuggle Incorporated. All rights reserved. + * + * This file is part of Xuggler. + * + * You can redistribute Xuggler and/or modify it under the terms of the GNU + * Affero General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Xuggler 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 Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xuggler. If not, see <http://www.gnu.org/licenses/>. + */ + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Utilities for generating and parsing URL parameter strings + * + * @author aclarke + * + */ +public class URLParams +{ + /** + * Parse the query string, placing the results in <code>paramsToFill</code>, + * assuming UTF-8 encoding. + * + * @param queryString + * The query string (without leading "?"). + * @param paramsToFill + * A list to add entries to; the list is not emptied first by this + * method. Entries are added in the order they are found. + * @see URLParams#parseQueryString(String, List, String) + */ + public static void parseQueryString(final String queryString, + final List<KeyValuePair> paramsToFill) + { + try + { + parseQueryString(queryString, paramsToFill, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("What? UTF-8 isn't a valid encoding", e); + } + } + + /** + * Parse the query string, and return as a new list, assuming UTF-8 encoding. + * + * @param queryString + * The query string (without leading "?"). + * @return A new list with all entries included, in the order they were found. + * @see URLParams#parseQueryString(String, List, String) + */ + public static List<KeyValuePair> parseQueryString(final String queryString) + { + final List<KeyValuePair> retval = new ArrayList<KeyValuePair>(); + parseQueryString(queryString, retval); + return retval; + } + + /** + * Parse the query string, and return as a new list, assuming UTF-8 encoding. + * + * @param queryString + * The query string (without leading "?"). + * @param encoding + * An encoding (e.g. "UTF-8") to decode parameters as. + * @return A new list with all entries included, in the order they were found. + * @throws UnsupportedEncodingException + * If the <code>encode</code> specified is not supported. + * @see URLParams#parseQueryString(String, List, String) + */ + public static List<KeyValuePair> parseQueryString(final String queryString, + final String encoding) throws UnsupportedEncodingException + { + final List<KeyValuePair> retval = new ArrayList<KeyValuePair>(); + parseQueryString(queryString, retval, encoding); + return retval; + } + + /** + * Parse the query string, placing the results in <code>paramsToFill</code>. + * + * @param queryString + * The query string (without leading "?"). + * @param parametersToFill + * A list to add entries to; the list is not emptied first by this + * method. Entries are added in the order they are found. + * @param encoding + * An encoding (e.g. "UTF-8") to decode parameters as. + * @throws UnsupportedEncodingException + * If the <code>encode</code> specified is not supported. + * @see URLParams#parseQueryString(String, List, String) + */ + public static void parseQueryString(final String queryString, + final List<KeyValuePair> parametersToFill, final String encoding) + throws UnsupportedEncodingException + { + final String params[] = queryString.split("&"); + for (String param : params) + { + final String[] tuple = param.split("="); + final KeyValuePair retval = new KeyValuePair(URLDecoder.decode( + tuple[0], encoding), tuple.length > 1 ? URLDecoder.decode(tuple[1], + encoding) : null); + parametersToFill.add(retval); + } + } + + /** + * Generates a query string suitable for appending to a URI that encodes the + * given parameters as UTF-8 encoding strings. + * + * @param params + * A list of name value pairs to encode. + * @return A string suitable for appending to URIs. + */ + public static String generateQueryString( + final List<? extends IKeyValuePair> params) + { + try + { + return generateQueryString(params, "UTF-8"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("What? UTF-8 isn't a valid encoding", e); + } + } + + /** + * Generates a query string suitable for appending to a URI that encodes the + * given parameters as specified by the encoding parameter. + * + * @param params + * A list of name value pairs to encode. + * @param encoding + * The text encoding to use + * @return A string suitable for appending to URIs. + * @throws UnsupportedEncodingException + * If the encoding is not supported. + */ + public static String generateQueryString( + final List<? extends IKeyValuePair> params, final String encoding) + throws UnsupportedEncodingException + { + if (params == null) + throw new IllegalArgumentException("need params"); + if (encoding == null) + throw new UnsupportedEncodingException(); + + final StringBuilder builder = new StringBuilder(); + final Iterator<? extends IKeyValuePair> iterator = params.iterator(); + boolean hasNext = iterator.hasNext(); + while (hasNext) + { + IKeyValuePair pair = iterator.next(); + if (pair != null) + { + final String key = pair.getKey(); + final String value = pair.getValue(); + builder.append(URLEncoder.encode(key, encoding)); + if (value != null) + { + builder.append("="); + builder.append(URLEncoder.encode(value, encoding)); + } + } + if (iterator.hasNext()) + { + if (pair != null) + builder.append("&"); + hasNext = true; + } + else + { + hasNext = false; + } + } + return builder.toString(); + } +} + diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnection.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnection.java index 49e8481b..5a0a13df 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnection.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/database/DatabaseConnection.java @@ -29,15 +29,17 @@ import android.database.sqlite.SQLiteDatabase; import android.util.Log; import android.util.SparseArray; +import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import de.luhmer.owncloudnewsreader.Constants; import de.luhmer.owncloudnewsreader.ListView.UnreadFolderCount; -import de.luhmer.owncloudnewsreader.model.AudioPodcastItem; import de.luhmer.owncloudnewsreader.model.PodcastFeedItem; +import de.luhmer.owncloudnewsreader.model.PodcastItem; import de.luhmer.owncloudnewsreader.model.RssFile; +import de.luhmer.owncloudnewsreader.services.PodcastDownloadService; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_ITEMS; import static de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_STARRED_ITEMS; @@ -932,7 +934,7 @@ public class DatabaseConnection { //Log.d("DatabaseConnection", "Inserted Rows: " + result); } - private static final String ALLOWED_PODCASTS_TYPES = "audio/mp3', 'audio/mpeg', 'audio/ogg', 'audio/opus', 'audio/ogg;codecs=opus"; + private static final String ALLOWED_PODCASTS_TYPES = "audio/mp3', 'audio/mpeg', 'audio/ogg', 'audio/opus', 'audio/ogg;codecs=opus', 'youtube"; public List<PodcastFeedItem> getListOfFeedsWithAudioPodcasts() { @@ -965,13 +967,13 @@ public class DatabaseConnection { return result; } - public List<AudioPodcastItem> getListOfAudioPodcastsForFeed(String feedId) { + public List<PodcastItem> getListOfAudioPodcastsForFeed(Context context, String feedId) { String buildSQL = "SELECT rowid, " + RSS_ITEM_TITLE + ", " + RSS_ITEM_ENC_LINK + ", " + RSS_ITEM_ENC_MIME + " FROM " + RSS_ITEM_TABLE + " WHERE " + RSS_ITEM_ENC_MIME + " IN ('" + ALLOWED_PODCASTS_TYPES + "') AND " + RSS_ITEM_SUBSCRIPTION_ID + " = " + feedId; - List<AudioPodcastItem> result = new ArrayList<AudioPodcastItem>(); + List<PodcastItem> result = new ArrayList<PodcastItem>(); Cursor cursor = database.rawQuery(buildSQL, null); try { @@ -981,11 +983,17 @@ public class DatabaseConnection { { cursor.moveToFirst(); do { - AudioPodcastItem podcastItem = new AudioPodcastItem(); + PodcastItem podcastItem = new PodcastItem(); podcastItem.itemId = cursor.getString(0); podcastItem.title = cursor.getString(1); podcastItem.link = cursor.getString(2); podcastItem.mimeType = cursor.getString(3); + + File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastItem.link, false)); + podcastItem.offlineCached = file.exists(); + + + result.add(podcastItem); } while(cursor.moveToNext()); } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/NewPodcastPlaybackListener.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/NewPodcastPlaybackListener.java new file mode 100644 index 00000000..f0fa6c19 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/NewPodcastPlaybackListener.java @@ -0,0 +1,7 @@ +package de.luhmer.owncloudnewsreader.events.podcast; + +/** + * Created by David on 29.06.2014. + */ +public class NewPodcastPlaybackListener { +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/RegisterVideoOutput.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/RegisterVideoOutput.java new file mode 100644 index 00000000..f2514a13 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/RegisterVideoOutput.java @@ -0,0 +1,18 @@ +package de.luhmer.owncloudnewsreader.events.podcast; + +import android.view.SurfaceView; +import android.view.View; + +/** + * Created by David on 28.06.2014. + */ +public class RegisterVideoOutput { + + public RegisterVideoOutput(SurfaceView surfaceView, View parentResizableView) { + this.surfaceView = surfaceView; + this.parentResizableView = parentResizableView; + } + + public SurfaceView surfaceView; + public View parentResizableView; +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/StartDownloadPodcast.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/StartDownloadPodcast.java new file mode 100644 index 00000000..0c109ae0 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/StartDownloadPodcast.java @@ -0,0 +1,10 @@ +package de.luhmer.owncloudnewsreader.events.podcast; + +import de.luhmer.owncloudnewsreader.model.PodcastItem; + +/** + * Created by David on 28.06.2014. + */ +public class StartDownloadPodcast { + public PodcastItem podcast; +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/UpdatePodcastStatusEvent.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/UpdatePodcastStatusEvent.java index f26bbf44..4d41abc5 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/UpdatePodcastStatusEvent.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/UpdatePodcastStatusEvent.java @@ -10,6 +10,8 @@ public class UpdatePodcastStatusEvent { private String title; private boolean playing; private boolean preparingFile; + private boolean fileLoaded; + private boolean isVideoFile; public String getTitle() { return title; @@ -31,12 +33,20 @@ public class UpdatePodcastStatusEvent { return preparingFile; } - public UpdatePodcastStatusEvent(long current, long max, boolean playing, String title, boolean preparingFile) { + public boolean isFileLoaded() { + return fileLoaded; + } + + public boolean isVideoFile() { return isVideoFile; } + + public UpdatePodcastStatusEvent(long current, long max, boolean playing, String title, boolean preparingFile, boolean fileLoaded, boolean isVideoFile) { this.current = current; this.max = max; this.playing = playing; this.title = title; this.preparingFile = preparingFile; + this.fileLoaded = fileLoaded; + this.isVideoFile = isVideoFile; } } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/VideoDoubleClicked.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/VideoDoubleClicked.java new file mode 100644 index 00000000..4b3f76e0 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/events/podcast/VideoDoubleClicked.java @@ -0,0 +1,7 @@ +package de.luhmer.owncloudnewsreader.events.podcast; + +/** + * Created by David on 30.06.2014. + */ +public class VideoDoubleClicked { +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/AlphaAnimator.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/AlphaAnimator.java new file mode 100644 index 00000000..319fa564 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/AlphaAnimator.java @@ -0,0 +1,53 @@ +package de.luhmer.owncloudnewsreader.helper; + +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; + +/** + * Created by David on 02.07.2014. + */ +public class AlphaAnimator { + + public static void AnimateVisibilityChange(final View view, final int visibilityTo) { + + + Animation animation; + if(visibilityTo == View.GONE) { + animation = new AlphaAnimation(1f, 0f); + } else { + view.setAlpha(0.1f); + view.setVisibility(View.VISIBLE); + animation = new AlphaAnimation(0f, 1f); + } + + + animation.setFillAfter(true); + animation.setDuration(1000); + animation.setStartOffset(1000); + //animation.setStartOffset(5000); + + /* + animation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + view.setVisibility(visibilityTo); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + */ + + animation.start(); + + + } +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/DatabaseUtils.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/DatabaseUtils.java index 51370fc5..8ef35a43 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/DatabaseUtils.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/DatabaseUtils.java @@ -55,6 +55,6 @@ public class DatabaseUtils { } public static File GetPath(Context context) { - return new File(ImageHandler.getPath(context) + "/dbBackup/" + DatabaseHelper.DATABASE_NAME); + return new File(FileUtils.getPath(context) + "/dbBackup/" + DatabaseHelper.DATABASE_NAME); } } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FavIconHandler.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FavIconHandler.java index 9f2ac621..0dbadfba 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FavIconHandler.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FavIconHandler.java @@ -46,7 +46,7 @@ public class FavIconHandler { { try { - File favIconFile = ImageHandler.getFullPathOfCacheFile(URL_TO_PAGE, ImageHandler.getPathFavIcons(context)); + File favIconFile = ImageHandler.getFullPathOfCacheFile(URL_TO_PAGE, FileUtils.getPathFavIcons(context)); if(favIconFile.isFile() && favIconFile.length() > 0) { if(feedID != null) { @@ -94,7 +94,7 @@ public class FavIconHandler { int key = Integer.parseInt(feedID); favIconToFeedId.put(key, favIconCache); - GetImageAsyncTask giAsync = new GetImageAsyncTask(WEB_URL_TO_FILE, favIconDownloadFinished, key, ImageHandler.getPathFavIcons(context), context, null); + GetImageAsyncTask giAsync = new GetImageAsyncTask(WEB_URL_TO_FILE, favIconDownloadFinished, key, FileUtils.getPathFavIcons(context), context, null); giAsync.scaleImage = true; giAsync.dstHeight = 2*32; giAsync.dstWidth = 2*32; @@ -146,7 +146,7 @@ public class FavIconHandler { imageView.setImageDrawable(null); - GetImageAsyncTask giAsync = new GetImageAsyncTask(WEB_URL_TO_FILE, imgDownloadFinished, key, ImageHandler.getPathFavIcons(context), context/*, imageView*/, lruCache); + GetImageAsyncTask giAsync = new GetImageAsyncTask(WEB_URL_TO_FILE, imgDownloadFinished, key, FileUtils.getPathFavIcons(context), context/*, imageView*/, lruCache); giAsync.scaleImage = true; giAsync.dstHeight = 2*32; giAsync.dstWidth = 2*32; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FileUtils.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FileUtils.java index d329987c..cb230b35 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FileUtils.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/FileUtils.java @@ -21,6 +21,9 @@ package de.luhmer.owncloudnewsreader.helper; +import android.content.Context; +import android.os.Environment; + import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -35,7 +38,7 @@ public class FileUtils { * <br/> * <i> Note: <code>fromFile</code> and <code>toFile</code> will be closed by * this function.</i> - * + * * @param fromFile * - FileInputStream for the file to copy from. * @param toFile @@ -60,4 +63,45 @@ public class FileUtils { } } } + + + + + + public static String getPath(Context context) { + String url; + Boolean isSDPresent = android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); + if(isSDPresent) + { + url = Environment.getExternalStorageDirectory().getAbsolutePath(); + if (android.os.Build.DEVICE.contains("Samsung") || android.os.Build.MANUFACTURER.contains("Samsung")) { + url = url + "/external_sd"; + } + //url = url + "/" + context.getString(R.string.app_name); + url = url + "/ownCloud News Reader"; + } + else + url = context.getCacheDir().getAbsolutePath(); + + return url; + } + + + + + public static String getPathPodcasts(Context context) + { + return getPath(context) + "/podcasts"; + } + + public static String getPathFavIcons(Context context) + { + return getPath(context) + "/favIcons"; + } + + public static String getPathImageCache(Context context) + { + return getPath(context) + "/imgCache"; + } + } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/ImageHandler.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/ImageHandler.java index 87a36ac8..90c2029f 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/ImageHandler.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/ImageHandler.java @@ -23,7 +23,6 @@ package de.luhmer.owncloudnewsreader.helper; import android.content.Context; import android.graphics.drawable.Drawable; -import android.os.Environment; import java.io.File; import java.io.FileOutputStream; @@ -116,34 +115,6 @@ public class ImageHandler { - public static String getPathFavIcons(Context context) - { - return getPath(context) + "/favIcons"; - } - - public static String getPathImageCache(Context context) - { - return getPath(context) + "/imgCache"; - } - - public static String getPath(Context context) { - String url; - Boolean isSDPresent = android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); - if(isSDPresent) - { - url = Environment.getExternalStorageDirectory().getAbsolutePath(); - if (android.os.Build.DEVICE.contains("Samsung") || android.os.Build.MANUFACTURER.contains("Samsung")) { - url = url + "/external_sd"; - } - //url = url + "/" + context.getString(R.string.app_name); - url = url + "/ownCloud News Reader"; - } - else - url = context.getCacheDir().getAbsolutePath(); - - return url; - } - public static List<String> getImageLinksFromText(String text) { @@ -168,14 +139,14 @@ public class ImageHandler { public static boolean clearCache(Context context) { - String path = getPath(context); + String path = FileUtils.getPath(context); boolean result = deleteDir(new File(path)); createNoMediaFile(context); return result; } public static void createNoMediaFile(Context context) { - String path = getPath(context); + String path = FileUtils.getPath(context); createEmptyFile(path + "/.nomedia"); } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/JavaYoutubeDownloader.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/JavaYoutubeDownloader.java new file mode 100644 index 00000000..260fabe7 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/JavaYoutubeDownloader.java @@ -0,0 +1,381 @@ +package de.luhmer.owncloudnewsreader.helper; + +import android.content.Context; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.CookieStore; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.client.utils.URIUtils; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.regex.Pattern; + +import de.luhmer.owncloudnewsreader.collections.IKeyValuePair; +import de.luhmer.owncloudnewsreader.collections.KeyValuePair; +import de.luhmer.owncloudnewsreader.collections.MapUtils; +import de.luhmer.owncloudnewsreader.collections.URLParams; + +public class JavaYoutubeDownloader { + + public static String newline = System.getProperty("line.separator"); + public static final String scheme = "http"; + public static final String host = "www.youtube.com"; + public static final Pattern commaPattern = Pattern.compile(","); + public static final char[] ILLEGAL_FILENAME_CHARACTERS = { '/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':' }; + + + + + + + public String getVideoID( String urlString ){ + int start = urlString.indexOf("?v=") + 3; + int end = urlString.indexOf("&", start); + if ( end == -1 ){ + end = urlString.length(); + } + + if(start == 2) { //Doesn't work + start = urlString.indexOf("/v/") + 3; + end = urlString.indexOf("?", start); + if (end == -1){ + end = urlString.length(); + } + } + + return urlString.substring(start, end); + } + + public String getExtension(int format) { + return "mp4"; + } + + + public String getDownloadUrl(String url, Context context) throws Throwable { + + //Map<String, String> videoInfo = getVideoInfo(getVideoID(url));// Html.fromHtml(getStringFromWebsite("http://youtube.com/get_video_info?video_id=" + getVideoID(url))).toString(); + /* + + byte[] b = videoInfo.getBytes(); + String videoInfoAscii = new String(b, "ASCII"); + */ + + //getFullPathOfPodcastYoutubeFile(url, context), ".mp4" + + //String urlTemp = videoInfo.get("sig"); + + return getDownloadUrl(getVideoID(url), "UTF-8", "ownCloud News Reader"); + } + + public static File getFullPathOfPodcastYoutubeFile(String WEB_URL_TO_FILE, Context context) throws Exception + { + String rootPath = FileUtils.getPathPodcasts(context); + URL url = new URL(WEB_URL_TO_FILE.trim()); + + MessageDigest m = MessageDigest.getInstance("MD5"); + m.reset(); + m.update(url.toString().getBytes()); + byte[] digest = m.digest(); + BigInteger bigInt = new BigInteger(1,digest); + String hashtext = bigInt.toString(16); + + return new File(rootPath + "/" + hashtext + ".mp4"); + } + + + public String getDownloadUrl(String videoId, String encoding, String userAgent) throws Throwable { + //Utils.log.fine("Retrieving " + videoId); + List<NameValuePair> qparams = new ArrayList<NameValuePair>(); + qparams.add(new BasicNameValuePair("video_id", videoId)); + URI uri = getUri("get_video_info", qparams); + + CookieStore cookieStore = new BasicCookieStore(); + HttpContext localContext = new BasicHttpContext(); + localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); + + HttpClient httpclient = new DefaultHttpClient(); + HttpGet httpget = new HttpGet(uri); + httpget.setHeader("User-Agent", userAgent); + + //Utils.log.finer("Executing " + uri); + HttpResponse response = httpclient.execute(httpget, localContext); + HttpEntity entity = response.getEntity(); + if (entity != null && response.getStatusLine().getStatusCode() == 200) { + InputStream instream = entity.getContent(); + String videoInfo = getStringFromInputStream(encoding, instream); + if (videoInfo != null && videoInfo.length() > 0) { + List<NameValuePair> infoMap = new ArrayList<NameValuePair>(); + URLEncodedUtils.parse(infoMap, new Scanner(videoInfo), encoding); + String downloadUrl = null; + String filename = videoId; + int bestQuality = -1; + for (NameValuePair pair : infoMap) { + String key = pair.getName(); + String val = pair.getValue(); + //Utils.log.finest(key + "=" + val); + if (key.equals("title")) { + filename = val; + } else if (key.equals("url_encoded_fmt_stream_map")) { + String[] formats = commaPattern.split(val); + + String fmtString = null; + //for (String fmt : formats) { + String fmt = formats[0]; { + int itagLocation = fmt.indexOf("itag="); + if ( itagLocation == -1 ) continue; + itagLocation += 5; + String subStr = null; + try { + subStr = fmt.substring(itagLocation, fmt.indexOf("&", itagLocation)); + } + catch( IndexOutOfBoundsException ex){ + return "Could not find the itag attribute to determine quality"; + } + int tempQuality = Integer.parseInt(fmt.substring(itagLocation, fmt.indexOf("&", itagLocation))); + if ( bestQuality < tempQuality ){ + bestQuality = tempQuality; + fmtString = fmt; + } + + } + //we are going to automatically download the best quality youtube + int begin = fmtString.indexOf("url="); + int sig = fmtString.indexOf("sig="); + if (begin != -1) { + int end = fmtString.indexOf("&", begin + 4); + int end2 = fmtString.indexOf("&", sig + 4); + if (end == -1) { + end = fmtString.length(); + } + if (end2 == -1 ){ + end2 = fmtString.length(); + } + String tempURL = fmtString.substring(begin+ 4, end ); + String signatureURL = URLEncoder.encode("&signature="+fmtString.substring(sig + 4, end2), "UTF-8"); + //downloadUrl = new String(URLCodec.decodeUrl((tempURL + signatureURL).getBytes())); + downloadUrl = URLDecoder.decode((tempURL + signatureURL), "UTF-8"); + + return downloadUrl; + //break; + } + } + } + + /* + if ( downloadUrl == null ){ + //Utils.log.fine("Content is protected"); + } + filename = cleanFilename(filename); + if (filename.length() == 0) { + filename = videoId; + } else { + filename += "_" + videoId; + } + filename += "." + extension; + File outputfile = new File(outputdir, filename); + if (downloadUrl != null) { + downloadWithHttpClient(userAgent, downloadUrl, outputfile); + } + */ + } + } + return "successful"; + } + + public void downloadWithHttpClient(String userAgent, String downloadUrl, File outputfile) throws Throwable { + + HttpGet httpget2 = new HttpGet(downloadUrl); + //Utils.log.finer("Executing " + httpget2.getURI()); + HttpClient httpclient2 = new DefaultHttpClient(); + + HttpResponse response2 = httpclient2.execute(httpget2); + HttpEntity entity2 = response2.getEntity(); + + if (entity2 != null && response2.getStatusLine().getStatusCode() == 200) { + long length = entity2.getContentLength(); + InputStream instream2 = entity2.getContent(); + //Utils.log.finer("Writing " + length + " bytes to " + outputfile); + if (outputfile.exists()) { + outputfile.delete(); + } + FileOutputStream outstream = new FileOutputStream(outputfile); + try { + byte[] buffer = new byte[2048]; + int count = -1; + while ((count = instream2.read(buffer)) != -1) { + outstream.write(buffer, 0, count); + } + outstream.flush(); + } finally { + outstream.close(); + } + } + } + + private String cleanFilename(String filename) { + for (char c : ILLEGAL_FILENAME_CHARACTERS) { + filename = filename.replace(c, '_'); + } + return filename; + } + + private static URI getUri(String path, List<NameValuePair> qparams) throws URISyntaxException { + URI uri = URIUtils.createURI(scheme, host, -1, "/" + path, URLEncodedUtils.format(qparams, "UTF-8"), null); + return uri; + } + + private String getStringFromInputStream(String encoding, InputStream instream) throws UnsupportedEncodingException, IOException { + Writer writer = new StringWriter(); + + char[] buffer = new char[1024]; + try { + Reader reader = new BufferedReader(new InputStreamReader(instream, encoding)); + int n; + while ((n = reader.read(buffer)) != -1) { + writer.write(buffer, 0, n); + } + } finally { + instream.close(); + } + String result = writer.toString(); + return result; + } + + + private String getStringFromWebsite(String url) { + String response = ""; + DefaultHttpClient client = new DefaultHttpClient(); + HttpGet httpGet = new HttpGet(url); + try { + HttpResponse execute = client.execute(httpGet); + InputStream content = execute.getEntity().getContent(); + + BufferedReader buffer = new BufferedReader( + new InputStreamReader(content)); + String s = ""; + //Charset cs = Charset.forName("ASCII"); // Or whatever encoding you want + + + while ((s = buffer.readLine()) != null) { + response += s; + } + + } catch (Exception e) { + e.printStackTrace(); + } + return response; + } + + + + + + /** + * Gets all parameters we can figure out about this youTubeId by scraping YouTube. + * The URL for the underlying video will have the key "location". + * @param youTubeId A YouTube ID + * @return A map with all parameters as key-value pairs. + * @throws IOException if we have networking troubles + */ + public static Map<String, String> getVideoInfo(String youTubeId) throws IOException + { + final Map<String, String> retval = new HashMap<String, String>(); + getVideoInfo(youTubeId, retval); + return retval; + } + /** + * Gets all parameters we can figure out about this youTubeId by scraping YouTube. + * The URL for the underlying video will have the key "location". + * @param youTubeId A YouTube ID + * @param map A map to fill with data; it is not cleared first. + * @throws IOException if we have networking troubles + */ + public static void getVideoInfo(String youTubeId, Map<String, String> map) throws IOException + { + final String host = "http://www.youtube.com"; + final List<IKeyValuePair> params = new ArrayList<IKeyValuePair>(); + params.add(new KeyValuePair("video_id", youTubeId)); + final String urlString = host + "/get_video_info?&"+ URLParams.generateQueryString(params); + + final URL url; + try + { + url = new URL(urlString); + } + catch (MalformedURLException e) + { + throw new RuntimeException("malformed url: " + urlString, e); + } + HttpURLConnection conn; + BufferedInputStream in; + byte[] data = new byte[4096]; // bad Art; fixed size + + conn = (HttpURLConnection)url.openConnection(); + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + // badness. + conn.disconnect(); + throw new RuntimeException("could not get video info: " + urlString); + } + in = new BufferedInputStream(conn.getInputStream()); + int offset=0; + do { + offset = in.read(data, offset, data.length-offset); + } while(offset >= 0 && offset < data.length); + conn.disconnect(); + // convert to string; ugh + String response = new String(data, "UTF-8"); + System.out.println("Response: "+response); + // convert into parameter map + final Map<String, String> youTubeParams = MapUtils.listToMap(URLParams.parseQueryString(response), MapUtils.ListToMapMode.FIRST_WINS); + + + final String token = youTubeParams.get("token"); + //if (token == null) + //throw new RuntimeException("Could not find youtube token: "+ urlString +"; response="+response); + + + params.clear(); + params.add(new KeyValuePair("video_id", youTubeId)); + params.add(new KeyValuePair("t", token)); + final String location = host + "/get_video?"+URLParams.generateQueryString(params); + map.put("location", location); + map.putAll(youTubeParams); + + } +} + + diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/SizeAnimator.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/SizeAnimator.java new file mode 100644 index 00000000..5db6f4b3 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/SizeAnimator.java @@ -0,0 +1,40 @@ +package de.luhmer.owncloudnewsreader.helper; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +/** + * Created by David on 30.06.2014. + */ +public class SizeAnimator { + + public SizeAnimator(View view, float mWidth, float mHeight, float oldWidth, float oldHeight, int duration) { + this.viewToSizeAnimate = view; + this.mWidth = mWidth; + this.mHeight = mHeight; + this.mOldHeight = oldHeight; + this.mOldWidth = oldWidth; + + sizeAnimator.setDuration(duration); + } + + View viewToSizeAnimate; + float mWidth; + float mHeight; + float mOldWidth; + float mOldHeight; + + + + public Animation sizeAnimator = new Animation() { + + @Override + protected void applyTransformation(float interpolatedTime, Transformation t) { + viewToSizeAnimate.getLayoutParams().width = (int)(mOldWidth + ((mWidth - mOldWidth) * interpolatedTime)); + viewToSizeAnimate.getLayoutParams().height = (int) (mOldHeight + ((mHeight - mOldHeight) * interpolatedTime)); + viewToSizeAnimate.setLayoutParams(viewToSizeAnimate.getLayoutParams()); + } + }; + +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java index 7fb53e4e..544b91c2 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/model/PodcastItem.java @@ -7,13 +7,26 @@ import java.io.Serializable; */ public class PodcastItem implements Serializable { + public PodcastItem() { + + } + + public PodcastItem(String itemId, String title, String link, String mimeType, boolean offlineCached) { + this.itemId = itemId; + this.title = title; + this.link = link; + this.mimeType = mimeType; + this.offlineCached = offlineCached; + } + public String itemId; public String title; public String link; public String mimeType; + public boolean offlineCached; - public int downloadProgress; + public Integer downloadProgress; - public static int DOWNLOAD_COMPLETED = -1; - public static int DOWNLOAD_NOT_STARTED = -2; + public static Integer DOWNLOAD_COMPLETED = -1; + public static Integer DOWNLOAD_NOT_STARTED = -2; } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/DownloadItemsToCache.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/DownloadItemsToCache.java index 2540a887..eb3c2c32 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/DownloadItemsToCache.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/DownloadItemsToCache.java @@ -28,8 +28,8 @@ import android.util.SparseArray; import de.luhmer.owncloudnewsreader.async_tasks.GetImageAsyncTask; import de.luhmer.owncloudnewsreader.helper.BitmapDrawableLruCache; +import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.ImageDownloadFinished; -import de.luhmer.owncloudnewsreader.helper.ImageHandler; public class DownloadItemsToCache { SparseArray<String> URLs; @@ -47,7 +47,7 @@ public class DownloadItemsToCache { key = URLs.keyAt(URLs.size() -1) + 1; URLs.append(key, URL_TO_IMAGE); - GetImageAsyncTask getImageAsync = new GetImageAsyncTask(URL_TO_IMAGE, imgDownloadFinished, key, ImageHandler.getPathImageCache(context), context, null); + GetImageAsyncTask getImageAsync = new GetImageAsyncTask(URL_TO_IMAGE, imgDownloadFinished, key, FileUtils.getPathImageCache(context), context, null); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) getImageAsync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, ((Void) null));// Execute in parallel diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/owncloud/InsertItemIntoDatabase.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/owncloud/InsertItemIntoDatabase.java index dbc7132b..2eb03b94 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/owncloud/InsertItemIntoDatabase.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/reader/owncloud/InsertItemIntoDatabase.java @@ -55,18 +55,29 @@ public class InsertItemIntoDatabase implements IHandleJsonObject { content = content.replaceAll("<img[^>]*auslieferung.commindo-media-ressourcen.de.*>", ""); content = content.replaceAll("<img[^>]*rss.buysellads.com.*>", ""); + String url = e.optString("url"); + String guid = e.optString("guid"); + String enclosureLink = e.optString("enclosureLink"); + String enclosureMime = e.optString("enclosureMime"); + + if(enclosureLink.isEmpty() && guid.startsWith("http://gdata.youtube.com/feeds/api/")) { + enclosureLink = url; + enclosureMime = "youtube"; + } + + return new RssFile(0, e.optString("id"), e.optString("title"), - e.optString("url"), content, + url, content, !e.optBoolean("unread"), null, e.optString("feedId"), null, date, e.optBoolean("starred"), - e.optString("guid"), + guid, e.optString("guidHash"), e.optString("lastModified"), e.optString("author"), - e.optString("enclosureLink"), - e.optString("enclosureMime")); + enclosureLink, + enclosureMime); } @Override diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/DownloadImagesService.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/DownloadImagesService.java index 492cef8d..79100813 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/DownloadImagesService.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/DownloadImagesService.java @@ -49,6 +49,7 @@ import de.luhmer.owncloudnewsreader.async_tasks.GetImageAsyncTask; import de.luhmer.owncloudnewsreader.database.DatabaseConnection; import de.luhmer.owncloudnewsreader.helper.BitmapDrawableLruCache; import de.luhmer.owncloudnewsreader.helper.FavIconHandler; +import de.luhmer.owncloudnewsreader.helper.FileUtils; import de.luhmer.owncloudnewsreader.helper.ImageDownloadFinished; import de.luhmer.owncloudnewsreader.helper.ImageHandler; @@ -144,7 +145,7 @@ public class DownloadImagesService extends IntentService { notificationManager.notify(NOTIFICATION_ID, notify); for (String link : links) - new GetImageAsyncTask(link, imgDownloadFinished, 999, ImageHandler.getPathImageCache(this), this, null).execute(); + new GetImageAsyncTask(link, imgDownloadFinished, 999, FileUtils.getPathImageCache(this), this, null).execute(); } } @@ -169,7 +170,7 @@ public class DownloadImagesService extends IntentService { private void RemoveOldImages(Context context) { HashMap<File, Long> files; - long size = ImageHandler.getFolderSize(new File(ImageHandler.getPath(context))); + long size = ImageHandler.getFolderSize(new File(FileUtils.getPath(context))); size = (long) (size / 1024d / 1024d); SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context); @@ -177,7 +178,7 @@ public class DownloadImagesService extends IntentService { if(size > max_allowed_size) { files = new HashMap<File, Long>(); - for(File file : ImageHandler.getFilesFromDir(new File(ImageHandler.getPathImageCache(context)))) + for(File file : ImageHandler.getFilesFromDir(new File(FileUtils.getPathImageCache(context)))) { files.put(file, file.lastModified()); } diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java new file mode 100644 index 00000000..0a88c77c --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastDownloadService.java @@ -0,0 +1,240 @@ +package de.luhmer.owncloudnewsreader.services; + +import android.annotation.TargetApi; +import android.app.DownloadManager; +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.ResultReceiver; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.security.MessageDigest; + +import de.greenrobot.event.EventBus; +import de.luhmer.owncloudnewsreader.helper.FileUtils; +import de.luhmer.owncloudnewsreader.helper.JavaYoutubeDownloader; +import de.luhmer.owncloudnewsreader.model.PodcastItem; + +/** + * An {@link IntentService} subclass for handling asynchronous task requests in + * a service on a separate handler thread. + * <p> + * TODO: Customize class - update intent actions, extra parameters and static + * helper methods. + */ +public class PodcastDownloadService extends IntentService { + // TODO: Rename actions, choose action names that describe tasks that this + // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS + private static final String ACTION_DOWNLOAD = "de.luhmer.owncloudnewsreader.services.action.DOWNLOAD"; + + + + // TODO: Rename parameters + private static final String EXTRA_RECEIVER = "de.luhmer.owncloudnewsreader.services.extra.RECEIVER"; + private static final String EXTRA_URL = "de.luhmer.owncloudnewsreader.services.extra.URL"; + private static final String EXTRA_PARAM2 = "de.luhmer.owncloudnewsreader.services.extra.PARAM2"; + private static final String TAG = "PodcastDownloadService"; + + private EventBus eventBus; + + /** + * Starts this service to perform action Foo with the given parameters. If + * the service is already performing a task this action will be queued. + * + * @see IntentService + */ + // TODO: Customize helper method + public static void startPodcastDownload(Context context, PodcastItem podcastItem/*, ResultReceiver receiver*/) { + Intent intent = new Intent(context, PodcastDownloadService.class); + intent.setAction(ACTION_DOWNLOAD); + intent.putExtra(EXTRA_URL, podcastItem); + //intent.putExtra(EXTRA_RECEIVER, receiver); + //intent.putExtra(EXTRA_PARAM2, param2); + context.startService(intent); + } + + + public PodcastDownloadService() { + super("PodcastDownloadService"); + + eventBus = EventBus.getDefault(); + } + + @Override + protected void onHandleIntent(Intent intent) { + + if (intent != null) { + final String action = intent.getAction(); + if (ACTION_DOWNLOAD.equals(action)) { + ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra(EXTRA_RECEIVER); + PodcastItem podcast = (PodcastItem) intent.getSerializableExtra(EXTRA_URL); + //final String param2 = intent.getStringExtra(EXTRA_PARAM2); + //handleActionDownload(podcast); + + downloadPodcast(podcast, this); + + + }/* else if (ACTION_BAZ.equals(action)) { + final String param1 = intent.getStringExtra(EXTRA_PARAM1); + final String param2 = intent.getStringExtra(EXTRA_PARAM2); + handleActionBaz(param1, param2); + }*/ + } + } + + /** + * Handle action Foo in the provided background thread with the provided + * parameters. + */ + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + private void handleActionDownload(PodcastItem podcast) { + Uri uri = Uri.parse(podcast.link); + DownloadManager.Request request = new DownloadManager.Request(uri); + request.setDescription(podcast.mimeType); + request.setTitle(podcast.title); + + // in order for this if to run, you must use the android 3.2 to compile your app + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + } + + String path = "file://" + getUrlToPodcastFile(this, podcast.link, true); + request.setDestinationUri(Uri.parse(path)); + //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "bla.txt"); + + // get download service and enqueue file + DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); + manager.enqueue(request); + } + + + public static String getUrlToPodcastFile(Context context, String WEB_URL_TO_FILE, boolean createDir) { + if(WEB_URL_TO_FILE.contains(JavaYoutubeDownloader.host)) + return getUrlToYoutubePodcastFile(context, WEB_URL_TO_FILE, createDir); + + File file = new File(WEB_URL_TO_FILE); + + String path = FileUtils.getPathPodcasts(context) + "/" + getHashOfString(WEB_URL_TO_FILE) + "/"; + if(createDir) + new File(path).mkdirs(); + + return path + file.getName(); + } + + private static String getUrlToYoutubePodcastFile(Context context, String WEB_URL_TO_FILE, boolean createDir) { + String path = FileUtils.getPathPodcasts(context) + "/" + getHashOfString(WEB_URL_TO_FILE) + "/"; + if(createDir) + new File(path).mkdirs(); + + return path + "video.mp4"; + } + + + public static String getHashOfString(String WEB_URL_TO_FILE) + { + try { + MessageDigest m = MessageDigest.getInstance("MD5"); + m.reset(); + m.update(WEB_URL_TO_FILE.trim().getBytes()); + byte[] digest = m.digest(); + BigInteger bigInt = new BigInteger(1,digest); + String hashtext = bigInt.toString(16); + + return hashtext; + } catch (Exception e) { + e.printStackTrace(); + } + return WEB_URL_TO_FILE; + } + + + private void downloadPodcast(PodcastItem podcast, Context context) { + try { + String urlTemp = podcast.link; + String path = getUrlToPodcastFile(this, urlTemp, true); + + if(podcast.link.contains(JavaYoutubeDownloader.host)) { + + path = getUrlToPodcastFile(context, urlTemp, true); + + try { + urlTemp = new JavaYoutubeDownloader().getDownloadUrl(podcast.link, context); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + } + URL url = new URL(urlTemp); + URLConnection connection = url.openConnection(); + connection.connect(); + connection.setConnectTimeout(10000); + connection.setReadTimeout(120000);//2min + // this will be useful so that you can show a typical 0-100% progress bar + int fileLength = connection.getContentLength(); + + // download the file + InputStream input = new BufferedInputStream(url.openStream()); + + + String pathCache = path + ".download"; + OutputStream output = new FileOutputStream(pathCache); + + + byte data[] = new byte[1024]; + long total = 0; + int count; + while ((count = input.read(data)) != -1) { + total += count; + + int progress = (int) (total * 100 / fileLength); + podcast.downloadProgress = progress; + eventBus.post(new DownloadProgressUpdate(podcast)); + + output.write(data, 0, count); + } + + output.flush(); + output.close(); + input.close(); + + + new File(pathCache).renameTo(new File(path)); + + } catch (IOException e) { + e.printStackTrace(); + } + + + podcast.downloadProgress = 100; + eventBus.post(new DownloadProgressUpdate(podcast)); + + /* + Bundle resultData = new Bundle(); + resultData.putInt("progress" ,100); + receiver.send(UPDATE_PROGRESS, resultData); + */ + } + + public static final int UPDATE_PROGRESS = 5555; + + + public class DownloadProgressUpdate { + + public DownloadProgressUpdate(PodcastItem podcast) { + this.podcast = podcast; + } + + public PodcastItem podcast; + } +} diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java index 7a656ba2..61c935ff 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/services/PodcastPlaybackService.java @@ -5,12 +5,16 @@ import android.content.Intent; import android.media.MediaPlayer; import android.os.Handler; import android.os.IBinder; +import android.util.Log; import android.view.SurfaceHolder; +import android.view.View; import java.io.IOException; import de.greenrobot.event.EventBus; -import de.luhmer.owncloudnewsreader.events.podcast.OpenAudioPodcastEvent; +import de.luhmer.owncloudnewsreader.R; +import de.luhmer.owncloudnewsreader.events.podcast.NewPodcastPlaybackListener; +import de.luhmer.owncloudnewsreader.events.podcast.OpenPodcastEvent; import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput; import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent; import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent; @@ -19,33 +23,35 @@ import de.luhmer.owncloudnewsreader.view.PodcastNotification; public class PodcastPlaybackService extends Service { + private static final String TAG = "PodcastPlaybackService"; PodcastNotification podcastNotification; @Override public void onCreate() { podcastNotification = new PodcastNotification(this); + mediaTitle = getString(R.string.no_podcast_selected); + super.onCreate(); } public PodcastPlaybackService() { - mediaPlayer = new MediaPlayer(); + mMediaPlayer = new MediaPlayer(); mHandler = new Handler(); eventBus = EventBus.getDefault(); eventBus.register(this); - - - mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mediaPlayer) { play(); isPreparing = false; + canCallGetDuration = true; } }); - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mediaPlayer) { pause();//Send the over signal @@ -53,39 +59,52 @@ public class PodcastPlaybackService extends Service { }); + eventBus.post(new PodcastPlaybackServiceStarted()); + + mHandler.postDelayed(mUpdateTimeTask, 0); + //openFile("/sdcard/Music/#Musik/Finest Tunes/Netsky - Running Low (Ft. Beth Ditto).mp3"); } @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return Service.START_STICKY; + //return super.onStartCommand(intent, flags, startId); + } + + @Override public IBinder onBind(Intent intent) { - // TODO: Return the communication channel to the service. - throw new UnsupportedOperationException("Not yet implemented"); + return null; + //throw new UnsupportedOperationException("Not yet implemented"); } private EventBus eventBus; private Handler mHandler; - private MediaPlayer mediaPlayer; + private MediaPlayer mMediaPlayer; private String mediaTitle; + View parentResizableView; public static final int delay = 500; //In milliseconds + private boolean canCallGetDuration = false;//Otherwise the player would call getDuration all the time without loading a media file private boolean isPreparing = false; + private boolean isVideoFile = false; public void openFile(String pathToFile, String mediaTitle) { try { - this.mediaTitle = mediaTitle; - - if(mediaPlayer.isPlaying()) + if(mMediaPlayer.isPlaying()) pause(); + this.mediaTitle = mediaTitle; + isPreparing = true; mHandler.postDelayed(mUpdateTimeTask, 0); - mediaPlayer.reset(); - mediaPlayer.setDataSource(pathToFile); - mediaPlayer.prepareAsync(); + mMediaPlayer.reset(); + mMediaPlayer.setDataSource(pathToFile); + mMediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); isPreparing = false; @@ -104,7 +123,7 @@ public class PodcastPlaybackService extends Service { }; public void onEvent(TogglePlayerStateEvent event) { - if(mediaPlayer.isPlaying()) { + if(mMediaPlayer.isPlaying()) { pause(); } else { play(); @@ -112,49 +131,91 @@ public class PodcastPlaybackService extends Service { } public void onEvent(WindPodcast event) { - if(mediaPlayer != null) { - double totalDuration = mediaPlayer.getDuration(); + if(mMediaPlayer != null) { + double totalDuration = mMediaPlayer.getDuration(); int position = (int)((totalDuration / 100d) * event.toPositionInPercent); - mediaPlayer.seekTo(position); + mMediaPlayer.seekTo(position); } } - public void onEventBackgroundThread(OpenAudioPodcastEvent event) { + public void onEventBackgroundThread(OpenPodcastEvent event) { + this.isVideoFile = event.isVideoFile; openFile(event.pathToFile, event.mediaTitle); } public void onEvent(RegisterVideoOutput videoOutput) { - if(mediaPlayer != null) { - //getHolder().addCallback(videoOutput); - videoOutput.surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + if(mMediaPlayer != null) { + if(videoOutput.surfaceView == null) { + mMediaPlayer.setDisplay(null); + Log.d(TAG, "Disable Screen output!"); + + mMediaPlayer.setScreenOnWhilePlaying(false); + } else { + if(videoOutput.surfaceView.getHolder() != mSurfaceHolder) { + parentResizableView = videoOutput.parentResizableView; + + videoOutput.surfaceView.getHolder().addCallback(mSHCallback); + //videoOutput.surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); + + populateVideo(); + + Log.d(TAG, "Enable Screen output!"); + } + } } } + public void onEvent(NewPodcastPlaybackListener newListener) { + sendMediaStatus(); + } + public void play() { - mediaPlayer.start(); + try { + int progress = mMediaPlayer.getCurrentPosition() / mMediaPlayer.getDuration(); + if (progress >= 1) { + mMediaPlayer.seekTo(0); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + mMediaPlayer.start(); mHandler.removeCallbacks(mUpdateTimeTask); mHandler.postDelayed(mUpdateTimeTask, 0); + + populateVideo(); + } + + private void populateVideo() { + double videoHeightRel = (double) mSurfaceWidth / (double) mMediaPlayer.getVideoWidth(); + int videoHeight = (int) (mMediaPlayer.getVideoHeight() * videoHeightRel); + + if (mSurfaceWidth != 0 && videoHeight != 0 && mSurfaceHolder != null) { + //mSurfaceHolder.setFixedSize(mSurfaceWidth, videoHeight); + + parentResizableView.getLayoutParams().height = videoHeight; + parentResizableView.setLayoutParams(parentResizableView.getLayoutParams()); + } } public void pause() { - if(mediaPlayer.isPlaying()) - mediaPlayer.pause(); + if(mMediaPlayer.isPlaying()) + mMediaPlayer.pause(); mHandler.removeCallbacks(mUpdateTimeTask); - sendMediaStatus(); } public void sendMediaStatus() { long totalDuration = 0; long currentDuration = 0; - if(!isPreparing) { - totalDuration = mediaPlayer.getDuration(); - currentDuration = mediaPlayer.getCurrentPosition(); + if(!isPreparing && canCallGetDuration) { + totalDuration = mMediaPlayer.getDuration(); + currentDuration = mMediaPlayer.getCurrentPosition(); } /* @@ -169,21 +230,43 @@ public class PodcastPlaybackService extends Service { songProgressBar.setProgress(progress); */ - UpdatePodcastStatusEvent audioPodcastEvent = new UpdatePodcastStatusEvent(currentDuration, totalDuration, mediaPlayer.isPlaying(), mediaTitle, isPreparing); + UpdatePodcastStatusEvent audioPodcastEvent = new UpdatePodcastStatusEvent(currentDuration, totalDuration, mMediaPlayer.isPlaying(), mediaTitle, isPreparing, canCallGetDuration, isVideoFile); eventBus.post(audioPodcastEvent); } + public class PodcastPlaybackServiceStarted { + + } + + /* + public class VideoAvailableState { + public VideoAvailableState(boolean isVideoAvailable) { + this.isVideoAvailable = isVideoAvailable; + } + + public boolean isVideoAvailable; + } + */ + + int mSurfaceWidth; + int mSurfaceHeight; + SurfaceHolder mSurfaceHolder; SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { - public void surfaceChanged(SurfaceHolder holder, int format, - int w, int h) + public void surfaceChanged(SurfaceHolder holder, int format, int surfaceWidth, int surfaceHeight) { - mSurfaceWidth = w; - mSurfaceHeight = h; - if (mIsPrepared && mVideoWidth == w && mVideoHeight == h) { + mSurfaceWidth = surfaceWidth; + mSurfaceHeight = surfaceHeight; + + //populateVideo(); + + //Log.d(TAG, "surfaceChanged"); + + /* + if (!isPreparing && mVideoWidth == w && mVideoHeight == h) { if (mSeekWhenPrepared != 0) { mMediaPlayer.seekTo(mSeekWhenPrepared); } @@ -192,24 +275,22 @@ public class PodcastPlaybackService extends Service { mMediaController.show(); } } + */ } public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; - openVideo(); + mMediaPlayer.setDisplay(mSurfaceHolder); + + mMediaPlayer.setScreenOnWhilePlaying(true); + + Log.d(TAG, "surfaceCreated"); } public void surfaceDestroyed(SurfaceHolder holder) { - // after we return from this we can't use the surface any more - mSurfaceHolder = null; - if (mMediaController != null) mMediaController.hide(); - if (mMediaPlayer != null) { - mMediaPlayer.reset(); - mMediaPlayer.release(); - mMediaPlayer = null; - } + Log.d(TAG, "surfaceDestroyed"); } }; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/OnPinchListener.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/OnPinchListener.java new file mode 100644 index 00000000..03689b95 --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/OnPinchListener.java @@ -0,0 +1,44 @@ +package de.luhmer.owncloudnewsreader.view; + +import android.view.ScaleGestureDetector; + +/** + * Created by David on 30.06.2014. + */ +public class OnPinchListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + private float mScaleFactor = 1.f; + + ZoomableRelativeLayout mZoomableRelativeLayout; + + public OnPinchListener(ZoomableRelativeLayout mZoomableRelativeLayout) { + this.mZoomableRelativeLayout = mZoomableRelativeLayout; + } + + float startingSpan; + float endSpan; + float startFocusX; + float startFocusY; + + + public boolean onScaleBegin(ScaleGestureDetector detector) { + startingSpan = detector.getCurrentSpan(); + startFocusX = detector.getFocusX(); + startFocusY = detector.getFocusY(); + return true; + } + + + + + /* + public boolean onScale(ScaleGestureDetector detector) { + mZoomableRelativeLayout.scale(detector.getCurrentSpan()/startingSpan, startFocusX, startFocusY); + return true; + } + */ + + public void onScaleEnd(ScaleGestureDetector detector) { + //mZoomableRelativeLayout.restore(); + } +}
\ No newline at end of file diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastNotification.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastNotification.java index 116db3d8..a54f3095 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastNotification.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastNotification.java @@ -54,19 +54,10 @@ public class PodcastNotification { int lastDrawableId; - long lastUpdate = 0; - int seconds = 1;//Time of delay between each update public void onEvent(UpdatePodcastStatusEvent podcast) { - /* - long current = new Date().getTime(); - - if((lastUpdate + (1000 * seconds)) > current) { + if(!podcast.isFileLoaded()) return; - } else { - lastUpdate = current; - } - */ int drawableId = podcast.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play; String actionText = podcast.isPlaying() ? "Pause" : "Play"; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastSlidingUpPanelLayout.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastSlidingUpPanelLayout.java index 6cc2216b..650869c4 100644 --- a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastSlidingUpPanelLayout.java +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/PodcastSlidingUpPanelLayout.java @@ -25,9 +25,9 @@ public class PodcastSlidingUpPanelLayout extends SlidingUpPanelLayout{ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - //return super.onInterceptTouchEvent(ev); + return super.onInterceptTouchEvent(ev); - return isDragViewHit((int)ev.getX(), (int)ev.getY()); + //return isDragViewHit((int)ev.getX(), (int)ev.getY()); } private View mDragView; diff --git a/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/ZoomableRelativeLayout.java b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/ZoomableRelativeLayout.java new file mode 100644 index 00000000..fdb7958a --- /dev/null +++ b/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/view/ZoomableRelativeLayout.java @@ -0,0 +1,242 @@ +package de.luhmer.owncloudnewsreader.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.widget.RelativeLayout; + +import de.greenrobot.event.EventBus; +import de.luhmer.owncloudnewsreader.events.podcast.VideoDoubleClicked; + +/** + * Created by David on 30.06.2014. + */ +//http://stackoverflow.com/questions/10013906/android-zoom-in-out-relativelayout-with-spread-pinch +public class ZoomableRelativeLayout extends RelativeLayout { + private static final int INVALID_POINTER_ID = -1; + private static final int INVALID_SIZE = -1; + + + public boolean disableScale = false; + public void setDisableScale(boolean disableScale) { + this.disableScale = disableScale; + } + + private static final String TAG = "ZoomableRelativeLayout"; + + private GestureDetector mDoubleTapDetector; + private ScaleGestureDetector mScaleDetector; + float mScaleFactor = 1; + public float getScaleFactor() { + return mScaleFactor; + } + + float mPosX; + float mPosY; + private float mLastTouchX; + private float mLastTouchY; + private int mActivePointerId; + + private float mInitHeight = INVALID_SIZE; + private float mInitWidth = INVALID_SIZE; + + public ZoomableRelativeLayout(Context context) { + super(context); + initZoomView(context); + } + + public ZoomableRelativeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + initZoomView(context); + } + + public ZoomableRelativeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initZoomView(context); + } + + + boolean mPositionReady = false; + public boolean isPositionReady() { + return mPositionReady; + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + if(hasWindowFocus) { + readVideoPosition(); + mPositionReady = true; + } + super.onWindowFocusChanged(hasWindowFocus); + } + + + + public void readVideoPosition() { + int position[] = new int[2]; + getLocationOnScreen(position); + mVideoXPosition = position[0]; + mVideoYPosition = position[1]; + + Log.d(TAG, "Grabbing new Video Wrapper Position. X:" + mVideoXPosition + " - Y:" + mVideoYPosition); + + //mVideoXPosition = getX(); + //mVideoYPosition = getY(); + } + + private void initZoomView(Context context) { + // Create our ScaleGestureDetector + mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); + mDoubleTapDetector = new GestureDetector(context, new DoubleTapListener()); + } + + /* + @Override + protected void dispatchDraw(Canvas canvas) { + canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.scale(mScaleFactor, mScaleFactor, mPosX, mPosY); + super.dispatchDraw(canvas); + canvas.restore(); + }*/ + + private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { + + // event when double tap occurs + @Override + public boolean onDoubleTap(MotionEvent e) { + float x = e.getX(); + float y = e.getY(); + + Log.d("Double Tap", "Tapped at: (" + x + "," + y + ")"); + + EventBus.getDefault().post(new VideoDoubleClicked()); + + return true; + } + } + + + private float mVideoXPosition; + private float mVideoYPosition; + public float getVideoXPosition() {return mVideoXPosition;} + public float getVideoYPosition() {return mVideoYPosition;} + + private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + if(!disableScale) { + readVideoPosition(); + } + + super.onScaleEnd(detector); + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + if(disableScale) + return true; + + if(mInitWidth == INVALID_SIZE) { + mInitWidth = getWidth(); + mInitHeight = getHeight(); + } + + mScaleFactor *= detector.getScaleFactor(); + + // Don't let the object get too small or too large. + mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f)); + + Log.d(TAG, "Scale:" + mScaleFactor); + + + getLayoutParams().width = (int)(mInitWidth * mScaleFactor); + getLayoutParams().height = (int)(mInitHeight * mScaleFactor); + setLayoutParams(getLayoutParams()); + + //invalidate(); + return true; + } + } + + + /* + public void restore() { + mScaleFactor = 1; + this.invalidate(); + }*/ + + + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Let the ScaleGestureDetector inspect all events. + mScaleDetector.onTouchEvent(ev); + mDoubleTapDetector.onTouchEvent(ev); + + final int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + + mLastTouchX = x; + mLastTouchY = y; + mActivePointerId = ev.getPointerId(0); + break; + } + + case MotionEvent.ACTION_MOVE: { + final int pointerIndex = ev.findPointerIndex(mActivePointerId); + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); + + // Only move if the ScaleGestureDetector isn't processing a gesture. + if (!mScaleDetector.isInProgress()) { + final float dx = x - mLastTouchX; + final float dy = y - mLastTouchY; + + mPosX += dx; + mPosY += dy; + + invalidate(); + } + + mLastTouchX = x; + mLastTouchY = y; + + break; + } + + case MotionEvent.ACTION_UP: { + mActivePointerId = INVALID_POINTER_ID; + break; + } + + case MotionEvent.ACTION_CANCEL: { + mActivePointerId = INVALID_POINTER_ID; + break; + } + + case MotionEvent.ACTION_POINTER_UP: { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastTouchX = ev.getX(newPointerIndex); + mLastTouchY = ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + } + break; + } + } + + return true; + } + +}
\ No newline at end of file |