Skip to content

Commit

Permalink
v1.0.1: improved robustness and implemented 'minimum time between pau…
Browse files Browse the repository at this point in the history
…ses' feature
  • Loading branch information
flxapps committed Jan 8, 2021
1 parent baa1538 commit 024deba
Show file tree
Hide file tree
Showing 35 changed files with 662 additions and 104 deletions.
8 changes: 6 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId "com.flx_apps.digitaldetox"
minSdkVersion 21
targetSdkVersion 29
versionCode 10000
versionName "1.0.0"
versionCode 10001
versionName "1.0.1"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

Expand Down Expand Up @@ -62,6 +62,10 @@ dependencies {
annotationProcessor "org.androidannotations:androidannotations:4.7.0"
implementation "org.androidannotations:androidannotations-api:4.7.0"
kapt "org.androidannotations:androidannotations:4.7.0"

// About Info Screen
implementation 'com.github.ditacristianionut:AppInfoBadge:1.3'

// implementation 'androidx.room:room-runtime:2.3.0-alpha03'
// kapt 'androidx.room:room-compiler:2.3.0-alpha03'

Expand Down
25 changes: 7 additions & 18 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app.name"
android:label="@string/app.name_"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:replace="android:label">

<activity
android:name=".MainActivity"
android:label="@string/app.name"
android:label="@string/app.name_"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -31,8 +33,8 @@

<service
android:name=".DetoxAccessibilityService_"
android:label="@string/app.accessibilityService.name"
android:description="@string/app.accessibilityService.description"
android:label="@string/app.accessibilityService.name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
Expand All @@ -46,24 +48,11 @@
android:name=".QuickSettingsTileService"
android:icon="@drawable/ic_quick_settings_tile"
android:label="@string/app.quickSettingsTile"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
>
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>


<!-- -->
<!-- <service-->
<!-- android:name=".DetoxNotificationListenerService"-->
<!-- android:enabled="true"-->
<!-- android:label="@string/app.notificationListenerService.name"-->
<!-- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.service.notification.NotificationListenerService" />-->
<!-- </intent-filter>-->
<!-- </service>-->
</application>

</manifest>
47 changes: 47 additions & 0 deletions app/src/main/java/com/flx_apps/digitaldetox/AboutFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.flx_apps.digitaldetox

import android.net.Uri
import android.os.Bundle
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.dci.dev.appinfobadge.AppInfoBadge
import com.dci.dev.appinfobadge.BaseInfoItem
import com.dci.dev.appinfobadge.InfoItemWithLink

class AboutFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appInfoBadgeFragment = AppInfoBadge
.headerColor { ContextCompat.getColor(context!!, R.color.colorPrimaryDark) }
.withAppIcon { true }
.withCustomItems { listOf(
InfoItemWithLink(
iconId = R.drawable.ic_coffee,
title = getString(R.string.about_coffee),
link = Uri.parse(getString(R.string.about_coffee_link))
),
InfoItemWithLink(
iconId = R.drawable.ic_patron,
title = getString(R.string.about_patron),
link = Uri.parse(getString(R.string.about_patron_link))
),
InfoItemWithLink(
iconId = R.drawable.ic_contact_mail_3,
title = getString(R.string.about_contact),
link = Uri.parse(getString(R.string.about_contact_link))
),
InfoItemWithLink(
iconId = R.drawable.ic_contact_site_github,
title = getString(R.string.about_github),
link = Uri.parse(getString(R.string.about_github_link))
)
)}
.withPermissions { false }
.withRater { false }
.withChangelog { false }
.withLibraries { false }
.withLicenses { false }
.show()
fragmentManager!!.beginTransaction().replace(R.id.nav_host_fragment, appInfoBadgeFragment).commit()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,50 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.widget.SwitchCompat
import androidx.recyclerview.widget.RecyclerView
import org.androidannotations.annotations.EBean

@EBean
open class AppExceptionsListAdapter(context: Context) : RecyclerView.Adapter<AppExceptionsListAdapter.ViewHolder>() {
lateinit var values: List<AppWhitelistItem>
lateinit var values: List<AppExceptionListItem>

var prefs: Prefs_ = Prefs_(context)

data class AppWhitelistItem(val title: String, val pckg: String) {
data class AppExceptionListItem(val title: String, val pckg: String) {
var isException = false
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_app_whitelist_list_item, parent, false)
.inflate(R.layout.fragment_app_exceptions_list_item, parent, false)
return ViewHolder(view)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
val context = holder.appIcon.context
holder.appTitle.text = item.title
holder.appPackage.text = item.pckg
holder.appIcon.setImageDrawable(holder.appIcon.context.packageManager.getApplicationIcon(item.pckg))
holder.btnSetWhitelisted.setOnCheckedChangeListener { button, b ->
holder.btnToggleExceptionState.setOnCheckedChangeListener { _, b ->
item.isException = b
prefs.edit().grayscaleExceptions().put(
prefs.grayscaleExceptions().get().let {
if (b) it?.plus(item.pckg) else it?.minus(item.pckg)
}
).apply()
}
holder.btnSetWhitelisted.isChecked = item.isException || prefs.grayscaleExceptions().get().contains(item.pckg)
holder.btnToggleExceptionState.isChecked = item.isException || prefs.grayscaleExceptions().get().contains(item.pckg)
}

override fun getItemCount(): Int = values.size

inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val appTitle: TextView = view.findViewById(R.id.appTitle)
val btnSetWhitelisted: CheckBox = view.findViewById(R.id.btnSetWhitelisted)
val appPackage: TextView = view.findViewById(R.id.appPackage)
val btnToggleExceptionState: SwitchCompat = view.findViewById(R.id.btnToggleExceptionState)
val appIcon: ImageView = view.findViewById(R.id.appIcon)

override fun toString(): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ open class AppExceptionsListFragment : Fragment() {
open fun loadApps() {
val pm: PackageManager? = activity?.packageManager
val apps = pm?.getInstalledApplications(0)
val appList: MutableList<AppExceptionsListAdapter.AppWhitelistItem> = ArrayList()
val appList: MutableList<AppExceptionsListAdapter.AppExceptionListItem> = ArrayList()
apps?.iterator()?.forEach {
appList.add(
AppExceptionsListAdapter.AppWhitelistItem(
AppExceptionsListAdapter.AppExceptionListItem(
it.loadLabel(pm) as String,
it.packageName
)
Expand All @@ -43,7 +43,7 @@ open class AppExceptionsListFragment : Fragment() {
}

@UiThread
open fun initAdapter(appList: List<AppExceptionsListAdapter.AppWhitelistItem>) {
open fun initAdapter(appList: List<AppExceptionsListAdapter.AppExceptionListItem>) {
if (!this::list.isInitialized) return

with(list) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package com.flx_apps.digitaldetox

import android.accessibilityservice.AccessibilityService
import android.app.NotificationManager
import android.content.Context
import android.view.KeyEvent
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.view.inputmethod.InputMethodManager
import org.androidannotations.annotations.EService
import org.androidannotations.annotations.SystemService
import org.androidannotations.annotations.sharedpreferences.Pref
Expand All @@ -22,43 +25,66 @@ open class DetoxAccessibilityService : AccessibilityService() {
@SystemService
lateinit var notificationManager: NotificationManager


private var isGrayscale = false
private var lastPackage = ""
private var isPausing = false
private var ignoredEventClasses = mutableSetOf(
"android.inputmethodservice.SoftInputWindow",
"com.android.systemui.volume"
)
private var ignoredPackages = mutableSetOf<String>()

override fun onCreate() {
super.onCreate()

// add all known keyboard packages to list of apps where we will not interfere with grayscale / color settings
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).enabledInputMethodList.forEach {
log(it.packageName)
ignoredPackages.add(it.packageName)
}
}

override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
// log("event=$accessibilityEvent")
log("package=${accessibilityEvent.packageName}, class=${accessibilityEvent.className}, event=${accessibilityEvent.eventType}")

val now = System.currentTimeMillis()
isPausing = now < prefs.pauseUntil().get()

// TYPE_ASSIST_READING_CONTEXT happens when the home button is long-pressed
if (accessibilityEvent.eventType == AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT) {
isPausing = DetoxUtil.togglePause(baseContext)
isGrayscale = !isPausing
}

val exceptions = setOf(
"android.inputmethodservice.SoftInputWindow",
"com.android.systemui.volume.VolumeDialogImpl\$CustomDialog"
)
if (isPausing || accessibilityEvent.packageName == lastPackage || accessibilityEvent.text.isNullOrEmpty() || accessibilityEvent.contentChangeTypes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED || exceptions.contains(
accessibilityEvent.className
)) return
// decide whether we want to handle this event or not
var skipEvent = isPausing
|| ignoredPackages.contains(accessibilityEvent.packageName)
|| accessibilityEvent.packageName == lastPackage
|| accessibilityEvent.text.isNullOrEmpty()
|| accessibilityEvent.contentChangeTypes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
ignoredEventClasses.forEach {
skipEvent = skipEvent || accessibilityEvent.className.contains(it)
}
if (skipEvent) { log("skip..."); return }

lastPackage = accessibilityEvent.packageName.toString()

log(accessibilityEvent.toString())
// forcefully enable DoNotDisturb
DetoxUtil.setZenMode(applicationContext, true)

val grayscale = prefs.grayscaleEnabled().get() && !prefs.grayscaleExceptions().get().contains(
accessibilityEvent.packageName
)
if (grayscale != isGrayscale) {
// decide whether we want to grayscale or color the screen
val grayscale = prefs.grayscaleEnabled().get()
&& !prefs.grayscaleExceptions().get().contains(accessibilityEvent.packageName)
if (grayscale != isGrayscale) { // wantedState != currentState
DetoxUtil.setGrayscale(applicationContext, grayscale)
isGrayscale = grayscale
}
}

override fun onKeyEvent(event: KeyEvent?): Boolean {
// TODO: implement pause feature here?
log("onKeyEvent=$event")
return super.onKeyEvent(event)
}

Expand Down
36 changes: 33 additions & 3 deletions app/src/main/java/com/flx_apps/digitaldetox/DetoxUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object DetoxUtil {
DISPLAY_DALTONIZER,
if (grayscale) 0 else -1
)
return result1 && result2;
return result1 && result2
}

@JvmStatic
Expand Down Expand Up @@ -106,12 +106,42 @@ object DetoxUtil {
): Boolean {
val now = System.currentTimeMillis()
val prefs = Prefs_(context)
val isPausing = !(now < prefs.pauseUntil().get()) // new pause state: inversion of "are we currently pausing?"
var isPausing = (now < prefs.pauseUntil().get())
if (!isPausing && now < prefs.nextPauseAllowedAt().get()) {
// we are currently not in a pause, and a pause is not allowed right now either
Toast.makeText(
context,
context.getString(
R.string.app_quickSettingsTile_noPause,
TimeUnit.MILLISECONDS.toMinutes(prefs.nextPauseAllowedAt().get() - now) + 1
),
Toast.LENGTH_SHORT
).show()
return false
}

isPausing = !isPausing // new pause state: inversion of "are we currently pausing?"
prefs.edit().pauseUntil().put(if (isPausing) now + TimeUnit.MINUTES.toMillis(prefs.pauseDuration().get().toLong()) else -1).apply()
setGrayscale(context, !isPausing)
setZenMode(context, !isPausing)
if (isPausing) {
Toast.makeText(context, context.getString(R.string.app_quickSettingsTile_paused, prefs.pauseDuration().get()), Toast.LENGTH_SHORT).show()
// a pause was made, let's show a hint to the user
Toast.makeText(
context,
context.getString(
R.string.app_quickSettingsTile_paused,
prefs.pauseDuration().get()
),
Toast.LENGTH_SHORT
).show()
prefs.edit().nextPauseAllowedAt()
.put(prefs.pauseUntil().get() + TimeUnit.MINUTES.toMillis(prefs.timeBetweenPauses().get().toLong()))
.apply()
} else {
// a pause was interrupted
prefs.edit().nextPauseAllowedAt()
.put(now + TimeUnit.MINUTES.toMillis(prefs.timeBetweenPauses().get().toLong()))
.apply()
}
return isPausing
}
Expand Down
Loading

0 comments on commit 024deba

Please sign in to comment.