diff --git a/app/build.gradle b/app/build.gradle index d1098bbf0..be21a8902 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ android { defaultConfig { applicationId "fr.gaulupeau.apps.InThePoche" - minSdkVersion 21 + minSdkVersion 14 targetSdkVersion 30 versionCode 228 versionName "2.4.2" @@ -66,12 +66,12 @@ dependencies { implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:greendao:3.3.0' annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.2.0' - implementation 'com.squareup.okhttp3:okhttp:4.9.0' - implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0' + implementation 'com.squareup.okhttp3:okhttp:3.12.12' + implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.12.12' implementation 'org.conscrypt:conscrypt-android:2.5.1' implementation 'com.facebook.stetho:stetho:1.5.1' implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' - implementation 'com.mikepenz:aboutlibraries:7.1.0' + implementation 'com.mikepenz:aboutlibraries:6.2.1' implementation 'com.github.di72nn.wallabag-api-wrapper:api-wrapper:v2.0.0-beta.6' implementation 'org.slf4j:slf4j-android:1.7.30' } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 34e8da605..3e4c6c5d9 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -14,6 +14,9 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} +-keepclassmembers class fr.gaulupeau.apps.Poche.ui.JsActionController { + public *; +} -keepclassmembers class fr.gaulupeau.apps.Poche.ui.JsAnnotationController { public *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1cf7969ae..5004fb5cd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,6 +138,14 @@ android:name="android.appwidget.provider" android:resource="@xml/icon_unread_info" /> + + + + + diff --git a/app/src/main/assets/annotations-android-app.js b/app/src/main/assets/annotations-android-app.js index d8592b650..658929639 100644 --- a/app/src/main/assets/annotations-android-app.js +++ b/app/src/main/assets/annotations-android-app.js @@ -6,7 +6,7 @@ document.addEventListener("DOMContentLoaded", function() { }); const authorization = { - permits() { return true; }, + permits: function() { return true; }, }; app.registry.registerUtility(authorization, 'authorizationPolicy'); @@ -46,7 +46,7 @@ document.addEventListener("DOMContentLoaded", function() { app.include(myStorage); - app.start().then(() => { + app.start().then(function() { app.annotations.load({}); }); }); diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/data/DbConnection.java b/app/src/main/java/fr/gaulupeau/apps/Poche/data/DbConnection.java index e3692a3c7..130c97673 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/data/DbConnection.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/data/DbConnection.java @@ -1,7 +1,10 @@ package fr.gaulupeau.apps.Poche.data; +import android.database.sqlite.SQLiteDatabase; +import android.os.Build; import android.util.Log; +import org.greenrobot.greendao.database.Database; import org.greenrobot.greendao.query.QueryBuilder; import fr.gaulupeau.apps.InThePoche.BuildConfig; @@ -25,8 +28,16 @@ public static DaoSession getSession() { Log.d(TAG, "creating new db session"); WallabagDbOpenHelper dbHelper = new WallabagDbOpenHelper(App.getInstance(), dbPath, null); - dbHelper.setWriteAheadLoggingEnabled(true); - DaoMaster daoMaster = new DaoMaster(dbHelper.getWritableDb()); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + dbHelper.setWriteAheadLoggingEnabled(true); + } + Database db = dbHelper.getWritableDb(); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + if(!((SQLiteDatabase)db.getRawDatabase()).enableWriteAheadLogging()) { + Log.w(TAG, "write ahead logging was not enabled"); + } + } + DaoMaster daoMaster = new DaoMaster(db); Holder.session = daoMaster.newSession(); } else { Log.d(TAG, "using existing db session"); diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/data/Settings.java b/app/src/main/java/fr/gaulupeau/apps/Poche/data/Settings.java index a309162f2..5ed52a175 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/data/Settings.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/data/Settings.java @@ -19,6 +19,7 @@ import fr.gaulupeau.apps.InThePoche.R; import fr.gaulupeau.apps.Poche.App; +import fr.gaulupeau.apps.Poche.network.ConnectivityChangeReceiver; import fr.gaulupeau.apps.Poche.service.WallabagJobService; import fr.gaulupeau.apps.Poche.ui.HttpSchemeHandlerActivity; import fr.gaulupeau.apps.Poche.ui.Sortable; @@ -51,7 +52,11 @@ public static boolean checkFirstRunInit(Context context) { } public static void enableConnectivityChangeReceiver(Context context, boolean enable) { - WallabagJobService.enable(context, enable); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + enableComponent(context, ConnectivityChangeReceiver.class, enable); + } else { + WallabagJobService.enable(context, enable); + } } // TODO: reuse in setHandleHttpScheme diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/data/dao/FtsDao.java b/app/src/main/java/fr/gaulupeau/apps/Poche/data/dao/FtsDao.java index 8ea4040d8..0f1cd9e0f 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/data/dao/FtsDao.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/data/dao/FtsDao.java @@ -1,5 +1,7 @@ package fr.gaulupeau.apps.Poche.data.dao; +import android.os.Build; + import org.greenrobot.greendao.database.Database; import fr.gaulupeau.apps.Poche.App; @@ -29,31 +31,44 @@ public class FtsDao { "article_content_after_delete_tr" }; + public static boolean isFtsSupported() { + // https://www.sqlite.org/changes.html#version_3_7_9 is required for the 'content' option + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; // https://stackoverflow.com/a/4377116 + } + public static String getQueryString() { return "select " + COLUMN_ID + " from " + TABLE_NAME + " where " + TABLE_NAME + " match "; } public static void createAll(Database db, boolean ifNotExists) { + if (!isFtsSupported()) return; + createViewForFts(db, ifNotExists); createTable(db, ifNotExists); createTriggers(db, ifNotExists); } public static void dropAll(Database db, boolean ifExists) { + if (!isFtsSupported()) return; + dropTriggers(db, ifExists); dropTable(db, ifExists); dropViewForFts(db, ifExists); } public static void deleteAllArticles(Database db) { + if (!isFtsSupported()) return; + dropTable(db, true); createTable(db, true); } private static void createTable(Database db, boolean ifNotExists) { - String options = ", content=\"" + VIEW_FOR_FTS_NAME + "\"" - + ", tokenize=" - + (App.getSettings().isFtsIcuTokenizerEnabled() ? "icu" : "unicode61"); + String options = ", content=\"" + VIEW_FOR_FTS_NAME + "\""; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + options += ", tokenize=" + + (App.getSettings().isFtsIcuTokenizerEnabled() ? "icu" : "unicode61"); + } db.execSQL("create virtual table " + getIfNotExistsConstraint(ifNotExists) + TABLE_NAME + " using fts4(" + diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/events/ConnectivityChangedEvent.java b/app/src/main/java/fr/gaulupeau/apps/Poche/events/ConnectivityChangedEvent.java index 6db7d1717..3c02adb6a 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/events/ConnectivityChangedEvent.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/events/ConnectivityChangedEvent.java @@ -1,3 +1,17 @@ package fr.gaulupeau.apps.Poche.events; -public class ConnectivityChangedEvent {} +public class ConnectivityChangedEvent { + + private boolean noConnectivity; + + public ConnectivityChangedEvent() {} + + public ConnectivityChangedEvent(boolean noConnectivity) { + this.noConnectivity = noConnectivity; + } + + public boolean isNoConnectivity() { + return noConnectivity; + } + +} diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/events/EventProcessor.java b/app/src/main/java/fr/gaulupeau/apps/Poche/events/EventProcessor.java index 578d38c32..f73dd6b6c 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/events/EventProcessor.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/events/EventProcessor.java @@ -66,6 +66,8 @@ public class EventProcessor { private Handler mainHandler; private NotificationManager notificationManager; + private boolean delayedNetworkChangedTask; + private NotificationCompat.Builder syncQueueNotificationBuilder; private NotificationCompat.Builder updateArticlesNotificationBuilder; private NotificationCompat.Builder sweepDeletedArticlesNotificationBuilder; @@ -118,11 +120,12 @@ public void onAlarmReceivedEvent(AlarmReceivedEvent event) { public void onConnectivityChangedEvent(ConnectivityChangedEvent event) { Log.d(TAG, "onConnectivityChangedEvent() started"); - Settings settings = getSettings(); - if (settings.isOfflineQueuePending() && settings.isConfigurationOk()) { - Log.d(TAG, "networkChanged() requesting SyncQueue operation"); - OperationsHelper.syncQueue(getContext(), true); + if(event.isNoConnectivity()) { + Log.d(TAG, "onConnectivityChangedEvent() isNoConnectivity is true; ignoring event"); + return; } + + networkChanged(false); } @Subscribe @@ -559,6 +562,33 @@ public void onActionResultEvent(ActionResultEvent event) { } } + private void networkChanged(boolean delayed) { + if(!delayed && delayedNetworkChangedTask) return; + + if(!WallabagConnection.isNetworkAvailable()) return; + + Settings settings = getSettings(); + + if(settings.isOfflineQueuePending() && settings.isConfigurationOk()) { + if(delayed) { + Log.d(TAG, "networkChanged() requesting SyncQueue operation"); + + OperationsHelper.syncQueue(getContext(), true); + + delayedNetworkChangedTask = false; + } else { + getMainHandler().postDelayed(new Runnable() { + @Override + public void run() { + networkChanged(true); + } + }, 3000); + + delayedNetworkChangedTask = true; + } + } + } + private void enableConnectivityChangeReceiver(boolean enable) { if(getSettings().isAutoSyncQueueEnabled()) { Log.d(TAG, "enableConnectivityChangeReceiver() enable connectivity change receiver: " + enable); diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/network/ConnectivityChangeReceiver.java b/app/src/main/java/fr/gaulupeau/apps/Poche/network/ConnectivityChangeReceiver.java new file mode 100644 index 000000000..f2326eedf --- /dev/null +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/network/ConnectivityChangeReceiver.java @@ -0,0 +1,24 @@ +package fr.gaulupeau.apps.Poche.network; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.util.Log; + +import fr.gaulupeau.apps.Poche.events.ConnectivityChangedEvent; +import fr.gaulupeau.apps.Poche.events.EventHelper; + +public class ConnectivityChangeReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if(ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + Log.d("ConnectivityChangeRcvr", "Connectivity changed"); + + EventHelper.postEvent(new ConnectivityChangedEvent( + intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false))); + } + } + +} diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/TaskService.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/TaskService.java index 4b4546693..6ce099f0a 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/TaskService.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/TaskService.java @@ -191,7 +191,7 @@ private void readyToStop() { private void enqueueTask(ParameterizedRunnable task, boolean ensureStarted) { Log.d(tag, "enqueueTask()"); - Objects.requireNonNull(task, "task is null"); + Objects.requireNonNull(task); Log.v(tag, "enqueueTask() enqueueing task"); taskQueue.add(task); diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/WallabagJobService.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/WallabagJobService.java index 3398dbbf5..f466a8e31 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/WallabagJobService.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/WallabagJobService.java @@ -6,11 +6,15 @@ import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Build; import android.util.Log; +import androidx.annotation.RequiresApi; + import fr.gaulupeau.apps.Poche.events.ConnectivityChangedEvent; import fr.gaulupeau.apps.Poche.events.EventHelper; +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public class WallabagJobService extends JobService { private static final int CONNECTIVITY_CHANGE_JOB_ID = 1; diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/ActionRequestTask.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/ActionRequestTask.java index 0e617c5a7..0881b4756 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/ActionRequestTask.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/ActionRequestTask.java @@ -16,7 +16,7 @@ public class ActionRequestTask extends SimpleTask { protected ActionRequest actionRequest; public ActionRequestTask(ActionRequest actionRequest) { - Objects.requireNonNull(actionRequest, "actionRequest is null"); + Objects.requireNonNull(actionRequest); this.actionRequest = actionRequest; } diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/SimpleTask.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/SimpleTask.java index 2a8dbd203..17c2f05cf 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/SimpleTask.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/tasks/SimpleTask.java @@ -52,7 +52,7 @@ public T createFromParcel(Parcel in) { T instance; try { instance = clazz.newInstance(); - } catch (IllegalAccessException | InstantiationException e) { + } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Uh oh"); } diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/ArticleUpdater.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/ArticleUpdater.java index b78109f2c..2547acab9 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/ArticleUpdater.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/ArticleUpdater.java @@ -5,6 +5,8 @@ import android.util.Log; import android.util.Pair; +import androidx.core.util.ObjectsCompat; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -13,7 +15,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import fr.gaulupeau.apps.Poche.data.DbUtils; @@ -357,15 +358,15 @@ private void processArticle(ArticlesChangedEvent event, boolean full, article.setUpdateDate(apiArticle.updatedAt); articleChanges.add(ChangeType.UPDATED_DATE_CHANGED); } - if (!Objects.equals(article.getPublishedAt(), apiArticle.publishedAt)) { + if (!ObjectsCompat.equals(article.getPublishedAt(), apiArticle.publishedAt)) { article.setPublishedAt(apiArticle.publishedAt); articleChanges.add(ChangeType.PUBLISHED_AT_CHANGED); } - if (!Objects.equals(article.getStarredAt(), apiArticle.starredAt)) { + if (!ObjectsCompat.equals(article.getStarredAt(), apiArticle.starredAt)) { article.setStarredAt(apiArticle.starredAt); articleChanges.add(ChangeType.STARRED_AT_CHANGED); } - if (!Objects.equals(article.getIsPublic(), apiArticle.isPublic)) { + if (!ObjectsCompat.equals(article.getIsPublic(), apiArticle.isPublic)) { article.setIsPublic(apiArticle.isPublic); articleChanges.add(ChangeType.IS_PUBLIC_CHANGED); } @@ -552,11 +553,11 @@ private boolean processAnnotations(wallabag.apiwrapper.models.Article apiArticle annotation.setQuote(apiAnnotation.quote); annotationChanged = true; } - if (!Objects.equals(annotation.getCreatedAt(), apiAnnotation.createdAt)) { + if (!ObjectsCompat.equals(annotation.getCreatedAt(), apiAnnotation.createdAt)) { annotation.setCreatedAt(apiAnnotation.createdAt); annotationChanged = true; } - if (!Objects.equals(annotation.getUpdatedAt(), apiAnnotation.updatedAt)) { + if (!ObjectsCompat.equals(annotation.getUpdatedAt(), apiAnnotation.updatedAt)) { annotation.setUpdatedAt(apiAnnotation.updatedAt); annotationChanged = true; } diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/OfflineChangesSynchronizer.java b/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/OfflineChangesSynchronizer.java index 371931374..e83fe6f60 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/OfflineChangesSynchronizer.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/service/workers/OfflineChangesSynchronizer.java @@ -5,11 +5,12 @@ import android.util.Log; import android.util.Pair; +import androidx.core.util.ObjectsCompat; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import fr.gaulupeau.apps.Poche.data.DbUtils; import fr.gaulupeau.apps.Poche.data.QueueHelper; @@ -307,7 +308,7 @@ private void addLink(AddLinkItem item) throws IncorrectConfigurationException, } private void updateGivenUrl(Article article, String givenUrl) { - if (!Objects.equals(article.getGivenUrl(), givenUrl)) { + if (!ObjectsCompat.equals(article.getGivenUrl(), givenUrl)) { article.setGivenUrl(givenUrl); article.update(); } diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TextItem.java b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TextItem.java index 59db8947f..84565977d 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TextItem.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TextItem.java @@ -29,7 +29,7 @@ static Type getType(String type) { Extra() {} Extra(Extra.Type type, int start, int end) { - this.type = Objects.requireNonNull(type, "type cannot be null"); + this.type = Objects.requireNonNull(type); this.start = start; this.end = end; } diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TtsService.java b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TtsService.java index ca24c14a5..fce4c9ca8 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TtsService.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/TtsService.java @@ -34,6 +34,7 @@ import androidx.core.util.Pair; import androidx.media.session.MediaButtonReceiver; +import java.util.HashMap; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -744,7 +745,14 @@ private void ttsSpeak(CharSequence text, int queueMode, String utteranceId) { Log.v(TAG, "ttsSpeak() speaking " + utteranceId + ": " + text); - tts.speak(text, queueMode, null, utteranceId); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + tts.speak(text, queueMode, null, utteranceId); + } else { + HashMap params = new HashMap<>(2); + params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId); + //noinspection deprecation + tts.speak(text.toString(), queueMode, params); + } Log.v(TAG, "ttsSpeak() call returned"); } @@ -825,55 +833,59 @@ private void onTtsInit(int status) { state = State.CREATED; } - tts.setOnUtteranceProgressListener(new UtteranceProgressListener() { - @Override - public void onStart(String utteranceId) { -// Log.v(TAG, "utteranceProgressListener.onStart()"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + tts.setOnUtteranceProgressListener(new UtteranceProgressListener() { + @Override + public void onStart(String utteranceId) { + // Log.v(TAG, "utteranceProgressListener.onStart()"); - setCurrentItemProgress(utteranceId, -1, -1); - } + setCurrentItemProgress(utteranceId, -1, -1); + } - @Override - public synchronized void onDone(String utteranceId) { - onSpeakDoneListener(utteranceId); - } + @Override + public synchronized void onDone(String utteranceId) { + onSpeakDoneListener(utteranceId); + } - @Override - public void onError(String utteranceId, int errorCode) { - Log.w(TAG, "utteranceProgressListener.onError() " + utteranceId - + ", errorCode: " + errorCode); - super.onError(utteranceId, errorCode); - } + @Override + public void onError(String utteranceId, int errorCode) { + Log.w(TAG, "utteranceProgressListener.onError() " + utteranceId + + ", errorCode: " + errorCode); + super.onError(utteranceId, errorCode); + } - @Override - public void onError(String utteranceId) { - Log.w(TAG, "utteranceProgressListener.onError() " + utteranceId); + @Override + public void onError(String utteranceId) { + Log.w(TAG, "utteranceProgressListener.onError() " + utteranceId); - resetCurrentItemProgress(); + resetCurrentItemProgress(); - onSpeakDoneListener(utteranceId); - } + onSpeakDoneListener(utteranceId); + } - @Override - public void onStop(String utteranceId, boolean interrupted) { - Log.d(TAG, "utteranceProgressListener.onStop() " + utteranceId - + ", " + interrupted); + @Override + public void onStop(String utteranceId, boolean interrupted) { + Log.d(TAG, "utteranceProgressListener.onStop() " + utteranceId + + ", " + interrupted); - resetCurrentItemProgress(); + resetCurrentItemProgress(); - onSpeakDoneListener(utteranceId); - } + onSpeakDoneListener(utteranceId); + } - @Override - public void onRangeStart(String utteranceId, int start, int end, int frame) { - super.onRangeStart(utteranceId, start, end, frame); + @Override + public void onRangeStart(String utteranceId, int start, int end, int frame) { + super.onRangeStart(utteranceId, start, end, frame); -// Log.v(TAG, String.format("utteranceProgressListener.onRangeStart(%s, %d, %d, %d)", -// utteranceId, start, end, frame)); + // Log.v(TAG, String.format("utteranceProgressListener.onRangeStart(%s, %d, %d, %d)", + // utteranceId, start, end, frame)); - setCurrentItemProgress(utteranceId, start, end); - } - }); + setCurrentItemProgress(utteranceId, start, end); + } + }); + } else { + tts.setOnUtteranceCompletedListener(this::onSpeakDoneListener); + } tts.setLanguage(convertVoiceNameToLocale(ttsVoice)); @@ -934,7 +946,11 @@ public void setEngineAndVoice(String engine, String voice) { final TextToSpeech ttsToShutdown = tts; new Thread(() -> { - ttsToShutdown.setOnUtteranceProgressListener(null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { + ttsToShutdown.setOnUtteranceProgressListener(null); + } else { + ttsToShutdown.setOnUtteranceCompletedListener(null); + } ttsToShutdown.stop(); ttsToShutdown.shutdown(); }).start(); diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/WebViewText.java b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/WebViewText.java index 3969f6541..2bbb70a92 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/tts/WebViewText.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/tts/WebViewText.java @@ -59,8 +59,8 @@ void parseWebViewDocument(Runnable callback) { parsingFinishedCallback = callback; ttsHost.getJsTtsController().setWebViewText(this); - ttsHost.getWebView().evaluateJavascript("javascript:" + JS_PARSE_DOCUMENT_SCRIPT - + ";parseDocumentText();", null); + ttsHost.getWebView().loadUrl("javascript:" + JS_PARSE_DOCUMENT_SCRIPT); + ttsHost.getWebView().loadUrl("javascript:parseDocumentText()"); } void onDocumentParseStart() { diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ArticleListFragment.java b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ArticleListFragment.java index 746b298df..fbf819569 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ArticleListFragment.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ArticleListFragment.java @@ -219,8 +219,12 @@ private QueryBuilder
getQueryBuilder() { } if (!TextUtils.isEmpty(searchQuery)) { - qb.where(new WhereCondition.PropertyCondition(ArticleDao.Properties.Id, " IN (" + - FtsDao.getQueryString() + DatabaseUtils.sqlEscapeString(searchQuery) + ")")); + if (FtsDao.isFtsSupported()) { + qb.where(new WhereCondition.PropertyCondition(ArticleDao.Properties.Id, " IN (" + + FtsDao.getQueryString() + DatabaseUtils.sqlEscapeString(searchQuery) + ")")); + } else { + qb.where(ArticleDao.Properties.Title.like("%" + searchQuery + "%")); + } } switch (sortOrder) { diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/JsActionController.java b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/JsActionController.java new file mode 100644 index 000000000..c18f02776 --- /dev/null +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/JsActionController.java @@ -0,0 +1,25 @@ +package fr.gaulupeau.apps.Poche.ui; + +import android.webkit.JavascriptInterface; + +public class JsActionController { + + public interface Callback { + void selectedText(String text); + } + + private final Callback callback; + + public JsActionController(Callback callback) { + this.callback = callback; + } + + @SuppressWarnings("unused") + @JavascriptInterface + public void selectedText(String text) { + if (callback != null) { + callback.selectedText(text); + } + } + +} diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ReadArticleActivity.java b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ReadArticleActivity.java index 6ca77994c..9f09b8f07 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ReadArticleActivity.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/ReadArticleActivity.java @@ -2,6 +2,7 @@ import android.annotation.SuppressLint; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -19,7 +20,6 @@ import android.webkit.ConsoleMessage; import android.webkit.HttpAuthHandler; import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Button; @@ -64,7 +64,7 @@ import fr.gaulupeau.apps.Poche.tts.TtsFragment; import fr.gaulupeau.apps.Poche.tts.TtsHost; -import static android.text.Html.escapeHtml; +import static fr.gaulupeau.apps.Poche.utils.TextTools.escapeHtml; public class ReadArticleActivity extends BaseActionBarActivity { @@ -161,7 +161,9 @@ public void onCreate(Bundle savedInstanceState) { WallabagConnection.initConscrypt(); if (BuildConfig.DEBUG) { - WebView.setWebContentsDebuggingEnabled(true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + WebView.setWebContentsDebuggingEnabled(true); + } } settings = App.getSettings(); @@ -404,17 +406,21 @@ public void onActionModeStarted(ActionMode mode) { mode.getMenuInflater().inflate(R.menu.read_article_activity, menu); menu.findItem(R.id.menu_tag).setOnMenuItemClickListener(item -> { - webViewContent.evaluateJavascript( - "(function(){return window.getSelection().toString()})()", - this::createTagFromSelection); - mode.finish(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webViewContent.evaluateJavascript( + "(function(){return window.getSelection().toString()})()", + this::createTagFromSelection); + mode.finish(); + } else { + webViewContent.loadUrl("javascript:hostActionController.selectedText(window.getSelection().toString())"); + } return true; }); MenuItem annotateItem = menu.findItem(R.id.menu_annotate); if (annotationsEnabled) { annotateItem.setOnMenuItemClickListener(i -> { - webViewContent.evaluateJavascript("invokeAnnotator();", null); + webViewContent.loadUrl("javascript:invokeAnnotator()"); // mode.finish(); // seems to reset selection too early (not on emulator though) return true; }); @@ -620,6 +626,7 @@ private void updatePrevNextButtons() { private void initWebView() { webViewContent.getSettings().setJavaScriptEnabled(true); + initJsActionController(); initTtsController(); initAnnotationController(); @@ -677,10 +684,9 @@ public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); } + @SuppressWarnings("deprecation") // can't use newer method until API 21 @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - String url = request.getUrl().toString(); - + public boolean shouldOverrideUrlLoading(WebView webView, String url) { // If we try to open current URL, do not propose to save it, directly open browser if (url.equals(articleUrl)) { openURL(url); @@ -789,6 +795,12 @@ public boolean onSingleTapConfirmed(MotionEvent e) { webViewContent.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); } + private void initJsActionController() { + JsActionController jsActionController = new JsActionController(this::createTagFromSelection); + + webViewContent.addJavascriptInterface(jsActionController, "hostActionController"); + } + private void initTtsController() { // add the controller now even if TTS is not used, // otherwise it won't be possible to enable TTS without content reloading diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/preferences/SettingsActivity.java b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/preferences/SettingsActivity.java index f408fe6ec..461208d00 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/ui/preferences/SettingsActivity.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/ui/preferences/SettingsActivity.java @@ -5,6 +5,7 @@ import android.app.AlarmManager; import android.content.DialogInterface; import android.content.SharedPreferences; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; @@ -174,6 +175,14 @@ public void onCreate(Bundle savedInstanceState) { handleHttpSchemePreference.setOnPreferenceChangeListener(this); } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CheckBoxPreference ftsIcuTokenizerPreference = (CheckBoxPreference) findPreference( + getString(R.string.pref_key_misc_ftsIcuTokenizer_enabled)); + if (ftsIcuTokenizerPreference != null) { + ftsIcuTokenizerPreference.setEnabled(false); + } + } + ListPreference dbPathListPreference = (ListPreference)findPreference( getString(R.string.pref_key_storage_dbPath)); if(dbPathListPreference != null) { diff --git a/app/src/main/java/fr/gaulupeau/apps/Poche/utils/TextTools.java b/app/src/main/java/fr/gaulupeau/apps/Poche/utils/TextTools.java index 4e8eb5629..c22d49433 100644 --- a/app/src/main/java/fr/gaulupeau/apps/Poche/utils/TextTools.java +++ b/app/src/main/java/fr/gaulupeau/apps/Poche/utils/TextTools.java @@ -4,6 +4,8 @@ import android.text.Html; import android.text.TextUtils; +import androidx.core.text.TextUtilsCompat; + public class TextTools { /** @@ -19,6 +21,14 @@ public static boolean equalOrEmpty(CharSequence s1, CharSequence s2) { || TextUtils.equals(s1, s2); } + public static String escapeHtml(String s) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return Html.escapeHtml(s); + } else { + return TextUtilsCompat.htmlEncode(s); // not sure + } + } + public static String unescapeHtml(String s) { if (s == null) return null; diff --git a/app/src/main/res/layout/activity_manage_article_tags.xml b/app/src/main/res/layout/activity_manage_article_tags.xml index e1f8d7d8a..71ccab1b0 100644 --- a/app/src/main/res/layout/activity_manage_article_tags.xml +++ b/app/src/main/res/layout/activity_manage_article_tags.xml @@ -21,6 +21,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginLeft="@dimen/activity_horizontal_margin" android:text="@string/manageTags_currentTags_none" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" /> diff --git a/app/src/main/res/layout/connection_wizard_provider_selection_fragment.xml b/app/src/main/res/layout/connection_wizard_provider_selection_fragment.xml index 0afb16de1..4a7f06460 100644 --- a/app/src/main/res/layout/connection_wizard_provider_selection_fragment.xml +++ b/app/src/main/res/layout/connection_wizard_provider_selection_fragment.xml @@ -70,6 +70,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableStart="@drawable/ic_qrcode_24dp" + android:drawableLeft="@drawable/ic_qrcode_24dp" android:text="@string/connectionWizard_misc_scanQrCode" /> diff --git a/app/src/main/res/layout/fragment_tts.xml b/app/src/main/res/layout/fragment_tts.xml index e4611011c..e19bc27c8 100644 --- a/app/src/main/res/layout/fragment_tts.xml +++ b/app/src/main/res/layout/fragment_tts.xml @@ -1,4 +1,5 @@ + app:srcCompat="@drawable/ic_snooze_24dp" /> + app:srcCompat="@drawable/ic_delete_black_24dp" /> diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 000000000..1dc9082f5 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4d155b2eb..fe5d947a3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -50,8 +50,6 @@