Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sleep Timer #20

Merged
merged 3 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android'
id 'kotlin-kapt'
id 'com.google.devtools.ksp'
}

def keystorePropertiesFile = rootProject.file("key.properties")
Expand All @@ -18,14 +18,14 @@ android {
applicationId "com.maary.liveinpeace"
minSdk 31
targetSdk 34
versionCode 4
versionName "2.1_beta"
versionCode 5
versionName "2.2_beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
viewBinding true
dataBinding true
dataBinding false
}
splits {
// Configures multiple APKs based on ABI.
Expand Down Expand Up @@ -85,16 +85,16 @@ android {

dependencies {

implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

implementation "androidx.activity:activity-ktx:1.7.2"
implementation 'androidx.databinding:databinding-runtime:8.0.2'
implementation 'androidx.activity:activity-ktx:1.9.0'
implementation 'androidx.databinding:databinding-runtime:8.4.1'


def lifecycle_version = "2.6.1"
def lifecycle_version = '2.8.0'

// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
Expand All @@ -103,12 +103,12 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"


def room_version = "2.6.0-alpha01"
def room_version = '2.6.1'

implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:2.5.1"
kapt "androidx.room:room-compiler:$room_version"
implementation 'androidx.room:room-ktx:2.6.1'
ksp "androidx.room:room-compiler:$room_version"

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
</intent-filter>
</service>

<service android:name=".service.HistoryTileService"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:icon="@drawable/ic_action_history"
android:label="@string/history"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>

<receiver android:name=".receiver.BootCompleteReceiver"
android:exported="false">
<intent-filter android:priority="1000">
Expand All @@ -62,6 +72,11 @@
android:exported="false">
<intent-filter>
<action android:name="com.maary.liveinpeace.MUTE_MEDIA" />
<action android:name="com.maary.liveinpeace.action.CANCEL" />
<action android:name="com.maary.liveinpeace.action.INCREMENT" />
<action android:name="com.maary.liveinpeace.action.DECREMENT" />
<action android:name="com.maary.liveinpeace.action.TOGGLE" />

</intent-filter>
</receiver>

Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/maary/liveinpeace/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Constants {
const val ID_NOTIFICATION_ALERT = 2
const val ID_NOTIFICATION_PROTECT = 4
const val ID_NOTIFICATION_WELCOME = 0
const val ID_NOTIFICATION_SLEEPTIMER = 5
// 设置图像式图标 Action
const val ACTION_NAME_SET_IMG = "com.maary.liveinpeace.receiver.SettingsReceiver.SetIconImg"
// 设置字符式图标 Action
Expand All @@ -37,6 +38,11 @@ class Constants {
const val ACTION_NAME_SETTINGS = "com.maary.liveinpeace.receiver.SettingsReceiver"
// 静音广播名称
const val BROADCAST_ACTION_MUTE = "com.maary.liveinpeace.MUTE_MEDIA"
const val BROADCAST_ACTION_SLEEPTIMER_CANCEL = "com.maary.liveinpeace.action.CANCEL"
const val BROADCAST_ACTION_SLEEPTIMER_INCREMENT = "com.maary.liveinpeace.action.INCREMENT"
const val BROADCAST_ACTION_SLEEPTIMER_DECREMENT = "com.maary.liveinpeace.action.DECREMENT"
const val BROADCAST_ACTION_SLEEPTIMER_TOGGLE = "com.maary.liveinpeace.sleeptimer.TOGGLE"
const val BROADCAST_ACTION_SLEEPTIMER_UPDATE = "com.maary.liveinpeace.sleeptimer.UPDATE"
// 前台服务状态改变广播
const val BROADCAST_ACTION_FOREGROUND = "com.maary.liveinpeace.ACTION_FOREGROUND_SERVICE_STATE"
const val BROADCAST_FOREGROUND_INTENT_EXTRA = "isForegroundServiceRunning"
Expand All @@ -48,6 +54,7 @@ class Constants {
const val CHANNEL_ID_ALERT = "LIP_ALERT"
const val CHANNEL_ID_PROTECT = "LIP_PROTECT"
const val CHANNEL_ID_WELCOME = "LIP_WELCOME"
const val CHANNEL_ID_SLEEPTIMER = "LIP_SLEEPTIMER"
// 提醒时间
const val ALERT_TIME: Long = 2*60*60*1000
// 延后时间
Expand All @@ -57,6 +64,7 @@ class Constants {
const val ID_NOTIFICATION_GROUP_SETTINGS = "LIP_notification_group_settings"
const val ID_NOTIFICATION_GROUP_ALERTS = "LIP_notification_group_alerts"
const val ID_NOTIFICATION_GROUP_PROTECT = "LIP_notification_group_protect"
const val ID_NOTIFICATION_GROUP_SLEEPTIMER = "LIP_notification_group_sleeptimer"
const val PATTERN_DATE_DATABASE = "yyyy-MM-dd"
const val PATTERN_DATE_BUTTON = "MM/dd"
}
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/java/com/maary/liveinpeace/HistoryActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
Expand Down Expand Up @@ -61,7 +60,7 @@ class HistoryActivity : AppCompatActivity(), DeviceMapChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = DataBindingUtil.setContentView(this, R.layout.activity_history)
binding = ActivityHistoryBinding.inflate(layoutInflater)
setContentView(binding.root)

var pickedDate : String = LocalDate.now().toString()
Expand Down
103 changes: 103 additions & 0 deletions app/src/main/java/com/maary/liveinpeace/SleepNotification.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.maary.liveinpeace

import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Icon
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_MUTE
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_CANCEL
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_DECREMENT
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_INCREMENT
import java.text.DateFormat
import java.util.Date
import java.util.concurrent.TimeUnit
import com.maary.liveinpeace.SleepNotification.Action.CANCEL
import com.maary.liveinpeace.SleepNotification.Action.INCREMENT
import com.maary.liveinpeace.SleepNotification.Action.DECREMENT
import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_SLEEPTIMER
import com.maary.liveinpeace.Constants.Companion.ID_NOTIFICATION_GROUP_SLEEPTIMER
import com.maary.liveinpeace.Constants.Companion.ID_NOTIFICATION_SLEEPTIMER
import com.maary.liveinpeace.receiver.MuteMediaReceiver


object SleepNotification {

private val TIMEOUT_INITIAL_MILLIS = TimeUnit.MINUTES.toMillis(30)
private val TIMEOUT_INCREMENT_MILLIS = TimeUnit.MINUTES.toMillis(10)
private val TIMEOUT_DECREMENT_MILLIS = TimeUnit.MINUTES.toMillis(10)

private enum class Action(private val value: String) {
CANCEL(BROADCAST_ACTION_SLEEPTIMER_CANCEL) {
override fun title(context: Context) = context.getText(android.R.string.cancel)
},
INCREMENT(BROADCAST_ACTION_SLEEPTIMER_INCREMENT) {
override fun title(context: Context) = "+" + TimeUnit.MILLISECONDS.toMinutes(TIMEOUT_INCREMENT_MILLIS)
},
DECREMENT(BROADCAST_ACTION_SLEEPTIMER_DECREMENT) {
override fun title(context: Context) = "-" + TimeUnit.MILLISECONDS.toMinutes(TIMEOUT_DECREMENT_MILLIS)
},
;

companion object {
fun parse(value: String?): Action? = entries.firstOrNull { it.value == value }
}

fun intent(context: Context): Intent = Intent(context, MuteMediaReceiver::class.java).setAction(value)

fun pendingIntent(context: Context, cancel: Boolean = false): PendingIntent? =
PendingIntent.getBroadcast(context, 0, intent(context), PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ).apply { if (cancel) cancel() }

fun action(context: Context, cancel: Boolean = false): Notification.Action.Builder =
Notification.Action.Builder(Icon.createWithResource(context, 0), title(context), pendingIntent(context, cancel))

abstract fun title(context: Context): CharSequence?
}

fun Context.notificationManager(): NotificationManager? = getSystemService(NotificationManager::class.java)

fun Context.find() = notificationManager()?.activeNotifications?.firstOrNull { it.id == ID_NOTIFICATION_SLEEPTIMER }?.notification

fun Context.handle(intent: Intent?) = when (Action.parse(intent?.action)) {
INCREMENT -> update(TIMEOUT_INCREMENT_MILLIS)
DECREMENT -> update(-TIMEOUT_DECREMENT_MILLIS)
CANCEL -> cancel()
null -> Unit
}

fun Context.toggle() = if (find() == null) show() else cancel()

private fun Context.cancel() = notificationManager()?.cancel(ID_NOTIFICATION_SLEEPTIMER) ?: Unit

private fun Context.update(timeout: Long) = find()?.let { it.`when` - System.currentTimeMillis() }?.let { if (it > -timeout) it + timeout else it }?.let { show(it) }

private fun Context.show(timeout: Long = TIMEOUT_INITIAL_MILLIS) {
require(timeout > 0)
val eta = System.currentTimeMillis() + timeout

val muteMediaIntent = Intent(this, MuteMediaReceiver::class.java)
muteMediaIntent.action = BROADCAST_ACTION_MUTE
val pendingMuteIntent = PendingIntent.getBroadcast(this, 0, muteMediaIntent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)


val notification = Notification.Builder(this, CHANNEL_ID_SLEEPTIMER)
.setCategory(Notification.CATEGORY_EVENT)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_tile)
.setSubText(DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(eta)))
.setShowWhen(true).setWhen(eta)
.setGroup(ID_NOTIFICATION_GROUP_SLEEPTIMER)
.setUsesChronometer(true).setChronometerCountDown(true)
.setTimeoutAfter(timeout)
.setDeleteIntent(pendingMuteIntent)
.addAction(INCREMENT.action(this).build())
.addAction(DECREMENT.action(this, cancel = timeout <= TIMEOUT_DECREMENT_MILLIS).build())
.addAction(CANCEL.action(this).build())
.build()
notificationManager()?.notify(ID_NOTIFICATION_SLEEPTIMER, notification)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,36 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import com.maary.liveinpeace.Constants
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_MUTE
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_CANCEL
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_DECREMENT
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_INCREMENT
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_TOGGLE
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_UPDATE
import com.maary.liveinpeace.SleepNotification.handle
import com.maary.liveinpeace.SleepNotification.toggle

class MuteMediaReceiver: BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
if (p1?.action == Constants.BROADCAST_ACTION_MUTE){
if (p1?.action == BROADCAST_ACTION_MUTE){
val audioManager = p0?.getSystemService(Context.AUDIO_SERVICE) as AudioManager
do {
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0)
} while (audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) > 0)
}

if (p1?.action == BROADCAST_ACTION_SLEEPTIMER_CANCEL ||
p1?.action == BROADCAST_ACTION_SLEEPTIMER_INCREMENT ||
p1?.action == BROADCAST_ACTION_SLEEPTIMER_DECREMENT) {
p0?.handle(p1)
val intent = Intent(BROADCAST_ACTION_SLEEPTIMER_UPDATE)
p0?.sendBroadcast(intent)
}

if (p1?.action == BROADCAST_ACTION_SLEEPTIMER_TOGGLE) {
p0?.toggle()
val intent = Intent(BROADCAST_ACTION_SLEEPTIMER_UPDATE)
p0?.sendBroadcast(intent)
}
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/maary/liveinpeace/receiver/SleepReceiver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.maary.liveinpeace.receiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_SLEEPTIMER_UPDATE

abstract class SleepReceiver: BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == BROADCAST_ACTION_SLEEPTIMER_UPDATE
) {
updateNotification(context)
}
}

abstract fun updateNotification(context: Context)
}
Loading
Loading