Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ class SettingsRepository(
const val KEY_DUO_SHOW_MEDIA = "duo_show_media"
const val KEY_DUO_SHOW_PROGRESS = "duo_show_progress"
const val KEY_DUO_SHOW_FLASHLIGHT = "duo_show_flashlight"
const val KEY_DUO_SHOW_CHARGING_SURGE = "duo_show_charging_surge"
const val KEY_DUO_HIDE_WHEN_SCREEN_OFF = "duo_hide_when_screen_off"
const val KEY_DUO_HIDE_WHEN_SCREEN_OFF_ONLY_IDLE = "duo_hide_when_screen_off_only_idle"
const val KEY_DUO_USE_MATERIAL_YOU = "duo_use_material_you"
Expand Down Expand Up @@ -1408,8 +1409,9 @@ class SettingsRepository(
): Boolean {
return try {
val json = inputStream.bufferedReader().use { it.readText() }
val type = object : com.google.gson.reflect.TypeToken<Map<String, Map<String, Map<String, Any>>>>() {}.type
val allConfigs: Map<String, Map<String, Map<String, Any>>> =
gson.fromJson(json, Map::class.java) as Map<String, Map<String, Map<String, Any>>>
gson.fromJson(json, type) ?: emptyMap()

allConfigs.forEach { (fileName, prefWrapper) ->
val p = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)
Expand Down Expand Up @@ -3109,6 +3111,9 @@ class SettingsRepository(
fun isDuoShowFlashlightEnabled(): Boolean = getBoolean(KEY_DUO_SHOW_FLASHLIGHT, true)
fun setDuoShowFlashlightEnabled(enabled: Boolean) = putBoolean(KEY_DUO_SHOW_FLASHLIGHT, enabled)

fun isDuoShowChargingSurgeEnabled(): Boolean = getBoolean(KEY_DUO_SHOW_CHARGING_SURGE, true)
fun setDuoShowChargingSurgeEnabled(enabled: Boolean) = putBoolean(KEY_DUO_SHOW_CHARGING_SURGE, enabled)

fun isDuoHideWhenScreenOffEnabled(): Boolean = getBoolean(KEY_DUO_HIDE_WHEN_SCREEN_OFF, true)
fun setDuoHideWhenScreenOffEnabled(enabled: Boolean) = putBoolean(KEY_DUO_HIDE_WHEN_SCREEN_OFF, enabled)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,36 @@ object FeatureRegistry {
enabled: Boolean,
) {}
},
object : Feature(
id = "Duo",
title = R.string.duo_title,
iconRes = R.drawable.rounded_motion_play_24,
category = R.string.cat_interface,
description = R.string.duo_desc,
aboutDescription = R.string.duo_desc,
permissionKeys = listOf("ACCESSIBILITY"),
hasMoreSettings = true,
showToggle = true,
isBeta = true,
parentFeatureId = "Display",
) {
override fun isDeviceSupported(context: Context): Boolean {
return true
}

override fun isEnabled(viewModel: MainViewModel) = viewModel.isDuoEnabled.value

override fun isToggleEnabled(
viewModel: MainViewModel,
context: Context,
) = viewModel.isAccessibilityEnabled.value

override fun onToggle(
viewModel: MainViewModel,
context: Context,
enabled: Boolean,
) = viewModel.setDuoEnabled(enabled)
},
object : Feature(
id = "Other customizations",
title = R.string.feat_other_customizations_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,57 @@ class DuoOverlayHandler(
}
}

private var lastChargingState = false

private val batteryReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == Intent.ACTION_BATTERY_CHANGED) {
val action = intent?.action ?: return
if (action == Intent.ACTION_BATTERY_CHANGED ||
action == Intent.ACTION_POWER_CONNECTED ||
action == Intent.ACTION_POWER_DISCONNECTED
) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
if (level >= 0 && scale > 0) {
val batteryPct = (level * 100) / scale
overlayView?.batteryLevel = batteryPct
}
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)
if (level >= 0 && scale > 0) {
val batteryPct = (level * 100) / scale
overlayView?.batteryLevel = batteryPct
}

val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isChargingStatus = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL

val plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val isPlugged = plugged == BatteryManager.BATTERY_PLUGGED_AC ||
plugged == BatteryManager.BATTERY_PLUGGED_USB ||
plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS

val isNowCharging = (isChargingStatus && isPlugged) || action == Intent.ACTION_POWER_CONNECTED

if (action == Intent.ACTION_POWER_DISCONNECTED || (!isNowCharging && lastChargingState)) {
lastChargingState = false
overlayView?.onPowerDisconnected()
} else if (isNowCharging) {
val voltageMv = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1)
val batteryManager = service.getSystemService(Context.BATTERY_SERVICE) as? BatteryManager
val currentNowUa = batteryManager?.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW) ?: 0

val wattage: Float? = if (voltageMv > 0 && currentNowUa != 0 && currentNowUa != Int.MIN_VALUE) {
val currentMa = Math.abs(currentNowUa) / 1000f
val volts = voltageMv / 1000f
(volts * (currentMa / 1000f))
} else null

val isFastCharging = (wattage != null && wattage >= 15f) ||
plugged == BatteryManager.BATTERY_PLUGGED_AC

if (!lastChargingState || action == Intent.ACTION_POWER_CONNECTED) {
lastChargingState = true
overlayView?.triggerChargingSurge(isFastCharging, wattage)
} else {
overlayView?.setChargingState(true, isFastCharging, wattage)
}
}
}
}
}
Expand Down Expand Up @@ -552,7 +594,8 @@ class DuoOverlayHandler(

private fun showOrUpdateOverlay() {
mainHandler.post {
val wm = windowManager ?: return@post
val wm = windowManager ?: (service.getSystemService(AccessibilityService.WINDOW_SERVICE) as? WindowManager) ?: return@post
windowManager = wm

val displayMetrics = DisplayMetrics()
@Suppress("DEPRECATION")
Expand All @@ -566,11 +609,26 @@ class DuoOverlayHandler(
var cameraRadiusPx = 18f * density * settingsRepository.getDuoCameraSize()

@Suppress("DEPRECATION")
val rotation = wm.defaultDisplay.rotation
val rotation = try {
wm.defaultDisplay.rotation
} catch (_: Exception) {
android.view.Surface.ROTATION_0
}

if (settingsRepository.isDuoAutoDetectEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
@Suppress("DEPRECATION")
val cutout = wm.defaultDisplay.cutout
val cutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
try {
wm.currentWindowMetrics.windowInsets.displayCutout
} catch (_: Exception) {
null
} ?: run {
@Suppress("DEPRECATION")
try { wm.defaultDisplay.cutout } catch (_: Exception) { null }
}
} else {
@Suppress("DEPRECATION")
try { wm.defaultDisplay.cutout } catch (_: Exception) { null }
}
if (cutout != null && cutout.boundingRects.isNotEmpty()) {
val targetRect = when (rotation) {
android.view.Surface.ROTATION_90 -> {
Expand Down Expand Up @@ -650,6 +708,7 @@ class DuoOverlayHandler(
this.showMedia = settingsRepository.isDuoShowMediaEnabled()
this.showProgress = settingsRepository.isDuoShowProgressEnabled()
this.showFlashlight = settingsRepository.isDuoShowFlashlightEnabled()
this.showChargingSurge = settingsRepository.isDuoShowChargingSurgeEnabled()
}

if (!isOverlayAdded) {
Expand All @@ -661,6 +720,11 @@ class DuoOverlayHandler(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
isTouchable = false
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
try {
wm.addView(overlayView, params)
isOverlayAdded = true
Expand Down Expand Up @@ -822,9 +886,14 @@ class DuoOverlayHandler(
private fun registerBatteryReceiver() {
if (!isBatteryReceiverRegistered) {
try {
val filter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
addAction(Intent.ACTION_POWER_CONNECTED)
addAction(Intent.ACTION_POWER_DISCONNECTED)
}
val intent = service.registerReceiver(
batteryReceiver,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
filter
)
isBatteryReceiverRegistered = true
intent?.let {
Expand All @@ -833,6 +902,18 @@ class DuoOverlayHandler(
if (level >= 0 && scale > 0) {
overlayView?.batteryLevel = (level * 100) / scale
}
val status = it.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
val isChargingStatus = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
val isPlugged = plugged == BatteryManager.BATTERY_PLUGGED_AC ||
plugged == BatteryManager.BATTERY_PLUGGED_USB ||
plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
if (isChargingStatus && isPlugged) {
lastChargingState = true
val isFast = plugged == BatteryManager.BATTERY_PLUGGED_AC
overlayView?.setChargingState(true, isFast, null)
}
}
} catch (e: Exception) {
Log.e("DuoOverlayHandler", "Failed to register battery receiver", e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import android.os.Handler
import android.os.Looper
import android.os.Vibrator
import android.view.KeyEvent
import android.view.Surface
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import com.sameerasw.essentials.data.repository.SettingsRepository
import com.sameerasw.essentials.domain.HapticFeedbackType
Expand Down Expand Up @@ -494,6 +496,19 @@ class ScreenOffAccessibilityService :
private fun checkFullscreenState() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
try {
val wm = getSystemService(Context.WINDOW_SERVICE) as? WindowManager
@Suppress("DEPRECATION")
val rotation = try {
wm?.defaultDisplay?.rotation ?: Surface.ROTATION_0
} catch (_: Exception) {
Surface.ROTATION_0
}
// In portrait orientation, the camera cutout is at the top of the display and Duo must stay visible
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
duoOverlayHandler.setFullscreen(false)
return
}

val currentWindows = windows
if (!currentWindows.isNullOrEmpty()) {
val hasStatusBar = currentWindows.any { it.type == android.view.accessibility.AccessibilityWindowInfo.TYPE_SYSTEM }
Expand All @@ -509,9 +524,15 @@ class ScreenOffAccessibilityService :

val isFullscreen = isCoveringFullDisplay && !hasStatusBar
duoOverlayHandler.setFullscreen(isFullscreen)
} else {
duoOverlayHandler.setFullscreen(false)
}
} else {
duoOverlayHandler.setFullscreen(false)
}
} catch (_: Exception) {}
} catch (_: Exception) {
duoOverlayHandler.setFullscreen(false)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,17 @@ fun DuoSettingsUI(
},
modifier = Modifier.highlight(highlightSetting == "duo_show_flashlight"),
)
IconToggleItem(
iconRes = R.drawable.rounded_bolt_24,
title = stringResource(R.string.duo_show_charging_surge_title),
description = stringResource(R.string.duo_show_charging_surge_desc),
isChecked = viewModel.isDuoShowChargingSurge.value,
onCheckedChange = { checked ->
HapticUtil.performVirtualKeyHaptic(view)
viewModel.setDuoShowChargingSurge(checked)
},
modifier = Modifier.highlight(highlightSetting == "duo_show_charging_surge"),
)
}

Text(
Expand Down
Loading
Loading