Forráskód Böngészése

Set device wallpaper based on theme (#112)

Co-authored-by: Joshua Kuestersteffen <jkuester@kuester7.com>
Co-authored-by: bakio86 <bakio86@mi.fu-berlin.de>
Hayri Bakici 3 éve
szülő
commit
b42ec632a5

+ 1 - 0
app/src/main/AndroidManifest.xml

@@ -7,6 +7,7 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
         tools:ignore="QueryAllPackagesPermission" />
     <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
+    <uses-permission android:name="android.permission.SET_WALLPAPER"/>
 
 
     <application

+ 97 - 12
app/src/main/java/com/sduduzog/slimlauncher/MainActivity.kt

@@ -1,37 +1,47 @@
 package com.sduduzog.slimlauncher
 
 import android.annotation.SuppressLint
+import android.app.WallpaperManager
 import android.content.SharedPreferences
 import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Canvas
 import android.os.Bundle
 import android.util.Log
 import android.view.GestureDetector
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
 import android.view.View
+import androidx.annotation.ColorInt
+import androidx.annotation.StyleRes
+import androidx.annotation.WorkerThread
 import androidx.appcompat.app.AppCompatActivity
 import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.lifecycle.lifecycleScope
 import androidx.navigation.NavController
 import androidx.navigation.Navigation.findNavController
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.di.MainFragmentFactoryEntryPoint
-import com.sduduzog.slimlauncher.utils.BaseFragment
-import com.sduduzog.slimlauncher.utils.HomeWatcher
-import com.sduduzog.slimlauncher.utils.IPublisher
-import com.sduduzog.slimlauncher.utils.ISubscriber
+import com.sduduzog.slimlauncher.utils.*
 import dagger.hilt.android.AndroidEntryPoint
 import dagger.hilt.android.EntryPointAccessors
-import java.lang.reflect.Method
 import kotlin.math.absoluteValue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.IOException
+import java.lang.reflect.Method
 
 
 @AndroidEntryPoint
 class MainActivity : AppCompatActivity(),
-        SharedPreferences.OnSharedPreferenceChangeListener,
-        HomeWatcher.OnHomePressedListener, IPublisher {
+    SharedPreferences.OnSharedPreferenceChangeListener,
+    HomeWatcher.OnHomePressedListener, IPublisher {
 
     private lateinit var settings: SharedPreferences
     private lateinit var navigator: NavController
     private lateinit var homeWatcher: HomeWatcher
+    private lateinit var unlauncherDataSource: UnlauncherDataSource
+
     private val subscribers: MutableSet<BaseFragment> = mutableSetOf()
 
     override fun attachSubscriber(s: ISubscriber) {
@@ -84,12 +94,16 @@ class MainActivity : AppCompatActivity(),
         homeWatcher.stopWatch()
     }
 
+    override fun onDestroy() {
+        super.onDestroy()
+        settings.unregisterOnSharedPreferenceChangeListener(this)
+    }
+
     override fun onWindowFocusChanged(hasFocus: Boolean) {
         super.onWindowFocusChanged(hasFocus)
         if (hasFocus) toggleStatusBar()
     }
 
-
     override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, s: String?) {
         if (s.equals(getString(R.string.prefs_settings_key_theme), true)) {
             recreate()
@@ -99,12 +113,75 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
-    override fun getTheme(): Resources.Theme {
-        val theme = super.getTheme()
+    override fun onApplyThemeResource(theme: Resources.Theme?, @StyleRes resid: Int, first: Boolean) {
+        super.onApplyThemeResource(theme, resid, first)
+        getUnlaucherDataSource().unlauncherAppsRepo.liveData().observe(this, {
+            if (!it.setThemeWallpaper && getUserSelectedThemeRes() == resid) {
+                // only change the wallpaper when user has allowed it and
+                // preventing to change the wallpaper multiple times once it is rechecked in the settings
+                return@observe
+            }
+            @ColorInt val backgroundColor = getThemeBackgroundColor(theme, resid)
+            if (backgroundColor == Int.MIN_VALUE) {
+                return@observe
+            }
+            lifecycleScope.launch(Dispatchers.IO) {
+                setWallpaperBackgroundColor(backgroundColor)
+            }
+        })
+    }
+
+    /**
+     * @return `Int.MIN_VALUE` if `android.R.attr.colorBackground` of `theme` could not be obtained.
+     */
+    @ColorInt
+    private fun getThemeBackgroundColor(theme: Resources.Theme?, @StyleRes themeRes: Int): Int {
+        val array =  theme?.obtainStyledAttributes(themeRes, intArrayOf(android.R.attr.colorBackground))
+        try {
+            return array?.getColor(0, Int.MIN_VALUE) ?: Int.MIN_VALUE
+        } finally {
+            array?.recycle()
+        }
+    }
+
+    @Throws(IOException::class)
+    @WorkerThread
+    private fun setWallpaperBackgroundColor(@ColorInt color: Int) {
+        val wallpaperManager = WallpaperManager.getInstance(applicationContext)
+        var width = wallpaperManager.desiredMinimumWidth
+        if (width <= 0) {
+            width = getScreenWidth(this)
+        }
+        var height = wallpaperManager.desiredMinimumHeight
+        if (height <= 0) {
+            height = getScreenHeight(this)
+        }
+        val wallpaperBitmap = createColoredWallpaperBitmap(color, width, height)
+        wallpaperManager.setBitmap(wallpaperBitmap)
+    }
+
+    private fun createColoredWallpaperBitmap(@ColorInt color: Int, width: Int, height: Int): Bitmap {
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bitmap)
+        canvas.drawColor(color)
+        return bitmap
+    }
+
+    override fun setTheme(resId: Int) {
+        val userThemeId = getUserSelectedThemeRes()
+        val id = if (resId != userThemeId) {
+            userThemeId
+        } else {
+            resId
+        }
+        super.setTheme(id)
+    }
+
+    @StyleRes
+    private fun getUserSelectedThemeRes(): Int {
         settings = getSharedPreferences(getString(R.string.prefs_settings), MODE_PRIVATE)
         val active = settings.getInt(getString(R.string.prefs_settings_key_theme), 0)
-        theme.applyStyle(resolveTheme(active), true)
-        return theme
+        return resolveTheme(active)
     }
 
     override fun onBackPressed() {
@@ -134,8 +211,16 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
+    private fun getUnlaucherDataSource(): UnlauncherDataSource {
+        if (!::unlauncherDataSource.isInitialized) {
+            unlauncherDataSource = UnlauncherDataSource(this, lifecycleScope)
+        }
+        return unlauncherDataSource
+    }
+
     companion object {
 
+        @StyleRes
         fun resolveTheme(i: Int): Int {
             return when (i) {
                 1 -> R.style.AppThemeDark

+ 9 - 1
app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt

@@ -70,7 +70,7 @@ class UnlauncherAppsRepository(
                 )
             }
 
-            if(appAdded) {
+            if (appAdded) {
                 sortAppsAlphabetically(unlauncherAppsBuilder)
             }
             unlauncherAppsBuilder.build()
@@ -156,6 +156,14 @@ class UnlauncherAppsRepository(
             packageName == app.packageName && className == app.className
         }
     }
+
+    fun updateSetAutomaticDeviceWallpaper(setDeviceWallpaper: Boolean) {
+        lifecycleScope.launch {
+            unlauncherAppsStore.updateData {
+                it.toBuilder().setSetThemeWallpaper(setDeviceWallpaper).build()
+            }
+        }
+    }
 }
 
 fun sortAppsAlphabetically(unlauncherAppsBuilder: UnlauncherApps.Builder) {

+ 1 - 1
app/src/main/java/com/sduduzog/slimlauncher/ui/dialogs/ChangeThemeDialog.kt

@@ -9,7 +9,7 @@ import androidx.core.content.edit
 import androidx.fragment.app.DialogFragment
 import com.sduduzog.slimlauncher.R
 
-class ChangeThemeDialog : DialogFragment(){
+class ChangeThemeDialog : DialogFragment() {
 
     private lateinit var settings: SharedPreferences
 

+ 1 - 6
app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt

@@ -12,10 +12,8 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.view.inputmethod.InputMethodManager
-import androidx.appcompat.app.AppCompatActivity
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener
-import androidx.lifecycle.Observer
 import androidx.lifecycle.lifecycleScope
 import androidx.navigation.Navigation
 import androidx.recyclerview.widget.LinearLayoutManager
@@ -41,10 +39,7 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
     private lateinit var receiver: BroadcastReceiver
     private lateinit var appDrawerAdapter: AppDrawerAdapter
 
-    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
-                              savedInstanceState: Bundle?): View {
-        return inflater.inflate(R.layout.home_fragment, container, false)
-    }
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = inflater.inflate(R.layout.home_fragment, container, false)
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)

+ 64 - 6
app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeAppDrawerFragment.kt

@@ -1,12 +1,17 @@
 package com.sduduzog.slimlauncher.ui.options
 
 import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.TextAppearanceSpan
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.navigation.Navigation
 import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
 import com.sduduzog.slimlauncher.utils.BaseFragment
+import com.sduduzog.slimlauncher.utils.isActivityDefaultLauncher
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.customize_app_drawer_fragment.*
 
@@ -19,9 +24,7 @@ class CustomizeAppDrawerFragment : BaseFragment() {
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
-    ): View? {
-        return inflater.inflate(R.layout.customize_app_drawer_fragment, container, false)
-    }
+    ): View? = inflater.inflate(R.layout.customize_app_drawer_fragment, container, false)
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
@@ -29,12 +32,67 @@ class CustomizeAppDrawerFragment : BaseFragment() {
         customize_app_drawer_fragment_visible_apps
             .setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_customiseAppDrawerFragment_to_customiseAppDrawerAppListFragment))
 
-        val unlauncherAppsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        setupKeyboardSwitch()
+    }
+
+    override fun onStart() {
+        super.onStart()
+        // setting up the switch text, since changing the default launcher re-starts the activity
+        // this should able to adapt to it.
+        setupAutomaticDeviceWallpaperSwitch()
+    }
+
+    private fun setupKeyboardSwitch() {
+        val appsRepo = getUnlauncherDataSource().unlauncherAppsRepo
         customize_app_drawer_open_keyboard_switch.setOnCheckedChangeListener { _, checked ->
-            unlauncherAppsRepo.updateActivateKeyboardInDrawer(checked)
+            appsRepo.updateActivateKeyboardInDrawer(checked)
         }
-        unlauncherAppsRepo.liveData().observe(viewLifecycleOwner) {
+        appsRepo.liveData().observe(viewLifecycleOwner) {
             customize_app_drawer_open_keyboard_switch.isChecked = it.activateKeyboardInDrawer
         }
     }
+
+    private fun setupAutomaticDeviceWallpaperSwitch() {
+        val appsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        val appIsDefaultLauncher = isActivityDefaultLauncher(activity)
+        setupDeviceWallpaperSwitchText(appIsDefaultLauncher)
+        customize_app_drawer_auto_device_theme_wallpaper.isEnabled = appIsDefaultLauncher
+
+        appsRepo.liveData().observe(viewLifecycleOwner) {
+            // always uncheck once app isn't default launcher
+            customize_app_drawer_auto_device_theme_wallpaper.isChecked = appIsDefaultLauncher && it.setThemeWallpaper
+        }
+        customize_app_drawer_auto_device_theme_wallpaper.setOnCheckedChangeListener { _, checked ->
+            appsRepo.updateSetAutomaticDeviceWallpaper(checked)
+        }
+    }
+
+    /**
+     * Adds a hint text underneath the default text when app is not the default launcher.
+     */
+    private fun setupDeviceWallpaperSwitchText(appIsDefaultLauncher: Boolean) {
+        val text = if (appIsDefaultLauncher) {
+            getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
+        } else {
+            buildSwitchTextWithHint()
+        }
+        customize_app_drawer_auto_device_theme_wallpaper.text = text
+    }
+
+    private fun buildSwitchTextWithHint(): CharSequence {
+        val titleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
+        // have a title text and a subtitle text to indicate that adapting the
+        // wallpaper can only be done when app it the default launcher
+        val subTitleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_subtext_no_default_launcher)
+
+        val spanBuilder = SpannableStringBuilder("$titleText\n$subTitleText")
+        spanBuilder.setSpan(TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Large), 0, titleText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+        spanBuilder.setSpan(
+            TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Small),
+            titleText.length + 1,
+            titleText.length + 1 + subTitleText.length,
+            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+        )
+        return spanBuilder
+    }
 }

+ 0 - 2
app/src/main/java/com/sduduzog/slimlauncher/utils/BaseFragment.kt

@@ -36,13 +36,11 @@ abstract class BaseFragment : Fragment(), ISubscriber {
                     val flags = requireActivity().window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
                     getFragmentView().systemUiVisibility = flags
                 }
-
             }
             val value = TypedValue()
             requireContext().theme.resolveAttribute(R.attr.colorPrimary, value, true)
             requireActivity().window.statusBarColor = value.data
         }
-
     }
 
     override fun onStart() {

+ 1 - 1
app/src/main/java/com/sduduzog/slimlauncher/utils/ISubscriber.kt

@@ -1,3 +1,3 @@
 package com.sduduzog.slimlauncher.utils
 
-interface ISubscriber{}
+interface ISubscriber

+ 73 - 0
app/src/main/java/com/sduduzog/slimlauncher/utils/Utils.kt

@@ -0,0 +1,73 @@
+package com.sduduzog.slimlauncher.utils
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.res.Configuration
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.Build
+import android.util.DisplayMetrics
+import android.view.WindowInsets
+
+
+private fun isAppDefaultLauncher(context: Context?): Boolean {
+    val intent = Intent(Intent.ACTION_MAIN)
+    intent.addCategory(Intent.CATEGORY_HOME)
+    val res = context?.packageManager?.resolveActivity(intent, 0)
+    if (res?.activityInfo == null) {
+        // should not happen. A home is always installed, isn't it?
+        return false
+    }
+    return context.packageName == res.activityInfo?.packageName
+}
+
+private fun intentContainsDefaultLauncher(intent: Intent?): Boolean = intent?.action == Intent.ACTION_MAIN && intent.categories?.contains(Intent.CATEGORY_HOME) == true
+
+fun isActivityDefaultLauncher(activity: Activity?): Boolean = isAppDefaultLauncher(activity) || intentContainsDefaultLauncher(activity?.intent)
+
+fun getScreenWidth(activity: Activity): Int {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+        val windowMetrics = activity.windowManager.currentWindowMetrics
+        val bounds: Rect = windowMetrics.bounds
+        val insets: Insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(
+            WindowInsets.Type.systemBars()
+        )
+        if (activity.resources.configuration.orientation
+            == Configuration.ORIENTATION_LANDSCAPE
+            && activity.resources.configuration.smallestScreenWidthDp < 600
+        ) { // landscape and phone
+            val navigationBarSize: Int = insets.right + insets.left
+            bounds.width() - navigationBarSize
+        } else { // portrait or tablet
+            bounds.width()
+        }
+    } else {
+        val outMetrics = DisplayMetrics()
+        activity.windowManager.defaultDisplay.getMetrics(outMetrics)
+        outMetrics.widthPixels
+    }
+}
+
+fun getScreenHeight(activity: Activity): Int {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+        val windowMetrics = activity.windowManager.currentWindowMetrics
+        val bounds: Rect = windowMetrics.bounds
+        val insets: Insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(
+            WindowInsets.Type.systemBars()
+        )
+        if (activity.resources.configuration.orientation
+            == Configuration.ORIENTATION_LANDSCAPE
+            && activity.resources.configuration.smallestScreenWidthDp < 600
+        ) { // landscape and phone
+            bounds.height()
+        } else { // portrait or tablet
+            val navigationBarSize: Int = insets.bottom
+            bounds.height() - navigationBarSize
+        }
+    } else {
+        val outMetrics = DisplayMetrics()
+        activity.windowManager.defaultDisplay.getMetrics(outMetrics)
+        outMetrics.heightPixels
+    }
+}

+ 1 - 0
app/src/main/proto/unlauncher_apps.proto

@@ -16,4 +16,5 @@ message UnlauncherApps {
   repeated UnlauncherApp apps = 1;
   int32 version = 2;
   bool activate_keyboard_in_drawer = 3;
+  bool set_theme_wallpaper = 4;
 }

+ 16 - 0
app/src/main/res/layout/customize_app_drawer_fragment.xml

@@ -37,4 +37,20 @@
         android:textSize="@dimen/_20ssp"
         app:layout_constraintTop_toBottomOf="@id/customize_app_drawer_fragment_visible_apps"
         app:layout_constraintStart_toStartOf="parent"/>
+
+    <androidx.appcompat.widget.SwitchCompat
+        android:id="@+id/customize_app_drawer_auto_device_theme_wallpaper"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/_16sdp"
+        android:layout_marginLeft="@dimen/_16sdp"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="@dimen/_16sdp"
+        android:layout_marginRight="@dimen/_16sdp"
+        android:layout_marginBottom="32dp"
+        android:text="@string/customize_app_drawer_fragment_auto_theme_wallpaper_text"
+        android:textAppearance="@style/TextAppearance.AppCompat"
+        android:textSize="@dimen/_18ssp"
+        app:layout_constraintTop_toBottomOf="@id/customize_app_drawer_open_keyboard_switch"
+        app:layout_constraintStart_toStartOf="parent"/>
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 2 - 0
app/src/main/res/values-de/strings.xml

@@ -28,5 +28,7 @@
     <string name="options_fragment_show_status_bar">Statusleiste zeigen</string>
     <string name="customize_app_drawer_fragment_open_keyboard">Tastatur anzeigen</string>
     <string name="customize_app_drawer_fragment_visible_apps">Sichtbare Apps</string>
+    <string name="customize_app_drawer_fragment_auto_theme_wallpaper_text">Theme Hintergrund als Wallpaper</string>
+    <string name="customize_app_drawer_fragment_auto_theme_wallpaper_subtext_no_default_launcher">App muss Standardlauncher sein</string>
     <string name="menu_reset">Zurücksetzen</string>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -45,6 +45,8 @@
     <string name="options_fragment_hide_status_bar">Hide Status Bar</string>
     <string name="options_fragment_show_status_bar">Show Status Bar</string>
     <string name="customize_app_drawer_fragment_open_keyboard">Open Keyboard</string>
+    <string name="customize_app_drawer_fragment_auto_theme_wallpaper_text">Set theme background as wallpaper</string>
+    <string name="customize_app_drawer_fragment_auto_theme_wallpaper_subtext_no_default_launcher">App needs to be default launcher</string>
     <string name="customize_app_drawer_fragment_visible_apps">Visible Apps</string>
     <string name="customise_apps_fragment_add">Add</string>
     <string name="menu_rename">Rename</string>