Skip to content

Commit

Permalink
- Target Android 15 (#219)
Browse files Browse the repository at this point in the history
- Support for Android 15 Pixel Launcher embedded Discover feed replacement
- Improved long press listener for scrollable widgets in Expanded Smartspace
- Group notification channels in Notification Target configuration by group, hide IDs when a description is not available
- Performance improvements
- Bug fixes
  • Loading branch information
KieronQuinn authored Jun 20, 2024
1 parent e72a204 commit 629319a
Show file tree
Hide file tree
Showing 27 changed files with 323 additions and 91 deletions.
34 changes: 17 additions & 17 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ plugins {

apply plugin: 'com.google.android.gms.oss-licenses-plugin'

def tagName = '1.7.4'
def tagCode = 174
def tagName = '1.7.5'
def tagCode = 175

android {
compileSdk 34

defaultConfig {
applicationId "com.kieronquinn.app.smartspacer"
minSdk 29
targetSdk 34
targetSdk 35
versionCode tagCode
versionName tagName

Expand Down Expand Up @@ -71,18 +71,18 @@ android {

dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.13.0-alpha02'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.13.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.lifecycle:lifecycle-service:2.8.0"
implementation "androidx.lifecycle:lifecycle-service:2.8.2"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.fragment:fragment-ktx:1.7.1"
implementation "androidx.fragment:fragment-ktx:1.8.0"
implementation "androidx.activity:activity-ktx:1.9.0"
implementation "androidx.work:work-runtime-ktx:2.9.0"
implementation "androidx.core:core-remoteviews:1.0.0"
implementation "androidx.core:core-remoteviews:1.1.0"
implementation "androidx.security:security-crypto:1.1.0-alpha06"
implementation project(path: ':proto')

Expand All @@ -106,11 +106,11 @@ dependencies {
//Bypass hidden API restrictions
implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'

implementation 'com.google.android.gms:play-services-location:21.2.0'
implementation 'com.google.android.gms:play-services-location:21.3.0'
implementation 'com.google.android.gms:play-services-maps:18.2.0'

implementation 'com.github.KieronQuinn:MonetCompat:0.4.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.code.gson:gson:2.11.0'

//Shizuku for Shell service
def shizuku_version = '13.1.5'
Expand All @@ -122,25 +122,25 @@ dependencies {
implementation "com.github.topjohnwu.libsu:service:${libsuVersion}"

//Dependency Injection
implementation "io.insert-koin:koin-android:3.5.3"
implementation "io.insert-koin:koin-android:3.5.6"

implementation 'com.jakewharton:process-phoenix:2.1.2'

//Image Loading
implementation 'com.github.bumptech.glide:glide:4.16.0'
ksp 'com.github.bumptech.glide:ksp:4.15.1'

implementation 'com.google.android.gms:play-services-location:21.2.0'
implementation 'com.google.android.gms:play-services-location:21.3.0'
implementation 'com.google.android.gms:play-services-maps:18.2.0'
implementation 'com.google.maps.android:android-maps-utils:3.8.0'
implementation 'me.saket:better-link-movement-method:2.2.0'

implementation 'com.airbnb.android:lottie:6.3.0'
implementation 'com.airbnb.android:lottie:6.4.1'
implementation 'com.github.skydoves:balloon:1.4.7'
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation "dev.rikka.tools.refine:runtime:$refine_version"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation "com.squareup.retrofit2:retrofit:2.11.0"
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22"
implementation "com.tbuonomo:dotsindicator:5.0"
Expand All @@ -156,10 +156,10 @@ dependencies {
implementation 'com.google.android.flexbox:flexbox:3.0.0'

//OSS libraries activity
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1'
implementation 'com.google.android.gms:play-services-oss-licenses:17.1.0'

//Firebase Analytics + Crashlytics
implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
implementation(platform("com.google.firebase:firebase-bom:33.1.0"))
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-analytics-ktx")

Expand Down
Binary file modified app/release/baselineProfiles/0/app-release.dm
Binary file not shown.
Binary file modified app/release/baselineProfiles/1/app-release.dm
Binary file not shown.
4 changes: 2 additions & 2 deletions app/release/output-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 173,
"versionName": "1.7.3",
"versionCode": 174,
"versionName": "1.7.4",
"outputFile": "app-release.apk"
}
],
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@
android:value="true" />
</activity-alias>

<activity-alias
android:name=".ui.activities.MinusOneExpandedActivity"
android:exported="true"
android:permission="android.permission.SYSTEM_APPLICATION_OVERLAY"
android:targetActivity=".ui.activities.ExpandedActivity">
<intent-filter>
<action android:name="com.android.launcher3.WINDOW_OVERLAY"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity-alias>

<activity
android:name=".ui.activities.configuration.ConfigurationActivity"
android:excludeFromRecents="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.kieronquinn.app.smartspacer.repositories.SearchRepository.SearchApp
import com.kieronquinn.app.smartspacer.repositories.ShizukuServiceRepository
import com.kieronquinn.app.smartspacer.repositories.SmartspaceRepository.SmartspacePageHolder
import com.kieronquinn.app.smartspacer.repositories.SmartspacerSettingsRepository
import com.kieronquinn.app.smartspacer.repositories.SmartspacerSettingsRepository.ExpandedBackground
import com.kieronquinn.app.smartspacer.repositories.SmartspacerSettingsRepository.TintColour
import com.kieronquinn.app.smartspacer.repositories.WallpaperRepository
import com.kieronquinn.app.smartspacer.repositories.WidgetRepository
Expand All @@ -38,6 +39,7 @@ import com.kieronquinn.app.smartspacer.sdk.model.expanded.ExpandedState.BaseShor
import com.kieronquinn.app.smartspacer.ui.screens.expanded.ExpandedSession.Complications.Complication
import com.kieronquinn.app.smartspacer.utils.extensions.getBackgroundColour
import com.kieronquinn.app.smartspacer.utils.extensions.isColorDark
import com.kieronquinn.app.smartspacer.utils.extensions.isDarkMode
import com.kieronquinn.app.smartspacer.utils.extensions.lockscreenShowing
import com.kieronquinn.app.smartspacer.utils.extensions.split
import com.kieronquinn.app.smartspacer.utils.extensions.whenCreated
Expand Down Expand Up @@ -97,12 +99,22 @@ class ExpandedSmartspacerSession(
}
}

private val isSystemDarkMode = resumeBus.mapLatest {
context.isDarkMode
}.stateIn(lifecycleScope, SharingStarted.Eagerly, context.isDarkMode)

private val isDark = combine(
isDarkMode.filterNotNull(),
isSystemDarkMode,
settingsRepository.expandedBackground.asFlow(),
settingsRepository.expandedTintColour.asFlow()
) { dark, mode ->
) { dark, systemDark, background, mode ->
when(mode){
TintColour.AUTOMATIC -> dark
TintColour.AUTOMATIC -> if(background == ExpandedBackground.SOLID) {
!systemDark
} else {
dark
}
TintColour.BLACK -> true
TintColour.WHITE -> false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import com.kieronquinn.app.smartspacer.utils.extensions.toColorOrNull
import com.kieronquinn.app.smartspacer.utils.extensions.toHexString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.properties.ReadWriteProperty
Expand Down Expand Up @@ -389,8 +393,10 @@ abstract class BaseSettingsRepositoryImpl: BaseSettingsRepository {
}

override fun asFlow() = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
trySend(rawSetting ?: default)
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if(key == this@SmartspacerSettingImpl.key) {
trySend(rawSetting ?: default)
}
}
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
trySend(rawSetting ?: default)
Expand All @@ -400,8 +406,10 @@ abstract class BaseSettingsRepositoryImpl: BaseSettingsRepository {
}.flowOn(Dispatchers.IO)

override fun asFlowNullable() = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
trySend(rawSetting)
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
if(key == this@SmartspacerSettingImpl.key) {
trySend(rawSetting)
}
}
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
if(existsSync()) trySend(rawSetting)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.kieronquinn.app.smartspacer.repositories

import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.app.PendingIntent
import android.companion.CompanionDeviceManager
Expand All @@ -20,10 +21,16 @@ import com.kieronquinn.app.smartspacer.receivers.StartShizukuReceiver
import com.kieronquinn.app.smartspacer.sdk.callbacks.IResolveIntentCallback
import com.kieronquinn.app.smartspacer.service.SmartspacerNotificationListenerService
import com.kieronquinn.app.smartspacer.service.SmartspacerNotificationWidgetService
import com.kieronquinn.app.smartspacer.utils.extensions.*
import com.kieronquinn.app.smartspacer.utils.extensions.hasNotificationPermission
import com.kieronquinn.app.smartspacer.utils.extensions.isNotificationServiceEnabled
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import android.app.Notification as AndroidNotification
import com.kieronquinn.app.smartspacer.components.notifications.NotificationChannel as NotificationsNotificationChannel
Expand Down Expand Up @@ -84,6 +91,11 @@ interface NotificationRepository {
*/
fun getNotificationChannelsForPackage(packageName: String): List<NotificationChannel>

/**
* Gets a list of notification channel groups for a given [packageName]
*/
fun getNotificationChannelGroupsForPackage(packageName: String): List<NotificationChannelGroup>

/**
* Returns whether a given [packageName] has a notification listener registered
*/
Expand Down Expand Up @@ -168,6 +180,11 @@ class NotificationRepositoryImpl(
?.filterUncategorised() ?: emptyList()
}

override fun getNotificationChannelGroupsForPackage(packageName: String): List<NotificationChannelGroup> {
return SmartspacerNotificationListenerService.getNotificationChannelGroups(packageName)
?: emptyList()
}

override fun hasNotificationListener(packageName: String): Boolean {
return notificationListeners.value.any { it.packageName == packageName }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kieronquinn.app.smartspacer.service

import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.content.ComponentName
import android.content.Context
import android.media.MediaMetadata
Expand Down Expand Up @@ -43,6 +44,14 @@ class SmartspacerNotificationListenerService: LifecycleNotificationListenerServi
return null
}
}

fun getNotificationChannelGroups(packageName: String): List<NotificationChannelGroup>? {
return try {
INSTANCE?.getNotificationChannelGroups(packageName, Process.myUserHandle())
}catch (e: SecurityException){
return null
}
}
}

private val notificationRepository by inject<NotificationRepository>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import androidx.core.view.WindowCompat
import com.kieronquinn.app.smartspacer.R
import com.kieronquinn.app.smartspacer.repositories.ExpandedRepository
import com.kieronquinn.app.smartspacer.repositories.SmartspacerSettingsRepository
import com.kieronquinn.app.smartspacer.repositories.SmartspacerSettingsRepository.ExpandedBackground
import com.kieronquinn.app.smartspacer.utils.extensions.whenCreated
import com.kieronquinn.monetcompat.app.MonetCompatActivity
import kotlinx.coroutines.flow.drop
import org.koin.android.ext.android.inject

class ExpandedActivity: MonetCompatActivity() {

companion object {
private const val KEY_IS_OVERLAY = "is_overlay"
private const val KEY_UID = "uid"
private const val CLASS_MINUS_ONE =
"com.kieronquinn.app.smartspacer.ui.activities.MinusOneExpandedActivity"

fun createOverlayIntent(context: Context, uid: Int): Intent {
return Intent(context, ExpandedActivity::class.java).apply {
Expand All @@ -42,6 +46,10 @@ class ExpandedActivity: MonetCompatActivity() {
return expandedActivity.intent.getBooleanExtra(KEY_IS_OVERLAY, false)
}

fun isMinusOne(expandedActivity: ExpandedActivity): Boolean {
return expandedActivity.intent.component?.className == CLASS_MINUS_ONE
}

fun getUid(expandedActivity: ExpandedActivity): Int {
return expandedActivity.intent.getIntExtra(KEY_UID, Process.myUid())
}
Expand All @@ -50,13 +58,18 @@ class ExpandedActivity: MonetCompatActivity() {
private val settings by inject<SmartspacerSettingsRepository>()
private val expandedRepository by inject<ExpandedRepository>()

private val isMinusOne by lazy {
intent.component?.className == CLASS_MINUS_ONE
}

override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
}
setupThemeForMinusOne()
super.onCreate(savedInstanceState)
setShowWhenLocked(true)
setShowWhenLocked(!isMinusOne)
WindowCompat.setDecorFitsSystemWindows(window, false)
setupOverlayBackPress()
whenCreated {
Expand All @@ -77,6 +90,29 @@ class ExpandedActivity: MonetCompatActivity() {
}
}

private fun setupThemeForMinusOne() {
if(!isMinusOne) return
settings.expandedBackground.getSync().setMinusOneTheme()
whenCreated {
//Minus one needs to restart on theme changes
settings.expandedBackground.asFlow().drop(1).collect {
finish()
}
}
}

private fun ExpandedBackground.setMinusOneTheme() {
val newTheme = when(this) {
ExpandedBackground.SOLID -> {
R.style.Theme_Smartspacer_MinusOne
}
else -> {
R.style.Theme_Smartspacer_Wallpaper
}
}
setTheme(newTheme)
}

private fun notifyOverlayBackPress() = whenCreated {
expandedRepository.onOverlayBackPressed()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,24 @@ class NotificationTargetConfigurationFragment: BoundFragment<FragmentConfigurati
val header = GenericSettingsItem.Header(
getString(R.string.target_notification_channels)
)
listOf(header) + availableChannels.map {
GenericSettingsItem.SwitchSetting(
options.channels.contains(it.id),
it.name,
it.description?.ifBlank { null } ?: it.id,
null
) { enabled ->
viewModel.onChannelChanged(it.id, enabled)
listOf(header) + availableChannels.toList()
.sortedWith(compareBy(nullsLast()) { it.first?.name?.toString()?.lowercase() })
.flatMap {
listOf(
GenericSettingsItem.Header(
it.first?.name ?: getString(R.string.target_notification_channels_no_group)
)
) + it.second.sortedBy { channel ->
channel.name?.toString()?.lowercase()
}.map { channel ->
GenericSettingsItem.SwitchSetting(
options.channels.contains(channel.id),
channel.name ?: "",
channel.description ?: "",
null
) { enabled ->
viewModel.onChannelChanged(channel.id, enabled)
}
}
}
}else if(options.packageName != null){
Expand Down
Loading

0 comments on commit 629319a

Please sign in to comment.