diff --git a/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt b/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt index e2fa892..212ac36 100644 --- a/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt +++ b/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt @@ -343,151 +343,6 @@ fun Context.isWorkProfileEnabled(): Boolean { } } -fun Context.backupSharedPreferences(backupFileNames: Array) { - // Check if external storage is writable - if (!isExternalStorageWritable()) { - showLongToast("External storage is not writable.") - return - } - - val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - val backupDir = File(downloadDir, Constants.PACKAGE_NAME) - - // Ensure the directory exists, create it if necessary - if (!backupDir.exists()) { - backupDir.mkdirs() - } - - for (backupFileName in backupFileNames) { - val sharedPreferences: SharedPreferences = - this.getSharedPreferences(backupFileName, Context.MODE_PRIVATE) - val allPrefs = sharedPreferences.all - - val backupFile = File(backupDir, "$backupFileName.xml") - - try { - backupFile.bufferedWriter().use { writer -> - writer.write("\n") - writer.write("\n") - - // Loop through all preferences - for ((key, value) in allPrefs) { - if (value != null) { - val valueString = value.toString().replace("'", "'") - val line = when (value) { - is Boolean, is Int, is Float, is Long, is String -> "\t<${value::class.simpleName!!.lowercase()} name='$key' value='$valueString' />\n" - else -> null - } - line?.let { - writer.write(it) - } - } - } - - writer.write("") - } - showLongToast("Backup for $backupFileName completed successfully.") - } catch (e: IOException) { - e.printStackTrace() - showLongToast("Failed to backup SharedPreferences $backupFileName: ${e.message}") - } - } -} - - -private fun isExternalStorageWritable(): Boolean { - return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED -} - -fun Context.restoreSharedPreferences(backupFileNames: Array) { - // Check if external storage is readable - if (!isExternalStorageReadable()) { - showLongToast("External storage is not readable.") - return - } - - val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) - val backupDir = File(downloadDir, Constants.PACKAGE_NAME) - - for (backupFileName in backupFileNames) { - val backupFile = File(backupDir, "$backupFileName.xml") - - if (backupFile.exists()) { - try { - FileInputStream(backupFile).use { fileInputStream -> - val factory = XmlPullParserFactory.newInstance() - factory.isNamespaceAware = true - val xpp = factory.newPullParser() - xpp.setInput(fileInputStream, null) - - var eventType = xpp.eventType - var key: String? = null - var value: Any? = null - - while (eventType != XmlPullParser.END_DOCUMENT) { - when (eventType) { - XmlPullParser.START_TAG -> { - when (xpp.name) { - "boolean", "int", "float", "long", "string" -> { - key = xpp.getAttributeValue(null, "name") - val valueString = xpp.getAttributeValue(null, "value") - value = when (xpp.name) { - "boolean" -> valueString.toBoolean() - "int" -> valueString.toInt() - "float" -> valueString.toFloat() - "long" -> valueString.toLong() - else -> valueString - } - } - } - } - - XmlPullParser.END_TAG -> { - when (xpp.name) { - "boolean", "int", "float", "long", "string" -> { - if (key != null && value != null) { - saveToSharedPreferences(backupFileName, key, value) - key = null - value = null - } - } - } - } - } - eventType = xpp.next() - } - } - showLongToast("Restore for $backupFileName completed successfully.") - } catch (e: Exception) { - Log.e("Exception", e.toString()) - showLongToast("Failed to restore SharedPreferences $backupFileName: ${e.message}") - } - } else { - showLongToast("Backup file for $backupFileName does not exist.") - } - } -} - -fun Context.saveToSharedPreferences(prefsFileName: String, key: String, value: Any) { - val sharedPreferences: SharedPreferences = this.getSharedPreferences(prefsFileName, Context.MODE_PRIVATE) - val editor = sharedPreferences.edit() - - when (value) { - is Boolean -> editor.putBoolean(key, value) - is Int -> editor.putInt(key, value) - is Float -> editor.putFloat(key, value) - is Long -> editor.putLong(key, value) - is String -> editor.putString(key, value) - } - - editor.apply() -} - -private fun isExternalStorageReadable(): Boolean { - val state = Environment.getExternalStorageState() - return Environment.MEDIA_MOUNTED == state || Environment.MEDIA_MOUNTED_READ_ONLY == state -} - fun Context.hasInternetPermission(): Boolean { val permission = Manifest.permission.INTERNET val result = ContextCompat.checkSelfPermission(this, permission) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt index 398f17a..7449771 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt @@ -1,6 +1,7 @@ package com.github.droidworksstudio.launcher.helper import android.annotation.SuppressLint +import android.app.Activity import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context @@ -16,8 +17,6 @@ import android.view.WindowInsets import android.widget.TextView import androidx.appcompat.widget.LinearLayoutCompat import androidx.navigation.NavOptions -import com.github.droidworksstudio.common.backupSharedPreferences -import com.github.droidworksstudio.common.restoreSharedPreferences import com.github.droidworksstudio.common.showLongToast import com.github.droidworksstudio.launcher.BuildConfig import com.github.droidworksstudio.launcher.utils.Constants @@ -29,7 +28,10 @@ import com.google.gson.Gson import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.net.UnknownHostException +import java.text.SimpleDateFormat import java.util.Calendar +import java.util.Date +import java.util.Locale import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -192,14 +194,25 @@ class AppHelper @Inject constructor() { context.startActivity(Intent.createChooser(emailIntent, "Choose Mail Application")) } - fun backupSharedPreferences(context: Context) { - val backupFileNames = arrayOf(Constants.PREFS_FILENAME, Constants.WEATHER_PREFS) - context.backupSharedPreferences(backupFileNames) + fun storeFile(activity: Activity) { + // Generate a unique filename with a timestamp + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val fileName = "backup_$timeStamp.json" + + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + putExtra(Intent.EXTRA_TITLE, fileName) + } + activity.startActivityForResult(intent, Constants.BACKUP_WRITE, null) } - fun restoreSharedPreferences(context: Context) { - val backupFileNames = arrayOf(Constants.PREFS_FILENAME, Constants.WEATHER_PREFS) - context.restoreSharedPreferences(backupFileNames) + fun loadFile(activity: Activity) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + } + activity.startActivityForResult(intent, Constants.BACKUP_READ, null) } fun getActionType(actionType: Constants.Swipe): NavOptions { diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/PreferenceHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/PreferenceHelper.kt index 7837b90..1826b7b 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/PreferenceHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/PreferenceHelper.kt @@ -3,14 +3,17 @@ package com.github.droidworksstudio.launcher.helper import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration +import android.util.Log import android.view.Gravity import com.github.droidworksstudio.launcher.utils.Constants +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject class PreferenceHelper @Inject constructor(@ApplicationContext context: Context) { - private val prefs: SharedPreferences = context.getSharedPreferences(Constants.PREFS_FILENAME, 0) + private val prefs: SharedPreferences = context.getSharedPreferences(Constants.PACKAGE_PREFS, 0) private val setColor = getColor(context) @@ -257,4 +260,45 @@ class PreferenceHelper @Inject constructor(@ApplicationContext context: Context) } } } + + fun saveToString(): String { + val all: HashMap = HashMap(prefs.all) + return Gson().toJson(all) + } + + fun loadFromString(json: String) { + val editor = prefs.edit() + val all: HashMap = + Gson().fromJson(json, object : TypeToken>() {}.type) + for ((key, value) in all) { + when (value) { + is String -> editor.putString(key, value) + is Boolean -> editor.putBoolean(key, value) + is Int -> editor.putInt(key, value) + is Double -> editor.putInt(key, value.toInt()) // we store everything as int + is Float -> editor.putInt(key, value.toInt()) + is MutableSet<*> -> { + val list = value.filterIsInstance().toSet() + editor.putStringSet(key, list) + } + + else -> { + Log.d("backup error", "$value") + } + } + } + editor.apply() + } + + fun clear() { + prefs.edit().clear().apply() + } + + fun clearAll(context: Context) { + val prefsLauncher: SharedPreferences = context.getSharedPreferences(Constants.PACKAGE_PREFS, 0) + val prefsWidgets: SharedPreferences = context.getSharedPreferences(Constants.WEATHER_PREFS, 0) + + prefsLauncher.edit().clear().apply() + prefsWidgets.edit().clear().apply() + } } \ No newline at end of file diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt index d8677d5..4aa8bc2 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/UpdateManagerHelper.kt @@ -162,15 +162,18 @@ class UpdateManagerHelper(private val fragment: Fragment) { @Deprecated("Deprecated in Java") fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == Constants.REQUEST_INSTALL_PERMISSION) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val canInstallPackages = context.packageManager.canRequestPackageInstalls() - if (canInstallPackages) { - // Permission granted, proceed with installation - installApk() - } else { - // Permission still not granted, handle accordingly - context.applicationContext.showLongToast("Please allow install permission to install.") + + when (requestCode) { + Constants.REQUEST_INSTALL_PERMISSION -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val canInstallPackages = context.packageManager.canRequestPackageInstalls() + if (canInstallPackages) { + // Permission granted, proceed with installation + installApk() + } else { + // Permission still not granted, handle accordingly + context.applicationContext.showLongToast("Please allow install permission to install.") + } } } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt index b7f1a35..2c606d8 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt @@ -2,7 +2,9 @@ package com.github.droidworksstudio.launcher.ui.activities import android.Manifest import android.annotation.SuppressLint +import android.app.Activity import android.content.Context +import android.content.Intent import android.content.SharedPreferences import android.content.pm.ActivityInfo import android.content.pm.PackageManager @@ -33,12 +35,16 @@ import com.github.droidworksstudio.common.showLongToast import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding import com.github.droidworksstudio.launcher.helper.AppHelper +import com.github.droidworksstudio.launcher.helper.AppReloader import com.github.droidworksstudio.launcher.helper.PreferenceHelper import com.github.droidworksstudio.launcher.utils.Constants import com.github.droidworksstudio.launcher.viewmodel.AppViewModel import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.FileOutputStream +import java.io.InputStreamReader import javax.inject.Inject @@ -274,13 +280,62 @@ class MainActivity : AppCompatActivity() { grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == Constants.REQUEST_LOCATION_PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // Permission granted, proceed with getting location - getLocation() - } else { - // Permission denied, show a message to the user - setLocationPermissionDenied(true) + when (requestCode) { + Constants.REQUEST_LOCATION_PERMISSION_CODE -> { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // Permission granted, proceed with getting location + getLocation() + } else { + // Permission denied, show a message to the user + setLocationPermissionDenied(true) + } + } + } + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (resultCode != Activity.RESULT_OK) { + // showToastLong(applicationContext, "Intent Error") + return + } + + when (requestCode) { + Constants.BACKUP_READ -> { + data?.data?.also { uri -> + applicationContext.contentResolver.openInputStream(uri).use { inputStream -> + val stringBuilder = StringBuilder() + BufferedReader(InputStreamReader(inputStream)).use { reader -> + var line: String? = reader.readLine() + while (line != null) { + stringBuilder.append(line) + line = reader.readLine() + } + } + + val string = stringBuilder.toString() + val prefs = PreferenceHelper(applicationContext) + prefs.clear() + prefs.loadFromString(string) + } + } + Handler(Looper.getMainLooper()).postDelayed({ + AppReloader.restartApp(applicationContext) + }, 500) + } + + Constants.BACKUP_WRITE -> { + data?.data?.also { uri -> + applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { file -> + FileOutputStream(file.fileDescriptor).use { stream -> + val text = PreferenceHelper(applicationContext).saveToString() + stream.channel.truncate(0) + stream.write(text.toByteArray()) + } + } + } } } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt index 79e854a..1b1e0f0 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsFragment.kt @@ -95,11 +95,21 @@ class SettingsFragment : Fragment(), binding.miscellaneousSearchEngineControl.text = preferenceHelper.searchEngines.getString(context) binding.miscellaneousLauncherFontsControl.text = preferenceHelper.launcherFont.getString(context) - updateGestureControlText(context, preferenceHelper.doubleTapAction, preferenceHelper.doubleTapApp, binding.gesturesDoubleTapControl) - updateGestureControlText(context, preferenceHelper.swipeUpAction, preferenceHelper.swipeUpApp, binding.gesturesSwipeUpControl) - updateGestureControlText(context, preferenceHelper.swipeDownAction, preferenceHelper.swipeDownApp, binding.gesturesSwipeDownControl) - updateGestureControlText(context, preferenceHelper.swipeLeftAction, preferenceHelper.swipeLeftApp, binding.gesturesSwipeLeftControl) - updateGestureControlText(context, preferenceHelper.swipeRightAction, preferenceHelper.swipeRightApp, binding.gesturesSwipeRightControl) + val actions = listOf( + Triple(preferenceHelper.doubleTapAction, preferenceHelper.doubleTapApp, binding.gesturesDoubleTapControl), + Triple(preferenceHelper.swipeUpAction, preferenceHelper.swipeUpApp, binding.gesturesSwipeUpControl), + Triple(preferenceHelper.swipeDownAction, preferenceHelper.swipeDownApp, binding.gesturesSwipeDownControl), + Triple(preferenceHelper.swipeLeftAction, preferenceHelper.swipeLeftApp, binding.gesturesSwipeLeftControl), + Triple(preferenceHelper.swipeRightAction, preferenceHelper.swipeRightApp, binding.gesturesSwipeRightControl) + ) + + actions.forEach { (action, app, control) -> + when (action) { + Constants.Action.OpenApp -> updateGestureControlText(context, action, app, control) + else -> {} + } + } + } // Function to update UI text based on action and app name @@ -189,12 +199,18 @@ class SettingsFragment : Fragment(), } binding.backupView.setOnClickListener { - appHelper.backupSharedPreferences(requireContext()) + appHelper.storeFile(requireActivity()) + } + + binding.clearView.setOnClickListener { + preferenceHelper.clearAll(context) + Handler(Looper.getMainLooper()).postDelayed({ + AppReloader.restartApp(context) + }, 500) } binding.restoreView.setOnClickListener { - appHelper.restoreSharedPreferences(requireContext()) - AppReloader.restartApp(context) + appHelper.loadFile(requireActivity()) } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt b/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt index 6ee1b02..9898d9c 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt @@ -8,18 +8,18 @@ import com.github.droidworksstudio.launcher.R object Constants { const val PACKAGE_NAME = "app.easy.launcher" const val PACKAGE_NAME_DEBUG = "$PACKAGE_NAME.debug" + const val PACKAGE_PREFS = "EasyLauncher.pref" const val WIDGETS_COUNT = 2 const val WIDGET_WEATHER = "WIDGET_WEATHER" const val WIDGET_BATTERY = "WIDGET_BATTERY" - const val WEATHER_UNITS = "WEATHER_UNITS" const val WEATHER_PREFS = "EasyWeather.pref" + const val WEATHER_UNITS = "WEATHER_UNITS" const val LATITUDE = "LATITUDE" const val LONGITUDE = "LONGITUDE" - const val PREFS_FILENAME = "EasyLauncher.pref" const val FIRST_LAUNCH = "FIRST_LAUNCH" const val SHOW_DATE = "SHOW_DATE" const val SHOW_TIME = "SHOW_TIME" @@ -80,7 +80,10 @@ object Constants { const val LAUNCHER_FONT = "LAUNCHER_FONT" const val REQUEST_INSTALL_PERMISSION = 123 - const val REQUEST_LOCATION_PERMISSION_CODE = 246 + const val REQUEST_LOCATION_PERMISSION_CODE = 234 + + const val BACKUP_WRITE = 987 + const val BACKUP_READ = 876 const val LOCATION_DENIED = "LOCATION_DENIED" diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index e6500de..8298d5a 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -883,6 +883,15 @@ android:layout_marginVertical="10dp" style="@style/TextDefaultStyle" /> + + Github Backup Preferences + Clear Preferences Restore Preferences Preferences backed up.