diff --git a/.gitignore b/.gitignore index d2701ef..3b5b758 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ device-2017-09-01-203214.png phone.psd .#CHANGELOG.md output.json +/private/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0659193..657b41f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ # Changelog / Ad-Free -### v1.0, 2019-07-12 + +### v1.1 +- Add support for miui based devices +- introduce global error handler and option for email bug report on crash +- introduce sc ad detection +- introduce option to dump spotify notification + +### v1.0, 2019-06-27 - Update fdroid store information (thanks @bennettscience) -- Introduce more supported audio formats for local music: mp3, wav, m4a -- Upgrade kotlin internals +- Introduce more supported audio formats for local music: mp3, wav, + m4a +- internal: update build tools ### v0.0.4.6, 2018-02-10 - Fix notification issue introduced with version 0.0.4.4. Ad blocking notification was not shown. diff --git a/app/build.gradle b/app/build.gradle index 777a918..d01d199 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "ch.abertschi.adfree" minSdkVersion 21 targetSdkVersion 27 - versionCode 30 - versionName "1.0" + versionCode 31 + versionName "1.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true } @@ -36,36 +36,36 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:27.0.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - testCompile 'junit:junit:4.12' - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - compile 'org.jetbrains.anko:anko-sdk23:0.9.1' // sdk19, sdk21, sdk23 are also available - compile 'org.jetbrains.anko:anko-support-v4:0.9.1' // In case you need support-v4 bindings - compile 'org.jetbrains.anko:anko-appcompat-v7:0.9.1' // For appcompat-v7 bindings - compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - compile 'com.github.javiersantos:AppUpdater:2.6.1' + implementation 'com.android.support:appcompat-v7:27.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation 'org.jetbrains.anko:anko-sdk23:0.9.1' // sdk19, sdk21, sdk23 are also available + implementation 'org.jetbrains.anko:anko-support-v4:0.9.1' // In case you need support-v4 bindings + implementation 'org.jetbrains.anko:anko-appcompat-v7:0.9.1' // For appcompat-v7 bindings + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + implementation 'com.github.javiersantos:AppUpdater:2.6.1' // compile 'com.github.kittinunf.fuel:fuel-android:1.5.0' implementation 'com.github.kittinunf.fuel:fuel:2.1.0' implementation 'com.github.kittinunf.fuel:fuel-android:2.1.0' - compile 'com.github.bmoliveira:snake-yaml:v1.18-android' + implementation 'com.github.bmoliveira:snake-yaml:v1.18-android' - compile 'io.reactivex.rxjava2:rxandroid:2.0.1' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' // Because RxAndroid releases are few and far between, it is recommended you also // explicitly depend on RxJava's latest version for bug fixes and new features. - compile 'io.reactivex.rxjava2:rxjava:2.0.9' - compile 'com.danikula:videocache:2.6.4' - compile 'com.github.angads25:filepicker:1.1.1' + implementation 'io.reactivex.rxjava2:rxjava:2.0.9' + implementation 'com.danikula:videocache:2.6.4' + implementation 'com.github.angads25:filepicker:1.1.1' - compile('com.thoughtworks.xstream:xstream:1.4.7') { + implementation('com.thoughtworks.xstream:xstream:1.4.7') { exclude group: 'xmlpull', module: 'xmlpull' } - compile 'com.daimajia.easing:library:2.0@aar' - compile 'com.daimajia.androidanimations:library:2.3@aar' + implementation 'com.daimajia.easing:library:2.0@aar' + implementation 'com.daimajia.androidanimations:library:2.3@aar' testCompile 'org.codehaus.groovy:groovy:2.4.11:grooid' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a0042e0..cfaa498 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools" + package="ch.abertschi.adfree"> @@ -13,7 +14,21 @@ android:label="@string/app_name" android:screenOrientation="portrait" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + + + + + + + + diff --git a/app/src/main/java/ch/abertschi/adfree/AdFreeApplication.kt b/app/src/main/java/ch/abertschi/adfree/AdFreeApplication.kt index ed0681c..1fd9a70 100644 --- a/app/src/main/java/ch/abertschi/adfree/AdFreeApplication.kt +++ b/app/src/main/java/ch/abertschi/adfree/AdFreeApplication.kt @@ -20,8 +20,7 @@ import ch.abertschi.adfree.plugin.localmusic.LocalMusicPlugin import ch.abertschi.adfree.plugin.mute.MutePlugin import ch.abertschi.adfree.util.NotificationUtils import org.jetbrains.anko.AnkoLogger -import org.jetbrains.anko.info -import org.jetbrains.anko.warn +import ch.abertschi.adfree.crashhandler.CrashExceptionHandler /** @@ -44,11 +43,18 @@ class AdFreeApplication : Application(), AnkoLogger { override fun onCreate() { super.onCreate() + Thread.setDefaultUncaughtExceptionHandler(CrashExceptionHandler(this)) + prefs = PreferencesFactory(applicationContext) - adDetectors = listOf(NotificationActionDetector() + + adDetectors = listOf( + NotificationActionDetector() , SpotifyTitleDetector(TrackRepository(this, prefs)) - , NotificationBundleAndroidTextDetector(), - ScDetector()) + , NotificationBundleAndroidTextDetector() + , ScDetector() + , MiuiNotificationDetector() +// , SpotifyNotificationTracer(getExternalFilesDir(null)) // TODO: for debug + ) audioManager = AudioController(applicationContext, prefs) remoteManager = RemoteManager(prefs) @@ -70,7 +76,5 @@ class AdFreeApplication : Application(), AnkoLogger { pluginHandler, notificationChannel) adDetector.addObserver(adStateController) - - } } diff --git a/app/src/main/java/ch/abertschi/adfree/AudioController.kt b/app/src/main/java/ch/abertschi/adfree/AudioController.kt index a17b7b0..5062b80 100644 --- a/app/src/main/java/ch/abertschi/adfree/AudioController.kt +++ b/app/src/main/java/ch/abertschi/adfree/AudioController.kt @@ -6,12 +6,14 @@ package ch.abertschi.adfree +import android.annotation.SuppressLint import android.content.Context import android.media.AudioManager import ch.abertschi.adfree.model.PreferencesFactory import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import io.reactivex.schedulers.Schedulers.* import org.jetbrains.anko.AnkoLogger import org.jetbrains.anko.debug import org.jetbrains.anko.info @@ -56,7 +58,7 @@ class AudioController(val context: Context, val prefs: PreferencesFactory) : Ank val am = context.applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, prefs.loadVoiceCallAudioVolume(), AudioManager.FLAG_SHOW_UI) Observable.just(true).delay(8000, TimeUnit.MILLISECONDS) - .subscribeOn(Schedulers.io()) + .subscribeOn(io()) .observeOn(AndroidSchedulers.mainThread()).subscribe { val volume = am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) prefs.storeVoiceCallAudioVolume(volume) @@ -70,7 +72,7 @@ class AudioController(val context: Context, val prefs: PreferencesFactory) : Ank var counter: Int = 0 Observable.just(1).delay(25, TimeUnit.MILLISECONDS) .repeat(times) - .subscribeOn(Schedulers.io()) + .subscribeOn(io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { info { counter } diff --git a/app/src/main/java/ch/abertschi/adfree/NotificationsListeners.kt b/app/src/main/java/ch/abertschi/adfree/NotificationsListeners.kt index c53d0b6..398c0d1 100644 --- a/app/src/main/java/ch/abertschi/adfree/NotificationsListeners.kt +++ b/app/src/main/java/ch/abertschi/adfree/NotificationsListeners.kt @@ -35,8 +35,8 @@ class NotificationsListeners : NotificationListenerService(), AnkoLogger { @Deprecated("for testing only") private fun recordNotification(sbn: StatusBarNotification) { val path = this.getExternalFilesDir(null) - val file = File(path, "adfree.txt") - val ids = File(path, "adfree-ids.txt") + val file = File(path, "adfree-new.txt") + val ids = File(path, "adfree-ids-new.txt") warn { XStream().toXML(sbn) } val stream = FileOutputStream(file, true) diff --git a/app/src/main/java/ch/abertschi/adfree/ad/AdDetector.kt b/app/src/main/java/ch/abertschi/adfree/ad/AdDetector.kt index 4772bba..935a0ad 100644 --- a/app/src/main/java/ch/abertschi/adfree/ad/AdDetector.kt +++ b/app/src/main/java/ch/abertschi/adfree/ad/AdDetector.kt @@ -50,22 +50,6 @@ class AdDetector(val detectors: List, fetchRemote() init = true } - -// if (isAd) { -// info { "ad-detected ###" } -//// info { XStream().toXML(payload) } -// var str = XStream().toXML(payload).trim() -// str = str.replace("\n\r", "") -// val i = str.length / 2 -// System.out.println(str.substring(0, i)) -// System.out.flush() -// System.out.println(str.substring(i + 1)) -// System.out.flush() -// DevelopUtils().serializeAndWriteToFile(payload, "ad") -// } else { -// DevelopUtils().serializeAndWriteToFile(payload, "no_ad") -// } - val eventType = if (isAd) EventType.IS_AD else EventType.NO_AD val event = AdEvent(eventType) submitEvent(event) diff --git a/app/src/main/java/ch/abertschi/adfree/crashhandler/CrashExceptionHandler.kt b/app/src/main/java/ch/abertschi/adfree/crashhandler/CrashExceptionHandler.kt new file mode 100644 index 0000000..9971237 --- /dev/null +++ b/app/src/main/java/ch/abertschi/adfree/crashhandler/CrashExceptionHandler.kt @@ -0,0 +1,85 @@ +package ch.abertschi.adfree.crashhandler + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.os.Build +import android.os.Build.MANUFACTURER +import android.os.Build.MODEL +import android.util.Log +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.text.SimpleDateFormat +import java.util.* +import kotlin.system.exitProcess + +/** + * Capture app crashes and launch Activity to report error + * @author abertschi + */ +class CrashExceptionHandler(val context: Context) : Thread.UncaughtExceptionHandler { + + @SuppressLint("SimpleDateFormat") + override fun uncaughtException(t: Thread?, e: Throwable?) { + e?.printStackTrace() // not all Android versions will print the stack trace automatically + + val (summary, logcat) = generateReport(e) + val filename = writeLogfile(logcat) + + val i = Intent() + i.action = SendCrashReportActivity.ACTION_NAME + i.flags = Intent.FLAG_ACTIVITY_NEW_TASK + i.putExtra(SendCrashReportActivity.EXTRA_LOGFILE, filename) + i.putExtra(SendCrashReportActivity.EXTRA_SUMMARY, summary) + context.startActivity(i) + + System.exit(1) + exitProcess(1) + } + + private fun writeLogfile(logcat: String): String { + val time = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss").format(Date()) + val filename = "adfree-crashlog-${time}.txt" + + val file = File(context.filesDir, filename) + file.writeText(logcat) + return filename + } + + @SuppressLint("SimpleDateFormat") + private fun generateReport(th: Throwable?): Pair { + val manager = context.packageManager + var info: PackageInfo? = null + try { + info = manager.getPackageInfo(context.packageName, 0) + } catch (e2: PackageManager.NameNotFoundException) { + } + + var model = MODEL + if (!model.startsWith(MANUFACTURER)) + model = "$MANUFACTURER $model" + + val summary = StringBuilder() + summary.append("Android version: " + Build.VERSION.SDK_INT + "\n") + summary.append("Device: $model\n") + summary.append("App version: " + (info?.versionCode ?: "(null)") + "\n") + summary.append("Time: " + SimpleDateFormat("yyyy-MM-dd-HH:mm:ss").format(Date()) + "\n") + summary.append("Root cause: \n" + Log.getStackTraceString(th) + "") + + val logcat = StringBuilder() + logcat.append("Logcat messages: \n" + th?.message) + logcat.append(readLogcat()) + return Pair(summary.toString(), logcat.toString()) + } + + private fun readLogcat(): String { + val process = Runtime.getRuntime().exec("logcat -d") + val bufferedReader = BufferedReader( + InputStreamReader(process.inputStream)) + val log = bufferedReader.readText() + return log + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/abertschi/adfree/crashhandler/SendCrashReportActivity.kt b/app/src/main/java/ch/abertschi/adfree/crashhandler/SendCrashReportActivity.kt new file mode 100644 index 0000000..db3ac2f --- /dev/null +++ b/app/src/main/java/ch/abertschi/adfree/crashhandler/SendCrashReportActivity.kt @@ -0,0 +1,127 @@ +package ch.abertschi.adfree.crashhandler + +import android.content.Context +import android.graphics.Typeface +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.text.Html +import android.view.View +import android.widget.TextView +import ch.abertschi.adfree.R +import android.widget.Toast +import org.jetbrains.anko.AnkoLogger +import org.jetbrains.anko.warn +import java.io.File +import java.lang.Exception +import android.content.Intent + +// TODO: refator this into presenter and view +class SendCrashReportActivity : AppCompatActivity(), View.OnClickListener, AnkoLogger { + + companion object { + val ACTION_NAME = "ch.abertschi.adfree.SEND_LOG_CRASH" + val EXTRA_LOGFILE = "ch.abertschi.adfree.extra.logfile" + val EXTRA_SUMMARY = "ch.abertschi.adfree.extra.summary" + val MAIL_ADDR = "apps@abertschi.ch" + val SUBJECT = "[ad-free-crash-report]" + } + + private var logfile: String? = null + private var summary: String? = null + + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + try { + parseIntent(this.intent) + doOnCreate() + } catch (e: Exception) { + warn(e) + Toast.makeText(this, "Error: $e", Toast.LENGTH_LONG).show() + } + } + + + fun parseIntent(i: Intent?) { + logfile = i?.extras?.getString(EXTRA_LOGFILE) + summary = i?.extras?.getString(EXTRA_SUMMARY) ?: "" + + } + + fun sendReport() { + try { + val file = File(applicationContext.filesDir, logfile) + val log = file.readText() + launchSendIntent(summary!!) + } catch (e: Exception) { + + } + } + + private fun launchSendIntent(msg: String) { + val sendIntent = Intent(Intent.ACTION_SEND) + sendIntent.putExtra(Intent.EXTRA_EMAIL, arrayOf(MAIL_ADDR)) + sendIntent.putExtra(Intent.EXTRA_TEXT, msg) + sendIntent.putExtra(Intent.EXTRA_SUBJECT, SUBJECT) + sendIntent.type = "text/plain" + this.applicationContext + .startActivity(Intent.createChooser(sendIntent, "Choose an Email client")) + } + + + private fun doOnCreate() { + setupUI() + } + + // TODO: Send logcat output and summary + private fun setupUI() { + setContentView(R.layout.crash_view) + setFinishOnTouchOutside(false) + val v = findViewById(R.id.crash_container) as View + v.setOnClickListener(this) + + var typeFace: Typeface = Typeface.createFromAsset(baseContext.assets, "fonts/Raleway-ExtraLight.ttf") + + + val title = findViewById(R.id.crash_Title) as TextView + title.typeface = typeFace + + title.setOnClickListener(this) + + val text = + "success is not final, failure is not fatal: it is the " + + "courage to continue that counts. -- " + + "Winston Churchill" + + title?.text = Html.fromHtml(text) + + val subtitle = findViewById(R.id.debugSubtitle) as TextView + subtitle.typeface = typeFace + + subtitle.setOnClickListener(this) + + val subtitletext = + "ad-free crashed. help to continue and " + + "send the crash report." + + + subtitle.text = Html.fromHtml(subtitletext) + } + + override fun onClick(v: View) { + logfile?.let { + try { + sendReport() + } catch (e: Exception) { + warn { "cant send crash report" } + warn { e } + e.printStackTrace() + Toast.makeText(this, "No crash report available.", + Toast.LENGTH_LONG).show() + } + } ?: run { + Toast.makeText(this, "No crash report available.", + Toast.LENGTH_LONG).show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/abertschi/adfree/detector/MiuiNotificationDetector.kt b/app/src/main/java/ch/abertschi/adfree/detector/MiuiNotificationDetector.kt new file mode 100644 index 0000000..170f007 --- /dev/null +++ b/app/src/main/java/ch/abertschi/adfree/detector/MiuiNotificationDetector.kt @@ -0,0 +1,63 @@ +package ch.abertschi.adfree.detector + +import android.app.Notification +import android.os.Bundle +import android.text.SpannableString +import org.jetbrains.anko.AnkoLogger +import org.jetbrains.anko.warn + +/** + * Perform inspection of miui notification bundles + */ +class MiuiNotificationDetector : AbstractStatusBarDetector(), AnkoLogger { + + override fun canHandle(payload: AdPayload): Boolean + = super.canHandle(payload) && payload?.statusbarNotification?.notification != null + + + override fun flagAsAdvertisement(payload: AdPayload): Boolean { + var flagAsAd = false + val bundle = getNotificationBundle(payload!!.statusbarNotification!!.notification) + + // Notification content: + // + // android.title + // + // 0 + // + // + // Advertisement + // + // + bundle.let { + val sp: SpannableString? = bundle?.get("android.title") as SpannableString? + sp?.run { + val count = getSpanCount(this) + flagAsAd = count != null && count == 0 + } + } + return flagAsAd + } + + private fun getSpanCount(sp: SpannableString): Int? { + try { + val f = sp.javaClass.superclass.getDeclaredField("mSpanCount") //NoSuchFieldException + f.isAccessible = true + return f.get(sp) as Int? + } catch (e: Exception) { + warn("Can not access notification mSpanCount with reflection, $e") + } + return null + } + + private fun getNotificationBundle(notification: Notification): Bundle? { + try { + val f = notification.javaClass.getDeclaredField("extras") //NoSuchFieldException + f.isAccessible = true + return f.get(notification) as Bundle + } catch (e: Exception) { + warn("Can not access notification bundle with reflection, $e") + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/abertschi/adfree/detector/NotificationBundleAndroidTextDetector.kt b/app/src/main/java/ch/abertschi/adfree/detector/NotificationBundleAndroidTextDetector.kt index 3f199c6..1ce2deb 100644 --- a/app/src/main/java/ch/abertschi/adfree/detector/NotificationBundleAndroidTextDetector.kt +++ b/app/src/main/java/ch/abertschi/adfree/detector/NotificationBundleAndroidTextDetector.kt @@ -9,6 +9,7 @@ package ch.abertschi.adfree.detector import android.app.Notification import android.os.Bundle import org.jetbrains.anko.AnkoLogger +import org.jetbrains.anko.warn /** @@ -16,28 +17,35 @@ import org.jetbrains.anko.AnkoLogger */ class NotificationBundleAndroidTextDetector : AbstractStatusBarDetector(), AnkoLogger { - override fun canHandle(payload: AdPayload): Boolean - = super.canHandle(payload) && payload?.statusbarNotification?.notification != null + override fun canHandle(payload: AdPayload): Boolean = super.canHandle(payload) + && payload?.statusbarNotification?.notification != null override fun flagAsAdvertisement(payload: AdPayload): Boolean { - val bundle = getNotificationBundle(payload!!.statusbarNotification!!.notification) - var flagAsAd = false - bundle.let { - val androidText: CharSequence? = bundle?.get("android.text") as CharSequence? - flagAsAd = androidText == null - && payload!!.statusbarNotification!!.notification!!.tickerText?.isNotEmpty() ?: false + try { + val bundle = getNotificationBundle(payload!!.statusbarNotification!!.notification) + var flagAsAd = false + bundle.let { + val androidText: CharSequence? = bundle?.get("android.text") as CharSequence? + flagAsAd = androidText == null + && payload!!.statusbarNotification!!.notification!! + .tickerText?.isNotEmpty() ?: false + } + return flagAsAd + + } catch (e: Exception) { + warn(e) } - return flagAsAd + return false } private fun getNotificationBundle(notification: Notification): Bundle? { try { - val f = notification.javaClass.getDeclaredField("extras") //NoSuchFieldException + //NoSuchFieldException + val f = notification.javaClass.getDeclaredField("extras") f.isAccessible = true return f.get(notification) as Bundle } catch (e: Exception) { - error("Can not access notification bundle with reflection, " + e) + error("Can not access notification bundle with reflection, $e") } } - } \ No newline at end of file diff --git a/app/src/main/java/ch/abertschi/adfree/detector/SpotifyNotificationTracer.kt b/app/src/main/java/ch/abertschi/adfree/detector/SpotifyNotificationTracer.kt new file mode 100644 index 0000000..7732ea8 --- /dev/null +++ b/app/src/main/java/ch/abertschi/adfree/detector/SpotifyNotificationTracer.kt @@ -0,0 +1,40 @@ +package ch.abertschi.adfree.detector + +import android.service.notification.StatusBarNotification +import com.thoughtworks.xstream.XStream +import org.jetbrains.anko.AnkoLogger +import org.jetbrains.anko.info +import org.jetbrains.anko.warn +import java.io.File +import java.io.FileOutputStream + +class SpotifyNotificationTracer(val storageFolder: File?) : AdDetectable, AnkoLogger { + + private val SPOTIFY_PACKAGE = "com.spotify" + private val FILENAME = "adfree-spotify.txt" + + override fun canHandle(payload: AdPayload): Boolean { + if (storageFolder == null) { + warn { "Given storageFolder is null, cant work. Disabling functionality ..." } + return false + } + + if (payload?.statusbarNotification?.key?.toLowerCase()?.contains(SPOTIFY_PACKAGE) == true) { + recordNotification(payload.statusbarNotification!!) + } + return false + } + + private fun recordNotification(sbn: StatusBarNotification) { + val file = File(storageFolder, FILENAME) + info { XStream().toXML(sbn) } + info("writing spotify notification content to $file}") + + val stream = FileOutputStream(file, true) + try { + stream.write(XStream().toXML(sbn).toByteArray()) + } finally { + stream.close() + } + } +} diff --git a/app/src/main/java/ch/abertschi/adfree/detector/SpotifyTitleDetector.kt b/app/src/main/java/ch/abertschi/adfree/detector/SpotifyTitleDetector.kt index 55393d8..62e6de9 100644 --- a/app/src/main/java/ch/abertschi/adfree/detector/SpotifyTitleDetector.kt +++ b/app/src/main/java/ch/abertschi/adfree/detector/SpotifyTitleDetector.kt @@ -13,10 +13,16 @@ import org.jetbrains.anko.AnkoLogger * AdDetectable that checks for the Keyword Spotify * * Created by abertschi on 15.04.17. + * + * */ -class SpotifyTitleDetector(val trackRepository: TrackRepository) : AbstractStatusBarDetector(), AnkoLogger { +// TODO: add option to tag ads manually +class SpotifyTitleDetector(val trackRepository: TrackRepository) : + AbstractStatusBarDetector(), AnkoLogger { - private val keyword: String = "Spotify —" + private val keywords = listOf( + "Spotify —" + ,"Advertisement —") override fun canHandle(payload: AdPayload): Boolean { getTitle(payload).let { payload.ignoreKeys.add(it!!) } @@ -24,7 +30,12 @@ class SpotifyTitleDetector(val trackRepository: TrackRepository) : AbstractStatu } override fun flagAsAdvertisement(payload: AdPayload): Boolean - = getTitle(payload)?.toLowerCase()?.trim()?.equals(keyword) ?: false + = getTitle(payload)?.toLowerCase()?.trim()?.run { + var isAdd = false + for(k in keywords) { + isAdd = isAdd || k.toLowerCase() == this + } + isAdd }?: false override fun flagAsMusic(payload: AdPayload): Boolean = getTitle(payload).let { trackRepository.getAllTracks().contains(it) } diff --git a/app/src/main/java/ch/abertschi/adfree/presenter/HomePresenter.kt b/app/src/main/java/ch/abertschi/adfree/presenter/HomePresenter.kt index c0b6eda..c1bf004 100644 --- a/app/src/main/java/ch/abertschi/adfree/presenter/HomePresenter.kt +++ b/app/src/main/java/ch/abertschi/adfree/presenter/HomePresenter.kt @@ -6,6 +6,7 @@ package ch.abertschi.adfree.presenter +import android.annotation.SuppressLint import android.content.Context import android.provider.Settings import ch.abertschi.adfree.model.PreferencesFactory @@ -30,7 +31,6 @@ class HomePresenter(val homeView: HomeView, val preferencesFactory: PreferencesF isInit = true showPermissionRequiredIfNecessary(context) homeView.setPowerState(preferencesFactory.isBlockingEnabled()) - checkForUpdates(context) } fun onResume(context: Context) { @@ -39,7 +39,8 @@ class HomePresenter(val homeView: HomeView, val preferencesFactory: PreferencesF fun hasNotificationPermission(context: Context): Boolean { val permission = - Settings.Secure.getString(context.contentResolver, "enabled_notification_listeners") + Settings.Secure.getString(context.contentResolver, + "enabled_notification_listeners") if (permission == null || !permission.contains(context.packageName)) { return false } @@ -53,23 +54,6 @@ class HomePresenter(val homeView: HomeView, val preferencesFactory: PreferencesF preferencesFactory.setBlockingEnabled(status) } - private fun checkForUpdates(context: Context) { - RemoteManager(preferencesFactory) - .getRemoteSettingsObservable() - .subscribe({ - if (it.useGithubReleasesForUpdateReminder && it.showSeakbarOnUpdate) { - Observable.create { source -> - val updater = UpdateManager(preferencesFactory) - .appUpdaterForInAppUse(context) - updater.start() - source.onComplete() - }.observeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe() - } - }) - } - private fun showPermissionRequiredIfNecessary(context: Context) { if (hasNotificationPermission(context)) { homeView.showEnjoyAdFree() diff --git a/app/src/main/java/ch/abertschi/adfree/util/DevelopUtils.kt b/app/src/main/java/ch/abertschi/adfree/util/DevelopUtils.kt deleted file mode 100644 index 95875b4..0000000 --- a/app/src/main/java/ch/abertschi/adfree/util/DevelopUtils.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Ad Free - * Copyright (c) 2017 by abertschi, www.abertschi.ch - * See the file "LICENSE" for the full license governing this code. - */ - -package ch.abertschi.adfree.util - -import org.jetbrains.anko.AnkoLogger - -/** - * Created by abertschi on 03.09.17. - */ -class DevelopUtils : AnkoLogger { - - fun serializeAndWriteToFile(o: Any, keyword: String = "") { -// -// val serialized = XStream().toXML(o) -// val time = System.currentTimeMillis() -// -//// val logFile = File(ContextCompat.getExternalFilesDirs(), time + "_" + keyword + "_log.file") -// info { "serializing: " + o.javaClass.canonicalName + " " + keyword + " to " + logFile.absolutePath} -// if (!logFile.exists()) { -// try { -// logFile.createNewFile() -// } catch (e: IOException) { -// e.printStackTrace() -// } -// -// } -// try { -// //BufferedWriter for performance, true to set append to file flag -// val buf = BufferedWriter(FileWriter(logFile, false)) -// buf.write(serialized) -// buf.close() -// } catch (e: IOException) { -// e.printStackTrace() -// } - - } -} \ No newline at end of file diff --git a/app/src/main/java/ch/abertschi/adfree/util/Serializer.kt b/app/src/main/java/ch/abertschi/adfree/util/Serializer.kt index 74f8d77..07bf168 100644 --- a/app/src/main/java/ch/abertschi/adfree/util/Serializer.kt +++ b/app/src/main/java/ch/abertschi/adfree/util/Serializer.kt @@ -16,7 +16,8 @@ class Serializer { } companion object { - val instance: ch.abertschi.adfree.util.Serializer by lazy { ch.abertschi.adfree.util.Serializer.Holder.INSTANCE } + val instance: ch.abertschi.adfree.util.Serializer + by lazy { ch.abertschi.adfree.util.Serializer.Holder.INSTANCE } } private val xstream: com.thoughtworks.xstream.XStream = com.thoughtworks.xstream.XStream() diff --git a/app/src/main/java/ch/abertschi/adfree/util/UsageFeedback.kt b/app/src/main/java/ch/abertschi/adfree/util/UsageFeedback.kt deleted file mode 100644 index 0cb99cb..0000000 --- a/app/src/main/java/ch/abertschi/adfree/util/UsageFeedback.kt +++ /dev/null @@ -1,86 +0,0 @@ -///* -// * Ad Free -// * Copyright (c) 2017 by abertschi, www.abertschi.ch -// * See the file "LICENSE" for the full license governing this code. -// */ -// -//package ch.abertschi.adfree.util -// -//import android.content.Context -//import ch.abertschi.adfree.R -//import ch.abertschi.adfree.model.PreferencesFactory -//import com.google.android.gms.analytics.GoogleAnalytics -//import com.google.android.gms.analytics.HitBuilders -//import com.google.android.gms.analytics.Tracker -//import org.jetbrains.anko.AnkoLogger -//import org.jetbrains.anko.info -// -// -///** -// * Created by abertschi on 01.05.17. -// */ -//class UsageFeedback(val context: Context, val prefs: PreferencesFactory) : AnkoLogger { -// -// companion object { -// val ADBLOCK: String = "adblock" -// val TRYOUTPLUGIN: String = "tryoutplugin" -// val ACTION: String = "action" -// val USE: String = "use" -// val FIRST_RUN: String = "first_run" -// } -// -// private var feedback: Tracker? = null -// -// fun trackFirstRun() { -// if (!prefs.isFirstRun()) { -// feedbackFirstRun() -// info { "AdFree first run" } -// } -// prefs.setFirstRun() -// } -// -// fun trackScreen(obj: Any) { -// trackScreen(obj.javaClass.simpleName) -// } -// -// fun trackScreen(name: String) { -// val t = getFeedback() -// t.setScreenName(name) -// t.send(HitBuilders.ScreenViewBuilder().build()) -// } -// -// fun feedbackAdBlock() { -// val t = getFeedback() -// t.send(HitBuilders.EventBuilder() -// .setCategory(ACTION) -// .setAction(ADBLOCK) -// .build()) -// } -// -// fun feedbackTryPlugin() { -// val t = getFeedback() -// t.send(HitBuilders.EventBuilder() -// .setCategory(ACTION) -// .setAction(TRYOUTPLUGIN) -// .build()) -// } -// -// fun feedbackFirstRun() { -// val t = getFeedback() -// t.send(HitBuilders.EventBuilder() -// .setCategory(USE) -// .setAction(FIRST_RUN) -// .build()) -// -// } -// -// @Synchronized -// fun getFeedback(): Tracker { -// if (feedback == null) { -// val analytics = GoogleAnalytics.getInstance(context) -// feedback = analytics.newTracker(R.xml.global_tracker) -// } -// return feedback!! -// } -// -//} diff --git a/app/src/main/java/ch/abertschi/adfree/view/MainActivity.kt b/app/src/main/java/ch/abertschi/adfree/view/MainActivity.kt index cb6df97..c2acc84 100644 --- a/app/src/main/java/ch/abertschi/adfree/view/MainActivity.kt +++ b/app/src/main/java/ch/abertschi/adfree/view/MainActivity.kt @@ -58,6 +58,7 @@ class MainActivity : FragmentActivity() { 0 -> return HomeActivity() 1 -> return SettingsActivity() else -> return AboutActivity() +// else -> return DebugActivity() } } diff --git a/app/src/main/java/ch/abertschi/adfree/view/about/AboutActivity.kt b/app/src/main/java/ch/abertschi/adfree/view/about/AboutActivity.kt index 1c9e946..ca976bd 100644 --- a/app/src/main/java/ch/abertschi/adfree/view/about/AboutActivity.kt +++ b/app/src/main/java/ch/abertschi/adfree/view/about/AboutActivity.kt @@ -6,6 +6,7 @@ package ch.abertschi.adfree.view.setting +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Typeface import android.net.Uri @@ -25,11 +26,9 @@ import ch.abertschi.adfree.view.ViewSettings import ch.abertschi.adfree.view.about.AboutView import org.jetbrains.anko.onClick - /** * Created by abertschi on 21.04.17. */ - class AboutActivity : Fragment(), AboutView { lateinit var typeFace: Typeface @@ -40,6 +39,7 @@ class AboutActivity : Fragment(), AboutView { return inflater?.inflate(R.layout.about_view, container, false) } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) typeFace = ViewSettings.instance(this.context!!).typeFace @@ -56,21 +56,24 @@ class AboutActivity : Fragment(), AboutView { val versionView = view?.findViewById(R.id.version) as TextView - versionView?.typeface = typeFace - versionView?.text = "v${BuildConfig.VERSION_NAME} / ${BuildConfig.VERSION_CODE}" + versionView.typeface = typeFace + versionView.text = "v${BuildConfig.VERSION_NAME} / ${BuildConfig.VERSION_CODE}" + versionView.onClick { + val browserIntent = Intent(Intent.ACTION_VIEW, + Uri.parse("https://github.com/abertschi/ad-free/blob/master/CHANGELOG.md")) + this.context!!.startActivity(browserIntent) + } view.findViewById(R.id.twitter).onClick { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://twitter.com/andrinbertschi?rel=adfree")) - this.getContext()!!.startActivity(browserIntent) + this.context!!.startActivity(browserIntent) } view.findViewById(R.id.website).onClick { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://abertschi.ch?rel=adfree")) - this.getContext()!!.startActivity(browserIntent) + this.context!!.startActivity(browserIntent) } - } - } diff --git a/app/src/main/java/ch/abertschi/adfree/view/home/HomeActivity.kt b/app/src/main/java/ch/abertschi/adfree/view/home/HomeActivity.kt index 957ac0d..5dd278b 100644 --- a/app/src/main/java/ch/abertschi/adfree/view/home/HomeActivity.kt +++ b/app/src/main/java/ch/abertschi/adfree/view/home/HomeActivity.kt @@ -100,7 +100,7 @@ class HomeActivity : Fragment(), HomeView, AnkoLogger { } override fun showEnjoyAdFree() { - val text = "enjoy your ad free music experience" + val text = "enjoy your ad-free music experience." setSloganText(text) enjoySloganText.setOnClickListener(null) powerButton.visibility = View.VISIBLE diff --git a/app/src/main/java/ch/abertschi/adfree/view/setting/SettingsActivity.kt b/app/src/main/java/ch/abertschi/adfree/view/setting/SettingsActivity.kt index a534904..59cf740 100644 --- a/app/src/main/java/ch/abertschi/adfree/view/setting/SettingsActivity.kt +++ b/app/src/main/java/ch/abertschi/adfree/view/setting/SettingsActivity.kt @@ -40,10 +40,11 @@ import org.jetbrains.anko.toast class SettingsActivity : Fragment(), SettingsView, AnkoLogger, PluginActivityAction { override fun signalizeTryOut() { - YoYo.with(Techniques.Shake) - .duration(800) - .repeat(0) - .playOn(activity?.findViewById(R.id.try_plugin_button)) + // TODO: crashes +// YoYo.with(Techniques.Shake) +// .duration(800) +// .repeat(0) +// .playOn(activity?.findViewById(R.id.try_plugin_button)) } private lateinit var typeFace: Typeface diff --git a/app/src/main/res/layout/crash_view.xml b/app/src/main/res/layout/crash_view.xml new file mode 100644 index 0000000..ff5396d --- /dev/null +++ b/app/src/main/res/layout/crash_view.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/debug_view.xml b/app/src/main/res/layout/debug_view.xml new file mode 100644 index 0000000..863a2b5 --- /dev/null +++ b/app/src/main/res/layout/debug_view.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/styles-common.xml b/app/src/main/res/values/styles-common.xml index 74c0031..30dd422 100644 --- a/app/src/main/res/values/styles-common.xml +++ b/app/src/main/res/values/styles-common.xml @@ -6,6 +6,6 @@ @color/colorPrimary @color/colorPrimaryDark #FFFFFF + @color/colorPrimaryDark -