Battery optimization banner

This commit is contained in:
Philipp Heckel 2022-01-18 16:49:00 -05:00
parent 8fcef8ddee
commit 9c616d3b7d
7 changed files with 160 additions and 8 deletions

View file

@ -225,6 +225,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
.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 {
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_BROADCAST_ENABLED = "BroadcastEnabled"
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_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_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 var instance: Repository? = null

View file

@ -5,11 +5,14 @@ import android.animation.AnimatorListenerAdapter
import android.app.AlertDialog
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -20,6 +23,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.work.*
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.firebase.FirebaseMessenger
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.service.SubscriberService
import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.fadeStatusBarColor
import io.heckel.ntfy.util.formatDateShort
import io.heckel.ntfy.util.shortUrl
import io.heckel.ntfy.util.topicShortUrl
import io.heckel.ntfy.util.*
import io.heckel.ntfy.work.PollWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -120,6 +121,34 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
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
dispatcher?.init()
@ -535,6 +564,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant"
const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
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
// (same as the JobScheduler API), but in practice 15 doesn't work. Using 16 here.

View file

@ -3,9 +3,14 @@ package io.heckel.ntfy.util
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.OpenableColumns
import android.provider.Settings
import android.view.Window
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Subscription
import java.security.SecureRandom
@ -185,3 +190,13 @@ fun supportedImage(mimeType: String?): Boolean {
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
}

View 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>

View file

@ -1,15 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">
<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
android:id="@+id/main_subscriptions_list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
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
android:id="@+id/main_subscriptions_list"
android:layout_width="match_parent"
@ -19,13 +95,14 @@
android:background="?android:attr/selectableItemBackground"
app:layoutManager="LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="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
android:layout_width="match_parent"
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"

View file

@ -67,6 +67,12 @@
</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 -->
<string name="add_dialog_title">Subscribe to topic</string>
<string name="add_dialog_description_below">

View 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