Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch

import java.io.IOException

class PreferencesDataStore(
context: Context,
dispatcher: CoroutineDispatcher,
fileName: String,
private val fileName: String,
migrations: List<DataMigration<Preferences>> = emptyList(),
onFileCorrupted: (ex: CorruptionException) -> Preferences = { emptyPreferences() },
) {
Expand All @@ -56,10 +59,20 @@ class PreferencesDataStore(
)

val data: Flow<Preferences> =
dataStore.data
dataStore.data.catch { throwable ->
if (throwable !is IOException) throw throwable

Log.e(TAG, "Can't read preferences file $fileName", throwable)
emit(emptyPreferences())
}

suspend fun edit(transform: suspend (MutablePreferences) -> Unit): Preferences =
dataStore.edit(transform)
try {
dataStore.edit(transform)
} catch (ioEx: IOException) {
Log.e(TAG, "Can't write preferences file $fileName", ioEx)
emptyPreferences()
}
}

inline fun <reified T : Enum<T>> Preferences.getEnum(key: Preferences.Key<String>): T? =
Expand All @@ -68,4 +81,4 @@ inline fun <reified T : Enum<T>> Preferences.getEnum(key: Preferences.Key<String
inline fun <reified T : Enum<T>> MutablePreferences.setEnum(key: Preferences.Key<String>, value: T) =
set(key, value.name)

private const val TAG = "PreferencesDataSource"
private const val TAG = "PreferencesDataSource"
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fun WindowManager.safeAddView(view: View?, params: WindowManager.LayoutParams?):
return try {
addView(view, params)
true
} catch (ex: WindowManager.BadTokenException) {
} catch (_: WindowManager.BadTokenException) {
Log.e(TAG, "Can't add view to window manager, permission is denied !")
false
}
Expand All @@ -40,11 +40,17 @@ fun WindowManager.safeUpdateViewLayout(view: View, params: WindowManager.LayoutP
return try {
updateViewLayout(view, params)
true
} catch (ex: IllegalArgumentException) {
} catch (_: IllegalArgumentException) {
false
}
}

fun WindowManager.safeRemoveView(view: View) {
try {
removeView(view)
} catch (_: IllegalArgumentException) {}
}

fun WindowManager.LayoutParams.disableMoveAnimations() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setCanPlayMoveAnimation(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,11 @@ abstract class BaseOverlay internal constructor(
* @param appContext the Android application context.
*/
private fun newOverlayContext(appContext: Context): Context {
val displayContext = appContext.createDefaultDisplayContext()
val baseContext = if (useWindowContext && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val display = appContext.getSystemService(DisplayManager::class.java)
.getDisplay(Display.DEFAULT_DISPLAY)
appContext.createDisplayContext(display)
.createWindowContext(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null)
displayContext.createWindowContext(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, null)
} else {
appContext
displayContext
}

return if (theme == null) baseContext
Expand All @@ -330,6 +328,21 @@ abstract class BaseOverlay internal constructor(
)
}

/**
* Get a context associated with the default display.
*
* The views of an overlay can request the display of their context (the text selection floating toolbar does, for
* instance). As the application context is not associated with any display, such request throws and crashes the
* application, so the base context of all overlays must be associated with the default display.
*/
private fun Context.createDefaultDisplayContext(): Context {
val display = getSystemService(DisplayManager::class.java)
?.getDisplay(Display.DEFAULT_DISPLAY)
?: return this

return createDisplayContext(display) ?: this
}

override fun dump(writer: PrintWriter, prefix: CharSequence) {
val contentPrefix = prefix.addDumpTabulationLvl()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import com.buzbuz.smartautoclicker.core.base.addDumpTabulationLvl
import com.buzbuz.smartautoclicker.core.base.extensions.disableMoveAnimations
import com.buzbuz.smartautoclicker.core.base.extensions.doWhenMeasured
import com.buzbuz.smartautoclicker.core.base.extensions.safeAddView
import com.buzbuz.smartautoclicker.core.base.extensions.safeRemoveView
import com.buzbuz.smartautoclicker.core.base.extensions.safeUpdateViewLayout
import com.buzbuz.smartautoclicker.core.common.overlays.R
import com.buzbuz.smartautoclicker.core.common.overlays.base.BaseOverlay
Expand Down Expand Up @@ -336,8 +337,8 @@ abstract class OverlayMenu(
positionDataSource.removeOnLockedPositionChangedListener(onLockedPositionChangedListener)
saveMenuPosition(displayConfigManager.displayConfig.orientation)

windowManager.removeView(menuLayout)
screenOverlayView?.let { windowManager.removeView(it) }
windowManager.safeRemoveView(menuLayout)
screenOverlayView?.let { windowManager.safeRemoveView(it) }
screenOverlayView = null

resizeController.release()
Expand Down Expand Up @@ -390,8 +391,8 @@ abstract class OverlayMenu(
lifecycleRegistry.currentState = Lifecycle.State.CREATED

windowManager.apply {
removeView(oldOverlayView)
removeView(menuLayout)
safeRemoveView(oldOverlayView)
safeRemoveView(menuLayout)
screenOverlayView?.let { overlayView ->
if (!safeAddView(overlayView, overlayLayoutParams)) {
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.annotation.CallSuper
import androidx.annotation.StyleRes

import com.buzbuz.smartautoclicker.core.base.extensions.safeAddView
import com.buzbuz.smartautoclicker.core.base.extensions.safeRemoveView
import com.buzbuz.smartautoclicker.core.common.overlays.base.BaseOverlay
import com.buzbuz.smartautoclicker.core.common.overlays.manager.OverlayManager

Expand Down Expand Up @@ -84,6 +85,6 @@ abstract class FullscreenOverlay(@StyleRes theme: Int? = null) : BaseOverlay(the

@CallSuper
override fun onStop() {
windowManager.removeView(view)
windowManager.safeRemoveView(view)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
package com.buzbuz.smartautoclicker.core.common.quality.domain

import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

/** Describe the different quality levels felt by the user. */
sealed class Quality(internal val backToHighDelay: Duration? = null) {
Expand All @@ -30,11 +29,11 @@ sealed class Quality(internal val backToHighDelay: Duration? = null) {
data object High : Quality()

/** The issue has occurred due to external perturbations, such as aggressive background service management */
data object ExternalIssue : Quality(5.minutes)
data object ExternalIssue : Quality(1.seconds)

/** The issue has occurred due to a crash of Smart AutoClicker. */
data object Crashed : Quality(1.hours)
data object Crashed : Quality(1.seconds)

/** The user is using the app for the first time. */
data object FirstTime : Quality(30.minutes)
data object FirstTime : Quality(1.seconds)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.buzbuz.smartautoclicker.feature.notifications.ui

import android.app.Notification
import android.content.Context
import android.widget.FrameLayout
import android.widget.RemoteViews
import androidx.annotation.IdRes
import androidx.core.app.NotificationCompat
Expand Down Expand Up @@ -45,6 +46,19 @@ internal class CustomLayoutNotificationBuilder(
updateState(context, initialState)
}

/**
* Inflate the custom views in our own process.
*
* The system UI inflates them in its own process once the notification is posted, and any failure
* there is reported back as an asynchronous RemoteServiceException that kills the app. Inflating
* here first lets [newServiceNotificationBuilder] fall back to the legacy notification instead.
*/
fun checkCustomViewsInflation(context: Context) {
val parent = FrameLayout(context)
contentView?.apply(context, parent)
bigContentView?.apply(context, parent)
}

override fun updateState(context: Context, state: ServiceNotificationState) {
setCustomContentView(getCustomContentView(context, state))
setCustomBigContentView(getCustomBigContentView(context, state))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.buzbuz.smartautoclicker.feature.notifications.ui

import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat
import com.buzbuz.smartautoclicker.core.base.data.AppComponentsProvider
import com.buzbuz.smartautoclicker.feature.notifications.model.ServiceNotificationState
Expand All @@ -40,9 +41,13 @@ internal fun Context.newServiceNotificationBuilder(

return try {
CustomLayoutNotificationBuilder(this, channelId, initialState, appComponentsProvider)
.apply { checkCustomViewsInflation(this@newServiceNotificationBuilder) }
} catch (ex: Exception) {
// Some devices doesn't support custom views in notification, use the regular format instead
Log.w(TAG, "Can't create the custom views notification, fallback to the legacy one", ex)
LegacyNotificationBuilder(this, channelId, initialState, appComponentsProvider)
}
}

private const val TAG = "ServiceNotificationBuilder"

23 changes: 14 additions & 9 deletions feature/notifications/src/main/res/layout/notification_service.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!--
Sizes are literal values on purpose: this layout is inflated by the system UI process, and some
devices (Huawei on Android 12) fail to resolve @dimen references for the layout params there,
making the notification inflation crash the app with a RemoteServiceException.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_size"
android:layout_height="48dp"
android:orientation="horizontal">

<TextView
Expand All @@ -29,8 +34,8 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="2dp"
android:layout_marginEnd="@dimen/margin_horizontal_default"
android:layout_marginVertical="@dimen/margin_vertical_small"
android:layout_marginEnd="16dp"
android:layout_marginVertical="4dp"
android:textColor="@color/notification_text_color"
android:maxLines="2"
android:gravity="center_vertical"
Expand All @@ -39,17 +44,17 @@
<ImageButton
android:id="@+id/button_play_pause"
style="@style/AppTheme.Notification.Button.Filled"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_marginHorizontal="@dimen/margin_horizontal_small"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginHorizontal="8dp"
android:src="@drawable/ic_notification_play_arrow"/>

<ImageButton
android:id="@+id/button_show_hide"
style="@style/AppTheme.Notification.Button.Outlined"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_marginStart="@dimen/margin_horizontal_small"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_notification_visible_on"/>

</LinearLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!--
Sizes are literal values on purpose: this layout is inflated by the system UI process, and some
devices (Huawei on Android 12) fail to resolve @dimen references for the layout params there,
making the notification inflation crash the app with a RemoteServiceException.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
Expand All @@ -28,15 +33,15 @@
style="@style/AppTheme.TextAppearance.BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_vertical_extra_large"
android:layout_marginBottom="16dp"
android:gravity="center"
android:textColor="@color/notification_text_color"
android:maxLines="1"
tools:text="My cool scenario name"/>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/notification_buttons_size"
android:layout_height="36dp"
android:baselineAligned="false"
android:orientation="horizontal">

Expand All @@ -48,8 +53,8 @@
<ImageButton
android:id="@+id/button_play_pause"
style="@style/AppTheme.Notification.Button.Filled"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/ic_notification_play_arrow" />

Expand All @@ -63,8 +68,8 @@
<ImageButton
android:id="@+id/button_show_hide"
style="@style/AppTheme.Notification.Button.Outlined"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/ic_notification_visible_on"/>

Expand All @@ -78,8 +83,8 @@
<ImageButton
android:id="@+id/button_config"
style="@style/AppTheme.Notification.Button.Outlined"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/ic_notification_settings_filled"/>

Expand All @@ -93,8 +98,8 @@
<ImageButton
android:id="@+id/button_exit"
style="@style/AppTheme.Notification.Button.Outlined"
android:layout_width="@dimen/notification_buttons_size"
android:layout_height="@dimen/notification_buttons_size"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_gravity="center"
android:src="@drawable/ic_notification_cancel"/>

Expand Down
1 change: 0 additions & 1 deletion feature/notifications/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<dimen name="notification_size">48dp</dimen>
<dimen name="notification_max_size_big">252dp</dimen>

<dimen name="notification_buttons_size">36dp</dimen>
Expand Down
1 change: 1 addition & 0 deletions feature/quick-settings-tile/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<activity
android:name=".ui.QSTileLauncherActivity"
android:theme="@style/AppTheme.TranslucentActivity"
android:taskAffinity=""
android:exported="false"
android:excludeFromRecents="true"/>

Expand Down
2 changes: 1 addition & 1 deletion feature/revenue/src/playStore/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
<activity
android:name="com.buzbuz.smartautoclicker.feature.revenue.ui.BillingActivity"
android:theme="@style/AppTheme.TranslucentActivity"
android:taskAffinity=""
android:exported="false"
android:noHistory="true"
android:excludeFromRecents="true"/>

</application>
Expand Down
2 changes: 1 addition & 1 deletion feature/review/src/playStore/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
<activity
android:name="com.buzbuz.smartautoclicker.feature.review.ui.ReviewActivity"
android:theme="@style/AppTheme.TranslucentActivity"
android:taskAffinity=""
android:exported="false"
android:noHistory="true"
android:excludeFromRecents="true"/>

</application>
Expand Down
Loading
Loading