diff options
Diffstat (limited to 'app/src/main')
17 files changed, 157 insertions, 34 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() diff --git a/app/src/main/play/en-US/whatsnew b/app/src/main/play/en-US/whatsnew index df5c33a5..80bd6988 100644 --- a/app/src/main/play/en-US/whatsnew +++ b/app/src/main/play/en-US/whatsnew @@ -4,4 +4,6 @@ v2.3.2 * Update theme * Disable bugsnag completely when opting out of analytics * Filter urls before sending to other apps -* Allow hiding main fab (see settings > newsfeed)
\ No newline at end of file +* Allow hiding main fab (see settings > newsfeed) +* Add some experimental options to debug login problems (settings > experimental) +* Enforce desktop user agent for now
\ No newline at end of file diff --git a/app/src/main/res/values/strings_pref_experimental.xml b/app/src/main/res/values/strings_pref_experimental.xml index 95d54ff2..fad29ff6 100644 --- a/app/src/main/res/values/strings_pref_experimental.xml +++ b/app/src/main/res/values/strings_pref_experimental.xml @@ -8,4 +8,10 @@ <string name="verbose_logging_desc">Enable verbose logging to help with crash reports. Logging will only be sent once an error is encountered, so repeat the issue to notify the dev. This will automatically be disabled if the app restarts.</string> <string name="restart_frost">Restart Frost</string> <string name="restart_frost_desc">Launch a cold restart for the application.</string> + + <!-- Debugging phishing warnings --> + <string name="web_only" translatable="false">Web Only</string> + <string name="web_only_desc" translatable="false">Having troubles? Enable to use web exclusive features. All parsing and background services will be disabled.</string> + <string name="leave_web_only_title" translatable="false">Leave web only mode</string> + <string name="leave_web_only_desc" translatable="false">Currently in web only mode. Would you like to disable it to continue?</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml index 6e23cfb4..6142f595 100644 --- a/app/src/main/res/xml/frost_changelog.xml +++ b/app/src/main/res/xml/frost_changelog.xml @@ -13,7 +13,9 @@ <item text="Disable bugsnag completely when opting out of analytics" /> <item text="Filter urls before sending to other apps" /> <item text="Allow hiding main fab (see settings > newsfeed)" /> - <item text="" /> + <item text="Add some experimental options to debug login problems (settings > experimental)" /> + <item text="Enforce desktop user agent for now" /> + <item text="Obfuscate js tags" /> <version title="v2.3.1" /> <item text="Hide all story panels if enabled" /> |