Battery optimization banner
This commit is contained in:
parent
8fcef8ddee
commit
9c616d3b7d
7 changed files with 160 additions and 8 deletions
|
@ -225,6 +225,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBatteryOptimizationsRemindTime(): Long {
|
||||||
|
return sharedPrefs.getLong(SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME, BATTERY_OPTIMIZATIONS_REMIND_TIME_ALWAYS)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setBatteryOptimizationsRemindTime(timeMillis: Long) {
|
||||||
|
sharedPrefs.edit()
|
||||||
|
.putLong(SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME, timeMillis)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
fun getUnifiedPushEnabled(): Boolean {
|
fun getUnifiedPushEnabled(): Boolean {
|
||||||
return sharedPrefs.getBoolean(SHARED_PREFS_UNIFIED_PUSH_ENABLED, true) // Enabled by default
|
return sharedPrefs.getBoolean(SHARED_PREFS_UNIFIED_PUSH_ENABLED, true) // Enabled by default
|
||||||
}
|
}
|
||||||
|
@ -350,6 +360,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
|
||||||
const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol"
|
const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol"
|
||||||
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
|
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
|
||||||
const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs"
|
const val SHARED_PREFS_RECORD_LOGS_ENABLED = "RecordLogs"
|
||||||
|
const val SHARED_PREFS_BATTERY_OPTIMIZATIONS_REMIND_TIME = "BatteryOptimizationsRemindTime"
|
||||||
const val SHARED_PREFS_UNIFIED_PUSH_ENABLED = "UnifiedPushEnabled"
|
const val SHARED_PREFS_UNIFIED_PUSH_ENABLED = "UnifiedPushEnabled"
|
||||||
const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL"
|
const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL"
|
||||||
|
|
||||||
|
@ -360,6 +371,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
|
||||||
const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp"
|
const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp"
|
||||||
const val CONNECTION_PROTOCOL_WS = "ws"
|
const val CONNECTION_PROTOCOL_WS = "ws"
|
||||||
|
|
||||||
|
const val BATTERY_OPTIMIZATIONS_REMIND_TIME_ALWAYS = 1L
|
||||||
|
const val BATTERY_OPTIMIZATIONS_REMIND_TIME_NEVER = Long.MAX_VALUE
|
||||||
|
|
||||||
private const val TAG = "NtfyRepository"
|
private const val TAG = "NtfyRepository"
|
||||||
private var instance: Repository? = null
|
private var instance: Repository? = null
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@ import android.animation.AnimatorListenerAdapter
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.ActionMode
|
import android.view.ActionMode
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
@ -20,6 +23,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import io.heckel.ntfy.R
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.app.Application
|
import io.heckel.ntfy.app.Application
|
||||||
|
import io.heckel.ntfy.db.Repository
|
||||||
import io.heckel.ntfy.db.Subscription
|
import io.heckel.ntfy.db.Subscription
|
||||||
import io.heckel.ntfy.firebase.FirebaseMessenger
|
import io.heckel.ntfy.firebase.FirebaseMessenger
|
||||||
import io.heckel.ntfy.log.Log
|
import io.heckel.ntfy.log.Log
|
||||||
|
@ -27,10 +31,7 @@ import io.heckel.ntfy.msg.ApiService
|
||||||
import io.heckel.ntfy.msg.NotificationDispatcher
|
import io.heckel.ntfy.msg.NotificationDispatcher
|
||||||
import io.heckel.ntfy.service.SubscriberService
|
import io.heckel.ntfy.service.SubscriberService
|
||||||
import io.heckel.ntfy.service.SubscriberServiceManager
|
import io.heckel.ntfy.service.SubscriberServiceManager
|
||||||
import io.heckel.ntfy.util.fadeStatusBarColor
|
import io.heckel.ntfy.util.*
|
||||||
import io.heckel.ntfy.util.formatDateShort
|
|
||||||
import io.heckel.ntfy.util.shortUrl
|
|
||||||
import io.heckel.ntfy.util.topicShortUrl
|
|
||||||
import io.heckel.ntfy.work.PollWorker
|
import io.heckel.ntfy.work.PollWorker
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -120,6 +121,34 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
SubscriberServiceManager.refresh(this)
|
SubscriberServiceManager.refresh(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Battery banner
|
||||||
|
val batteryRemindTimeReached = repository.getBatteryOptimizationsRemindTime() < System.currentTimeMillis()
|
||||||
|
val ignoringBatteryOptimizations = isIgnoringBatteryOptimizations(this)
|
||||||
|
val showBatteryBanner = batteryRemindTimeReached && !ignoringBatteryOptimizations
|
||||||
|
val batteryBanner = findViewById<View>(R.id.main_banner_battery)
|
||||||
|
batteryBanner.visibility = if (showBatteryBanner) View.VISIBLE else View.GONE
|
||||||
|
Log.d(TAG, "Battery: ignoring optimizations = $ignoringBatteryOptimizations (we want this to be true); remind time reached = $batteryRemindTimeReached")
|
||||||
|
if (showBatteryBanner) {
|
||||||
|
val dismissButton = findViewById<Button>(R.id.main_banner_battery_dismiss)
|
||||||
|
val askLaterButton = findViewById<Button>(R.id.main_banner_battery_ask_later)
|
||||||
|
val fixNowButton = findViewById<Button>(R.id.main_banner_battery_fix_now)
|
||||||
|
dismissButton.setOnClickListener {
|
||||||
|
batteryBanner.visibility = View.GONE
|
||||||
|
repository.setBatteryOptimizationsRemindTime(Repository.BATTERY_OPTIMIZATIONS_REMIND_TIME_NEVER)
|
||||||
|
}
|
||||||
|
askLaterButton.setOnClickListener {
|
||||||
|
batteryBanner.visibility = View.GONE
|
||||||
|
repository.setBatteryOptimizationsRemindTime(System.currentTimeMillis() + ONE_DAY_MILLIS)
|
||||||
|
}
|
||||||
|
fixNowButton.setOnClickListener {
|
||||||
|
batteryBanner.visibility = View.GONE
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create notification channels right away, so we can configure them immediately after installing the app
|
// Create notification channels right away, so we can configure them immediately after installing the app
|
||||||
dispatcher?.init()
|
dispatcher?.init()
|
||||||
|
|
||||||
|
@ -535,6 +564,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
|
||||||
const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant"
|
const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant"
|
||||||
const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
|
const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
|
||||||
const val ANIMATION_DURATION = 80L
|
const val ANIMATION_DURATION = 80L
|
||||||
|
const val ONE_DAY_MILLIS = 86400000L
|
||||||
|
|
||||||
// As per documentation: The minimum repeat interval that can be defined is 15 minutes
|
// As per documentation: The minimum repeat interval that can be defined is 15 minutes
|
||||||
// (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here.
|
// (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here.
|
||||||
|
|
|
@ -3,9 +3,14 @@ package io.heckel.ntfy.util
|
||||||
import android.animation.ArgbEvaluator
|
import android.animation.ArgbEvaluator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PowerManager
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
|
import io.heckel.ntfy.R
|
||||||
import io.heckel.ntfy.db.Notification
|
import io.heckel.ntfy.db.Notification
|
||||||
import io.heckel.ntfy.db.Subscription
|
import io.heckel.ntfy.db.Subscription
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
@ -185,3 +190,13 @@ fun supportedImage(mimeType: String?): Boolean {
|
||||||
return listOf("image/jpeg", "image/png").contains(mimeType)
|
return listOf("image/jpeg", "image/png").contains(mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if battery optimization is enabled, see https://stackoverflow.com/a/49098293/1440785
|
||||||
|
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
|
||||||
|
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
val appName = context.applicationContext.packageName
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
return powerManager.isIgnoringBatteryOptimizations(appName)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
9
app/src/main/res/drawable/ic_battery_alert_red_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_battery_alert_red_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M15.67,4L14,4L14,2h-4v2L8.33,4C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33L17,5.33C17,4.6 16.4,4 15.67,4zM13,18h-2v-2h2v2zM13,14h-2L11,9h2v5z"
|
||||||
|
android:fillColor="#F44336"/>
|
||||||
|
</vector>
|
|
@ -1,15 +1,91 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:shapeAppearance="?shapeAppearanceLargeComponent" app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:id="@+id/main_banner_battery" android:visibility="visible">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/main_banner_battery_constraint" android:elevation="5dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="28dp"
|
||||||
|
android:layout_height="28dp" app:srcCompat="@drawable/ic_battery_alert_red_24dp"
|
||||||
|
android:id="@+id/main_banner_battery_image"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/main_banner_battery_text"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/main_banner_battery_text"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/main_banner_battery_text"
|
||||||
|
android:layout_marginStart="15dp"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/main_banner_battery_text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/main_banner_battery_text"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginEnd="15dp" android:layout_marginTop="15dp"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/main_banner_battery_image"
|
||||||
|
android:layout_marginStart="10dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/main_banner_battery_ask_later"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:text="@string/main_banner_battery_button_ask_later"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/main_banner_battery_fix_now"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/main_banner_battery_text"
|
||||||
|
android:layout_marginBottom="5dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/main_banner_battery_dismiss"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:text="@string/main_banner_battery_button_dismiss"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/main_banner_battery_ask_later"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/main_banner_battery_text"
|
||||||
|
android:layout_marginBottom="5dp"/>
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/main_banner_battery_fix_now"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:layout_marginEnd="15dp"
|
||||||
|
android:text="@string/main_banner_battery_button_fix_now"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/main_banner_battery_text"
|
||||||
|
android:layout_marginBottom="5dp"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/main_subscriptions_list_container"
|
android:id="@+id/main_subscriptions_list_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:visibility="gone">
|
android:visibility="visible"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/main_banner_battery">
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/main_subscriptions_list"
|
android:id="@+id/main_subscriptions_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -19,13 +95,14 @@
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
app:layoutManager="LinearLayoutManager"/>
|
app:layoutManager="LinearLayoutManager"/>
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
|
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/fab" app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/fab" app:layout_constraintStart_toStartOf="parent"
|
||||||
android:id="@+id/main_no_subscriptions">
|
android:id="@+id/main_no_subscriptions" android:visibility="gone">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"
|
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"
|
||||||
|
|
|
@ -67,6 +67,12 @@
|
||||||
</string>
|
</string>
|
||||||
<string name="main_unified_push_toast">This subscription is managed by %1$s via UnifiedPush</string>
|
<string name="main_unified_push_toast">This subscription is managed by %1$s via UnifiedPush</string>
|
||||||
|
|
||||||
|
<!-- Main activity: Battery banner -->
|
||||||
|
<string name="main_banner_battery_text">Battery optimization should be disabled to avoid issues with notification delivery.</string>
|
||||||
|
<string name="main_banner_battery_button_ask_later">Ask later</string>
|
||||||
|
<string name="main_banner_battery_button_dismiss">Dismiss</string>
|
||||||
|
<string name="main_banner_battery_button_fix_now">Fix now</string>
|
||||||
|
|
||||||
<!-- Add dialog -->
|
<!-- Add dialog -->
|
||||||
<string name="add_dialog_title">Subscribe to topic</string>
|
<string name="add_dialog_title">Subscribe to topic</string>
|
||||||
<string name="add_dialog_description_below">
|
<string name="add_dialog_description_below">
|
||||||
|
|
1
assets/battery_alert_black_24dp.svg
Normal file
1
assets/battery_alert_black_24dp.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V5.33C17 4.6 16.4 4 15.67 4zM13 18h-2v-2h2v2zm0-4h-2V9h2v5z"/></svg>
|
After Width: | Height: | Size: 317 B |
Loading…
Reference in a new issue