diff options
author | Tom Hennen <TomHennen@users.noreply.github.com> | 2015-08-02 13:11:59 -0400 |
---|---|---|
committer | Tom Hennen <TomHennen@users.noreply.github.com> | 2015-08-02 13:11:59 -0400 |
commit | ab68619aaceb545533bf8efd7faec500935270f5 (patch) | |
tree | 30b244a3a66be349581e3bac3e1da4c5a7039a73 | |
parent | 83bf67a771593ddd8150c97b505c98a641596942 (diff) | |
parent | 5f0ddbc15d68d82e85c41156d2873231ac32b986 (diff) | |
download | AntennaPod-ab68619aaceb545533bf8efd7faec500935270f5.zip |
Merge pull request #1043 from mfietz/glide
Glide
60 files changed, 1191 insertions, 870 deletions
diff --git a/app/build.gradle b/app/build.gradle index daeb9ca39..cd6fbd354 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ dependencies { } compile 'commons-io:commons-io:2.4' compile 'org.jsoup:jsoup:1.7.3' - compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.github.bumptech.glide:glide:3.6.1' compile 'com.squareup.okhttp:okhttp:2.3.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0' compile 'com.squareup.okio:okio:1.2.0' diff --git a/app/proguard.cfg b/app/proguard.cfg index 536f54eca..6020b5751 100644 --- a/app/proguard.cfg +++ b/app/proguard.cfg @@ -89,3 +89,10 @@ # android-iconify -keep class com.joanzapata.** { *; } + +# Glide +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { + **[] $VALUES; + public *; +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24c9c2726..768115643 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -327,6 +327,10 @@ android:scheme="package"/> </intent-filter> </receiver> + + <meta-data + android:name="de.danoeh.antennapod.core.glide.ApGlideModule" + android:value="GlideModule" /> </application> </manifest> diff --git a/app/src/main/assets/LICENSE_GLIDE.txt b/app/src/main/assets/LICENSE_GLIDE.txt new file mode 100644 index 000000000..f5111eeab --- /dev/null +++ b/app/src/main/assets/LICENSE_GLIDE.txt @@ -0,0 +1,94 @@ +License for everything not in third_party and not otherwise marked: + +Copyright 2014 Google, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Google, Inc. +--------------------------------------------------------------------------------------------- +License for third_party/disklrucache: + +Copyright 2012 Jake Wharton +Copyright 2011 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--------------------------------------------------------------------------------------------- +License for third_party/gif_decoder: + +Copyright (c) 2013 Xcellent Creations, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--------------------------------------------------------------------------------------------- +License for third_party/gif_encoder/AnimatedGifEncoder.java and +third_party/gif_encoder/LZWEncoder.java: + +No copyright asserted on the source code of this class. May be used for any +purpose, however, refer to the Unisys LZW patent for restrictions on use of +the associated LZWEncoder class. Please forward any corrections to +kweiner@fmsware.com. + +----------------------------------------------------------------------------- +License for third_party/gif_encoder/NeuQuant.java + +Copyright (c) 1994 Anthony Dekker + +NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See +"Kohonen neural networks for optimal colour quantization" in "Network: +Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of +the algorithm. + +Any party obtaining a copy of these files from the author, directly or +indirectly, is granted, free of charge, a full and unrestricted irrevocable, +world-wide, paid up, royalty-free, nonexclusive right and license to deal in +this software and documentation files (the "Software"), including without +limitation the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to permit persons who +receive copies from any such party to do so, with the only requirement being +that this copyright notice remain intact. diff --git a/app/src/main/assets/LICENSE_PICASSO.txt b/app/src/main/assets/LICENSE_PICASSO.txt deleted file mode 100644 index 0bf6b9f8e..000000000 --- a/app/src/main/assets/LICENSE_PICASSO.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2013 Square, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/app/src/main/assets/LICENSE_STACKBLUR.txt b/app/src/main/assets/LICENSE_STACKBLUR.txt new file mode 100644 index 000000000..b66736ccb --- /dev/null +++ b/app/src/main/assets/LICENSE_STACKBLUR.txt @@ -0,0 +1,71 @@ +Apache License, Version 2.0 + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/src/main/java/de/danoeh/antennapod/AppConfig.java b/app/src/main/java/de/danoeh/antennapod/AppConfig.java deleted file mode 100644 index 24f13d4a3..000000000 --- a/app/src/main/java/de/danoeh/antennapod/AppConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.danoeh.antennapod; - -public final class AppConfig { - /** Should be used when setting User-Agent header for HTTP-requests. */ - public final static String USER_AGENT = "AntennaPod/0.9.9.4"; - -} diff --git a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java index 4b709ab74..f47fe3944 100644 --- a/app/src/main/java/de/danoeh/antennapod/PodcastApp.java +++ b/app/src/main/java/de/danoeh/antennapod/PodcastApp.java @@ -4,11 +4,11 @@ import android.app.Application; import android.content.Intent; import android.content.res.Configuration; -import de.danoeh.antennapod.core.asynctask.PicassoProvider; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.FeedMediaSizeService; +import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.spa.SPAUtil; /** Main application class. */ @@ -39,9 +39,10 @@ public class PodcastApp extends Application { singleton = this; LOGICAL_DENSITY = getResources().getDisplayMetrics().density; - PicassoProvider.setupPicassoInstance(this); + UpdateManager.init(this); UserPreferences.init(this); PlaybackPreferences.init(this); + NetworkUtils.init(this); EventDistributor.getInstance(); SPAUtil.sendSPAppsQueryFeedsIntent(this); diff --git a/app/src/main/java/de/danoeh/antennapod/UpdateManager.java b/app/src/main/java/de/danoeh/antennapod/UpdateManager.java new file mode 100644 index 000000000..2f6bb1b50 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/UpdateManager.java @@ -0,0 +1,88 @@ +package de.danoeh.antennapod; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.File; +import java.util.List; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedImage; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.DBWriter; + +/* + * This class's job is do perform maintenance tasks whenever the app has been updated + */ +public class UpdateManager { + + public static final String TAG = UpdateManager.class.getSimpleName(); + + private static final String PREF_NAME = "app_version"; + private static final String KEY_VERSION_CODE = "version_code"; + + private static int currentVersionCode; + + private static Context context; + private static SharedPreferences prefs; + + public static void init(Context context) { + UpdateManager.context = context; + prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + PackageManager pm = context.getPackageManager(); + try { + PackageInfo info = pm.getPackageInfo(context.getPackageName(), 0); + currentVersionCode = info.versionCode; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to obtain package info for package name: " + context.getPackageName(), e); + currentVersionCode = 0; + return; + } + final int oldVersionCode = getStoredVersionCode(); + Log.d(TAG, "old: " + oldVersionCode + ", current: " + currentVersionCode); + if(oldVersionCode < currentVersionCode) { + onUpgrade(oldVersionCode, currentVersionCode); + setCurrentVersionCode(); + } + } + + public static int getStoredVersionCode() { + return prefs.getInt(KEY_VERSION_CODE, -1); + } + + public static void setCurrentVersionCode() { + prefs.edit().putInt(KEY_VERSION_CODE, currentVersionCode).apply(); + } + + private static void onUpgrade(final int oldVersionCode, final int newVersionCode) { + if(oldVersionCode < 1030099) { + // delete the now obsolete image cache + // from now on, Glide will handle caching images + new Thread() { + public void run() { + List<Feed> feeds = DBReader.getFeedList(context); + for (Feed podcast : feeds) { + List<FeedItem> episodes = DBReader.getFeedItemList(context, podcast); + for (FeedItem episode : episodes) { + FeedImage image = episode.getImage(); + if (image != null && image.isDownloaded() && image.getFile_url() != null) { + File imageFile = new File(image.getFile_url()); + if (imageFile.exists()) { + imageFile.delete(); + } + image.setFile_url(null); // calls setDownloaded(false) + DBWriter.setFeedImage(context, image); + } + } + } + } + }.start(); + } + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index cae29db4b..28301313b 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -27,7 +27,8 @@ import android.widget.ImageButton; import android.widget.ListView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.StringUtils; @@ -556,9 +557,13 @@ public class AudioplayerActivity extends MediaplayerActivity implements ItemDesc } txtvTitle.setText(media.getEpisodeTitle()); getSupportActionBar().setTitle(""); - Picasso.with(this) + Glide.with(this) .load(media.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(butShowCover); setNavButtonVisibility(); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java index 287ae3568..9114fdd1a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java @@ -17,7 +17,8 @@ import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; @@ -124,9 +125,13 @@ public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity { subscribeButton = (Button) header.findViewById(R.id.butSubscribe); if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) { - Picasso.with(this) + Glide.with(this) .load(feed.getImage().getDownload_url()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(cover); } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java index 54171a9a3..4d3a43c56 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -25,8 +25,9 @@ import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.joanzapata.android.iconify.Iconify; -import com.squareup.picasso.Picasso; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; @@ -124,9 +125,13 @@ public class FeedInfoActivity extends ActionBarActivity { @Override public void run() { - Picasso.with(FeedInfoActivity.this) + Glide.with(FeedInfoActivity.this) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(imgvCover); } }); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java index bc3d3abbc..aebcc5e41 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesListAdapter.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -11,7 +13,13 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; + +import java.lang.ref.WeakReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -72,6 +80,7 @@ public class AllEpisodesListAdapter extends BaseAdapter { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.new_episodes_listitem, parent, false); + holder.placeholder = (TextView) convertView.findViewById(R.id.txtvPlaceholder); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); holder.pubDate = (TextView) convertView .findViewById(R.id.txtvPublished); @@ -82,13 +91,15 @@ public class AllEpisodesListAdapter extends BaseAdapter { .findViewById(R.id.imgvInPlaylist); holder.progress = (ProgressBar) convertView .findViewById(R.id.pbar_progress); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); + holder.cover = (ImageView) convertView.findViewById(R.id.imgvCover); holder.txtvDuration = (TextView) convertView.findViewById(R.id.txtvDuration); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); } + holder.placeholder.setVisibility(View.VISIBLE); + holder.placeholder.setText(item.getFeed().getTitle()); holder.title.setText(item.getTitle()); holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); if (showOnlyNewEpisodes || false == item.isNew()) { @@ -141,14 +152,54 @@ public class AllEpisodesListAdapter extends BaseAdapter { holder.butSecondary.setTag(item); holder.butSecondary.setOnClickListener(secondaryActionListener); - Picasso.with(context) + Glide.with(context) .load(item.getImageUri()) - .fit() - .into(holder.imageView); + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover)); return convertView; } + private class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference<Uri> fallback; + private final WeakReference<TextView> placeholder; + private final WeakReference<ImageView> cover; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + Glide.with(context) + .load(fallbackUri) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if(txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } + } + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -159,11 +210,12 @@ public class AllEpisodesListAdapter extends BaseAdapter { static class Holder { + TextView placeholder; TextView title; TextView pubDate; View statusUnread; ImageView queueStatus; - ImageView imageView; + ImageView cover; ProgressBar progress; TextView txtvDuration; ImageButton butSecondary; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java index c34d90a30..c3486f2f2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DefaultActionButtonCallback.java @@ -12,11 +12,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; - import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; @@ -61,7 +58,7 @@ public class DefaultActionButtonCallback implements ActionButtonCallback { boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); if (!isDownloading && !media.isDownloaded()) { LongList queueIds = DBReader.getQueueIDList(context); - if (NetworkUtils.isDownloadAllowed(context) || userAllowedMobileDownloads()) { + if (NetworkUtils.isDownloadAllowed() || userAllowedMobileDownloads()) { try { DBTasks.downloadFeedItems(context, item); Toast.makeText(context, R.string.status_downloading_label, Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 15e0a7a33..0cf761503 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -10,7 +10,8 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -88,9 +89,13 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { holder.butSecondary.setOnClickListener(secondaryActionListener); - Picasso.with(context) + Glide.with(context) .load(item.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(holder.imageView); return convertView; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index 444d08da9..a063ad9b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -15,7 +15,8 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.ArrayUtils; @@ -259,9 +260,13 @@ public class NavListAdapter extends BaseAdapter holder = (FeedHolder) convertView.getTag(); } - Picasso.with(context) + Glide.with(context) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(holder.image); holder.title.setText(feed.getTitle()); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java index 431b645c6..73d7c9252 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueListAdapter.java @@ -1,7 +1,10 @@ package de.danoeh.antennapod.adapter; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.Uri; import android.text.format.DateUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,7 +14,13 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; + +import java.lang.ref.WeakReference; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; @@ -25,6 +34,7 @@ import de.danoeh.antennapod.core.util.Converter; */ public class QueueListAdapter extends BaseAdapter { + private static final String TAG = QueueListAdapter.class.getSimpleName(); private final Context context; private final ItemAccess itemAccess; @@ -76,7 +86,8 @@ public class QueueListAdapter extends BaseAdapter { convertView = inflater.inflate(R.layout.queue_listitem, parent, false); holder.dragHandle = (ImageView) convertView.findViewById(R.id.drag_handle); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); + holder.placeholder = (TextView) convertView.findViewById(R.id.txtvPlaceholder); + holder.cover = (ImageView) convertView.findViewById(R.id.imgvCover); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); holder.pubDate = (TextView) convertView.findViewById(R.id.txtvPubDate); holder.progressLeft = (TextView) convertView.findViewById(R.id.txtvProgressLeft); @@ -86,7 +97,6 @@ public class QueueListAdapter extends BaseAdapter { .findViewById(R.id.butSecondaryAction); holder.progress = (ProgressBar) convertView .findViewById(R.id.progressBar); - holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage); convertView.setTag(holder); } else { holder = (Holder) convertView.getTag(); @@ -98,10 +108,11 @@ public class QueueListAdapter extends BaseAdapter { holder.dragHandle.setVisibility(View.VISIBLE); } + holder.placeholder.setText(item.getFeed().getTitle()); + holder.title.setText(item.getTitle()); FeedMedia media = item.getMedia(); - holder.title.setText(item.getTitle()); String pubDate = DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL); holder.pubDate.setText(pubDate.replace(" ", "\n")); @@ -144,14 +155,56 @@ public class QueueListAdapter extends BaseAdapter { holder.butSecondary.setTag(item); holder.butSecondary.setOnClickListener(secondaryActionListener); - Picasso.with(context) + Log.d(TAG, item.getFeed().getImageUri().toString()); + + Glide.with(context) .load(item.getImageUri()) - .fit() - .into(holder.imageView); + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(item.getFeed().getImageUri(), holder.placeholder, holder.cover)); return convertView; } + private class CoverTarget extends GlideDrawableImageViewTarget { + + private final WeakReference<Uri> fallback; + private final WeakReference<TextView> placeholder; + private final WeakReference<ImageView> cover; + + public CoverTarget(Uri fallbackUri, TextView txtvPlaceholder, ImageView imgvCover) { + super(imgvCover); + fallback = new WeakReference<>(fallbackUri); + placeholder = new WeakReference<>(txtvPlaceholder); + cover = new WeakReference<>(imgvCover); + } + + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + Uri fallbackUri = fallback.get(); + TextView txtvPlaceholder = placeholder.get(); + ImageView imgvCover = cover.get(); + if(fallbackUri != null && txtvPlaceholder != null && imgvCover != null) { + Glide.with(context) + .load(fallbackUri) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() + .into(new CoverTarget(null, txtvPlaceholder, imgvCover)); + } + } + + @Override + public void onResourceReady(GlideDrawable drawable, GlideAnimation anim) { + super.onResourceReady(drawable, anim); + TextView txtvPlaceholder = placeholder.get(); + if(txtvPlaceholder != null) { + txtvPlaceholder.setVisibility(View.INVISIBLE); + } + } + } + private View.OnClickListener secondaryActionListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -160,10 +213,10 @@ public class QueueListAdapter extends BaseAdapter { } }; - static class Holder { ImageView dragHandle; - ImageView imageView; + ImageView cover; + TextView placeholder; TextView title; TextView pubDate; TextView progressLeft; diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java index cedce7903..8c1464e97 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -8,7 +8,8 @@ import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.Feed; @@ -73,9 +74,13 @@ public class SearchlistAdapter extends BaseAdapter { holder.title.setText(feed.getTitle()); holder.subtitle.setVisibility(View.GONE); - Picasso.with(context) + Glide.with(context) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(holder.cover); } else if (component.getClass() == FeedItem.class) { @@ -86,9 +91,13 @@ public class SearchlistAdapter extends BaseAdapter { holder.subtitle.setText(result.getSubtitle()); } - Picasso.with(context) + Glide.with(context) .load(item.getFeed().getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(holder.cover); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java index b85709c5e..b9993b455 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/gpodnet/PodcastListAdapter.java @@ -8,7 +8,8 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.apache.commons.lang3.StringUtils; @@ -49,9 +50,13 @@ public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> { } if (StringUtils.isNotBlank(podcast.getLogoUrl())) { - Picasso.with(convertView.getContext()) + Glide.with(convertView.getContext()) .load(podcast.getLogoUrl()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(holder.image); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index 4fc2838b7..08ffdd197 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -1,23 +1,18 @@ package de.danoeh.antennapod.adapter.itunes; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + import org.json.JSONException; import org.json.JSONObject; -import java.io.IOException; import java.util.List; import de.danoeh.antennapod.R; @@ -46,55 +41,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { this.context = context; } - /** - * Updates the given ImageView with the image in the given Podcast's imageUrl - */ - class FetchImageTask extends AsyncTask<Void,Void,Bitmap>{ - /** - * Current podcast - */ - private final Podcast podcast; - - /** - * ImageView to be updated - */ - private final ImageView imageView; - - /** - * Constructor - * - * @param podcast Podcast that has the image - * @param imageView UI image to be updated - */ - FetchImageTask(Podcast podcast, ImageView imageView){ - this.podcast = podcast; - this.imageView = imageView; - } - - //Get the image from the url - @Override - protected Bitmap doInBackground(Void... params) { - HttpClient client = new DefaultHttpClient(); - HttpGet get = new HttpGet(podcast.imageUrl); - try { - HttpResponse response = client.execute(get); - return BitmapFactory.decodeStream(response.getEntity().getContent()); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - //Set the background image for the podcast - @Override - protected void onPostExecute(Bitmap img) { - super.onPostExecute(img); - if(img!=null) { - imageView.setImageBitmap(img); - } - } - } - @Override public View getView(int position, View convertView, ViewGroup parent) { //Current podcast @@ -121,7 +67,13 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { viewHolder.titleView.setText(podcast.title); //Update the empty imageView with the image from the feed - new FetchImageTask(podcast,viewHolder.coverView).execute(); + Glide.with(context) + .load(podcast.imageUrl) + .placeholder(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .fitCenter() + .dontAnimate() + .into(viewHolder.coverView); //Feed the grid view return view; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 3076f8136..fbd27779d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -10,7 +10,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.R; @@ -80,8 +81,12 @@ public class CoverFragment extends Fragment implements public void run() { Context c = getActivity(); if (c != null) { - Picasso.with(c) + Glide.with(c) .load(media.getImageUri()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .dontAnimate() .into(imgvCover); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 1679051f6..454fe35dd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -11,7 +11,8 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.service.playback.PlaybackService; @@ -197,9 +198,13 @@ public class ExternalPlayerFragment extends Fragment { if (media != null) { txtvTitle.setText(media.getEpisodeTitle()); - Picasso.with(getActivity()) + Glide.with(getActivity()) .load(media.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(imgvCover); fragmentLayout.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index e0ed3d02c..90bf623da 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -35,7 +35,8 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import java.util.List; @@ -336,9 +337,15 @@ public class ItemFragment extends Fragment implements LoaderManager.LoaderCallba txtvTitle.setText(item.getTitle()); txtvPublished.setText(DateUtils.formatDateTime(getActivity(), item.getPubDate().getTime(), DateUtils.FORMAT_ABBREV_ALL)); - Picasso.with(getActivity()).load(item.getImageUri()) - .fit() + Glide.with(getActivity()) + .load(item.getImageUri()) + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(imgvCover); + progbarDownload.setVisibility(View.INVISIBLE); if (item.hasMedia() && downloaderList != null) { for (Downloader downloader : downloaderList) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java index 2a1a7aaed..232dfc214 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -6,6 +6,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Color; +import android.graphics.LightingColorFilter; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -31,9 +32,10 @@ import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.joanzapata.android.iconify.IconDrawable; import com.joanzapata.android.iconify.Iconify; -import com.squareup.picasso.Picasso; import org.apache.commons.lang3.Validate; @@ -46,7 +48,6 @@ import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.core.asynctask.DownloadObserver; import de.danoeh.antennapod.core.asynctask.FeedRemover; -import de.danoeh.antennapod.core.asynctask.PicassoProvider; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.feed.EventDistributor; @@ -56,6 +57,7 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItemFilter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.QueueEvent; +import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; @@ -505,17 +507,26 @@ public class ItemlistFragment extends ListFragment { txtvTitle.setText(feed.getTitle()); txtvAuthor.setText(feed.getAuthor()); - Picasso.with(getActivity()) + + // https://github.com/bumptech/glide/issues/529 + imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); + + Glide.with(getActivity()) .load(feed.getImageUri()) .placeholder(R.color.image_readability_tint) .error(R.color.image_readability_tint) - .transform(PicassoProvider.blurTransformation) - .resize(PicassoProvider.BLUR_IMAGE_SIZE, PicassoProvider.BLUR_IMAGE_SIZE) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .transform(new FastBlurTransformation(getActivity())) + .dontAnimate() .into(imgvBackground); - Picasso.with(getActivity()) + Glide.with(getActivity()) .load(feed.getImageUri()) - .fit() + .placeholder(R.color.light_gray) + .error(R.color.light_gray) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .fitCenter() + .dontAnimate() .into(imgvCover); butShowInfo.setOnClickListener(new View.OnClickListener() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index ba4ababf5..24c9fc425 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -476,7 +476,7 @@ public class QueueFragment extends Fragment { // refresh information bar String info = queue.size() + getString(R.string.episodes_suffix); if(queue.size() > 0) { - int duration = 0; + long duration = 0; for(FeedItem item : queue) { if(item.getMedia() != null) { duration += item.getMedia().getDuration(); diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java index 3588879ae..8621d66be 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -346,6 +346,26 @@ public class PreferenceController { return true; } }); + ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE) + .setOnPreferenceChangeListener( + new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + if (o instanceof String) { + int newValue = Integer.valueOf((String) o) * 1024 * 1024; + if(newValue != UserPreferences.getImageCacheSize()) { + AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity()); + dialog.setTitle(android.R.string.dialog_alert_title); + dialog.setMessage(R.string.pref_restart_required); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.show(); + } + return true; + } + return false; + } + } + ); buildSmartMarkAsPlayedPreference(); buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences @@ -411,7 +431,7 @@ public class PreferenceController { entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled); } else { Integer v = Integer.parseInt(values[x]); - entries[x] = v + " " + res.getString(R.string.time_unit_seconds); + entries[x] = res.getQuantityString(R.plurals.time_unit_seconds, v); } } pref.setEntries(entries); diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index a3a200058..2615ec5c8 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -9,7 +9,6 @@ import android.util.Log; import org.apache.commons.lang3.StringUtils; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.NetworkUtils; @@ -20,13 +19,10 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { if (StringUtils.equals(intent.getAction(), ConnectivityManager.CONNECTIVITY_ACTION)) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); + Log.d(TAG, "Received intent"); - if (NetworkUtils.autodownloadNetworkAvailable(context)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "auto-dl network available, starting auto-download"); + if (NetworkUtils.autodownloadNetworkAvailable()) { + Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, // otherwise cancel all downloads @@ -34,12 +30,9 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = cm.getActiveNetworkInfo(); if (ni == null || ni.getType() != ConnectivityManager.TYPE_WIFI) { - if (BuildConfig.DEBUG) - Log.i(TAG, - "Device is no longer connected to Wi-Fi. Cancelling ongoing downloads"); + Log.i(TAG, "Device is no longer connected to Wi-Fi. Cancelling ongoing downloads"); DownloadRequester.getInstance().cancelAllDownloads(context); } - } } } diff --git a/app/src/main/res/layout/new_episodes_listitem.xml b/app/src/main/res/layout/new_episodes_listitem.xml index ff1318fc8..ec5ab1e6a 100644 --- a/app/src/main/res/layout/new_episodes_listitem.xml +++ b/app/src/main/res/layout/new_episodes_listitem.xml @@ -7,18 +7,36 @@ android:orientation="horizontal" tools:background="@android:color/darker_gray"> - <ImageView - android:id="@+id/imgvImage" - android:layout_width="@dimen/thumbnail_length_itemlist" - android:layout_height="@dimen/thumbnail_length_itemlist" - android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:contentDescription="@string/cover_label" - android:scaleType="centerCrop" - tools:src="@drawable/ic_stat_antenna_default" - tools:background="@android:color/holo_green_dark" /> + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/txtvPlaceholder" + android:layout_width="@dimen/thumbnail_length_itemlist" + android:layout_height="@dimen/thumbnail_length_itemlist" + android:layout_gravity="center_vertical" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginLeft="@dimen/listitem_threeline_horizontalpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:background="@color/light_gray" + android:ellipsize="end" + android:maxLines="3" + android:gravity="center"/> + + <ImageView + android:id="@+id/imgvCover" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_alignLeft="@id/txtvPlaceholder" + android:layout_alignTop="@id/txtvPlaceholder" + android:layout_alignRight="@id/txtvPlaceholder" + android:layout_alignBottom="@id/txtvPlaceholder" + android:contentDescription="@string/cover_label" + tools:src="@drawable/ic_stat_antenna_default" + tools:background="@android:color/holo_green_dark" /> + + </RelativeLayout> <RelativeLayout android:layout_width="0dp" diff --git a/app/src/main/res/layout/queue_listitem.xml b/app/src/main/res/layout/queue_listitem.xml index 39e9c72a5..38076ff51 100644 --- a/app/src/main/res/layout/queue_listitem.xml +++ b/app/src/main/res/layout/queue_listitem.xml @@ -21,17 +21,32 @@ tools:src="@drawable/ic_drag_handle" tools:background="@android:color/holo_green_dark" /> - <ImageView - android:id="@+id/imgvImage" - android:layout_width="@dimen/thumbnail_length_queue_item" - android:layout_height="@dimen/thumbnail_length_queue_item" - android:layout_gravity="center_vertical" - android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" - android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" - android:contentDescription="@string/cover_label" - android:scaleType="centerCrop" - tools:src="@drawable/ic_stat_antenna_default" - tools:background="@android:color/holo_green_dark"/> + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/txtvPlaceholder" + android:layout_width="@dimen/thumbnail_length_queue_item" + android:layout_height="@dimen/thumbnail_length_queue_item" + android:layout_marginBottom="@dimen/listitem_threeline_verticalpadding" + android:layout_marginTop="@dimen/listitem_threeline_verticalpadding" + android:layout_gravity="center_vertical" + android:gravity="center" + android:background="@color/light_gray" + android:maxLines="3" + android:ellipsize="end"/> + <ImageView + android:id="@+id/imgvCover" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignLeft="@id/txtvPlaceholder" + android:layout_alignTop="@id/txtvPlaceholder" + android:layout_alignRight="@id/txtvPlaceholder" + android:layout_alignBottom="@id/txtvPlaceholder" + android:contentDescription="@string/cover_label" + tools:src="@drawable/ic_stat_antenna_default" + tools:background="@android:color/holo_green_dark"/> + </RelativeLayout> <RelativeLayout android:layout_width="0dp" diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index c12ddc816..bfaba604a 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -204,6 +204,13 @@ <Preference android:title="@string/choose_data_directory" android:key="prefChooseDataDir"/> + <ListPreference + android:entryValues="@array/image_cache_size_values" + android:entries="@array/image_cache_size_options" + android:title="@string/pref_image_cache_size_title" + android:key="prefImageCacheSize" + android:summary="@string/pref_image_cache_size_sum" + android:defaultValue="0"/> <Preference android:key="prefOpmlExport" android:title="@string/opml_export_label"/> @@ -211,7 +218,6 @@ android:key="prefAbout" android:title="@string/about_pref"/> - </PreferenceCategory> </PreferenceScreen> diff --git a/app/src/main/templates/about.html b/app/src/main/templates/about.html index 7dd00cd16..2aca7ca4d 100644 --- a/app/src/main/templates/about.html +++ b/app/src/main/templates/about.html @@ -68,8 +68,11 @@ licensed under the Apache 2.0 license <a href="LICENSE_PRESTO.txt">(View)</a> <h2>jsoup <a href="http://jsoup.org/">(Link)</a></h2> licensed under the MIT license <a href="LICENSE_JSOUP.txt">(View)</a> -<h2>Picasso <a href="https://github.com/square/picasso">(Link)</a></h2> -licensed under the Apache 2.0 license <a href="LICENSE_PICASSO.txt">(View)</a> +<h2>Glide <a href="https://github.com/bumptech/glide/">(Link)</a></h2> +licensed under the Simplified BSD license <a href="LICENSE_GLIDE.txt">(View)</a> + +<h2>StackBlur <a href="https://github.com/kikoso/android-stackblur">(Link)</a></h2> +licensed under the Apache 2.0 license <a href="LICENSE_STACKBLUR.txt">(View)</a> <h2>OkHttp <a href="https://github.com/square/okhttp">(Link)</a></h2> licensed under the Apache 2.0 license <a href="LICENSE_OKHTTP.txt">(View)</a> diff --git a/core/build.gradle b/core/build.gradle index ae2c11070..5e721bbc3 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -42,7 +42,8 @@ dependencies { compile 'commons-io:commons-io:2.4' compile 'com.jayway.android.robotium:robotium-solo:5.2.1' compile 'org.jsoup:jsoup:1.7.3' - compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.github.bumptech.glide:glide:3.6.1' + compile 'com.github.bumptech.glide:okhttp-integration:1.3.0' compile 'com.squareup.okhttp:okhttp:2.3.0' compile 'com.squareup.okhttp:okhttp-urlconnection:2.3.0' compile 'com.squareup.okio:okio:1.2.0' diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java index 7429b50b3..2727b1447 100644 --- a/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java +++ b/core/src/androidTest/java/de/danoeh/antennapod/core/tests/util/DateUtilsTest.java @@ -13,6 +13,7 @@ public class DateUtilsTest extends AndroidTestCase { public void testParseDateWithMicroseconds() throws Exception { GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4); + exp.setTimeZone(TimeZone.getTimeZone("UTC")); Date expected = new Date(exp.getTimeInMillis() + 963); Date actual = DateUtils.parse("2015-03-28T13:31:04.963870"); assertEquals(expected, actual); @@ -20,6 +21,7 @@ public class DateUtilsTest extends AndroidTestCase { public void testParseDateWithCentiseconds() throws Exception { GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4); + exp.setTimeZone(TimeZone.getTimeZone("UTC")); Date expected = new Date(exp.getTimeInMillis() + 960); Date actual = DateUtils.parse("2015-03-28T13:31:04.96"); assertEquals(expected, actual); @@ -27,6 +29,7 @@ public class DateUtilsTest extends AndroidTestCase { public void testParseDateWithDeciseconds() throws Exception { GregorianCalendar exp = new GregorianCalendar(2015, 2, 28, 13, 31, 4); + exp.setTimeZone(TimeZone.getTimeZone("UTC")); Date expected = new Date(exp.getTimeInMillis() + 900); Date actual = DateUtils.parse("2015-03-28T13:31:04.9"); assertEquals(expected.getTime()/1000, actual.getTime()/1000); @@ -82,4 +85,20 @@ public class DateUtilsTest extends AndroidTestCase { assertEquals(expected, actual); } + public void testAsctime() throws Exception { + GregorianCalendar exp = new GregorianCalendar(2011, 4, 25, 12, 33, 00); + exp.setTimeZone(TimeZone.getTimeZone("UTC")); + Date expected = new Date(exp.getTimeInMillis()); + Date actual = DateUtils.parse("Wed, 25 May 2011 12:33:00"); + assertEquals(expected, actual); + } + + public void testMultipleConsecutiveSpaces() throws Exception { + GregorianCalendar exp = new GregorianCalendar(2010, 2, 23, 6, 6, 26); + exp.setTimeZone(TimeZone.getTimeZone("UTC")); + Date expected = new Date(exp.getTimeInMillis()); + Date actual = DateUtils.parse("Tue, 23 Mar 2010 01:06:26 -0500"); + assertEquals(expected, actual); + } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java index 5d2d5d441..f91d4557e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/FlattrClickWorker.java @@ -90,7 +90,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke return ExitCode.NO_TOKEN; } - if (!NetworkUtils.networkAvailable(context)) { + if (!NetworkUtils.networkAvailable()) { return ExitCode.NO_NETWORK; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java index c0d8049db..edd69f15b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoImageResource.java +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/ImageResource.java @@ -6,7 +6,7 @@ import android.net.Uri; * Classes that implement this interface provide access to an image resource that can * be loaded by the Picasso library. */ -public interface PicassoImageResource { +public interface ImageResource { /** * This scheme should be used by PicassoImageResources to diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java deleted file mode 100644 index 8e47a5b71..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java +++ /dev/null @@ -1,533 +0,0 @@ -package de.danoeh.antennapod.core.asynctask; - -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.MediaMetadataRetriever; -import android.net.Uri; -import android.text.TextUtils; -import android.util.Log; - -import com.squareup.okhttp.Interceptor; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Response; -import com.squareup.picasso.Cache; -import com.squareup.picasso.LruCache; -import com.squareup.picasso.OkHttpDownloader; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Request; -import com.squareup.picasso.RequestHandler; -import com.squareup.picasso.Transformation; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import de.danoeh.antennapod.core.service.download.HttpDownloader; -import de.danoeh.antennapod.core.storage.DBReader; - -/** - * Provides access to Picasso instances. - */ -public class PicassoProvider { - - private static final String TAG = "PicassoProvider"; - - private static final boolean DEBUG = false; - - private static ExecutorService executorService; - private static Cache memoryCache; - - private static synchronized ExecutorService getExecutorService() { - if (executorService == null) { - executorService = Executors.newFixedThreadPool(3); - } - return executorService; - } - - private static synchronized Cache getMemoryCache(Context context) { - if (memoryCache == null) { - memoryCache = new LruCache(context); - } - return memoryCache; - } - - private static volatile boolean picassoSetup = false; - - public static synchronized void setupPicassoInstance(Context appContext) { - if (picassoSetup) { - return; - } - OkHttpClient client = new OkHttpClient(); - client.interceptors().add(new BasicAuthenticationInterceptor(appContext)); - Picasso picasso = new Picasso.Builder(appContext) - .indicatorsEnabled(DEBUG) - .loggingEnabled(DEBUG) - .downloader(new OkHttpDownloader(client)) - .addRequestHandler(new MediaRequestHandler(appContext)) - .executor(getExecutorService()) - .memoryCache(getMemoryCache(appContext)) - .listener(new Picasso.Listener() { - @Override - public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) { - Log.e(TAG, "Failed to load Uri:" + uri.toString()); - e.printStackTrace(); - } - }) - .build(); - Picasso.setSingletonInstance(picasso); - picassoSetup = true; - } - - private static class BasicAuthenticationInterceptor implements Interceptor { - - private final Context context; - - public BasicAuthenticationInterceptor(Context context) { - this.context = context; - } - - @Override - public Response intercept(Chain chain) throws IOException { - com.squareup.okhttp.Request request = chain.request(); - String url = request.urlString(); - String authentication = DBReader.getImageAuthentication(context, url); - - if(TextUtils.isEmpty(authentication)) { - Log.d(TAG, "no credentials for '" + url + "'"); - return chain.proceed(request); - } - - // add authentication - String[] auth = authentication.split(":"); - String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1"); - com.squareup.okhttp.Request newRequest = request - .newBuilder() - .addHeader("Authorization", credentials) - .build(); - Log.d(TAG, "Basic authentication with ISO-8859-1 encoding"); - Response response = chain.proceed(newRequest); - if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { - credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8"); - newRequest = request - .newBuilder() - .addHeader("Authorization", credentials) - .build(); - Log.d(TAG, "Basic authentication with UTF-8 encoding"); - return chain.proceed(newRequest); - } else { - return response; - } - } - } - - private static class MediaRequestHandler extends RequestHandler { - - final Context context; - - public MediaRequestHandler(Context context) { - super(); - this.context = context; - } - - @Override - public boolean canHandleRequest(Request data) { - return StringUtils.equals(data.uri.getScheme(), PicassoImageResource.SCHEME_MEDIA); - } - - @Override - public Result load(Request data, int networkPolicy) throws IOException { - Bitmap bitmap = null; - MediaMetadataRetriever mmr = null; - try { - mmr = new MediaMetadataRetriever(); - mmr.setDataSource(data.uri.getPath()); - byte[] image = mmr.getEmbeddedPicture(); - if (image != null) { - bitmap = decodeStreamFromByteArray(data, image); - } - } catch (RuntimeException e) { - Log.e(TAG, "Failed to decode image in media file", e); - } finally { - if (mmr != null) { - mmr.release(); - } - } - - if (bitmap == null) { - // this should never, happen, but sometimes it does, so fallback - // check for fallback Uri - String fallbackParam = data.uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK); - if (fallbackParam != null) { - Uri fallback = Uri.parse(fallbackParam); - bitmap = decodeStreamFromFile(data, fallback); - } - } - - if (bitmap == null) { - Log.e(TAG, "Could not load media"); - return null; - } - - return new Result(bitmap, Picasso.LoadedFrom.DISK); - - } - - /* Copied/Adapted from Picasso RequestHandler classes */ - - private Bitmap decodeStreamFromByteArray(Request data, byte[] bytes) throws IOException { - - final BitmapFactory.Options options = createBitmapOptions(data); - final ByteArrayInputStream in = new ByteArrayInputStream(bytes); - in.mark(0); - if (requiresInSampleSize(options)) { - try { - BitmapFactory.decodeStream(in, null, options); - } finally { - in.reset(); - } - calculateInSampleSize(data.targetWidth, data.targetHeight, options, data); - } - try { - return BitmapFactory.decodeStream(in, null, options); - } finally { - IOUtils.closeQuietly(in); - } - } - - private Bitmap decodeStreamFromFile(Request data, Uri uri) throws IOException { - ContentResolver contentResolver = context.getContentResolver(); - final BitmapFactory.Options options = createBitmapOptions(data); - if (requiresInSampleSize(options)) { - InputStream is = null; - try { - is = contentResolver.openInputStream(uri); - BitmapFactory.decodeStream(is, null, options); - } finally { - IOUtils.closeQuietly(is); - } - calculateInSampleSize(data.targetWidth, data.targetHeight, options, data); - } - InputStream is = contentResolver.openInputStream(uri); - try { - return BitmapFactory.decodeStream(is, null, options); - } finally { - IOUtils.closeQuietly(is); - } - } - - private BitmapFactory.Options createBitmapOptions(Request data) { - final boolean justBounds = data.hasSize(); - final boolean hasConfig = data.config != null; - BitmapFactory.Options options = null; - if (justBounds || hasConfig) { - options = new BitmapFactory.Options(); - options.inJustDecodeBounds = justBounds; - if (hasConfig) { - options.inPreferredConfig = data.config; - } - } - return options; - } - - private static boolean requiresInSampleSize(BitmapFactory.Options options) { - return options != null && options.inJustDecodeBounds; - } - - private static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options, - Request request) { - calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options, - request); - } - - private static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height, - BitmapFactory.Options options, Request request) { - int sampleSize = 1; - if (height > reqHeight || width > reqWidth) { - final int heightRatio; - final int widthRatio; - if (reqHeight == 0) { - sampleSize = (int) Math.floor((float) width / (float) reqWidth); - } else if (reqWidth == 0) { - sampleSize = (int) Math.floor((float) height / (float) reqHeight); - } else { - heightRatio = (int) Math.floor((float) height / (float) reqHeight); - widthRatio = (int) Math.floor((float) width / (float) reqWidth); - sampleSize = request.centerInside - ? Math.max(heightRatio, widthRatio) - : Math.min(heightRatio, widthRatio); - } - } - options.inSampleSize = sampleSize; - options.inJustDecodeBounds = false; - } - } - - public static final int BLUR_RADIUS = 1; - public static final int BLUR_IMAGE_SIZE = 100; - public static final String BLUR_KEY = "blur"; - - public static final Transformation blurTransformation = new Transformation() { - @Override - public Bitmap transform(Bitmap source) { - Bitmap result = fastblur(source, BLUR_RADIUS); - if (result == null) { - // just return the original - // for some reason we couldn't transform it. - return source; - } - source.recycle(); - return result; - } - - @Override - public String key() { - return BLUR_KEY; - } - }; - - public static Bitmap fastblur(Bitmap sentBitmap, int radius) { - - // Stack Blur v1.0 from - // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html - // - // Java Author: Mario Klingemann <mario at quasimondo.com> - // http://incubator.quasimondo.com - // created Feburary 29, 2004 - // Android port : Yahel Bouaziz <yahel at kayenko.com> - // http://www.kayenko.com - // ported april 5th, 2012 - - // This is a compromise between Gaussian Blur and Box blur - // It creates much better looking blurs than Box Blur, but is - // 7x faster than my Gaussian Blur implementation. - // - // I called it Stack Blur because this describes best how this - // filter works internally: it creates a kind of moving stack - // of colors whilst scanning through the image. Thereby it - // just has to add one new block of color to the right side - // of the stack and remove the leftmost color. The remaining - // colors on the topmost layer of the stack are either added on - // or reduced by one, depending on if they are on the right or - // on the left side of the stack. - // - // If you are using this algorithm in your code please add - // the following line: - // - // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com> - Bitmap.Config config = sentBitmap.getConfig(); - if (config == null) { - // Sometimes the config can be null, in those cases - // we don't do a transform. - return null; - } - - Bitmap bitmap = sentBitmap.copy(config, true); - - if (radius < 1) { - return (null); - } - - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - - int[] pix = new int[w * h]; - Log.e("pix", w + " " + h + " " + pix.length); - bitmap.getPixels(pix, 0, w, 0, 0, w, h); - - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; - - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; - - int divsum = (div + 1) >> 1; - divsum *= divsum; - int dv[] = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); - } - - yw = yi = 0; - - int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } - } - - Log.e("pix", w + " " + h + " " + pix.length); - bitmap.setPixels(pix, 0, w, 0, 0, w, h); - - return (bitmap); - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java index f6322d6f4..2a483ca9b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; +import de.danoeh.antennapod.core.asynctask.ImageResource; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.flattr.FlattrStatus; import de.danoeh.antennapod.core.util.flattr.FlattrThing; @@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrThing; * * @author daniel */ -public class Feed extends FeedFile implements FlattrThing, PicassoImageResource { +public class Feed extends FeedFile implements FlattrThing, ImageResource { public static final int FEEDFILETYPE_FEED = 0; public static final String TYPE_RSS2 = "rss"; public static final String TYPE_RSS091 = "rss"; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java index c6f24367e..f77a78721 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedImage.java @@ -4,10 +4,10 @@ import android.net.Uri; import java.io.File; -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; +import de.danoeh.antennapod.core.asynctask.ImageResource; -public class FeedImage extends FeedFile implements PicassoImageResource { +public class FeedImage extends FeedFile implements ImageResource { public static final int FEEDFILETYPE_FEEDIMAGE = 1; protected String title; diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java index 12b9a7db5..9229172f0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java @@ -10,7 +10,7 @@ import java.util.List; import java.util.concurrent.Callable; import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; +import de.danoeh.antennapod.core.asynctask.ImageResource; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.ShownotesProvider; import de.danoeh.antennapod.core.util.flattr.FlattrStatus; @@ -21,7 +21,7 @@ import de.danoeh.antennapod.core.util.flattr.FlattrThing; * * @author daniel */ -public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, PicassoImageResource { +public class FeedItem extends FeedComponent implements ShownotesProvider, FlattrThing, ImageResource { /** * The id/guid that can be found in the rss/atom feed. Might not be set. @@ -333,7 +333,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr public Uri getImageUri() { if(media != null && media.hasEmbeddedPicture()) { return media.getImageUri(); - } else if (hasItemImageDownloaded()) { + } else if (image != null) { return image.getImageUri(); } else if (feed != null) { return feed.getImageUri(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java new file mode 100644 index 000000000..0baff9723 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java @@ -0,0 +1,33 @@ +package de.danoeh.antennapod.core.glide; + +import android.content.Context; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.load.DecodeFormat; +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.GlideModule; + +import java.io.InputStream; + +import de.danoeh.antennapod.core.preferences.UserPreferences; + +/** + * {@see com.bumptech.glide.integration.okhttp.OkHttpGlideModule} + */ +public class ApGlideModule implements GlideModule { + + @Override + public void applyOptions(Context context, GlideBuilder builder) { + builder.setDecodeFormat(DecodeFormat.PREFER_ARGB_8888); + builder.setDiskCache(new InternalCacheDiskCacheFactory(context, + UserPreferences.getImageCacheSize())); + } + + @Override + public void registerComponents(Context context, Glide glide) { + glide.register(GlideUrl.class, InputStream.class, new ApOkHttpUrlLoader.Factory()); + } + +}
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java new file mode 100644 index 000000000..433ba8db4 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApOkHttpUrlLoader.java @@ -0,0 +1,140 @@ +package de.danoeh.antennapod.core.glide; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import com.bumptech.glide.integration.okhttp.OkHttpStreamFetcher; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.GenericLoaderFactory; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.service.download.HttpDownloader; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.util.NetworkUtils; + +/** + * @see com.bumptech.glide.integration.okhttp.OkHttpUrlLoader + */ +public class ApOkHttpUrlLoader implements ModelLoader<GlideUrl, InputStream> { + + private static final String TAG = ApOkHttpUrlLoader.class.getSimpleName(); + + /** + * The default factory for {@link ApOkHttpUrlLoader}s. + */ + public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> { + + private static volatile OkHttpClient internalClient; + private OkHttpClient client; + + private static OkHttpClient getInternalClient() { + if (internalClient == null) { + synchronized (Factory.class) { + if (internalClient == null) { + internalClient = new OkHttpClient(); + internalClient.interceptors().add(new NetworkAllowanceInterceptor()); + internalClient.interceptors().add(new BasicAuthenticationInterceptor()); + } + } + } + return internalClient; + } + + /** + * Constructor for a new Factory that runs requests using a static singleton client. + */ + public Factory() { + this(getInternalClient()); + } + + /** + * Constructor for a new Factory that runs requests using given client. + */ + public Factory(OkHttpClient client) { + this.client = client; + } + + @Override + public ModelLoader<GlideUrl, InputStream> build(Context context, GenericLoaderFactory factories) { + return new ApOkHttpUrlLoader(client); + } + + @Override + public void teardown() { + // Do nothing, this instance doesn't own the client. + } + } + + private final OkHttpClient client; + + public ApOkHttpUrlLoader(OkHttpClient client) { + this.client = client; + } + + @Override + public DataFetcher<InputStream> getResourceFetcher(GlideUrl model, int width, int height) { + return new OkHttpStreamFetcher(client, model); + } + + private static class NetworkAllowanceInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + if (NetworkUtils.isDownloadAllowed()) { + return chain.proceed(chain.request()); + } else { + return null; + } + } + + } + + private static class BasicAuthenticationInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + com.squareup.okhttp.Request request = chain.request(); + String url = request.urlString(); + Context context = ClientConfig.applicationCallbacks.getApplicationInstance(); + String authentication = DBReader.getImageAuthentication(context, url); + + if(TextUtils.isEmpty(authentication)) { + Log.d(TAG, "no credentials for '" + url + "'"); + return chain.proceed(request); + } + + // add authentication + String[] auth = authentication.split(":"); + String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1"); + com.squareup.okhttp.Request newRequest = request + .newBuilder() + .addHeader("Authorization", credentials) + .build(); + Log.d(TAG, "Basic authentication with ISO-8859-1 encoding"); + Response response = chain.proceed(newRequest); + if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8"); + newRequest = request + .newBuilder() + .addHeader("Authorization", credentials) + .build(); + Log.d(TAG, "Basic authentication with UTF-8 encoding"); + return chain.proceed(newRequest); + } else { + return response; + } + } + } + +}
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java new file mode 100644 index 000000000..ee58c2f39 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java @@ -0,0 +1,267 @@ +package de.danoeh.antennapod.core.glide; + +import android.content.Context; +import android.graphics.Bitmap; +import android.media.ThumbnailUtils; +import android.util.Log; + +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; + +public class FastBlurTransformation extends BitmapTransformation { + + private static final String TAG = FastBlurTransformation.class.getSimpleName(); + + private static final int STACK_BLUR_RADIUS = 1; + private static final int BLUR_IMAGE_WIDTH = 150; + + public FastBlurTransformation(Context context) { + super(context); + } + + @Override + protected Bitmap transform(BitmapPool pool, Bitmap source, + int outWidth, int outHeight) { + int targetWidth = BLUR_IMAGE_WIDTH; + int targetHeight = (int) (1.0 * outHeight * targetWidth / outWidth); + Bitmap resized = ThumbnailUtils.extractThumbnail(source, targetWidth, targetHeight); + Bitmap result = fastBlur(resized, STACK_BLUR_RADIUS); + if (result == null) { + Log.w(TAG, "result was null"); + return source; + } + return result; + } + + @Override + public String getId() { + return "FastBlurTransformation[width=" + BLUR_IMAGE_WIDTH + "px,radius=" + STACK_BLUR_RADIUS +"]"; + } + + private static Bitmap fastBlur(Bitmap bitmap, int radius) { + + // Stack Blur v1.0 from + // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html + // + // Java Author: Mario Klingemann <mario at quasimondo.com> + // http://incubator.quasimondo.com + // created Feburary 29, 2004 + // Android port : Yahel Bouaziz <yahel at kayenko.com> + // http://www.kayenko.com + // ported april 5th, 2012 + + // This is a compromise between Gaussian Blur and Box blur + // It creates much better looking blurs than Box Blur, but is + // 7x faster than my Gaussian Blur implementation. + // + // I called it Stack Blur because this describes best how this + // filter works internally: it creates a kind of moving stack + // of colors whilst scanning through the image. Thereby it + // just has to add one new block of color to the right side + // of the stack and remove the leftmost color. The remaining + // colors on the topmost layer of the stack are either added on + // or reduced by one, depending on if they are on the right or + // on the left side of the stack. + // + // If you are using this algorithm in your code please add + // the following line: + // + // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com> + + if (radius < 1) { + return null; + } + + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + int[] pix = new int[w * h]; + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int dv[] = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < h; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < w; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += w; + } + for (x = 0; x < w; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * w; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += w; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < h; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * w; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += w; + } + } + bitmap.setPixels(pix, 0, w, 0, 0, w, h); + return bitmap; + } + +}
\ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 9f6b144ef..789a2366e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -78,6 +78,7 @@ public class UserPreferences { // Other public static final String PREF_DATA_FOLDER = "prefDataFolder"; + public static final String PREF_IMAGE_CACHE_SIZE = "prefImageCacheSize"; // Mediaplayer public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed"; @@ -213,7 +214,6 @@ public class UserPreferences { public static String getPlaybackSpeed() { return prefs.getString(PREF_PLAYBACK_SPEED, "1.0"); - } public static String[] getPlaybackSpeedArray() { @@ -278,6 +278,12 @@ public class UserPreferences { return prefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false); } + public static int getImageCacheSize() { + String cacheSizeString = prefs.getString(PREF_IMAGE_CACHE_SIZE, "100"); + int cacheSize = Integer.valueOf(cacheSizeString) * 1024 * 1024; + return cacheSize; + } + public static int getFastFowardSecs() { return prefs.getInt(PREF_FAST_FORWARD_SECS, 30); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java index 0045f1eb0..b959c7301 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java @@ -19,7 +19,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "Received intent"); - if (NetworkUtils.isDownloadAllowed(context)) { + if (NetworkUtils.isDownloadAllowed()) { DBTasks.refreshAllFeeds(context, null); } else { Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed"); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/FeedMediaSizeService.java b/core/src/main/java/de/danoeh/antennapod/core/service/FeedMediaSizeService.java index 44fb14f0c..2f6e67a28 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/FeedMediaSizeService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/FeedMediaSizeService.java @@ -28,13 +28,13 @@ public class FeedMediaSizeService extends IntentService { @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "onHandleIntent()"); - if(false == NetworkUtils.isDownloadAllowed(this)) { + if(false == NetworkUtils.isDownloadAllowed()) { return; } List<FeedMedia> list = DBReader.getFeedMediaUnknownSize(this); for (FeedMedia media : list) { Log.d(TAG, "Getting size currently " + media.getSize() + " for " + media.getDownload_url()); - if(false == NetworkUtils.isDownloadAllowed(this)) { + if(false == NetworkUtils.isDownloadAllowed()) { return; } long size = Integer.MIN_VALUE; diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java index 3f2222f42..b80b4303f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java @@ -108,7 +108,7 @@ public class GpodnetSyncService extends Service { private synchronized void sync() { - if (GpodnetPreferences.loggedIn() == false || NetworkUtils.networkAvailable(this) == false) { + if (GpodnetPreferences.loggedIn() == false || NetworkUtils.networkAvailable() == false) { stopSelf(); return; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index 3df305027..19a710d24 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -777,52 +777,18 @@ public class DownloadService extends Service { for (int i = 0; i < savedFeeds.length; i++) { Feed savedFeed = savedFeeds[i]; - // Download Feed Image if provided and not downloaded - if (savedFeed.getImage() != null - && savedFeed.getImage().isDownloaded() == false) { - Log.d(TAG, "Feed has image; Downloading...."); - savedFeed.getImage().setOwner(savedFeed); - final Feed savedFeedRef = savedFeed; - try { - requester.downloadImage(DownloadService.this, - savedFeedRef.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - DownloadService.this, - new DownloadStatus( - savedFeedRef.getImage(), - savedFeedRef - .getImage() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage() - ) - ); - } - } // queue new media files for automatic download for (FeedItem item : savedFeed.getItems()) { if(item.getPubDate() == null) { Log.d(TAG, item.toString()); } - if(item.getImage() != null && item.getImage().isDownloaded() == false) { - item.getImage().setOwner(item); - try { - requester.downloadImage(DownloadService.this, - item.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - } - } if (!item.isPlayed() && item.hasMedia() && !item.getMedia().isDownloaded()) { newMediaFiles.add(item.getMedia().getId()); } } // If loadAllPages=true, check if another page is available and queue it for download - final boolean loadAllPages = results.get(i).first.getArguments().getBoolean(DownloadRequester.REQUEST_ARG_LOAD_ALL_PAGES); final Feed feed = results.get(i).second.feed; if (loadAllPages && feed.getNextPageLink() != null) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 737f0aefd..aa1816e1c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -29,12 +29,16 @@ import android.view.KeyEvent; import android.view.SurfaceHolder; import android.widget.Toast; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.List; +import java.util.concurrent.ExecutionException; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; @@ -769,7 +773,7 @@ public class PlaybackService extends Service { /** * Used by setupNotification to load notification data in another thread. */ - private AsyncTask<Void, Void, Void> notificationSetupTask; + private Thread notificationSetupThread; /** * Prepares notification and starts the service in the foreground. @@ -780,50 +784,47 @@ public class PlaybackService extends Service { PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT); - if (notificationSetupTask != null) { - notificationSetupTask.cancel(true); + if (notificationSetupThread != null) { + notificationSetupThread.interrupt(); } - notificationSetupTask = new AsyncTask<Void, Void, Void>() { + Runnable notificationSetupTask = new Runnable() { Bitmap icon = null; @Override - protected Void doInBackground(Void... params) { + public void run() { Log.d(TAG, "Starting background work"); if (android.os.Build.VERSION.SDK_INT >= 11) { if (info.playable != null) { + int iconSize = getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); try { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - icon = Picasso.with(PlaybackService.this) + icon = Glide.with(PlaybackService.this) .load(info.playable.getImageUri()) - .resize(iconSize, iconSize) + .asBitmap() + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .into(-1, -1) // this resizing would not be exact, so we have + // scale the bitmap ourselves .get(); - } catch (IOException e) { + icon = Bitmap.createScaledBitmap(icon, iconSize, iconSize, true); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { e.printStackTrace(); } } - } if (icon == null) { icon = BitmapFactory.decodeResource(getApplicationContext().getResources(), ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext())); } - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); if (mediaPlayer == null) { return; } PlayerStatus playerStatus = mediaPlayer.getPlayerStatus(); final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()); - if (!isCancelled() && - started && - info.playable != null) { + if (!Thread.currentThread().isInterrupted() && started && info.playable != null) { String contentText = info.playable.getFeedTitle(); String contentTitle = info.playable.getEpisodeTitle(); Notification notification = null; @@ -901,15 +902,9 @@ public class PlaybackService extends Service { Log.d(TAG, "Notification set up"); } } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - notificationSetupTask - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - notificationSetupTask.execute(); - } - + notificationSetupThread = new Thread(notificationSetupTask); + notificationSetupThread.start(); } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java index 92de1eee7..0e6b23696 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java @@ -40,7 +40,7 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm { public void run() { // true if we should auto download based on network status - boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable(context) + boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable() && UserPreferences.isEnableAutodownload(); // true if we should auto download based on power status diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index cfbfd152b..5ed0a6ee3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -871,7 +871,11 @@ public final class DBReader { if (cursor.moveToFirst()) { String username = cursor.getString(0); String password = cursor.getString(1); - return username + ":" + password; + if(username != null && password != null) { + return username + ":" + password; + } else { + return ""; + } } return ""; } finally { diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index b08013c65..770c4f7d7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -284,24 +284,6 @@ public final class DBTasks { } /** - * Notifies the database about a missing FeedImage file. This method will attempt to re-download the file. - * - * @param context Used for requesting the download. - * @param image The FeedImage object. - */ - public static void notifyInvalidImageFile(final Context context, - final FeedImage image) { - Log.i(TAG, - "The DB was notified about an invalid image download. It will now try to re-download the image file"); - try { - DownloadRequester.getInstance().downloadImage(context, image); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Log.w(TAG, "Failed to download invalid feed image"); - } - } - - /** * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the * DB and send a FeedUpdateBroadcast. */ diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java index ca6aa0178..74dce17f4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java @@ -18,7 +18,6 @@ import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedFile; -import de.danoeh.antennapod.core.feed.FeedImage; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadRequest; @@ -186,15 +185,6 @@ public class DownloadRequester { downloadFeed(context, feed, false); } - public synchronized void downloadImage(Context context, FeedImage image) - throws DownloadRequestException { - if (feedFileValid(image)) { - FeedFile container = (image.getOwner() instanceof FeedFile) ? (FeedFile) image.getOwner() : null; - download(context, image, container, new File(getImagefilePath(context), - getImagefileName(image)), false, null, null, 0, false, null); - } - } - public synchronized void downloadMedia(Context context, FeedMedia feedmedia) throws DownloadRequestException { if (feedFileValid(feedmedia)) { @@ -332,20 +322,6 @@ public class DownloadRequester { return "feed-" + FileNameGenerator.generateFileName(filename); } - public synchronized String getImagefilePath(Context context) - throws DownloadRequestException { - return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH) - .toString() + "/"; - } - - public synchronized String getImagefileName(FeedImage image) { - String filename = image.getDownload_url(); - if (image.getOwner() != null && image.getOwner().getHumanReadableIdentifier() != null) { - filename = image.getOwner().getHumanReadableIdentifier(); - } - return "image-" + FileNameGenerator.generateFileName(filename); - } - public synchronized String getMediafilePath(Context context, FeedMedia media) throws DownloadRequestException { File externalStorage = getExternalFilesDirOrThrowException( diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java index 917f99564..2bf88b3f3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Converter.java @@ -26,7 +26,7 @@ public final class Converter { /** Determines the length of the number for best readability.*/ private static final int NUM_LENGTH = 1024; - + private static final int DAYS_MIL = 86400000; private static final int HOURS_MIL = 3600000; private static final int MINUTES_MIL = 60000; private static final int SECONDS_MIL = 1000; @@ -104,18 +104,18 @@ public final class Converter { } /** Converts milliseconds to a localized string containing hours and minutes */ - public static String getDurationStringLocalized(Context context, int duration) { - int h = duration / HOURS_MIL; - int rest = duration - h * HOURS_MIL; + public static String getDurationStringLocalized(Context context, long duration) { + int h = (int)(duration / HOURS_MIL); + int rest = (int)(duration - h * HOURS_MIL); int m = rest / MINUTES_MIL; String result = ""; if(h > 0) { - String hours = context.getString(R.string.time_unit_hours); - result += h + " " + hours + " "; + String hours = context.getResources().getQuantityString(R.plurals.time_unit_hours, h, h); + result += hours + " "; } - String minutes = context.getString(R.string.time_unit_minutes); - result += m + " " + minutes; + String minutes = context.getResources().getQuantityString(R.plurals.time_unit_minutes, m, m); + result += minutes; return result; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java index 7b06128f9..c0233f684 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java @@ -8,6 +8,7 @@ import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import java.util.TimeZone; /** * Parses several date formats. @@ -17,50 +18,66 @@ public class DateUtils { private static final String TAG = "DateUtils"; private static final SimpleDateFormat parser = new SimpleDateFormat("", Locale.US); + private static final TimeZone defaultTimezone = TimeZone.getTimeZone("GMT"); + static { parser.setLenient(false); + parser.setTimeZone(defaultTimezone); } public static Date parse(final String input) { if(input == null) { - throw new IllegalArgumentException("Date most not be null"); + throw new IllegalArgumentException("Date must not be null"); } - String date = input.replace('/', '-'); + String date = input.trim().replace('/', '-').replaceAll("( ){2,}+", " "); + + // if datetime is more precise than seconds, make sure the value is in ms if(date.contains(".")) { int start = date.indexOf('.'); int current = start+1; while(current < date.length() && Character.isDigit(date.charAt(current))) { current++; } + // even more precise than microseconds: discard further decimal places if(current - start > 4) { if(current < date.length()-1) { date = date.substring(0, start + 4) + date.substring(current); } else { date = date.substring(0, start + 4); } + // less than 4 decimal places: pad to have a consistent format for the parser } else if(current - start < 4) { if(current < date.length()-1) { date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start)) + date.substring(current); } else { date = date.substring(0, current) + StringUtils.repeat("0", 4-(current-start)); } - } } String[] patterns = { "dd MMM yy HH:mm:ss Z", "dd MMM yy HH:mm Z", "EEE, dd MMM yyyy HH:mm:ss Z", + "EEE, dd MMM yyyy HH:mm:ss", "EEE, dd MMMM yyyy HH:mm:ss Z", + "EEE, dd MMMM yyyy HH:mm:ss", + "EEEE, dd MMM yyyy HH:mm:ss Z", "EEEE, dd MMM yy HH:mm:ss Z", + "EEEE, dd MMM yyyy HH:mm:ss", + "EEEE, dd MMM yy HH:mm:ss", "EEE MMM d HH:mm:ss yyyy", "EEE, dd MMM yyyy HH:mm Z", + "EEE, dd MMM yyyy HH:mm", "EEE, dd MMMM yyyy HH:mm Z", + "EEE, dd MMMM yyyy HH:mm", + "EEEE, dd MMM yyyy HH:mm Z", "EEEE, dd MMM yy HH:mm Z", + "EEEE, dd MMM yyyy HH:mm", + "EEEE, dd MMM yy HH:mm", "EEE MMM d HH:mm yyyy", "yyyy-MM-dd'T'HH:mm:ss", - "yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss.SSS Z", + "yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-ddZ", @@ -77,7 +94,7 @@ public class DateUtils { } } - Log.d(TAG, "Could not parse date string \"" + input + "\""); + Log.d(TAG, "Could not parse date string \"" + input + "\" [" + date + "]"); return null; } @@ -117,6 +134,7 @@ public class DateUtils { public static String formatRFC3339UTC(Date date) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + format.setTimeZone(defaultTimezone); return format.format(date); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java index 3a349e221..9296039f0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java @@ -10,14 +10,16 @@ import android.util.Log; import java.util.Arrays; import java.util.List; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.preferences.UserPreferences; public class NetworkUtils { - private static final String TAG = "NetworkUtils"; - private NetworkUtils() { + private static final String TAG = NetworkUtils.class.getSimpleName(); + private static Context context; + + public static void init(Context context) { + NetworkUtils.context = context; } /** @@ -26,18 +28,16 @@ public class NetworkUtils { * network that is on the 'selected networks' list of the Wi-Fi filter for * automatic downloads and false otherwise. * */ - public static boolean autodownloadNetworkAvailable(Context context) { + public static boolean autodownloadNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null) { if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Device is connected to Wi-Fi"); + Log.d(TAG, "Device is connected to Wi-Fi"); if (networkInfo.isConnected()) { if (!UserPreferences.isEnableAutodownloadWifiFilter()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Auto-dl filter is disabled"); + Log.d(TAG, "Auto-dl filter is disabled"); return true; } else { WifiManager wm = (WifiManager) context @@ -48,31 +48,28 @@ public class NetworkUtils { .getAutodownloadSelectedNetworks()); if (selectedNetworks.contains(Integer.toString(wifiInfo .getNetworkId()))) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Current network is on the selected networks list"); + Log.d(TAG, "Current network is on the selected networks list"); return true; } } } } } - if (BuildConfig.DEBUG) - Log.d(TAG, "Network for auto-dl is not available"); + Log.d(TAG, "Network for auto-dl is not available"); return false; } - public static boolean networkAvailable(Context context) { + public static boolean networkAvailable() { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getActiveNetworkInfo(); return info != null && info.isConnected(); } - public static boolean isDownloadAllowed(Context context) { - return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(context); + public static boolean isDownloadAllowed() { + return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(); } - public static boolean connectedToWifi(Context context) { + public static boolean connectedToWifi() { ConnectivityManager connManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mWifi = connManager diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java index 7ebd580f7..752e95985 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java @@ -7,7 +7,7 @@ import android.util.Log; import java.util.List; -import de.danoeh.antennapod.core.asynctask.PicassoImageResource; +import de.danoeh.antennapod.core.asynctask.ImageResource; import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; @@ -18,7 +18,7 @@ import de.danoeh.antennapod.core.util.ShownotesProvider; * Interface for objects that can be played by the PlaybackService. */ public interface Playable extends Parcelable, - ShownotesProvider, PicassoImageResource { + ShownotesProvider, ImageResource { /** * Save information about the playable in a preference so that it can be diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml index 59b522cf5..8f1268993 100644 --- a/core/src/main/res/values/arrays.xml +++ b/core/src/main/res/values/arrays.xml @@ -168,4 +168,20 @@ <item>not_downloaded</item> </string-array> + <string-array name="image_cache_size_options"> + <item>20 MiB</item> + <item>50 MiB</item> + <item>100 MiB</item> + <item>250 MiB</item> + <item>500 MiB</item> + </string-array> + + <string-array name="image_cache_size_values"> + <item>20</item> + <item>50</item> + <item>100</item> + <item>250</item> + <item>500</item> + </string-array> + </resources> diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index e558a5c4e..bc0521dcd 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -3,6 +3,7 @@ <color name="white">#FFFFFF</color> <color name="gray">#808080</color> + <color name="light_gray">#bfbfbf</color> <color name="black">#000000</color> <color name="bright_blue">#33B5E5</color> <color name="ics_gray">#858585</color> diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index a692dbc73..23c58d16f 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -335,12 +335,12 @@ <string name="pref_persistNotify_sum">Keep notification and lockscreen controls when playback is paused.</string> <string name="pref_showDownloadReport_title">Show Download Report</string> <string name="pref_showDownloadReport_sum">If downloads fail, generate a report that shows the details of the failure.</string> - <string name="pref_expand_notify_unsupport_toast">Android versions before 4.1 do not support expanded notifications.</string> <string name="pref_queueAddToFront_sum">Add new episodes to the front of the queue.</string> <string name="pref_queueAddToFront_title">Enqueue at Front</string> <string name="pref_smart_mark_as_played_disabled">Disabled</string> - + <string name="pref_image_cache_size_title">Image Cache Size</string> + <string name="pref_image_cache_size_sum">Size of the disk cache for images.</string> <!-- Auto-Flattr dialog --> <string name="auto_flattr_enable">Enable automatic flattring</string> @@ -384,9 +384,18 @@ <string name="sleep_timer_label">Sleep timer</string> <string name="time_left_label">Time left:\u0020</string> <string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string> - <string name="time_unit_seconds">seconds</string> - <string name="time_unit_minutes">minutes</string> - <string name="time_unit_hours">hours</string> + <plurals name="time_unit_seconds"> + <item quantity="one">1 second</item> + <item quantity="other">%d seconds</item> + </plurals> + <plurals name="time_unit_minutes"> + <item quantity="one">1 minute</item> + <item quantity="other">%d minutes</item> + </plurals> + <plurals name="time_unit_hours"> + <item quantity="one">1 hour</item> + <item quantity="other">%d hours</item> + </plurals> <!-- gpodder.net --> <string name="gpodnet_taglist_header">CATEGORIES</string> @@ -435,6 +444,7 @@ <string name="pref_pausePlaybackForFocusLoss_title">Pause for Interruptions</string> <string name="pref_resumeAfterCall_sum">Resume playback after a phone call completes</string> <string name="pref_resumeAfterCall_title">Resume after Call</string> + <string name="pref_restart_required">AnntennaPod has to be restarted for this change to take effect.</string> <!-- Online feed view --> <string name="subscribe_label">Subscribe</string> diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index 8619869c8..85c908f64 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -263,7 +263,9 @@ <style name="BigBlurryBackground"> <item name="android:scaleType">centerCrop</item> - <item name="android:tint">@color/image_readability_tint</item> + <!-- <item name="android:tint">@color/image_readability_tint</item> --> + <!-- Reactivate when Glide's tinting has been fixed for Android 5.x + Remove color filter from ItemlistFragment --> </style> |