Merge pull request #675 from nextcloud/download-article
Add support to download full articles for offline viewing (using WebA…
This commit is contained in:
commit
12eb597f80
15 changed files with 651 additions and 95 deletions
|
@ -0,0 +1,86 @@
|
|||
package de.luhmer.owncloudnewsreader.tests;
|
||||
|
||||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class DownloadWebPageServiceTest {
|
||||
|
||||
//private String expectedAppName;
|
||||
|
||||
@Rule
|
||||
public ActivityTestRule<NewsReaderListActivity> mActivityRule = new ActivityTestRule<>(NewsReaderListActivity.class);
|
||||
|
||||
/*
|
||||
private UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
|
||||
private Activity getActivity() {
|
||||
return mActivityRule.getActivity();
|
||||
}
|
||||
|
||||
@Before
|
||||
private void setUp() {
|
||||
expectedAppName = getActivity().getString(R.string.app_name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartDownload() {
|
||||
openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
|
||||
onView(withText(getActivity().getString(R.string.action_download_articles_offline))).perform(click());
|
||||
|
||||
}
|
||||
|
||||
private void clearAllNotifications() {
|
||||
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
uiDevice.openNotification();
|
||||
long timeoutInMillis = 1000;
|
||||
uiDevice.wait(Until.hasObject(By.textStartsWith(expectedAppName)), timeoutInMillis);
|
||||
//UiObject2 clearAll = uiDevice.findObject(By.res(clearAllNotificationRes));
|
||||
//clearAll.click();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSendNotificationWhichContainsTitleTextAndAllCities() {
|
||||
String expectedAppName = "Test";
|
||||
String expectedAllCities = "Test";
|
||||
String expectedTitle = "Test";
|
||||
String expectedText = "Test";
|
||||
// TODO do something here..!
|
||||
uiDevice.openNotification();
|
||||
//uiDevice.wait(Until.hasObject(By.textStartsWith(expectedAppName)), timeout);
|
||||
UiObject2 title = uiDevice.findObject(By.text(expectedTitle));
|
||||
UiObject2 text= uiDevice.findObject(By.textStartsWith(expectedText));
|
||||
//UiObject2 allCities= uiDevice.findObject(By.res(expectedAllCitiesActionRes));
|
||||
assertEquals(expectedTitle, title.getText());
|
||||
assertTrue(text.getText().startsWith(expectedText));
|
||||
//assertEquals(expectedAllCities.toLowerCase(), allCities.getText().toLowerCase());
|
||||
clearAllNotifications();
|
||||
}
|
||||
|
||||
private class ClickOnSendNotification implements ViewAction {
|
||||
|
||||
private final String TAG = ClickOnSendNotification.class.getCanonicalName();
|
||||
|
||||
public String getDescription() {
|
||||
return "Click on the send notification button";
|
||||
}
|
||||
|
||||
public Matcher<View> getConstraints() {
|
||||
return Matchers.allOf(isDisplayed(), isAssignableFrom(Button.class));
|
||||
}
|
||||
|
||||
public void perform(@Nullable UiController uiController, @Nullable View view) {
|
||||
//view.findViewById(R.id.stop).performClick();
|
||||
Log.d(TAG, "perform() called with: uiController = [" + uiController + "], view = [" + view + "]");
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
|
@ -2,10 +2,9 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.luhmer.owncloudnewsreader"
|
||||
android:installLocation="internalOnly"
|
||||
android:versionCode="134"
|
||||
android:versionName="0.9.9.19"
|
||||
android:installLocation="internalOnly">
|
||||
|
||||
android:versionName="0.9.9.19">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -20,25 +19,21 @@
|
|||
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
|
||||
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
|
||||
|
||||
<!--
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
-->
|
||||
|
||||
|
||||
<!-- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> -->
|
||||
<application
|
||||
android:name=".NewsReaderApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:name=".NewsReaderApplication"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:icon, android:label, android:theme, android:name">
|
||||
<activity
|
||||
android:name=".NewsReaderListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop">
|
||||
|
||||
<!-- android:configChanges="keyboardHidden|orientation|screenSize" -->
|
||||
|
@ -48,37 +43,29 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".NewsDetailActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/title_activity_news_detail" >
|
||||
</activity>
|
||||
|
||||
android:label="@string/title_activity_news_detail"></activity>
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/title_activity_settings" >
|
||||
</activity>
|
||||
|
||||
<activity android:name=".DownloadImagesActivity">
|
||||
</activity>
|
||||
|
||||
android:label="@string/title_activity_settings"></activity>
|
||||
<activity android:name=".DownloadImagesActivity"></activity>
|
||||
<activity
|
||||
android:name=".SyncIntervalSelectorActivity"
|
||||
android:label="@string/title_activity_sync_interval_selector" >
|
||||
</activity>
|
||||
|
||||
android:label="@string/title_activity_sync_interval_selector"></activity>
|
||||
<activity
|
||||
android:name=".NewFeedActivity"
|
||||
android:label="@string/title_activity_new_feed"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize|stateVisible" >
|
||||
|
||||
android:windowSoftInputMode="adjustResize|stateVisible">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:host="*" />
|
||||
<data android:pathPattern=".*\\atom.xml" />
|
||||
|
@ -86,25 +73,32 @@
|
|||
<data android:pathPattern=".*\\.rss" />
|
||||
<data android:pathPattern=".*/feed" />
|
||||
<data android:pathPattern=".*feed/*" />
|
||||
|
||||
<data android:scheme="http" android:host="*"
|
||||
android:pathPattern=".*\\.opml" />
|
||||
<data android:scheme="https" android:host="*"
|
||||
android:pathPattern=".*\\.opml" />
|
||||
<data android:scheme="content" android:host="*"
|
||||
android:pathPattern=".*\\.opml" />
|
||||
<data android:scheme="file" android:host="*"
|
||||
android:pathPattern=".*\\.opml" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.opml"
|
||||
android:scheme="http" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.opml"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.opml"
|
||||
android:scheme="content" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPattern=".*\\.opml"
|
||||
android:scheme="file" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="DirectoryChooserActivity" />
|
||||
<activity android:name=".DirectoryChooserActivity" />
|
||||
|
||||
<receiver android:name=".events.podcast.broadcastreceiver.PodcastNotificationToggle">
|
||||
<intent-filter>
|
||||
|
@ -113,7 +107,6 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
<!--
|
||||
**********************************************************************
|
||||
* Sync Adapter and Service
|
||||
|
@ -123,12 +116,15 @@
|
|||
<service
|
||||
android:name=".services.DownloadImagesService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service android:name=".services.SyncItemStateService"
|
||||
<service
|
||||
android:name=".services.SyncItemStateService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
<service
|
||||
android:name=".services.DownloadWebPageService"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<service
|
||||
android:name=".services.OwnCloudAuthenticatorService"
|
||||
android:exported="true" >
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.accounts.AccountAuthenticator" />
|
||||
</intent-filter>
|
||||
|
@ -137,11 +133,10 @@
|
|||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
|
||||
<service android:name=".services.OwnCloudSyncService" />
|
||||
<service
|
||||
android:name=".services.OwnCloudSettingsSyncService"
|
||||
android:exported="true" >
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
@ -155,9 +150,7 @@
|
|||
android:name=".providers.OwnCloudSyncProvider"
|
||||
android:authorities="de.luhmer.owncloudnewsreader"
|
||||
android:label="@string/auto_sync_string"
|
||||
android:syncable="true" >
|
||||
</provider>
|
||||
|
||||
android:syncable="true"></provider>
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
@ -177,13 +170,20 @@
|
|||
<!-- android:theme="@style/Theme.Transparent" > -->
|
||||
<!-- </activity> -->
|
||||
|
||||
<receiver android:name=".helper.NotificationActionReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="YES_ACTION"/>
|
||||
<action android:name="STOP_ACTION"/>
|
||||
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!--
|
||||
**********************************************************************
|
||||
* Widget Provider Receiver
|
||||
**********************************************************************
|
||||
-->
|
||||
<receiver android:name=".widget.WidgetProvider" >
|
||||
<receiver android:name=".widget.WidgetProvider">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
@ -197,26 +197,17 @@
|
|||
android:name=".widget.WidgetService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
|
||||
<service
|
||||
android:name=".services.PodcastPlaybackService"
|
||||
android:enabled="true"
|
||||
android:exported="true" >
|
||||
</service>
|
||||
android:exported="true"></service>
|
||||
<service
|
||||
android:name=".services.PodcastDownloadService"
|
||||
android:exported="false" >
|
||||
</service>
|
||||
|
||||
|
||||
|
||||
|
||||
android:exported="false"></service>
|
||||
<service
|
||||
android:name="de.luhmer.owncloudnewsreader.chrometabs.KeepAliveService"
|
||||
android:name=".chrometabs.KeepAliveService"
|
||||
android:exported="true"
|
||||
android:process=":remote" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
|
@ -13,14 +13,12 @@ public class Constants {
|
|||
//public static final String LAST_SYNC = "LAST_SYNC";
|
||||
|
||||
public static final int maxItemsCount = 1500;
|
||||
public static final String LAST_UPDATE_NEW_ITEMS_COUNT_STRING = "LAST_UPDATE_NEW_ITEMS_COUNT_STRING";
|
||||
public static final String NEWS_WEB_VERSION_NUMBER_STRING = "NewsWebVersionNumber";
|
||||
public static final String NOTIFICATION_ACTION_STOP_STRING = "NOTIFICATION_STOP";
|
||||
|
||||
public static final int MIN_NEXTCLOUD_FILES_APP_VERSION_CODE = 30030052;
|
||||
|
||||
public static final String LAST_UPDATE_NEW_ITEMS_COUNT_STRING = "LAST_UPDATE_NEW_ITEMS_COUNT_STRING";
|
||||
|
||||
|
||||
public static final String NEWS_WEB_VERSION_NUMBER_STRING = "NewsWebVersionNumber";
|
||||
|
||||
|
||||
public static boolean IsNextCloud(Context context) {
|
||||
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
|
|
@ -46,11 +46,13 @@ import android.webkit.WebSettings;
|
|||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
|
@ -65,6 +67,7 @@ import de.luhmer.owncloudnewsreader.helper.AdBlocker;
|
|||
import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper;
|
||||
import de.luhmer.owncloudnewsreader.helper.ColorHelper;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadWebPageService;
|
||||
|
||||
public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Listener {
|
||||
|
||||
|
@ -75,9 +78,10 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
|
||||
public static int background_color = Integer.MIN_VALUE;
|
||||
|
||||
@BindView(R.id.webview) WebView mWebView;
|
||||
@BindView(R.id.progressBarLoading) ProgressBar mProgressBarLoading;
|
||||
@BindView(R.id.progressbar_webview) ProgressBar mProgressbarWebView;
|
||||
protected @BindView(R.id.webview) WebView mWebView;
|
||||
protected @BindView(R.id.progressBarLoading) ProgressBar mProgressBarLoading;
|
||||
protected @BindView(R.id.progressbar_webview) ProgressBar mProgressbarWebView;
|
||||
protected @BindView(R.id.tv_offline_version) TextView mTvOfflineVersion;
|
||||
|
||||
|
||||
private int section_number;
|
||||
|
@ -265,27 +269,32 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
int selectedBrowser = Integer.parseInt(mPrefs.getString(SettingsActivity.SP_DISPLAY_BROWSER, "0"));
|
||||
|
||||
boolean result = true;
|
||||
switch(selectedBrowser) {
|
||||
case 0: // Custom Tabs
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorPrimary));
|
||||
builder.setShowTitle(true);
|
||||
builder.setStartAnimations(getActivity(), R.anim.slide_in_right, R.anim.slide_out_left);
|
||||
builder.setExitAnimations(getActivity(), R.anim.slide_in_left, R.anim.slide_out_right);
|
||||
builder.build().launchUrl(getActivity(), Uri.parse(url));
|
||||
result = true;
|
||||
break;
|
||||
case 1: // External Browser
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(browserIntent);
|
||||
break;
|
||||
case 2: // Built in
|
||||
result = super.shouldOverrideUrlLoading(view, url);
|
||||
break;
|
||||
File webArchiveFile = DownloadWebPageService.getWebPageArchiveFileForUrl(getActivity(), url);
|
||||
if(webArchiveFile.exists()) { // Test if WebArchive exists for url
|
||||
mTvOfflineVersion.setVisibility(View.VISIBLE);
|
||||
mWebView.loadUrl("file://" + webArchiveFile.getAbsolutePath());
|
||||
return true;
|
||||
} else {
|
||||
mTvOfflineVersion.setVisibility(View.GONE);
|
||||
switch (selectedBrowser) {
|
||||
case 0: // Custom Tabs
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(ContextCompat.getColor(getActivity(), R.color.colorPrimary));
|
||||
builder.setShowTitle(true);
|
||||
builder.setStartAnimations(getActivity(), R.anim.slide_in_right, R.anim.slide_out_left);
|
||||
builder.setExitAnimations(getActivity(), R.anim.slide_in_left, R.anim.slide_out_right);
|
||||
builder.build().launchUrl(getActivity(), Uri.parse(url));
|
||||
return true;
|
||||
case 1: // External Browser
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
startActivity(browserIntent);
|
||||
return true;
|
||||
case 2: // Built in
|
||||
return super.shouldOverrideUrlLoading(view, url);
|
||||
default:
|
||||
throw new IllegalStateException("Unknown selection!");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
//return super.shouldOverrideUrlLoading(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.Manifest;
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
|
@ -31,9 +32,11 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
|
@ -91,6 +94,7 @@ import de.luhmer.owncloudnewsreader.helper.PostDelayHandler;
|
|||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.reader.nextcloud.RssItemObservable;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadImagesService;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadWebPageService;
|
||||
import de.luhmer.owncloudnewsreader.services.OwnCloudSyncService;
|
||||
import de.luhmer.owncloudnewsreader.services.events.SyncFailedEvent;
|
||||
import de.luhmer.owncloudnewsreader.services.events.SyncFinishedEvent;
|
||||
|
@ -145,6 +149,7 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
private SearchView searchView;
|
||||
|
||||
private PublishSubject<String> searchPublishSubject;
|
||||
private static final int REQUEST_CODE_PERMISSION_DOWNLOAD_WEB_ARCHIVE = 1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -780,7 +785,7 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
break;
|
||||
|
||||
case R.id.menu_StartImageCaching:
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(this);
|
||||
final DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(this);
|
||||
|
||||
long highestItemId = dbConn.getLowestRssItemIdUnread();
|
||||
|
||||
|
@ -825,10 +830,36 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
searchView.setIconified(false);
|
||||
searchView.setFocusable(true);
|
||||
searchView.requestFocusFromTouch();
|
||||
return true;
|
||||
|
||||
case R.id.menu_download_web_archive:
|
||||
startDownloadWebPagesForOfflineReading();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void startDownloadWebPagesForOfflineReading() {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.v("Permission error","You have permission");
|
||||
} else {
|
||||
Log.e("Permission error","Asking for permission");
|
||||
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_PERMISSION_DOWNLOAD_WEB_ARCHIVE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { //you dont need to worry about these stuff below api level 23
|
||||
Log.v("Permission error","You already have the permission");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(new Intent(this, DownloadWebPageService.class));
|
||||
} else {
|
||||
startService(new Intent(this, DownloadWebPageService.class));
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadMoreItems()
|
||||
{
|
||||
String username = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("edt_username", null);
|
||||
|
@ -920,6 +951,19 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
if(requestCode == REQUEST_CODE_PERMISSION_DOWNLOAD_WEB_ARCHIVE) {
|
||||
startDownloadWebPagesForOfflineReading();
|
||||
} else {
|
||||
Log.d(TAG, "No action defined here yet..");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureCorrectTheme(Intent data) {
|
||||
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String oldListLayout = data.getStringExtra(SettingsActivity.SP_FEED_LIST_LAYOUT);
|
||||
|
|
|
@ -67,6 +67,7 @@ import de.luhmer.owncloudnewsreader.helper.AppCompatPreferenceActivity;
|
|||
import de.luhmer.owncloudnewsreader.helper.ImageHandler;
|
||||
import de.luhmer.owncloudnewsreader.helper.PostDelayHandler;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadWebPageService;
|
||||
|
||||
/**
|
||||
* A {@link PreferenceActivity} that presents a set of application settings. On
|
||||
|
@ -648,9 +649,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
|||
protected Void doInBackground(Void... params) {
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(context);
|
||||
dbConn.resetDatabase();
|
||||
ImageHandler.clearCache();
|
||||
return null;
|
||||
}
|
||||
ImageHandler.clearCache();
|
||||
DownloadWebPageService.clearWebArchiveCache(context);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
|
|
|
@ -343,6 +343,10 @@ public class DatabaseConnectionOrm {
|
|||
return daoSession.getRssItemDao().queryBuilder().where(RssItemDao.Properties.Read_temp.eq(false)).limit(100).orderDesc(RssItemDao.Properties.PubDate).listLazy();
|
||||
}
|
||||
|
||||
public LazyList<RssItem> getAllUnreadRssItemsForDownloadWebPageService() {
|
||||
return daoSession.getRssItemDao().queryBuilder().where(RssItemDao.Properties.Read_temp.eq(false)).orderDesc(RssItemDao.Properties.PubDate).listLazy();
|
||||
}
|
||||
|
||||
public LazyList<RssItem> getAllItemsWithIdHigher(long id) {
|
||||
return daoSession.getRssItemDao().queryBuilder().where(RssItemDao.Properties.Id.ge(id)).listLazy();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package de.luhmer.owncloudnewsreader.helper;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.services.events.StopWebArchiveDownloadEvent;
|
||||
|
||||
import static de.luhmer.owncloudnewsreader.Constants.NOTIFICATION_ACTION_STOP_STRING;
|
||||
|
||||
public class NotificationActionReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (NOTIFICATION_ACTION_STOP_STRING.equals(action)) {
|
||||
EventBus.getDefault().post(new StopWebArchiveDownloadEvent());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,6 +77,23 @@ public class NextcloudNotificationManager {
|
|||
return mNotificationDownloadImages;
|
||||
}
|
||||
|
||||
public static NotificationCompat.Builder buildNotificationDownloadWebPageService(Context context, String channelId) {
|
||||
getNotificationManagerAndCreateChannel(context, channelId);
|
||||
|
||||
Intent intentNewsReader = new Intent(context, NewsReaderListActivity.class);
|
||||
PendingIntent pIntent = PendingIntent.getActivity(context, 0, intentNewsReader, 0);
|
||||
NotificationCompat.Builder mNotificationWebPages = new NotificationCompat.Builder(context, channelId)
|
||||
.setContentTitle(context.getResources().getString(R.string.app_name))
|
||||
.setContentText("Downloading webpages for offline usage")
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setOngoing(true);
|
||||
|
||||
return mNotificationWebPages;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void ShowNotificationImageDownloadLimitReached(Context context, String channelId, int limit) {
|
||||
|
|
|
@ -0,0 +1,357 @@
|
|||
package de.luhmer.owncloudnewsreader.services;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.webkit.ConsoleMessage;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
||||
import de.luhmer.owncloudnewsreader.helper.NotificationActionReceiver;
|
||||
import de.luhmer.owncloudnewsreader.notification.NextcloudNotificationManager;
|
||||
import de.luhmer.owncloudnewsreader.services.events.StopWebArchiveDownloadEvent;
|
||||
|
||||
import static de.luhmer.owncloudnewsreader.Constants.NOTIFICATION_ACTION_STOP_STRING;
|
||||
|
||||
/**
|
||||
* An {@link Service} subclass for handling asynchronous task requests in
|
||||
* a service on a separate handler thread.
|
||||
* <p>
|
||||
* helper methods.
|
||||
*/
|
||||
public class DownloadWebPageService extends Service {
|
||||
|
||||
private static final String TAG = DownloadWebPageService.class.getCanonicalName();
|
||||
private static final int JOB_ID = 1002;
|
||||
|
||||
private static final String CHANNEL_ID = "Download Web Page Service";
|
||||
private static final String WebArchiveFinalPrefix = "web_archive_";
|
||||
private static final int NUMBER_OF_CORES = 4;
|
||||
private NotificationCompat.Builder mNotificationWebPages;
|
||||
private static final int NOTIFICATION_ID = JOB_ID;
|
||||
private NotificationManager mNotificationManager;
|
||||
|
||||
|
||||
// Sets the amount of time an idle thread waits before terminating
|
||||
private static final int KEEP_ALIVE_TIME = 1;
|
||||
// Sets the Time Unit to seconds
|
||||
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
|
||||
|
||||
private final AtomicInteger doneCount = new AtomicInteger();
|
||||
private Integer totalCount = 0;
|
||||
|
||||
private ThreadPoolExecutor mDownloadThreadPool;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Log.d(TAG, "onCreate() called");
|
||||
super.onCreate();
|
||||
|
||||
initNotification();
|
||||
|
||||
downloadWebPages();
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy() called");
|
||||
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initNotification() {
|
||||
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
mNotificationWebPages = NextcloudNotificationManager.buildNotificationDownloadWebPageService(this, CHANNEL_ID);
|
||||
|
||||
Intent stopIntent = new Intent(this, NotificationActionReceiver.class);
|
||||
stopIntent.setAction(NOTIFICATION_ACTION_STOP_STRING);
|
||||
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(this, 0, stopIntent, PendingIntent.FLAG_ONE_SHOT);
|
||||
mNotificationWebPages.addAction(R.drawable.ic_action_pause, "Stop", stopPendingIntent);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(StopWebArchiveDownloadEvent event) {
|
||||
mDownloadThreadPool.shutdownNow();
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private void runOnMainThreadAndWait(final Runnable runnable) throws InterruptedException {
|
||||
synchronized(runnable) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
synchronized (runnable) {
|
||||
runnable.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
runnable.wait(); // unlocks runnable while waiting
|
||||
}
|
||||
}
|
||||
|
||||
private void delayedRunOnMainThread(Runnable runnable, int waitMillis) {
|
||||
try {
|
||||
Thread.sleep(waitMillis);
|
||||
runOnMainThreadAndWait(runnable);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error occurred..", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void downloadWebPages() {
|
||||
mNotificationWebPages.setProgress(0, 100, true);
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotificationWebPages.build());
|
||||
|
||||
final DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(DownloadWebPageService.this);
|
||||
final BlockingQueue<Runnable> downloadWorkQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
getWebPageArchiveStorage(this).mkdirs();
|
||||
|
||||
for (RssItem rssItem : dbConn.getAllUnreadRssItemsForDownloadWebPageService()) {
|
||||
downloadWorkQueue.add(new DownloadWebPage(rssItem.getLink()));
|
||||
}
|
||||
//downloadWorkQueue.clear();
|
||||
|
||||
/*
|
||||
List<RssItem> items = dbConn.getAllUnreadRssItemsForDownloadWebPageService();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
downloadWorkQueue.add(new DownloadWebPage(items.get(i).getLink()));
|
||||
}
|
||||
*/
|
||||
|
||||
startDownloadingQueue(downloadWorkQueue);
|
||||
}
|
||||
|
||||
public static void clearWebArchiveCache(Context context) {
|
||||
getWebPageArchiveStorage(context).mkdirs();
|
||||
|
||||
String path = getWebPageArchiveStorage(context).getAbsolutePath();
|
||||
Log.d("Files", "Path: " + path);
|
||||
File directory = new File(path);
|
||||
File[] files = directory.listFiles();
|
||||
Log.d("Files", "Size: " + files.length);
|
||||
for (File file : files) {
|
||||
String name = file.getName();
|
||||
//og.d("Files", "FileName: " + file.getName());
|
||||
if (name.startsWith(WebArchiveFinalPrefix)) {
|
||||
Log.v(TAG, "Deleting file: " + name);
|
||||
//file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startDownloadingQueue(BlockingQueue<Runnable> downloadWorkQueue) {
|
||||
totalCount = downloadWorkQueue.size();
|
||||
|
||||
// Creates a thread pool manager
|
||||
mDownloadThreadPool = new ThreadPoolExecutor(
|
||||
NUMBER_OF_CORES, // Initial pool size
|
||||
NUMBER_OF_CORES, // Max pool size
|
||||
KEEP_ALIVE_TIME,
|
||||
KEEP_ALIVE_TIME_UNIT,
|
||||
downloadWorkQueue);
|
||||
|
||||
// Start all tasks in queue
|
||||
mDownloadThreadPool.prestartAllCoreThreads();
|
||||
|
||||
// Tell ThreadPoolExecutor to stop once done
|
||||
mDownloadThreadPool.shutdown();
|
||||
|
||||
// If no articles are present, remove notification right away. Otherwise the user has to close it manually
|
||||
if(totalCount == 0) {
|
||||
mNotificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadWebPage implements Runnable {
|
||||
|
||||
private String url;
|
||||
private WebView webView;
|
||||
private final Object lock;
|
||||
|
||||
DownloadWebPage(String url) {
|
||||
this.url = url;
|
||||
lock = new Object();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//Log.v(TAG, "Running DownloadWebPage for url: " + url);
|
||||
synchronized (lock) {
|
||||
File webArchiveFile = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
if (!webArchiveFile.exists()) {
|
||||
//Log.v(TAG, "Loading page:");
|
||||
initWebView();
|
||||
loadUrlInWebViewAndWait();
|
||||
} /* else {
|
||||
Log.v(TAG, "Already cached article: " + url);
|
||||
} */
|
||||
}
|
||||
updateNotificationProgress();
|
||||
}
|
||||
|
||||
private void initWebView() {
|
||||
try {
|
||||
runOnMainThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView = new WebView(DownloadWebPageService.this);
|
||||
webView.setWebViewClient(new DownloadImageWebViewClient(lock));
|
||||
webView.setWebChromeClient(new DownloadImageWebViewChromeClient());
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error while setting up WebView", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadUrlInWebViewAndWait() {
|
||||
try {
|
||||
runOnMainThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
});
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error while opening url", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadImageWebViewChromeClient extends WebChromeClient {
|
||||
@Override
|
||||
public boolean onConsoleMessage(ConsoleMessage cm) {
|
||||
//Log.d("TAG", cm.message() + " at " + cm.sourceId() + ":" + cm.lineNumber());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadImageWebViewClient extends WebViewClient {
|
||||
private final String TAG = DownloadImageWebViewClient.class.getName();
|
||||
private final Object lock;
|
||||
private boolean failed = false;
|
||||
|
||||
DownloadImageWebViewClient(Object lock) {
|
||||
this.lock = lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
//Log.e(TAG, "onReceivedError() called with: view = [" + view + "], request = [" + request + "], error = [" + error + "]");
|
||||
failed = true;
|
||||
super.onReceivedError(view, request, error);
|
||||
}
|
||||
|
||||
public void onPageFinished(final WebView view, final String url) {
|
||||
//Log.e(TAG, "onPageFinished() called with: view = [" + view + "], url = [" + url + "]");
|
||||
|
||||
if(failed) {
|
||||
Log.e(TAG, "Skipping onPageFinished as request failed.. " + url);
|
||||
} else {
|
||||
saveWebArchive(view, url);
|
||||
}
|
||||
|
||||
// Notify waiting thread that we're done..
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveWebArchive(final WebView view, final String url) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delayedRunOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Can't store directly on external dir.. (workaround -> store on internal storage first and move then))
|
||||
final File webArchive = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
final File webArchiveExternalStorage = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
view.saveWebArchive(webArchive.getAbsolutePath(), false, new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
// Move file to external storage once done writing
|
||||
webArchive.renameTo(webArchiveExternalStorage);
|
||||
//boolean success = webArchive.renameTo(webArchiveExternalStorage);
|
||||
//Log.v(TAG, "Move succeeded: " + success);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void updateNotificationProgress() {
|
||||
int current = doneCount.incrementAndGet();
|
||||
Log.d(TAG, String.format("updateNotificationProgress (%d/%d)", current, totalCount));
|
||||
|
||||
if(current == totalCount) {
|
||||
//mNotificationManager.cancel(NOTIFICATION_ID);
|
||||
EventBus.getDefault().post(new StopWebArchiveDownloadEvent());
|
||||
} else {
|
||||
mNotificationWebPages
|
||||
.setContentText((current) + "/" + totalCount + " - Downloading Images for offline usage")
|
||||
.setProgress(totalCount, current, false);
|
||||
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotificationWebPages.build());
|
||||
}
|
||||
}
|
||||
|
||||
public static File getWebPageArchiveStorage(Context context) {
|
||||
//return context.getFilesDir();
|
||||
//return new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "nextcloud-news/web-archive/");
|
||||
|
||||
return new File(context.getExternalCacheDir(), "web-archive/");
|
||||
//return new File(Environment.getExternalStorageDirectory(), "nextcloud-news/web-archive/");
|
||||
}
|
||||
|
||||
public static File getWebPageArchiveFileForUrl(Context context, String url) {
|
||||
return new File(getWebPageArchiveStorage(context), getWebPageArchiveFilename(url));
|
||||
}
|
||||
|
||||
public static String getWebPageArchiveFilename(String url) {
|
||||
return WebArchiveFinalPrefix + url.hashCode() + ".mht";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package de.luhmer.owncloudnewsreader.services.events;
|
||||
|
||||
public class StopWebArchiveDownloadEvent {
|
||||
}
|
|
@ -10,7 +10,8 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:hardwareAccelerated="true" />
|
||||
android:hardwareAccelerated="true"
|
||||
android:layout_above="@id/tv_offline_version"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressbar_webview"
|
||||
|
@ -30,5 +31,16 @@
|
|||
android:layout_centerVertical="true"
|
||||
android:layout_centerHorizontal="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_offline_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="25dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:text="@string/tv_showing_cached_version"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@color/material_red_600"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
|
@ -17,6 +17,12 @@
|
|||
app:actionViewClass="android.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_download_web_archive"
|
||||
android:enabled="true"
|
||||
android:title="@string/action_download_articles_offline"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/menu_StartImageCaching"
|
||||
android:title="@string/menu_StartImageCaching"
|
||||
android:orderInCategory="95"
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
<color name="material_grey_600" tools:override="true">#757575</color>
|
||||
<color name="material_grey_900" tools:override="true">#212121</color>
|
||||
|
||||
<color name="material_red_600" tools:override="true">#e53935</color>
|
||||
|
||||
<!-- see also assets/web.css -->
|
||||
<color name="news_detail_background_color">#eeeeee</color>
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<string name="menu_downloadMoreItems">Download more items</string>
|
||||
|
||||
<string name="img_view_thumbnail" translatable="false">Thumbnail</string>
|
||||
<string name="tv_showing_cached_version">Showing cached version</string>
|
||||
|
||||
<!-- Action Bar Items -->
|
||||
<string name="action_starred">Starred</string>
|
||||
|
@ -40,6 +41,7 @@
|
|||
<string name="action_sync_settings">Sync Settings</string>
|
||||
<string name="action_add_new_feed">Add new feed</string>
|
||||
<string name="action_textToSpeech">Read out</string>
|
||||
<string name="action_download_articles_offline">Download articles offline</string>
|
||||
<plurals name="notification_new_items_ticker">
|
||||
<item quantity="one">You have %d new unread item</item>
|
||||
<item quantity="other">You have %d new unread items</item>
|
||||
|
|
Loading…
Reference in a new issue