diff options
Diffstat (limited to 'app/src/main/kotlin')
14 files changed, 145 insertions, 32 deletions
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index d6d6faea..2ebdd215 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -35,7 +35,7 @@ import com.pitchedapps.frost.db.FbTabsDb import com.pitchedapps.frost.db.FrostDatabase import com.pitchedapps.frost.db.NotificationDb import com.pitchedapps.frost.glide.GlideApp -import com.pitchedapps.frost.services.scheduleNotifications +import com.pitchedapps.frost.services.scheduleNotificationsFromPrefs import com.pitchedapps.frost.services.setupNotificationChannels import com.pitchedapps.frost.utils.BuildUtils import com.pitchedapps.frost.utils.FrostPglAdBlock @@ -96,7 +96,7 @@ class FrostApp : Application() { setupNotificationChannels(applicationContext) - applicationContext.scheduleNotifications(Prefs.notificationFreq) + scheduleNotificationsFromPrefs() /** * Drawer profile loading logic diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt index 05321b69..521049e7 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/BaseMainActivity.kt @@ -83,6 +83,7 @@ import com.pitchedapps.frost.facebook.parsers.SearchParser import com.pitchedapps.frost.facebook.profilePictureUrl import com.pitchedapps.frost.fragments.BaseFragment import com.pitchedapps.frost.fragments.WebFragment +import com.pitchedapps.frost.services.scheduleNotificationsFromPrefs import com.pitchedapps.frost.utils.ACTIVITY_SETTINGS import com.pitchedapps.frost.utils.EXTRA_COOKIES import com.pitchedapps.frost.utils.L @@ -90,6 +91,7 @@ import com.pitchedapps.frost.utils.MAIN_TIMEOUT_DURATION import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.REQUEST_FAB import com.pitchedapps.frost.utils.REQUEST_NAV +import com.pitchedapps.frost.utils.REQUEST_NOTIFICATION import com.pitchedapps.frost.utils.REQUEST_REFRESH import com.pitchedapps.frost.utils.REQUEST_RESTART import com.pitchedapps.frost.utils.REQUEST_RESTART_APPLICATION @@ -493,6 +495,9 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, if (hasRequest(REQUEST_FAB)) { fragmentChannel.offer(lastPosition) } + if (hasRequest(REQUEST_NOTIFICATION)) { + scheduleNotificationsFromPrefs() + } } } @@ -635,12 +640,11 @@ abstract class BaseMainActivity : BaseActivity(), MainActivityContract, override fun getPageTitle(position: Int): CharSequence = getString(pages[position].titleId) override fun getItemPosition(fragment: Any) = - if (fragment !is BaseFragment) - POSITION_UNCHANGED - else if (fragment is WebFragment || fragment.valid) - POSITION_UNCHANGED - else - POSITION_NONE + when { + fragment !is BaseFragment -> POSITION_UNCHANGED + fragment is WebFragment || fragment.valid -> POSITION_UNCHANGED + else -> POSITION_NONE + } } override val lowerVideoPadding: PointF diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt index f6316470..032ff31e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbConst.kt @@ -29,12 +29,15 @@ const val FB_LOGIN_URL = "${FB_URL_BASE}login" const val FB_HOME_URL = "${FB_URL_BASE}home.php" // Default user agent -const val USER_AGENT_MOBILE = +private const val USER_AGENT_MOBILE_CONST = "Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A3000) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36" // Desktop agent, for pages like messages -const val USER_AGENT_DESKTOP = +private const val USER_AGENT_DESKTOP_CONST = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Safari/537.36" +const val USER_AGENT_MOBILE = USER_AGENT_DESKTOP_CONST +const val USER_AGENT_DESKTOP = USER_AGENT_DESKTOP_CONST + /** * Animation transition delay, just to ensure that the styles * have properly set in diff --git a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt index 55fbcd40..c580c9ed 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/fragments/FragmentBase.kt @@ -67,7 +67,7 @@ abstract class BaseFragment : Fragment(), CoroutineScope, FragmentContract, Dyna data: FbItem, position: Int ): BaseFragment { - val fragment = if (!useFallback) base() else WebFragment() + val fragment = if (useFallback || Prefs.webOnly) WebFragment() else base() val d = if (data == FbItem.FEED) FeedSort(Prefs.feedSort).item else data fragment.withArguments( ARG_URL to d.url, diff --git a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt index 00c7bcfc..3416c420 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/injectors/JsInjector.kt @@ -17,9 +17,11 @@ package com.pitchedapps.frost.injectors import android.webkit.WebView +import androidx.annotation.VisibleForTesting +import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.web.FrostWebViewClient import org.apache.commons.text.StringEscapeUtils -import java.util.Locale +import kotlin.random.Random class JsBuilder { private val css = StringBuilder() @@ -38,7 +40,7 @@ class JsBuilder { } fun single(tag: String): JsBuilder { - this.tag = "_frost_${tag.toLowerCase(Locale.CANADA)}" + this.tag = TagObfuscator.obfuscateTag(tag) return this } @@ -52,14 +54,19 @@ class JsBuilder { val cssMin = css.replace(Regex("\\s*\n\\s*"), "") append("var a=document.createElement('style');") append("a.innerHTML='$cssMin';") - if (tag != null) append("a.id='$tag';") + if (tag != null) { + append("a.id='$tag';") + } append("document.head.appendChild(a);") } - if (js.isNotBlank()) + if (js.isNotBlank()) { append(js) + } } var content = builder.append("}()").toString() - if (tag != null) content = singleInjector(tag, content) + if (tag != null) { + content = singleInjector(tag, content) + } return content } @@ -102,3 +109,34 @@ class JsInjector(val function: String) : InjectorContract { override fun inject(webView: WebView) = webView.evaluateJavascript(function, null) } + +/** + * Helper object to obfuscate window tags for JS injection. + */ +@VisibleForTesting +internal object TagObfuscator { + + fun obfuscateTag(tag: String): String { + val rnd = Random(tag.hashCode() + salt) + val obfuscated = buildString { + append(prefix) + append('_') + appendRandomChars(rnd, 16) + } + L.v { "TagObfuscator: Obfuscating tag '$tag' to '$obfuscated'" } + return obfuscated + } + + private val salt: Long = System.currentTimeMillis() + + private val prefix: String by lazy { + val rnd = Random(System.currentTimeMillis()) + buildString { appendRandomChars(rnd, 8) } + } + + private fun Appendable.appendRandomChars(random: Random, count: Int) { + for (i in 1..count) { + append('a' + random.nextInt(26)) + } + } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index bb5594fe..cab1311c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -43,6 +43,7 @@ import com.pitchedapps.frost.facebook.parsers.NotifParser import com.pitchedapps.frost.facebook.parsers.ParseNotification import com.pitchedapps.frost.glide.FrostGlide import com.pitchedapps.frost.glide.GlideApp +import com.pitchedapps.frost.settings.hasNotifications import com.pitchedapps.frost.utils.ARG_USER_ID import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs @@ -321,6 +322,12 @@ data class FrostNotification( NotificationManagerCompat.from(context).notify(tag, id, notif.build()) } +fun Context.scheduleNotificationsFromPrefs(): Boolean { + val shouldSchedule = Prefs.hasNotifications + return if (shouldSchedule) scheduleNotifications(Prefs.notificationFreq) + else scheduleNotifications(-1) +} + fun Context.scheduleNotifications(minutes: Long): Boolean = scheduleJob<NotificationService>(NOTIFICATION_PERIODIC_JOB, minutes) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt index 0d707dae..fe59c421 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt @@ -31,6 +31,7 @@ import com.pitchedapps.frost.utils.EnumBundle import com.pitchedapps.frost.utils.EnumBundleCompanion import com.pitchedapps.frost.utils.EnumCompanion import com.pitchedapps.frost.utils.L +import com.pitchedapps.frost.utils.Prefs import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -162,13 +163,17 @@ class FrostRequestService : BaseJobService() { override fun onStartJob(params: JobParameters?): Boolean { super.onStartJob(params) + if (Prefs.webOnly) { + L.i { "Web only; skipping request service" } + return false + } val bundle = params?.extras if (bundle == null) { L.eThrow("Launched ${this::class.java.simpleName} without param data") return false } val cookie = bundle.getCookie() - if (cookie.isNullOrBlank()) { + if (cookie.isBlank()) { L.eThrow("Launched ${this::class.java.simpleName} without cookie") return false } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 95726974..091fbb4b 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -72,6 +72,10 @@ class NotificationService : BaseJobService() { override fun onStartJob(params: JobParameters?): Boolean { super.onStartJob(params) L.i { "Fetching notifications" } + if (Prefs.webOnly) { + L.i { "Web only mode; skipping notification service" } + return false + } launch { try { sendNotifications(params) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt index 7aac7526..d0963665 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Experimental.kt @@ -43,6 +43,13 @@ fun SettingsActivity.getExperimentalPrefs(): KPrefAdapterBuilder.() -> Unit = { // Experimental content starts here ------------------ + checkbox(R.string.web_only, Prefs::webOnly, { + Prefs.webOnly = it + shouldRestartMain() + }) { + descRes = R.string.web_only_desc + } + // Experimental content ends here -------------------- checkbox(R.string.verbose_logging, Prefs::verboseLogging, { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt index 4ef76b3b..ccf04935 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/settings/Notifications.kt @@ -22,6 +22,7 @@ import android.media.RingtoneManager import android.os.Build import android.provider.Settings import ca.allanwang.kau.kpref.activity.KPrefAdapterBuilder +import ca.allanwang.kau.kpref.activity.KPrefItemActions import ca.allanwang.kau.kpref.activity.items.KPrefText import ca.allanwang.kau.utils.materialDialog import ca.allanwang.kau.utils.minuteToText @@ -35,8 +36,8 @@ import com.pitchedapps.frost.activities.SettingsActivity import com.pitchedapps.frost.db.FrostDatabase import com.pitchedapps.frost.db.deleteAll import com.pitchedapps.frost.services.fetchNotifications -import com.pitchedapps.frost.services.scheduleNotifications import com.pitchedapps.frost.utils.Prefs +import com.pitchedapps.frost.utils.REQUEST_NOTIFICATION import com.pitchedapps.frost.utils.frostSnackbar import com.pitchedapps.frost.utils.frostUri import com.pitchedapps.frost.views.Keywords @@ -45,9 +46,27 @@ import kotlinx.coroutines.launch /** * Created by Allan Wang on 2017-06-29. */ + +val Prefs.hasNotifications: Boolean + get() = !webOnly && (notificationsGeneral || notificationsInstantMessages) + @SuppressLint("InlinedApi") fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { + fun KPrefItemActions.leaveWebOnlyDialog() { + if (Prefs.webOnly) { + materialDialog { + title(R.string.leave_web_only_title) + message(R.string.leave_web_only_desc) + positiveButton(R.string.kau_yes) { + Prefs.webOnly = false + reload() + } + negativeButton(R.string.kau_no) + } + } + } + text( R.string.notification_frequency, Prefs::notificationFreq, @@ -63,16 +82,14 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { initialSelection = options.indexOf(item.pref) ) { _, index, _ -> item.pref = options[index] - scheduleNotifications(item.pref) + setFrostResult(REQUEST_NOTIFICATION) } } } - enabler = { - val enabled = Prefs.notificationsGeneral || Prefs.notificationsInstantMessages - if (!enabled) - scheduleNotifications(-1) - enabled + onDisabledClick = { + leaveWebOnlyDialog() } + enabler = { Prefs.hasNotifications } textGetter = { minuteToText(it) } } @@ -97,12 +114,19 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { reloadByTitle(R.string.notification_frequency) }) { descRes = R.string.notification_general_desc + enabler = { !Prefs.webOnly } + onDisabledClick = { + leaveWebOnlyDialog() + } } checkbox(R.string.notification_general_all_accounts, Prefs::notificationAllAccounts, { Prefs.notificationAllAccounts = it }) { descRes = R.string.notification_general_all_accounts_desc - enabler = Prefs::notificationsGeneral + enabler = { !Prefs.webOnly && Prefs.notificationsGeneral } + onDisabledClick = { + leaveWebOnlyDialog() + } } checkbox(R.string.notification_messages, Prefs::notificationsInstantMessages, @@ -113,12 +137,19 @@ fun SettingsActivity.getNotificationPrefs(): KPrefAdapterBuilder.() -> Unit = { reloadByTitle(R.string.notification_frequency) }) { descRes = R.string.notification_messages_desc + enabler = { !Prefs.webOnly } + onDisabledClick = { + leaveWebOnlyDialog() + } } checkbox(R.string.notification_messages_all_accounts, Prefs::notificationsImAllAccounts, { Prefs.notificationsImAllAccounts = it }) { descRes = R.string.notification_messages_all_accounts_desc - enabler = Prefs::notificationsInstantMessages + enabler = { !Prefs.webOnly && Prefs.notificationsInstantMessages } + onDisabledClick = { + leaveWebOnlyDialog() + } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Const.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Const.kt index daca9676..5f65bba1 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Const.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Const.kt @@ -32,5 +32,6 @@ const val REQUEST_TEXT_ZOOM = 1 shl 8 const val REQUEST_NAV = 1 shl 9 const val REQUEST_SEARCH = 1 shl 10 const val REQUEST_FAB = 1 shl 11 +const val REQUEST_NOTIFICATION = 1 shl 12 const val MAIN_TIMEOUT_DURATION = 30 * 60 * 1000 // 30 min diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt index eb8e4b35..f17645d0 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Prefs.kt @@ -191,6 +191,8 @@ object Prefs : KPref() { var showCreateFab: Boolean by kpref("show_create_fab", true) + var webOnly: Boolean by kpref("web_only", false) + inline val mainActivityLayout: MainActivityLayout get() = MainActivityLayout(mainActivityLayoutType) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt index ecedc997..025119aa 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostWebViewClients.kt @@ -91,7 +91,7 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { override fun onPageCommitVisible(view: WebView, url: String?) { super.onPageCommitVisible(view, url) injectBackgroundColor() - if (url.isFacebookUrl) + if (url.isFacebookUrl) { view.jsInject( // CssHider.CORE, CssHider.HEADER, @@ -111,8 +111,9 @@ open class FrostWebViewClient(val web: FrostWebView) : BaseWebViewClient() { JsAssets.CONTEXT_A, JsAssets.MEDIA ) - else + } else { refresh.offer(false) + } } override fun onPageFinished(view: WebView, url: String?) { @@ -212,19 +213,27 @@ class FrostWebViewClientMenu(web: FrostWebView) : FrostWebViewClient(web) { override fun onPageFinished(view: WebView, url: String?) { super.onPageFinished(view, url) - if (url == null) return - if (url.shouldInjectMenu) jsInject(JsAssets.MENU) + if (url == null) { + return + } + if (url.shouldInjectMenu) { + jsInject(JsAssets.MENU) + } } override fun emit(flag: Int) { super.emit(flag) when (flag) { - EMIT_FINISH -> super.injectAndFinish() + EMIT_FINISH -> { + super.injectAndFinish() + } } } override fun onPageFinishedActions(url: String) { v { "Should inject ${url.shouldInjectMenu}" } - if (!url.shouldInjectMenu) injectAndFinish() + if (!url.shouldInjectMenu) { + injectAndFinish() + } } } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt index 09796b15..8e437c29 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/LoginWebView.kt @@ -33,6 +33,7 @@ import com.pitchedapps.frost.db.CookieEntity import com.pitchedapps.frost.facebook.FB_LOGIN_URL import com.pitchedapps.frost.facebook.FB_USER_MATCHER import com.pitchedapps.frost.facebook.FbCookie +import com.pitchedapps.frost.facebook.USER_AGENT_MOBILE import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.injectors.CssHider import com.pitchedapps.frost.injectors.jsInject @@ -57,6 +58,7 @@ class LoginWebView @JvmOverloads constructor( @SuppressLint("SetJavaScriptEnabled") private fun setupWebview() { settings.javaScriptEnabled = true + settings.userAgentString = USER_AGENT_MOBILE setLayerType(View.LAYER_TYPE_HARDWARE, null) webViewClient = LoginClient() webChromeClient = LoginChromeClient() |