From a002e9c9e73d0950a6b2c76ca34bd299a43e6a5e Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Thu, 2 May 2024 18:20:13 +0800 Subject: [PATCH 1/2] target sdk 34 --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 5 +- .../java/com/maary/liveinpeace/Constants.kt | 3 + .../liveinpeace/service/QSTileService.kt | 168 +++++++++++++----- .../res/drawable/outline_battery_saver_24.xml | 5 + .../outline_notifications_active_24.xml | 5 + app/src/main/res/layout/item_connection.xml | 16 +- app/src/main/res/values/strings.xml | 6 + build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 10 files changed, 161 insertions(+), 57 deletions(-) create mode 100644 app/src/main/res/drawable/outline_battery_saver_24.xml create mode 100644 app/src/main/res/drawable/outline_notifications_active_24.xml diff --git a/app/build.gradle b/app/build.gradle index 3287150..131a3bd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,12 +12,12 @@ keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) android { namespace 'com.maary.liveinpeace' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.maary.liveinpeace" minSdk 31 - targetSdk 33 + targetSdk 34 versionCode 4 versionName "2.1_beta" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5233a50..b9f10f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + + android:exported="false" + android:foregroundServiceType="specialUse"> diff --git a/app/src/main/java/com/maary/liveinpeace/Constants.kt b/app/src/main/java/com/maary/liveinpeace/Constants.kt index 2c5079b..0974f07 100644 --- a/app/src/main/java/com/maary/liveinpeace/Constants.kt +++ b/app/src/main/java/com/maary/liveinpeace/Constants.kt @@ -15,12 +15,14 @@ class Constants { const val PREF_NOTIFY_TEXT_SIZE = "notification_text_size" const val PREF_WATCHING_CONNECTING_TIME = "watching_connecting" const val PREF_ENABLE_EAR_PROTECTION = "ear_protection_enabled" + const val PREF_WELCOME_FINISHED = "welcome_finished" // 设置通知 id const val ID_NOTIFICATION_SETTINGS = 3 // 前台通知 id const val ID_NOTIFICATION_FOREGROUND = 1 const val ID_NOTIFICATION_ALERT = 2 const val ID_NOTIFICATION_PROTECT = 4 + const val ID_NOTIFICATION_WELCOME = 0 // 设置图像式图标 Action const val ACTION_NAME_SET_IMG = "com.maary.liveinpeace.receiver.SettingsReceiver.SetIconImg" // 设置字符式图标 Action @@ -45,6 +47,7 @@ class Constants { const val CHANNEL_ID_SETTINGS = "LIP_SETTINGS" const val CHANNEL_ID_ALERT = "LIP_ALERT" const val CHANNEL_ID_PROTECT = "LIP_PROTECT" + const val CHANNEL_ID_WELCOME = "LIP_WELCOME" // 提醒时间 const val ALERT_TIME: Long = 2*60*60*1000 // 延后时间 diff --git a/app/src/main/java/com/maary/liveinpeace/service/QSTileService.kt b/app/src/main/java/com/maary/liveinpeace/service/QSTileService.kt index 478944c..7fe9171 100644 --- a/app/src/main/java/com/maary/liveinpeace/service/QSTileService.kt +++ b/app/src/main/java/com/maary/liveinpeace/service/QSTileService.kt @@ -1,28 +1,41 @@ package com.maary.liveinpeace.service import android.Manifest +import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager import android.graphics.drawable.Icon +import android.net.Uri import android.os.Build +import android.os.PowerManager import android.provider.Settings import android.service.quicksettings.Tile import android.service.quicksettings.TileService import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.content.edit +import com.maary.liveinpeace.Constants import com.maary.liveinpeace.Constants.Companion.BROADCAST_ACTION_FOREGROUND import com.maary.liveinpeace.Constants.Companion.BROADCAST_FOREGROUND_INTENT_EXTRA import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_ALERT import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_DEFAULT import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_PROTECT import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_SETTINGS +import com.maary.liveinpeace.Constants.Companion.CHANNEL_ID_WELCOME +import com.maary.liveinpeace.Constants.Companion.ID_NOTIFICATION_GROUP_SETTINGS +import com.maary.liveinpeace.Constants.Companion.ID_NOTIFICATION_SETTINGS +import com.maary.liveinpeace.Constants.Companion.ID_NOTIFICATION_WELCOME +import com.maary.liveinpeace.Constants.Companion.PREF_WELCOME_FINISHED import com.maary.liveinpeace.Constants.Companion.REQUESTING_WAIT_MILLIS +import com.maary.liveinpeace.Constants.Companion.SHARED_PREF import com.maary.liveinpeace.R class QSTileService: TileService() { @@ -33,6 +46,54 @@ class QSTileService: TileService() { val tile = qsTile var waitMillis = REQUESTING_WAIT_MILLIS + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (notificationManager.getNotificationChannel(CHANNEL_ID_DEFAULT) == null){ + createNotificationChannel( + NotificationManager.IMPORTANCE_MIN, + CHANNEL_ID_DEFAULT, + resources.getString(R.string.default_channel), + resources.getString(R.string.default_channel_description) + ) + } + if (notificationManager.getNotificationChannel(CHANNEL_ID_SETTINGS) == null) { + createNotificationChannel( + NotificationManager.IMPORTANCE_MIN, + CHANNEL_ID_SETTINGS, + resources.getString(R.string.channel_settings), + resources.getString(R.string.settings_channel_description) + ) + } + if (notificationManager.getNotificationChannel(CHANNEL_ID_ALERT) == null) { + createNotificationChannel( + NotificationManager.IMPORTANCE_HIGH, + CHANNEL_ID_ALERT, + resources.getString(R.string.channel_alert), + resources.getString(R.string.alert_channel_description) + ) + } + if (notificationManager.getNotificationChannel(CHANNEL_ID_PROTECT) == null) { + val channel = NotificationChannel( + CHANNEL_ID_PROTECT, + resources.getString(R.string.channel_protection), + NotificationManager.IMPORTANCE_LOW).apply { + description = resources.getString(R.string.protection_channel_description) + enableVibration(false) + setSound(null, null) + } + // Register the channel with the system + notificationManager.createNotificationChannel(channel) + } + if (notificationManager.getNotificationChannel(CHANNEL_ID_WELCOME) == null){ + createNotificationChannel( + NotificationManager.IMPORTANCE_MIN, + CHANNEL_ID_WELCOME, + resources.getString(R.string.welcome_channel), + resources.getString(R.string.welcome_channel_description) + ) + } + + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + while(ActivityCompat.checkSelfPermission( applicationContext, Manifest.permission.POST_NOTIFICATIONS @@ -44,49 +105,27 @@ class QSTileService: TileService() { waitMillis *= 2 } + val sharedPref = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE) + while (!sharedPref.getBoolean(PREF_WELCOME_FINISHED, false)){ + if ( powerManager.isIgnoringBatteryOptimizations(packageName) && + ActivityCompat.checkSelfPermission( + applicationContext, Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED) { + sharedPref.edit { + putBoolean(PREF_WELCOME_FINISHED, true) + } + break + } else { + createWelcomeNotification() + Thread.sleep(waitMillis.toLong()) + waitMillis *= 2 + } + } + val intent = Intent(this, ForegroundService::class.java) if (!ForegroundService.isForegroundServiceRunning()){ - - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (notificationManager.getNotificationChannel(CHANNEL_ID_DEFAULT) == null){ - createNotificationChannel( - NotificationManager.IMPORTANCE_MIN, - CHANNEL_ID_DEFAULT, - resources.getString(R.string.default_channel), - resources.getString(R.string.default_channel_description) - ) - } - if (notificationManager.getNotificationChannel(CHANNEL_ID_SETTINGS) == null) { - createNotificationChannel( - NotificationManager.IMPORTANCE_MIN, - CHANNEL_ID_SETTINGS, - resources.getString(R.string.channel_settings), - resources.getString(R.string.settings_channel_description) - ) - } - if (notificationManager.getNotificationChannel(CHANNEL_ID_ALERT) == null) { - createNotificationChannel( - NotificationManager.IMPORTANCE_HIGH, - CHANNEL_ID_ALERT, - resources.getString(R.string.channel_alert), - resources.getString(R.string.alert_channel_description) - ) - } - if (notificationManager.getNotificationChannel(CHANNEL_ID_PROTECT) == null) { - val channel = NotificationChannel( - CHANNEL_ID_PROTECT, - resources.getString(R.string.channel_protection), - NotificationManager.IMPORTANCE_LOW).apply { - description = resources.getString(R.string.protection_channel_description) - enableVibration(false) - setSound(null, null) - } - // Register the channel with the system - notificationManager.createNotificationChannel(channel) - } - applicationContext.startForegroundService(intent) tile.state = Tile.STATE_ACTIVE tile.icon = Icon.createWithResource(this, R.drawable.icon_qs_one) @@ -101,11 +140,12 @@ class QSTileService: TileService() { tile.updateTile() } + @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun onStartListening() { super.onStartListening() val intentFilter = IntentFilter() intentFilter.addAction(BROADCAST_ACTION_FOREGROUND) - registerReceiver(foregroundServiceReceiver, intentFilter) + registerReceiver(foregroundServiceReceiver, intentFilter, RECEIVER_NOT_EXPORTED) } override fun onStopListening() { @@ -149,8 +189,48 @@ class QSTileService: TileService() { notificationManager.createNotificationChannel(channel) } - private fun requestNotificationsPermission() = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - putExtra(Settings.EXTRA_APP_PACKAGE, packageName) - }.let(::startActivityAndCollapse) + private fun requestNotificationsPermission() { + val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(Settings.EXTRA_APP_PACKAGE, packageName) + } + val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + startActivityAndCollapse(pendingIntent) + } + } + + @SuppressLint("BatteryLife") + private fun createWelcomeNotification() { + Log.v("MUTE_", "CREATING WELCOME") + val welcome = NotificationCompat.Builder(this, CHANNEL_ID_WELCOME) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_baseline_settings_24) + .setShowWhen(false) + .setContentTitle(getString(R.string.welcome)) + .setContentText(getString(R.string.welcome_description)) + .setOnlyAlertOnce(true) + .setGroupSummary(false) + .setGroup(ID_NOTIFICATION_GROUP_SETTINGS) + + val batteryIntent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + batteryIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .data = Uri.parse("package:$packageName") + + val pendingBatteryIntent = PendingIntent.getActivity(this, 0, batteryIntent, PendingIntent.FLAG_IMMUTABLE) + + val batteryAction = NotificationCompat.Action.Builder( + R.drawable.outline_battery_saver_24, + getString(R.string.request_permission_battery), + pendingBatteryIntent + ).build() + + welcome.addAction(batteryAction) + + val notificationManager: NotificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + notificationManager.notify(ID_NOTIFICATION_WELCOME, welcome.build()) + + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/outline_battery_saver_24.xml b/app/src/main/res/drawable/outline_battery_saver_24.xml new file mode 100644 index 0000000..ed790ee --- /dev/null +++ b/app/src/main/res/drawable/outline_battery_saver_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/outline_notifications_active_24.xml b/app/src/main/res/drawable/outline_notifications_active_24.xml new file mode 100644 index 0000000..32611df --- /dev/null +++ b/app/src/main/res/drawable/outline_notifications_active_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/item_connection.xml b/app/src/main/res/layout/item_connection.xml index e0ece23..8bdc59d 100644 --- a/app/src/main/res/layout/item_connection.xml +++ b/app/src/main/res/layout/item_connection.xml @@ -11,23 +11,25 @@ android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="@id/device_name" + app:layout_constraintBottom_toBottomOf="@id/device_connection_time" android:contentDescription="@string/icon_of_device" tools:src="@drawable/ic_headphone" /> + android:layout_marginEnd="16dp" + android:maxLines="2" + tools:text="HeadphoneHeadphoneHeadphoneHeadphoneHeadphone"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28eedd9..8b5b861 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,12 @@ Ears Protected Protecting ears Auto adjust volume below 25 when connecting to a new device. + Initial Settings + Permission settings for first time use. + Welcome + LiveInPeace needs permissions to work. + Battery + Notification Shhhhhhh… Perfectly Balanced, As All Things Should Be… diff --git a/build.gradle b/build.gradle index 115194f..8d7c329 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.0.2' apply false - id 'com.android.library' version '8.0.2' apply false + id 'com.android.application' version '8.3.1' apply false + id 'com.android.library' version '8.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.9.0-Beta' apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dd4d381..7a55c6d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri May 12 10:24:55 CST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 4b0d04675e205534b8777e729d1a9419af1f8204 Mon Sep 17 00:00:00 2001 From: Maary <24504742+Steve-Mr@users.noreply.github.com> Date: Thu, 2 May 2024 18:28:29 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=B8=AD=E6=96=87=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values-zh-rCN/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ca862f1..2fe72c7 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -43,4 +43,10 @@ 保护了耳朵 保护耳朵 有新设备连接时自动将音量调节至 25 以下 + 初始设置 + 初次使用权限设置 + 欢迎 + LiveInPeace 需要这些权限 + 电池 + 通知 \ No newline at end of file