Skip to content

Commit b874d55

Browse files
Feature: Added the ability to backup and restore apps.
1 parent 198b230 commit b874d55

File tree

6 files changed

+122
-8
lines changed

6 files changed

+122
-8
lines changed

app/src/main/java/com/github/droidworksstudio/launcher/data/dao/AppInfoDAO.kt

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ interface AppInfoDAO {
1919
@Insert(onConflict = OnConflictStrategy.IGNORE)
2020
suspend fun insertAll(apps: List<AppInfo>)
2121

22+
@Insert(onConflict = OnConflictStrategy.REPLACE)
23+
suspend fun restoreAll(apps: List<AppInfo>)
24+
25+
@Query("DELETE FROM app")
26+
suspend fun clearAll()
27+
2228
@Update
2329
suspend fun update(app: AppInfo)
2430

app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt

+66-1
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@ import com.github.droidworksstudio.common.showLongToast
3131
import com.github.droidworksstudio.launcher.BuildConfig
3232
import com.github.droidworksstudio.launcher.R
3333
import com.github.droidworksstudio.launcher.accessibility.ActionService
34+
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
35+
import com.github.droidworksstudio.launcher.data.entities.AppInfo
3436
import com.github.droidworksstudio.launcher.helper.weather.WeatherResponse
3537
import com.github.droidworksstudio.launcher.utils.Constants
3638
import com.github.droidworksstudio.launcher.utils.WeatherApiService
3739
import com.google.gson.Gson
40+
import com.google.gson.reflect.TypeToken
41+
import kotlinx.coroutines.flow.first
3842
import retrofit2.Retrofit
3943
import retrofit2.converter.gson.GsonConverterFactory
4044
import java.net.UnknownHostException
@@ -316,7 +320,7 @@ class AppHelper @Inject constructor() {
316320
fun storeFile(activity: Activity) {
317321
// Generate a unique filename with a timestamp
318322
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
319-
val fileName = "backup_$timeStamp.json"
323+
val fileName = "Settings_backup_$timeStamp.json"
320324

321325
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
322326
addCategory(Intent.CATEGORY_OPENABLE)
@@ -334,6 +338,67 @@ class AppHelper @Inject constructor() {
334338
activity.startActivityForResult(intent, Constants.BACKUP_READ, null)
335339
}
336340

341+
fun storeFileApps(activity: Activity) {
342+
// Generate a unique filename with a timestamp
343+
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
344+
val fileName = "Apps_backup_$timeStamp.json"
345+
346+
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
347+
addCategory(Intent.CATEGORY_OPENABLE)
348+
type = "application/json"
349+
putExtra(Intent.EXTRA_TITLE, fileName)
350+
}
351+
activity.startActivityForResult(intent, Constants.BACKUP_WRITE_APPS, null)
352+
}
353+
354+
fun loadFileApps(activity: Activity) {
355+
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
356+
addCategory(Intent.CATEGORY_OPENABLE)
357+
type = "application/json"
358+
}
359+
activity.startActivityForResult(intent, Constants.BACKUP_READ_APPS, null)
360+
}
361+
362+
suspend fun backupAppInfo(context: Context, dao: AppInfoDAO, uri: Uri) {
363+
try {
364+
dao.getAllAppsFlow()
365+
.first() // Get the first emission from the Flow
366+
.let { allApps ->
367+
val gson = Gson()
368+
val jsonString = gson.toJson(allApps)
369+
370+
// Write JSON to the selected URI
371+
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
372+
outputStream.write(jsonString.toByteArray())
373+
} ?: throw Exception("Failed to open output stream")
374+
}
375+
} catch (e: Exception) {
376+
e.printStackTrace()
377+
}
378+
}
379+
380+
suspend fun restoreAppInfo(context: Context, dao: AppInfoDAO, uri: Uri) {
381+
try {
382+
// Open an InputStream from the selected Uri
383+
context.contentResolver.openInputStream(uri)?.use { inputStream ->
384+
// Read the content from the InputStream
385+
val jsonString = inputStream.bufferedReader().use { it.readText() }
386+
387+
// Convert JSON to List<AppInfo>
388+
val gson = Gson()
389+
val type = object : TypeToken<List<AppInfo>>() {}.type
390+
val appInfoList: List<AppInfo> = gson.fromJson(jsonString, type)
391+
392+
// Reinsert data into the database
393+
dao.restoreAll(appInfoList)
394+
} ?: throw Exception("Failed to open input stream from URI")
395+
396+
} catch (e: Exception) {
397+
e.printStackTrace()
398+
}
399+
}
400+
401+
337402
fun getActionType(actionType: Constants.Swipe): NavOptions {
338403
return when (actionType) {
339404
Constants.Swipe.DoubleTap -> {

app/src/main/java/com/github/droidworksstudio/launcher/ui/activities/MainActivity.kt

+26
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ import com.github.droidworksstudio.common.isTablet
3434
import com.github.droidworksstudio.common.showLongToast
3535
import com.github.droidworksstudio.common.showShortToast
3636
import com.github.droidworksstudio.launcher.R
37+
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
3738
import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding
3839
import com.github.droidworksstudio.launcher.helper.AppHelper
3940
import com.github.droidworksstudio.launcher.helper.AppReloader
4041
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
42+
import com.github.droidworksstudio.launcher.repository.AppInfoRepository
4143
import com.github.droidworksstudio.launcher.utils.Constants
4244
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
4345
import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel
@@ -65,9 +67,15 @@ class MainActivity : AppCompatActivity() {
6567
@Inject
6668
lateinit var preferenceHelper: PreferenceHelper
6769

70+
@Inject
71+
lateinit var appInfoRepository: AppInfoRepository
72+
6873
@Inject
6974
lateinit var appHelper: AppHelper
7075

76+
@Inject
77+
lateinit var appDao: AppInfoDAO
78+
7179
private lateinit var sharedPreferences: SharedPreferences
7280
private lateinit var handler: Handler
7381

@@ -96,6 +104,7 @@ class MainActivity : AppCompatActivity() {
96104
setupNavController()
97105
setupOrientation()
98106
setupLocationManager()
107+
setLanguage()
99108
}
100109

101110
@Suppress("DEPRECATION")
@@ -406,6 +415,23 @@ class MainActivity : AppCompatActivity() {
406415
}
407416
applicationContext.showShortToast(getString(R.string.settings_reload_app_backup))
408417
}
418+
419+
Constants.BACKUP_WRITE_APPS -> {
420+
data?.data?.also { uri ->
421+
lifecycleScope.launch {
422+
appHelper.backupAppInfo(applicationContext, appDao, uri)
423+
}
424+
}
425+
}
426+
427+
Constants.BACKUP_READ_APPS -> {
428+
data?.data?.also { uri ->
429+
lifecycleScope.launch {
430+
appHelper.restoreAppInfo(applicationContext, appDao, uri)
431+
}
432+
}
433+
AppReloader.restartApp(applicationContext)
434+
}
409435
}
410436
}
411437
}

app/src/main/java/com/github/droidworksstudio/launcher/ui/settings/SettingsAdvancedFragment.kt

+15-2
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ import android.view.View
1212
import android.view.ViewGroup
1313
import androidx.appcompat.app.AlertDialog
1414
import androidx.fragment.app.Fragment
15+
import androidx.lifecycle.lifecycleScope
1516
import androidx.navigation.NavController
1617
import androidx.navigation.fragment.findNavController
1718
import com.github.droidworksstudio.common.resetDefaultLauncher
1819
import com.github.droidworksstudio.launcher.R
20+
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
1921
import com.github.droidworksstudio.launcher.databinding.FragmentSettingsAdvancedBinding
2022
import com.github.droidworksstudio.launcher.helper.AppHelper
2123
import com.github.droidworksstudio.launcher.helper.AppReloader
2224
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
2325
import com.github.droidworksstudio.launcher.listener.ScrollEventListener
2426
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2527
import dagger.hilt.android.AndroidEntryPoint
28+
import kotlinx.coroutines.launch
2629
import javax.inject.Inject
2730

2831
@AndroidEntryPoint
@@ -38,6 +41,9 @@ class SettingsAdvancedFragment : Fragment(),
3841
@Inject
3942
lateinit var appHelper: AppHelper
4043

44+
@Inject
45+
lateinit var appDAO: AppInfoDAO
46+
4147
private lateinit var navController: NavController
4248

4349
private lateinit var context: Context
@@ -125,8 +131,10 @@ class SettingsAdvancedFragment : Fragment(),
125131

126132
// Define the items for the dialog (Backup, Restore, Clear Data)
127133
val items = arrayOf(
128-
getString(R.string.advanced_settings_backup_restore_backup),
129-
getString(R.string.advanced_settings_backup_restore_restore),
134+
getString(R.string.advanced_settings_backup_restore_backup_prefs),
135+
getString(R.string.advanced_settings_backup_restore_restore_prefs),
136+
getString(R.string.advanced_settings_backup_restore_backup_apps),
137+
getString(R.string.advanced_settings_backup_restore_restore_apps),
130138
getString(R.string.advanced_settings_backup_restore_clear)
131139
)
132140

@@ -136,6 +144,8 @@ class SettingsAdvancedFragment : Fragment(),
136144
when (which) {
137145
0 -> appHelper.storeFile(requireActivity())
138146
1 -> appHelper.loadFile(requireActivity())
147+
2 -> appHelper.storeFileApps(requireActivity())
148+
3 -> appHelper.loadFileApps(requireActivity())
139149
else -> confirmClearData()
140150
}
141151
}
@@ -159,6 +169,9 @@ class SettingsAdvancedFragment : Fragment(),
159169

160170
private fun clearData() {
161171
preferenceHelper.clearAll(context)
172+
lifecycleScope.launch {
173+
appDAO.clearAll()
174+
}
162175
Handler(Looper.getMainLooper()).postDelayed({
163176
AppReloader.restartApp(context)
164177
}, 500)

app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ object Constants {
2525
const val LATITUDE = "LATITUDE"
2626
const val LONGITUDE = "LONGITUDE"
2727

28+
const val LOCATION_DENIED = "LOCATION_DENIED"
29+
2830
const val FIRST_LAUNCH = "FIRST_LAUNCH"
2931
const val SHOW_DATE = "SHOW_DATE"
3032
const val SHOW_TIME = "SHOW_TIME"
@@ -34,7 +36,6 @@ object Constants {
3436
const val SHOW_STATUS_BAR = "SHOW_STATUS_BAR"
3537
const val SHOW_NAVIGATION_BAR = "SHOW_NAVIGATION_BAR"
3638

37-
3839
const val SHOW_WEATHER_WIDGET = "SHOW_WEATHER_WIDGET"
3940
const val SHOW_WEATHER_WIDGET_SUN_SET_RISE = "SHOW_WEATHER_WIDGET_SUN_SET_RISE"
4041
const val SHOW_BATTERY_WIDGET = "SHOW_BATTERY_WIDGET"
@@ -114,7 +115,8 @@ object Constants {
114115
const val BACKUP_WRITE = 987
115116
const val BACKUP_READ = 876
116117

117-
const val LOCATION_DENIED = "LOCATION_DENIED"
118+
const val BACKUP_WRITE_APPS = 456
119+
const val BACKUP_READ_APPS = 654
118120

119121
const val TRIPLE_TAP_DELAY_MS = 300
120122
const val LONG_PRESS_DELAY_MS = 500

app/src/main/res/values/strings.xml

+5-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@
4141
<string name="advanced_settings_backup_restore_title">Backup/Restore</string>
4242
<string name="advanced_settings_backup_restore_description">Please be careful what you wish for?</string>
4343

44-
<string name="advanced_settings_backup_restore_backup">Backup Preferences</string>
45-
<string name="advanced_settings_backup_restore_restore">Restore Preferences</string>
46-
<string name="advanced_settings_backup_restore_clear">Clear Preferences</string>
44+
<string name="advanced_settings_backup_restore_backup_prefs">Backup Preferences</string>
45+
<string name="advanced_settings_backup_restore_restore_prefs">Restore Preferences</string>
46+
<string name="advanced_settings_backup_restore_backup_apps">Backup Apps</string>
47+
<string name="advanced_settings_backup_restore_restore_apps">Restore Apps</string>
48+
<string name="advanced_settings_backup_restore_clear">Clear All Data</string>
4749

4850
<string name="advanced_settings_backup_restore_clear_title">Clear Preferences</string>
4951
<string name="advanced_settings_backup_restore_clear_description">Are you sure you want to do this?</string>

0 commit comments

Comments
 (0)