From 827f337739afcf67b728d24cb112a2ccf23b8256 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 10 Jan 2024 14:56:58 -0700 Subject: [PATCH 01/69] export widget settings UI for import/export widget settings; adds menu items and file selection to widgetlist and widgetconfig activities. #757 --- .../SuntimesConfigActivity0.java | 168 +++++++++++++++++- .../SuntimesWidgetListActivity.java | 164 ++++++++++++++++- .../settings/ExportWidgetSettingsTask.java | 69 +++++++ app/src/main/res/menu/widgetconfig.xml | 10 ++ app/src/main/res/menu/widgetlist.xml | 10 ++ app/src/main/res/values/strings.xml | 8 + 6 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 1d8078c3b..bdb8175a3 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2023 Forrest Guice + Copyright (C) 2014-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -21,6 +21,7 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.appwidget.AppWidgetManager; +import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; import android.content.Intent; @@ -78,6 +79,7 @@ import com.forrestguice.suntimeswidget.getfix.PlacesActivity; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.actions.EditActionView; +import com.forrestguice.suntimeswidget.settings.ExportWidgetSettingsTask; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; @@ -89,9 +91,11 @@ import com.forrestguice.suntimeswidget.themes.SuntimesTheme.ThemeDescriptor; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.views.PopupMenuCompat; +import com.forrestguice.suntimeswidget.views.Toast; import com.forrestguice.suntimeswidget.views.TooltipCompat; import com.forrestguice.suntimeswidget.views.ViewUtils; +import java.io.File; import java.lang.ref.WeakReference; import java.security.InvalidParameterException; import java.util.ArrayList; @@ -115,12 +119,16 @@ public class SuntimesConfigActivity0 extends AppCompatActivity protected static final String HELPTAG_SUBSTITUTIONS = "help_substitutions"; + public static final int IMPORT_REQUEST = 100; + public static final int EXPORT_REQUEST = 200; + protected int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; protected boolean reconfigure = false; protected ContentValues themeValues; private ActionBar actionBar; protected TextView text_appWidgetID; + protected View progressView; protected ScrollView scrollView; @@ -1932,6 +1940,126 @@ public void run() { } }; + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void showProgress( Context context, CharSequence title, CharSequence message ) + { + if (progressView != null) { + progressView.setVisibility(View.VISIBLE); + } + } + public void dismissProgress() + { + if (progressView != null) { + progressView.setVisibility(View.GONE); + } + } + + /** + * exportSettings + */ + protected void exportSettings(Context context) + { + String exportTarget = "SuntimesWidget_" + appWidgetId; + if (Build.VERSION.SDK_INT >= 19) + { + String filename = exportTarget + ExportWidgetSettingsTask.FILEEXT; + Intent intent = ExportTask.getCreateFileIntent(filename, ExportWidgetSettingsTask.MIMETYPE); + try { + startActivityForResult(intent, EXPORT_REQUEST); + return; + + } catch (ActivityNotFoundException e) { + Log.e("ExportSettings", "SAF is unavailable? (" + e + ").. falling back to legacy export method."); + } + } + + ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache + task.setTaskListener(exportSettingsListener); + task.execute(); + + } + public void exportSettings(Context context, @NonNull Uri uri) + { + Log.i("ExportSettings", "Starting export task: " + uri); + ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); + task.setTaskListener(exportSettingsListener); + task.execute(); + } + + private final ExportWidgetSettingsTask.TaskListener exportSettingsListener = new ExportWidgetSettingsTask.TaskListener() + { + @Override + public void onStarted() + { + //setRetainInstance(true); + Context context = SuntimesConfigActivity0.this; + showProgress(context, context.getString(R.string.exportwidget_dialog_title), context.getString(R.string.exportwidget_dialog_message)); + } + + @Override + public void onFinished(ExportWidgetSettingsTask.ExportResult results) + { + //setRetainInstance(false); + dismissProgress(); + + Context context = SuntimesConfigActivity0.this; + if (context != null) + { + File file = results.getExportFile(); + String path = ((file != null) ? file.getAbsolutePath() + : ExportTask.getFileName(context.getContentResolver(), results.getExportUri())); + + if (results.getResult()) + { + //if (isAdded()) { + String successMessage = context.getString(R.string.msg_export_success, path); + Toast.makeText(context, successMessage, Toast.LENGTH_LONG).show(); + //} + + if (Build.VERSION.SDK_INT >= 19) { + if (results.getExportUri() == null) { + ExportTask.shareResult(context, file, results.getMimeType()); + } + } else { + ExportTask.shareResult(context, file, results.getMimeType()); + } + return; + } + + //if (isAdded()) { + String failureMessage = context.getString(R.string.msg_export_failure, path); + Toast.makeText(context, failureMessage, Toast.LENGTH_LONG).show(); + //} + } + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * importSettings + */ + protected boolean importSettings(Context context) + { + if (context != null) { + startActivityForResult(ExportTask.getOpenFileIntent("text/*"), IMPORT_REQUEST); + return true; + } + return false; + } + public boolean importSettings(Context context, @NonNull Uri uri) + { + Log.i("ImportSettings", "Starting import task: " + uri); + // TODO + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + /** * Click handler executed when the "Add Widget" button is pressed. */ @@ -2425,6 +2553,14 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { + case EXPORT_REQUEST: + onExportResult(resultCode, data); + break; + + case IMPORT_REQUEST: + onImportResult(resultCode, data); + break; + case LocationConfigDialog.REQUEST_LOCATION: onLocationResult(resultCode, data); break; @@ -2439,6 +2575,28 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) } } + protected void onExportResult(int resultCode, Intent data) + { + if (resultCode == Activity.RESULT_OK) + { + Uri uri = (data != null ? data.getData() : null); + if (uri != null) { + exportSettings(this, uri); + } + } + } + + protected void onImportResult(int resultCode, Intent data) + { + if (resultCode == Activity.RESULT_OK) + { + Uri uri = (data != null ? data.getData() : null); + if (uri != null) { + importSettings(this, uri); + } + } + } + protected void onLocationResult(int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK && data != null) @@ -2635,6 +2793,14 @@ public boolean onOptionsItemSelected(MenuItem item) showAbout(); return true; + case R.id.action_import: + importSettings(SuntimesConfigActivity0.this); + return true; + + case R.id.action_export: + exportSettings(SuntimesConfigActivity0.this); + return true; + case R.id.action_save: addWidget(); return true; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 9cf497679..027b0cc3f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2023 Forrest Guice + Copyright (C) 2014-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ package com.forrestguice.suntimeswidget; +import android.app.Activity; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; @@ -36,6 +37,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; @@ -66,9 +68,12 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.ExportWidgetSettingsTask; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; +import com.forrestguice.suntimeswidget.views.Toast; import com.forrestguice.suntimeswidget.widgets.DateWidget0; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -82,9 +87,13 @@ public class SuntimesWidgetListActivity extends AppCompatActivity private static final String KEY_LISTVIEW_TOP = "widgetlisttop"; private static final String KEY_LISTVIEW_INDEX = "widgetlistindex"; + public static final int IMPORT_REQUEST = 100; + public static final int EXPORT_REQUEST = 200; + private ActionBar actionBar; private ListView widgetList; private WidgetListAdapter widgetListAdapter; + protected View progressView; private static final SuntimesUtils utils = new SuntimesUtils(); public SuntimesWidgetListActivity() @@ -126,6 +135,34 @@ public void onStart() updateWidgetAlarms(this); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) + { + case EXPORT_REQUEST: + if (resultCode == Activity.RESULT_OK) + { + Uri uri = (data != null ? data.getData() : null); + if (uri != null) { + exportSettings(SuntimesWidgetListActivity.this, uri); + } + } + break; + + case IMPORT_REQUEST: + if (resultCode == Activity.RESULT_OK) + { + Uri uri = (data != null ? data.getData() : null); + if (uri != null) { + importSettings(SuntimesWidgetListActivity.this, uri); + } + } + break; + } + } + /** * OnResume: the user is now interacting w/ the Activity (running state) */ @@ -301,6 +338,123 @@ protected void launchActionList(Context context) overridePendingTransition(R.anim.transition_next_in, R.anim.transition_next_out); } + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public void showProgress( Context context, CharSequence title, CharSequence message ) + { + if (progressView != null) { + progressView.setVisibility(View.VISIBLE); + } + } + public void dismissProgress() + { + if (progressView != null) { + progressView.setVisibility(View.GONE); + } + } + + /** + * exportSettings + * @param context Context + */ + protected void exportSettings(Context context) + { + String exportTarget = "SuntimesWidgets"; + if (Build.VERSION.SDK_INT >= 19) + { + String filename = exportTarget + ExportWidgetSettingsTask.FILEEXT; + Intent intent = ExportTask.getCreateFileIntent(filename, ExportWidgetSettingsTask.MIMETYPE); + try { + startActivityForResult(intent, EXPORT_REQUEST); + return; + + } catch (ActivityNotFoundException e) { + Log.e("ExportSettings", "SAF is unavailable? (" + e + ").. falling back to legacy export method."); + } + } + + ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache + task.setTaskListener(exportSettingsListener); + task.execute(); + } + public void exportSettings(Context context, @NonNull Uri uri) + { + Log.i("ExportSettings", "Starting export task: " + uri); + ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); + task.setTaskListener(exportSettingsListener); + task.execute(); + } + private final ExportWidgetSettingsTask.TaskListener exportSettingsListener = new ExportWidgetSettingsTask.TaskListener() + { + @Override + public void onStarted() + { + //setRetainInstance(true); + Context context = SuntimesWidgetListActivity.this; + showProgress(context, context.getString(R.string.exportwidget_dialog_title), context.getString(R.string.exportwidget_dialog_message)); + } + + @Override + public void onFinished(ExportWidgetSettingsTask.ExportResult results) + { + //setRetainInstance(false); + dismissProgress(); + + Context context = SuntimesWidgetListActivity.this; + if (context != null) + { + File file = results.getExportFile(); + String path = ((file != null) ? file.getAbsolutePath() + : ExportTask.getFileName(context.getContentResolver(), results.getExportUri())); + + if (results.getResult()) + { + //if (isAdded()) { + String successMessage = context.getString(R.string.msg_export_success, path); + Toast.makeText(context, successMessage, Toast.LENGTH_LONG).show(); + //} + + if (Build.VERSION.SDK_INT >= 19) { + if (results.getExportUri() == null) { + ExportTask.shareResult(context, file, results.getMimeType()); + } + } else { + ExportTask.shareResult(context, file, results.getMimeType()); + } + return; + } + + //if (isAdded()) { + String failureMessage = context.getString(R.string.msg_export_failure, path); + Toast.makeText(context, failureMessage, Toast.LENGTH_LONG).show(); + //} + } + } + }; + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public boolean importSettings(Context context) + { + if (context != null) { + startActivityForResult(ExportTask.getOpenFileIntent("text/*"), IMPORT_REQUEST); + return true; + } + return false; + } + + public boolean importSettings(Context context, @NonNull Uri uri) + { + Log.i("ImportSettings", "Starting import task: " + uri); + // TODO + return true; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + /** * @param widgetItem a WidgetListItem (referencing some widget id) */ @@ -663,6 +817,14 @@ public boolean onOptionsItemSelected(MenuItem item) launchActionList(SuntimesWidgetListActivity.this); return true; + case R.id.action_import: + importSettings(SuntimesWidgetListActivity.this); + return true; + + case R.id.action_export: + exportSettings(SuntimesWidgetListActivity.this); + return true; + case R.id.action_help: showHelp(); return true; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java new file mode 100644 index 000000000..49a798aa5 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java @@ -0,0 +1,69 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.content.Context; +import android.net.Uri; + +import com.forrestguice.suntimeswidget.ExportTask; + +import java.io.BufferedOutputStream; +import java.io.IOException; + +public class ExportWidgetSettingsTask extends ExportTask +{ + public static final String FILEEXT = ".txt"; + public static final String MIMETYPE = "text/plain"; + + public ExportWidgetSettingsTask(Context context, String exportTarget) + { + super(context, exportTarget); + initTask(); + } + public ExportWidgetSettingsTask(Context context, String exportTarget, boolean useExternalStorage, boolean saveToCache) + { + super(context, exportTarget, useExternalStorage, saveToCache); + initTask(); + } + public ExportWidgetSettingsTask(Context context, Uri uri) + { + super(context, uri); + initTask(); + } + + private void initTask() + { + ext = FILEEXT; + mimeType = MIMETYPE; + } + + @Override + public boolean export( Context context, BufferedOutputStream out ) throws IOException + { + // TODO + return false; + } + + @Override + public void cleanup( Context context ) + { + // TODO + } + +} diff --git a/app/src/main/res/menu/widgetconfig.xml b/app/src/main/res/menu/widgetconfig.xml index 3a6718ff1..d83ec9b31 100644 --- a/app/src/main/res/menu/widgetconfig.xml +++ b/app/src/main/res/menu/widgetconfig.xml @@ -9,6 +9,16 @@ android:title="@string/configAction_addWidget" app:showAsAction="always" tools:ignore="AlwaysShowAction" /> + + + + + + + + Show widget help No Widgets + @string/configAction_export + Export Settings + @string/configAction_import + Restore Settings + + Exporting + Exporting widget settings to file. + Places Manage Places From bc03cce900feab64de2eb669c6ee032e4076ae4d Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 10 Jan 2024 23:06:10 -0700 Subject: [PATCH 02/69] export widget settings export widget settings to json (#757) --- .../SuntimesConfigActivity0.java | 6 ++- .../SuntimesWidgetListActivity.java | 17 +++++++++ .../settings/ExportWidgetSettingsTask.java | 38 ++++++++++++++++--- .../settings/WidgetSettings.java | 32 ++++++++++++++++ 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index bdb8175a3..ebe6ca043 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1961,6 +1961,8 @@ public void dismissProgress() */ protected void exportSettings(Context context) { + saveSettings(context); + String exportTarget = "SuntimesWidget_" + appWidgetId; if (Build.VERSION.SDK_INT >= 19) { @@ -1977,14 +1979,16 @@ protected void exportSettings(Context context) ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache task.setTaskListener(exportSettingsListener); + task.setAppWidgetId(appWidgetId); task.execute(); - } public void exportSettings(Context context, @NonNull Uri uri) { Log.i("ExportSettings", "Starting export task: " + uri); + saveSettings(context); ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); task.setTaskListener(exportSettingsListener); + task.setAppWidgetId(appWidgetId); task.execute(); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 027b0cc3f..e080c35b0 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -354,6 +354,21 @@ public void dismissProgress() } } + protected static ArrayList getAllWidgetIds(Context context) + { + AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); + String packageName = context.getPackageName(); + ArrayList ids = new ArrayList<>(); + for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) + { + int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); + for (int id : widgetIds) { + ids.add(id); + } + } + return ids; + } + /** * exportSettings * @param context Context @@ -376,6 +391,7 @@ protected void exportSettings(Context context) ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache task.setTaskListener(exportSettingsListener); + task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); } public void exportSettings(Context context, @NonNull Uri uri) @@ -383,6 +399,7 @@ public void exportSettings(Context context, @NonNull Uri uri) Log.i("ExportSettings", "Starting export task: " + uri); ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); task.setTaskListener(exportSettingsListener); + task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); } private final ExportWidgetSettingsTask.TaskListener exportSettingsListener = new ExportWidgetSettingsTask.TaskListener() diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java index 49a798aa5..0ab6b6f2b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java @@ -18,13 +18,18 @@ package com.forrestguice.suntimeswidget.settings; +import android.content.ContentValues; import android.content.Context; import android.net.Uri; import com.forrestguice.suntimeswidget.ExportTask; +import org.json.JSONObject; + import java.io.BufferedOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; public class ExportWidgetSettingsTask extends ExportTask { @@ -56,14 +61,37 @@ private void initTask() @Override public boolean export( Context context, BufferedOutputStream out ) throws IOException { - // TODO - return false; + for (int i=0; i map = ExportTask.toMap(values); + return new JSONObject(map).toString(); + } + + /** + * @param value export single appWidgetId + */ + public void setAppWidgetId(int value) + { + appWidgetIds.clear(); + appWidgetIds.add(value); + } + public void setAppWidgetIds(ArrayList values) { - // TODO + appWidgetIds.clear(); + appWidgetIds.addAll(values); } + protected ArrayList appWidgetIds = new ArrayList<>(); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index 7f13be88a..f2dd5f5b3 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -18,6 +18,7 @@ package com.forrestguice.suntimeswidget.settings; +import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; @@ -59,6 +60,8 @@ import java.util.Calendar; import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.TimeZone; /** @@ -3297,4 +3300,33 @@ public static void initDisplayStrings( Context context ) CalendarSettings.initDisplayStrings(context); WidgetActions.initDisplayStrings(context); } + + public static ContentValues toContentValues(Context context, int appWidgetId) + { + SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); + Map map = prefs.getAll(); + Set keys = map.keySet(); + + ContentValues values = new ContentValues(); + for (String key : keys) + { + if (key.startsWith(WidgetSettings.PREF_PREFIX_KEY + appWidgetId)) + { + if (map.get(key).getClass().equals(String.class)) + { + //Log.d("DEBUG", key + " is String"); + values.put(key, prefs.getString(key, null)); + + } else if (map.get(key).getClass().equals(Integer.class)) { + //Log.d("DEBUG", key + " is Integer"); + values.put(key, prefs.getInt(key, -1)); + + } else if (map.get(key).getClass().equals(Boolean.class)) { + //Log.d("DEBUG", key + " is boolean"); + values.put(key, prefs.getBoolean(key, false)); + } + } + } + return values; + } } From 345c625910b4540b4626cb0cf56e41c024508031 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 11 Jan 2024 12:39:29 -0700 Subject: [PATCH 03/69] import widget settings --- .../SuntimesConfigActivity0.java | 47 ++- .../SuntimesWidgetListActivity.java | 16 +- .../settings/WidgetSettings.java | 99 ++++++ ...ask.java => WidgetSettingsExportTask.java} | 16 +- .../settings/WidgetSettingsImportTask.java | 329 ++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 6 files changed, 481 insertions(+), 28 deletions(-) rename app/src/main/java/com/forrestguice/suntimeswidget/settings/{ExportWidgetSettingsTask.java => WidgetSettingsExportTask.java} (82%) create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index ebe6ca043..dc9800cc3 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -40,6 +40,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -79,8 +80,9 @@ import com.forrestguice.suntimeswidget.getfix.PlacesActivity; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.actions.EditActionView; -import com.forrestguice.suntimeswidget.settings.ExportWidgetSettingsTask; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettings; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; import com.forrestguice.suntimeswidget.settings.WidgetThemes; @@ -1966,8 +1968,8 @@ protected void exportSettings(Context context) String exportTarget = "SuntimesWidget_" + appWidgetId; if (Build.VERSION.SDK_INT >= 19) { - String filename = exportTarget + ExportWidgetSettingsTask.FILEEXT; - Intent intent = ExportTask.getCreateFileIntent(filename, ExportWidgetSettingsTask.MIMETYPE); + String filename = exportTarget + WidgetSettingsExportTask.FILEEXT; + Intent intent = ExportTask.getCreateFileIntent(filename, WidgetSettingsExportTask.MIMETYPE); try { startActivityForResult(intent, EXPORT_REQUEST); return; @@ -1977,7 +1979,7 @@ protected void exportSettings(Context context) } } - ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache + WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, exportTarget, true, true); // export to external cache task.setTaskListener(exportSettingsListener); task.setAppWidgetId(appWidgetId); task.execute(); @@ -1986,13 +1988,13 @@ public void exportSettings(Context context, @NonNull Uri uri) { Log.i("ExportSettings", "Starting export task: " + uri); saveSettings(context); - ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); + WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, uri); task.setTaskListener(exportSettingsListener); task.setAppWidgetId(appWidgetId); task.execute(); } - private final ExportWidgetSettingsTask.TaskListener exportSettingsListener = new ExportWidgetSettingsTask.TaskListener() + private final WidgetSettingsExportTask.TaskListener exportSettingsListener = new WidgetSettingsExportTask.TaskListener() { @Override public void onStarted() @@ -2003,7 +2005,7 @@ public void onStarted() } @Override - public void onFinished(ExportWidgetSettingsTask.ExportResult results) + public void onFinished(WidgetSettingsExportTask.ExportResult results) { //setRetainInstance(false); dismissProgress(); @@ -2054,13 +2056,40 @@ protected boolean importSettings(Context context) } return false; } - public boolean importSettings(Context context, @NonNull Uri uri) + public boolean importSettings(final Context context, @NonNull Uri uri) { Log.i("ImportSettings", "Starting import task: " + uri); - // TODO + WidgetSettingsImportTask task = new WidgetSettingsImportTask(context); + task.setTaskListener(new WidgetSettingsImportTask.TaskListener() + { + @Override + public void onStarted() { + showProgress(context, context.getString(R.string.importwidget_dialog_title), context.getString(R.string.importwidget_dialog_message)); + } + + @Override + public void onFinished(WidgetSettingsImportTask.TaskResult result) + { + dismissProgress(); + if (result.getResult() && result.numResults() > 0) + { + Toast.makeText(context, "TODO: found " + result.numResults() + " items.", Toast.LENGTH_SHORT).show(); // TODO + Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + ContentValues values = WidgetSettings.replaceKeyPrefix(result.getItems()[0], appWidgetId); + WidgetSettings.putValues(context, values); + loadSettings(context); // reload + + } else { + Toast.makeText(context, context.getString(R.string.msg_import_failure), Toast.LENGTH_SHORT).show(); + } + } + }); + task.execute(uri); return true; } + + //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index e080c35b0..857b56081 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -68,7 +68,7 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; -import com.forrestguice.suntimeswidget.settings.ExportWidgetSettingsTask; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.views.Toast; import com.forrestguice.suntimeswidget.widgets.DateWidget0; @@ -378,8 +378,8 @@ protected void exportSettings(Context context) String exportTarget = "SuntimesWidgets"; if (Build.VERSION.SDK_INT >= 19) { - String filename = exportTarget + ExportWidgetSettingsTask.FILEEXT; - Intent intent = ExportTask.getCreateFileIntent(filename, ExportWidgetSettingsTask.MIMETYPE); + String filename = exportTarget + WidgetSettingsExportTask.FILEEXT; + Intent intent = ExportTask.getCreateFileIntent(filename, WidgetSettingsExportTask.MIMETYPE); try { startActivityForResult(intent, EXPORT_REQUEST); return; @@ -389,7 +389,7 @@ protected void exportSettings(Context context) } } - ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, exportTarget, true, true); // export to external cache + WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, exportTarget, true, true); // export to external cache task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); @@ -397,12 +397,12 @@ protected void exportSettings(Context context) public void exportSettings(Context context, @NonNull Uri uri) { Log.i("ExportSettings", "Starting export task: " + uri); - ExportWidgetSettingsTask task = new ExportWidgetSettingsTask(context, uri); + WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, uri); task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); } - private final ExportWidgetSettingsTask.TaskListener exportSettingsListener = new ExportWidgetSettingsTask.TaskListener() + private final WidgetSettingsExportTask.TaskListener exportSettingsListener = new WidgetSettingsExportTask.TaskListener() { @Override public void onStarted() @@ -413,7 +413,7 @@ public void onStarted() } @Override - public void onFinished(ExportWidgetSettingsTask.ExportResult results) + public void onFinished(WidgetSettingsExportTask.ExportResult results) { //setRetainInstance(false); dismissProgress(); @@ -511,7 +511,7 @@ protected void updateWidgetAlarms(Context context) } /** - * ListItem representing a running widget; specifies appWidgetId, and configuration activity. + * ListItem representing a running widget; specifies appWidgetId, and configuration activity.f */ public static class WidgetListItem { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index f2dd5f5b3..214eb2eb1 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -24,6 +24,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import com.forrestguice.suntimeswidget.R; @@ -3321,6 +3322,14 @@ public static ContentValues toContentValues(Context context, int appWidgetId) //Log.d("DEBUG", key + " is Integer"); values.put(key, prefs.getInt(key, -1)); + } else if (map.get(key).getClass().equals(Long.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getLong(key, -1)); + + } else if (map.get(key).getClass().equals(Float.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getFloat(key, -1)); + } else if (map.get(key).getClass().equals(Boolean.class)) { //Log.d("DEBUG", key + " is boolean"); values.put(key, prefs.getBoolean(key, false)); @@ -3329,4 +3338,94 @@ public static ContentValues toContentValues(Context context, int appWidgetId) } return values; } + + public static ContentValues putValueInto(ContentValues values, String key, Object value) + { + if (value == null) { + values.putNull(key); + + } else if (value.getClass().equals(String.class)) { + values.put(key, (String) value); + + } else if (value.getClass().equals(Long.class)) { + values.put(key, (Long) value); + + } else if (value.getClass().equals(Integer.class)) { + values.put(key, (Integer) value); + + } else if (value.getClass().equals(Boolean.class)) { + values.put(key, (Boolean) value); + + } else if (value.getClass().equals(Byte.class)) { + values.put(key, (Byte) value); + + } else if (value.getClass().equals(Float.class)) { + values.put(key, (Float) value); + + } else if (value.getClass().equals(Short.class)) { + values.put(key, (Short) value); + + } else if (value.getClass().equals(Double.class)) { + values.put(key, (Double) value); + } + return values; + } + + public static ContentValues replaceKeyPrefix(ContentValues values, int replacementId) + { + ContentValues v = new ContentValues(); + for (String key : values.keySet()) + { + String[] parts = key.split("_"); + parts[1] = Integer.toString(replacementId); + String k = TextUtils.join("_", parts); + WidgetSettings.putValueInto(v, k, values.get(key)); + } + return v; + } + + public static void putValues(Context context, ContentValues values) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); + for (String key : values.keySet()) + { + Object value = values.get(key); + Log.d("DEBUG", key + " :: " + value); + if (value == null) { + continue; + } + if (value.getClass().equals(String.class)) + { + String s = (String) value; + if (s.toLowerCase().equals("true") || s.toLowerCase().equals("false")) { + prefs.putBoolean(key, Boolean.parseBoolean(s)); + + } else if (!s.trim().isEmpty() && s.endsWith("L") && TextUtils.isDigitsOnly(s.substring(0, s.length()-1))) { + prefs.putLong(key, Long.parseLong(s)); + + } else if (!s.trim().isEmpty() && s.endsWith("f") && TextUtils.isDigitsOnly(s.substring(0, s.length()-1))) { + prefs.putFloat(key, Float.parseFloat(s)); + + } else if (!s.trim().isEmpty() && TextUtils.isDigitsOnly(s)) { + prefs.putInt(key, Integer.parseInt(s)); + + } else { + prefs.putString(key, (String) value); + } + + } else if (value.getClass().equals(Long.class)) { + prefs.putLong(key, (Long) value); + + } else if (value.getClass().equals(Integer.class)) { + prefs.putInt(key, (Integer) value); + + } else if (value.getClass().equals(Boolean.class)) { + prefs.putBoolean(key, (Boolean) value); + + } else if (value.getClass().equals(Float.class)) { + prefs.putFloat(key, (Float) value); + } + } + prefs.apply(); + } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java similarity index 82% rename from app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java rename to app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java index 0ab6b6f2b..95785ebf6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/ExportWidgetSettingsTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java @@ -31,22 +31,22 @@ import java.util.ArrayList; import java.util.HashMap; -public class ExportWidgetSettingsTask extends ExportTask +public class WidgetSettingsExportTask extends ExportTask { public static final String FILEEXT = ".txt"; public static final String MIMETYPE = "text/plain"; - public ExportWidgetSettingsTask(Context context, String exportTarget) + public WidgetSettingsExportTask(Context context, String exportTarget) { super(context, exportTarget); initTask(); } - public ExportWidgetSettingsTask(Context context, String exportTarget, boolean useExternalStorage, boolean saveToCache) + public WidgetSettingsExportTask(Context context, String exportTarget, boolean useExternalStorage, boolean saveToCache) { super(context, exportTarget, useExternalStorage, saveToCache); initTask(); } - public ExportWidgetSettingsTask(Context context, Uri uri) + public WidgetSettingsExportTask(Context context, Uri uri) { super(context, uri); initTask(); @@ -65,7 +65,7 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce { Integer appWidgetId = appWidgetIds.get(i); if (appWidgetId != null) { - String json = toJson(WidgetSettings.toContentValues(context, appWidgetId)); + String json = WidgetSettingsImportTask.WidgetSettingsJson.toJson(WidgetSettings.toContentValues(context, appWidgetId)); out.write(json.getBytes()); } } @@ -73,12 +73,6 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce return true; } - private static String toJson(ContentValues values) - { - HashMap map = ExportTask.toMap(values); - return new JSONObject(map).toString(); - } - /** * @param value export single appWidgetId */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java new file mode 100644 index 000000000..5e77f45f7 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -0,0 +1,329 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.annotation.TargetApi; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.JsonReader; +import android.util.Log; + +import com.forrestguice.suntimeswidget.ExportTask; + +import org.json.JSONObject; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class WidgetSettingsImportTask extends AsyncTask +{ + public static final long MIN_WAIT_TIME = 2000; + + private final WeakReference contextRef; + + protected boolean isPaused = false; + public void pauseTask() { + isPaused = true; + } + public void resumeTask() { + isPaused = false; + } + public boolean isPaused() { + return isPaused; + } + + public WidgetSettingsImportTask(Context context) { + contextRef = new WeakReference<>(context); + } + + @Override + protected void onPreExecute() + { + Log.d(getClass().getSimpleName(), "onPreExecute"); + if (taskListener != null) { + taskListener.onStarted(); + } + } + + @Override + protected TaskResult doInBackground(Uri... params) + { + Log.d(getClass().getSimpleName(), "doInBackground: starting"); + Uri uri = null; + if (params.length > 0) { + uri = params[0]; + } + + long startTime = System.currentTimeMillis(); + boolean result = false; + ArrayList items = new ArrayList<>(); + Exception error = null; + + Context context = contextRef.get(); + if (context != null && uri != null) + { + try { + InputStream in = context.getContentResolver().openInputStream(uri); + if (in != null) + { + Log.d(getClass().getSimpleName(), "doInBackground: reading"); + WidgetSettingsJson.readItems(context, in, items); + result = true; + error = null; + + } else { + Log.e(getClass().getSimpleName(), "Failed to import from " + uri + ": null input stream!"); + result = false; + error = null; + } + } catch (IOException e) { + Log.e(getClass().getSimpleName(), "Failed to import from " + uri + ": " + e); + result = false; + items = null; + error = e; + } + } + + Log.d(getClass().getSimpleName(), "doInBackground: waiting"); + long endTime = System.currentTimeMillis(); + while ((endTime - startTime) < MIN_WAIT_TIME || isPaused) { + endTime = System.currentTimeMillis(); + } + + Log.d(getClass().getSimpleName(), "doInBackground: finishing"); + return new TaskResult(result, uri, (items != null ? items.toArray(new ContentValues[0]) : null), error); + } + + @Override + protected void onProgressUpdate(ContentValues... progressItems) { + super.onProgressUpdate(progressItems); + } + + @Override + protected void onPostExecute( TaskResult result ) + { + Log.d(getClass().getSimpleName(), "onPostExecute: " + result.getResult()); + if (taskListener != null) { + taskListener.onFinished(result); + } + } + + /** + * TaskResult + */ + public static class TaskResult + { + public TaskResult(boolean result, Uri uri, @Nullable ContentValues[] items, Exception e) + { + this.result = result; + this.items = items; + this.uri = uri; + this.e = e; + } + + private boolean result; + public boolean getResult() + { + return result; + } + + private ContentValues[] items; + public ContentValues[] getItems() + { + return items; + } + + private Uri uri; + public Uri getUri() + { + return uri; + } + + public int numResults() { + return (items != null ? items.length : 0); + } + + private Exception e; + public Exception getException() + { + return e; + } + } + + /** + * TaskListener + */ + public static abstract class TaskListener + { + public void onStarted() {} + public void onFinished( TaskResult result ) {} + } + protected TaskListener taskListener = null; + public void setTaskListener( TaskListener listener ) { + taskListener = listener; + } + public void clearTaskListener() { + taskListener = null; + } + + /** + * WidgetSettingsJson + */ + public static class WidgetSettingsJson + { + public static final String TAG = "WidgetSettingsJson"; + + public static void readItems(Context context, InputStream in, ArrayList items) throws IOException + { + if (Build.VERSION.SDK_INT >= 11) + { + //noinspection CharsetObjectCanBeUsed + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + reader.setLenient(true); + try { + readItems(context, reader, items); + } finally { + reader.close(); + in.close(); + } + } else { + Log.w(TAG, "Unsupported; skipping import"); + in.close(); + } + } + + @TargetApi(11) + protected static void readItems(Context context, JsonReader reader, ArrayList items) throws IOException + { + switch (reader.peek()) { + case BEGIN_ARRAY: readItemArray(context, reader, items); break; + case BEGIN_OBJECT: ContentValues item = readItem(context, reader); + if (item != null) { + items.add(item); + } + break; + default: reader.skipValue(); break; + } + } + + @TargetApi(11) + protected static void readItemArray(Context context, JsonReader reader, ArrayList items) throws IOException + { + try { + reader.beginArray(); + while (reader.hasNext()) { + readItems(context, reader, items); + } + reader.endArray(); + } catch (EOFException e) { + Log.e(TAG, "unexpected end of file! " + e); + } + } + + @Nullable + @TargetApi(11) + protected static ContentValues readItem(Context context, JsonReader reader) + { + Map map = readJsonObject(reader); + if (map != null) + { + try { + return ExportTask.toContentValues(map); + + } catch (Exception e) { + Log.e(TAG, "readItem: skipping item because of " + e); + return null; + } + } else return null; + } + + @Nullable + @TargetApi(11) + protected static Map readJsonObject(JsonReader reader) + { + try { + Map map = new HashMap<>(); + reader.beginObject(); + while (reader.hasNext()) + { + String key = reader.nextName(); + if (reader.hasNext()) + { + Object value = null; + switch (reader.peek()) + { + case BEGIN_ARRAY: skipJsonArray(reader); break; + case BEGIN_OBJECT: skipJsonObject(reader); break; + case BOOLEAN: value = reader.nextBoolean(); break; + case NULL: value = null; reader.nextNull(); break; + case NUMBER: case STRING: + default: value = reader.nextString(); break; + } + map.put(key, value); + } + } + reader.endObject(); + return map; + + } catch (IOException e) { + Log.e(TAG, "readJsonObject: skipping item because of " + e); + return null; + } + } + + @TargetApi(11) + protected static void skipJsonObject(JsonReader reader) throws IOException + { + reader.beginObject(); + while (reader.hasNext()) { + reader.skipValue(); + } + reader.endObject(); + } + + @TargetApi(11) + protected static void skipJsonArray(JsonReader reader) throws IOException + { + reader.beginArray(); + while (reader.hasNext()) { + reader.skipValue(); + } + reader.endArray(); + } + + public static String toJson(ContentValues values) + { + HashMap map = ExportTask.toMap(values); + return new JSONObject(map).toString(); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37186d08f..32b7d3ac4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1197,6 +1197,8 @@ Exporting Exporting widget settings to file. + Importing + Importing widget settings to file. Places From 6fd16f21bfc9e942ec625202ff20d66ea6f7da1b Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 16:46:21 -0700 Subject: [PATCH 04/69] WidgetSettings type map --- .../settings/WidgetSettings.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index 214eb2eb1..f485f73ab 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -64,6 +64,7 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.TreeMap; /** * Shared preferences used by individual widgets; uses getSharedPreferences (stored in com.forrestguice.suntimeswidget.xml). @@ -249,6 +250,132 @@ public class WidgetSettings /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// + public static String[] ALL_KEYS = new String[] + { + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_THEME, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SHOWTITLE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_TITLETEXT, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SHOWLABELS, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_SUN1x1, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_SUNPOS1x1, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_SUNPOS3x1, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_SUNPOS3x2, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_MOON1x1, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_ALLOWRESIZE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SCALETEXT, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SCALEBASE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_GRAVITY, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_TIMEFORMATMODE, + + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_CALCULATOR, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_RISESETORDER, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMEMODE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMEMODE2, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMEMODE2_OVERRIDE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMEMODE3, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMENOTE_RISE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMENOTE_SET, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TRACKINGMODE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TRACKINGLEVEL, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_COMPAREMODE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWCOMPARE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWNOON, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWWEEKS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWHOURS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWSECONDS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWTIMEDATE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWABBRMONTH, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_LOCALIZE_HEMISPHERE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_OBSERVERHEIGHT, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_UNITS_LENGTH, + + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_MODE, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_LONGITUDE, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_LATITUDE, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_ALTITUDE, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_ALTITUDE_ENABLED, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_LABEL, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_FROMAPP, + PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_MODE, + PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_FROMAPP, + PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_CUSTOM, + PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_SOLARMODE, + + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_MODE, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_YEAR, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_MONTH, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_DAY, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_OFFSET, + PREF_PREFIX_KEY_DATE + PREF_KEY_NEXTUPDATE, + + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_MODE, + }; + public static String[] BOOL_KEYS = new String[] + { + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SHOWTITLE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SHOWLABELS, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_ALLOWRESIZE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SCALETEXT, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SCALEBASE, + + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TIMEMODE2_OVERRIDE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWCOMPARE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWNOON, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWWEEKS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWHOURS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWSECONDS, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWTIMEDATE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_SHOWABBRMONTH, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_LOCALIZE_HEMISPHERE, + + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_ALTITUDE_ENABLED, + PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_FROMAPP, + PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_FROMAPP + }; + public static String[] FLOAT_KEYS = new String[] { PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_OBSERVERHEIGHT }; + public static String[] LONG_KEYS = new String[] { PREF_KEY_NEXTUPDATE }; + public static String[] INT_KEYS = new String[] + { + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_GRAVITY, // enum as ordinal + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_YEAR, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_MONTH, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_DAY, + PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_OFFSET, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TRACKINGLEVEL, + }; + + private static final Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + TreeMap types = new TreeMap<>(); + putType(types, Long.class, LONG_KEYS); + putType(types, Float.class, FLOAT_KEYS); + putType(types, Integer.class, INT_KEYS); + putType(types, Boolean.class, BOOL_KEYS); + + types.put(PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_LATITUDE, String.class); // double as String + types.put(PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_LONGITUDE, String.class); // double as String + types.put(PREF_PREFIX_KEY_LOCATION + PREF_KEY_LOCATION_ALTITUDE, String.class); // double as String + + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } + private static void putType(Map map, Class type, String... keys) { + for (String key : keys) { + map.put(key, type); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + /** * LengthUnit */ From 0a9098322caa5375bb9c1f6484b7731946327f88 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 16:47:18 -0700 Subject: [PATCH 05/69] WidgetSettingsImportTask verify value types --- .../SuntimesConfigActivity0.java | 10 +- .../settings/WidgetSettings.java | 126 -------------- .../settings/WidgetSettingsExportTask.java | 45 ++++- .../settings/WidgetSettingsImportTask.java | 155 +++++++++++++++++- 4 files changed, 202 insertions(+), 134 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index dc9800cc3..5db12bcba 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -25,6 +25,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Paint; @@ -40,7 +41,6 @@ import android.support.v7.app.ActionBar; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.Toolbar; -import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -2075,8 +2075,10 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) { Toast.makeText(context, "TODO: found " + result.numResults() + " items.", Toast.LENGTH_SHORT).show(); // TODO Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); - ContentValues values = WidgetSettings.replaceKeyPrefix(result.getItems()[0], appWidgetId); - WidgetSettings.putValues(context, values); + + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + WidgetSettingsImportTask.importValues(prefs, result.getItems()[0], appWidgetId); + loadSettings(context); // reload } else { @@ -2088,8 +2090,6 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) return true; } - - //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index f485f73ab..2f6ec5967 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -3429,130 +3429,4 @@ public static void initDisplayStrings( Context context ) WidgetActions.initDisplayStrings(context); } - public static ContentValues toContentValues(Context context, int appWidgetId) - { - SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); - Map map = prefs.getAll(); - Set keys = map.keySet(); - - ContentValues values = new ContentValues(); - for (String key : keys) - { - if (key.startsWith(WidgetSettings.PREF_PREFIX_KEY + appWidgetId)) - { - if (map.get(key).getClass().equals(String.class)) - { - //Log.d("DEBUG", key + " is String"); - values.put(key, prefs.getString(key, null)); - - } else if (map.get(key).getClass().equals(Integer.class)) { - //Log.d("DEBUG", key + " is Integer"); - values.put(key, prefs.getInt(key, -1)); - - } else if (map.get(key).getClass().equals(Long.class)) { - //Log.d("DEBUG", key + " is Long"); - values.put(key, prefs.getLong(key, -1)); - - } else if (map.get(key).getClass().equals(Float.class)) { - //Log.d("DEBUG", key + " is Long"); - values.put(key, prefs.getFloat(key, -1)); - - } else if (map.get(key).getClass().equals(Boolean.class)) { - //Log.d("DEBUG", key + " is boolean"); - values.put(key, prefs.getBoolean(key, false)); - } - } - } - return values; - } - - public static ContentValues putValueInto(ContentValues values, String key, Object value) - { - if (value == null) { - values.putNull(key); - - } else if (value.getClass().equals(String.class)) { - values.put(key, (String) value); - - } else if (value.getClass().equals(Long.class)) { - values.put(key, (Long) value); - - } else if (value.getClass().equals(Integer.class)) { - values.put(key, (Integer) value); - - } else if (value.getClass().equals(Boolean.class)) { - values.put(key, (Boolean) value); - - } else if (value.getClass().equals(Byte.class)) { - values.put(key, (Byte) value); - - } else if (value.getClass().equals(Float.class)) { - values.put(key, (Float) value); - - } else if (value.getClass().equals(Short.class)) { - values.put(key, (Short) value); - - } else if (value.getClass().equals(Double.class)) { - values.put(key, (Double) value); - } - return values; - } - - public static ContentValues replaceKeyPrefix(ContentValues values, int replacementId) - { - ContentValues v = new ContentValues(); - for (String key : values.keySet()) - { - String[] parts = key.split("_"); - parts[1] = Integer.toString(replacementId); - String k = TextUtils.join("_", parts); - WidgetSettings.putValueInto(v, k, values.get(key)); - } - return v; - } - - public static void putValues(Context context, ContentValues values) - { - SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); - for (String key : values.keySet()) - { - Object value = values.get(key); - Log.d("DEBUG", key + " :: " + value); - if (value == null) { - continue; - } - if (value.getClass().equals(String.class)) - { - String s = (String) value; - if (s.toLowerCase().equals("true") || s.toLowerCase().equals("false")) { - prefs.putBoolean(key, Boolean.parseBoolean(s)); - - } else if (!s.trim().isEmpty() && s.endsWith("L") && TextUtils.isDigitsOnly(s.substring(0, s.length()-1))) { - prefs.putLong(key, Long.parseLong(s)); - - } else if (!s.trim().isEmpty() && s.endsWith("f") && TextUtils.isDigitsOnly(s.substring(0, s.length()-1))) { - prefs.putFloat(key, Float.parseFloat(s)); - - } else if (!s.trim().isEmpty() && TextUtils.isDigitsOnly(s)) { - prefs.putInt(key, Integer.parseInt(s)); - - } else { - prefs.putString(key, (String) value); - } - - } else if (value.getClass().equals(Long.class)) { - prefs.putLong(key, (Long) value); - - } else if (value.getClass().equals(Integer.class)) { - prefs.putInt(key, (Integer) value); - - } else if (value.getClass().equals(Boolean.class)) { - prefs.putBoolean(key, (Boolean) value); - - } else if (value.getClass().equals(Float.class)) { - prefs.putFloat(key, (Float) value); - } - } - prefs.apply(); - } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java index 95785ebf6..78e3664eb 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.net.Uri; import com.forrestguice.suntimeswidget.ExportTask; @@ -30,6 +31,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; +import java.util.Set; public class WidgetSettingsExportTask extends ExportTask { @@ -64,8 +67,10 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce for (int i=0; i values) } protected ArrayList appWidgetIds = new ArrayList<>(); + public static ContentValues toContentValues(SharedPreferences prefs, int appWidgetId) + { + Map map = prefs.getAll(); + Set keys = map.keySet(); + + ContentValues values = new ContentValues(); + for (String key : keys) + { + if (key.startsWith(WidgetSettings.PREF_PREFIX_KEY + appWidgetId)) + { + if (map.get(key).getClass().equals(String.class)) + { + //Log.d("DEBUG", key + " is String"); + values.put(key, prefs.getString(key, null)); + + } else if (map.get(key).getClass().equals(Integer.class)) { + //Log.d("DEBUG", key + " is Integer"); + values.put(key, prefs.getInt(key, -1)); + + } else if (map.get(key).getClass().equals(Long.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getLong(key, -1)); + + } else if (map.get(key).getClass().equals(Float.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getFloat(key, -1)); + + } else if (map.get(key).getClass().equals(Boolean.class)) { + //Log.d("DEBUG", key + " is boolean"); + values.put(key, prefs.getBoolean(key, false)); + } + } + } + return values; + } + } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 5e77f45f7..83046e278 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -21,6 +21,7 @@ import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; +import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -283,7 +284,8 @@ protected static Map readJsonObject(JsonReader reader) case BEGIN_OBJECT: skipJsonObject(reader); break; case BOOLEAN: value = reader.nextBoolean(); break; case NULL: value = null; reader.nextNull(); break; - case NUMBER: case STRING: + case NUMBER: // int, long, or double + case STRING: default: value = reader.nextString(); break; } map.put(key, value); @@ -326,4 +328,155 @@ public static String toJson(ContentValues values) } + public static ContentValues putValueInto(ContentValues values, String key, Object value) + { + if (value == null) { + values.putNull(key); + + } else if (value.getClass().equals(String.class)) { + values.put(key, (String) value); + + } else if (value.getClass().equals(Long.class)) { + values.put(key, (Long) value); + + } else if (value.getClass().equals(Integer.class)) { + values.put(key, (Integer) value); + + } else if (value.getClass().equals(Boolean.class)) { + values.put(key, (Boolean) value); + + } else if (value.getClass().equals(Byte.class)) { + values.put(key, (Byte) value); + + } else if (value.getClass().equals(Float.class)) { + values.put(key, (Float) value); + + } else if (value.getClass().equals(Short.class)) { + values.put(key, (Short) value); + + } else if (value.getClass().equals(Double.class)) { + values.put(key, (Double) value); + } + return values; + } + + public static ContentValues replaceKeyPrefix(ContentValues values, int replacementId) + { + ContentValues v = new ContentValues(); + for (String key : values.keySet()) + { + String[] parts = key.split("_"); + parts[1] = Integer.toString(replacementId); + String k = TextUtils.join("_", parts); + v = putValueInto(v, k, values.get(key)); + } + return v; + } + + public static void importValue(SharedPreferences.Editor prefs, Class type, String key, Object value) + { + Log.d("WidgetSettings", "import " + key + " as type " + type.getSimpleName()); + + if (type.equals(String.class)) { + prefs.putString(key, (String) value); + + } else if (type.equals(Integer.class)) { + prefs.putInt(key, (Integer) value); + + } else if (type.equals(Boolean.class)) { + prefs.putBoolean(key, (Boolean) value); + + } else if (type.equals(Long.class)) { + prefs.putLong(key, (Long) value); + + } else if (type.equals(Float.class)) { + prefs.putFloat(key, (Float) value); + + } else { + Log.w("WidgetSettings", "unrecognized type " + type.getSimpleName() + ", skipping key " + key); + } + } + + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) + { + Map prefTypes = WidgetSettings.getPrefTypes(); + for (String key : values.keySet()) + { + Object value = values.get(key); + Log.d("DEBUG", key + " :: " + value); + if (value == null) { + continue; + } + + String[] keyParts = key.split("_"); + keyParts[1] = appWidgetId + ""; + String k = TextUtils.join("_", keyParts); // replacement key + String k0 = k.replaceFirst(WidgetSettings.PREF_PREFIX_KEY + appWidgetId, ""); + + if (prefTypes.containsKey(k0)) + { + Class expectedType = prefTypes.get(k0); + Class valueType = value.getClass(); + if (valueType.equals(expectedType)) { + importValue(prefs, expectedType, k, value); // types match (direct cast) + + } else { + if (expectedType.equals(String.class)) { + importValue(prefs, String.class, k, value.toString()); // int, long, double, or bool as String + + } else if (expectedType.equals(Boolean.class)) { + if (valueType.equals(String.class)) // bool as String + { + String s = (String) value; + if (s.toLowerCase().equals("true") || s.toLowerCase().equals("false")) { + importValue(prefs, Boolean.class, k, Boolean.parseBoolean(s)); + } else { + Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + s + " (String)"); + } + } else { + Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } + + } else if (expectedType.equals(Integer.class)) { + if (valueType.equals(String.class)) { // int as String + try { + importValue(prefs, Integer.class, k, Integer.parseInt((String) value)); + } catch (NumberFormatException e) { + Log.w("WidgetSettings", "skipping " + k + "... " + e); + } + } else { + Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } + + } else if (expectedType.equals(Long.class)) { + if (valueType.equals(String.class)) { // long as String + try { + importValue(prefs, Long.class, k, Long.parseLong((String) value)); + } catch (NumberFormatException e) { + Log.w("WidgetSettings", "skipping " + k + "... " + e); + } + } else { + Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } + + } else if (expectedType.equals(Double.class)) { + if (valueType.equals(String.class)) { // double as String + try { + importValue(prefs, Double.class, k, Double.parseDouble((String) value)); + } catch (NumberFormatException e) { + Log.w("WidgetSettings", "skipping " + k + "... " + e); + } + } else { + Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } + } + } + + } else { + Log.w("WidgetSettings", k0 + " is not recognized! skipping key..."); + } + } + prefs.apply(); + } + } \ No newline at end of file From f6b72f4e6210820fb7d7439ca79d9c99e8d8717c Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 18:05:23 -0700 Subject: [PATCH 06/69] getPrefTypes --- .../calendar/CalendarSettings.java | 32 ++++++++++++ .../settings/WidgetActions.java | 43 ++++++++++++++++ .../settings/WidgetSettings.java | 6 +-- .../settings/WidgetSettingsImportTask.java | 51 +++++++++---------- 4 files changed, 102 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java index 79b5134ef..5f27e82c6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java @@ -23,6 +23,9 @@ import com.forrestguice.suntimeswidget.settings.WidgetSettings; +import java.util.Map; +import java.util.TreeMap; + /** * @see WidgetSettings */ @@ -57,6 +60,35 @@ public class CalendarSettings public static final String PREF_DEF_CALENDAR_FORMATPATTERN_THAISOLAR = "MMMM d, yyyy"; // TODO public static final String PREF_DEF_CALENDAR_FORMATPATTERN_VIETNAMESE = "EEE, d. MMMM r(U)"; // TODO: review + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + public static final String[] ALL_KEYS = { + PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_SHOWDATE, + PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_MODE, + PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_FORMATPATTERN + }; + public static final String[] BOOL_KEYS = { + PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_SHOWDATE + }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + for (String key : BOOL_KEYS) { + types.put(key, Boolean.class); + } + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java index d0c2c4abc..45697e44f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java @@ -52,7 +52,9 @@ import java.util.Arrays; import java.util.Locale; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import static com.forrestguice.suntimeswidget.actions.SuntimesActionsContract.TAG_DEFAULT; @@ -109,6 +111,47 @@ public class WidgetActions /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// + public static final String[] ALL_KEYS = new String[] + { + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_TITLE, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_DESC, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_COLOR, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_TAGS, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_PACKAGE, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_ACTION, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_EXTRAS, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_DATA, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_DATATYPE, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_TYPE, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_LIST + }; + + public static final String[] INT_KEYS = new String[] { + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_COLOR, + }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + for (String key : INT_KEYS) { + types.put(key, Integer.class); + } + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + /** * LaunchType */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index 2f6ec5967..3e34f9528 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -299,7 +299,7 @@ public class WidgetSettings PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_MODE, PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_FROMAPP, PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_CUSTOM, - PREF_PREFIX_KEY_TIMEZONE + PREF_KEY_TIMEZONE_SOLARMODE, + PREF_PREFIX_KEY_GENERAL + PREF_KEY_TIMEZONE_SOLARMODE, // in _general PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_MODE, PREF_PREFIX_KEY_DATE + PREF_KEY_DATE_YEAR, @@ -344,12 +344,12 @@ public class WidgetSettings PREF_PREFIX_KEY_GENERAL + PREF_KEY_GENERAL_TRACKINGLEVEL, }; - private static final Map types = null; + private static Map types = null; public static Map getPrefTypes() { if (types == null) { - TreeMap types = new TreeMap<>(); + types = new TreeMap<>(); putType(types, Long.class, LONG_KEYS); putType(types, Float.class, FLOAT_KEYS); putType(types, Integer.class, INT_KEYS); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 83046e278..692e0ad41 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -31,6 +31,7 @@ import android.util.Log; import com.forrestguice.suntimeswidget.ExportTask; +import com.forrestguice.suntimeswidget.calendar.CalendarSettings; import org.json.JSONObject; @@ -373,10 +374,9 @@ public static ContentValues replaceKeyPrefix(ContentValues values, int replaceme return v; } - public static void importValue(SharedPreferences.Editor prefs, Class type, String key, Object value) + public static boolean importValue(SharedPreferences.Editor prefs, Class type, String key, Object value) { - Log.d("WidgetSettings", "import " + key + " as type " + type.getSimpleName()); - + boolean retValue = true; if (type.equals(String.class)) { prefs.putString(key, (String) value); @@ -391,20 +391,27 @@ public static void importValue(SharedPreferences.Editor prefs, Class type, Strin } else if (type.equals(Float.class)) { prefs.putFloat(key, (Float) value); + } else retValue = false; - } else { - Log.w("WidgetSettings", "unrecognized type " + type.getSimpleName() + ", skipping key " + key); - } + if (retValue) { + Log.i("WidgetSettings", "imported: added " + key + " as type " + type.getSimpleName()); + } else Log.w("WidgetSettings", "import: skipping " + key + "... unrecognized type " + type.getSimpleName()); + + return retValue; } public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) { Map prefTypes = WidgetSettings.getPrefTypes(); + prefTypes.putAll(CalendarSettings.getPrefTypes()); + prefTypes.putAll(WidgetActions.getPrefTypes()); + + for (String key : values.keySet()) { Object value = values.get(key); - Log.d("DEBUG", key + " :: " + value); if (value == null) { + Log.w("WidgetSettings", "import: skipping " + key + "... contains null value"); continue; } @@ -430,50 +437,40 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va String s = (String) value; if (s.toLowerCase().equals("true") || s.toLowerCase().equals("false")) { importValue(prefs, Boolean.class, k, Boolean.parseBoolean(s)); - } else { - Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + s + " (String)"); - } - } else { - Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); - } + } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + s + " (String)"); + } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } else if (expectedType.equals(Integer.class)) { if (valueType.equals(String.class)) { // int as String try { importValue(prefs, Integer.class, k, Integer.parseInt((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "skipping " + k + "... " + e); + Log.w("WidgetSettings", "import: skipping " + k + "... " + e); } - } else { - Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); - } + } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } else if (expectedType.equals(Long.class)) { if (valueType.equals(String.class)) { // long as String try { importValue(prefs, Long.class, k, Long.parseLong((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "skipping " + k + "... " + e); + Log.w("WidgetSettings", "import: skipping " + k + "... " + e); } - } else { - Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); - } + } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } else if (expectedType.equals(Double.class)) { if (valueType.equals(String.class)) { // double as String try { importValue(prefs, Double.class, k, Double.parseDouble((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "skipping " + k + "... " + e); + Log.w("WidgetSettings", "import: skipping " + k + "... " + e); } - } else { - Log.w("WidgetSettings", "skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); - } + } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } } - } else { - Log.w("WidgetSettings", k0 + " is not recognized! skipping key..."); + Log.w("WidgetSettings", "import: skipping " + k0 + "... unrecognized key"); } } prefs.apply(); From 59a484ca11bc433ce491c59fd0d60ce3df24c496 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 18:28:24 -0700 Subject: [PATCH 07/69] getPrefTypes --- .../map/WorldMapWidgetSettings.java | 30 +++++++++++++++++++ .../settings/WidgetSettingsImportTask.java | 5 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/map/WorldMapWidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/map/WorldMapWidgetSettings.java index adec5e84a..a05909dcc 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/map/WorldMapWidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/map/WorldMapWidgetSettings.java @@ -28,6 +28,9 @@ import com.forrestguice.suntimeswidget.R; import com.forrestguice.suntimeswidget.settings.WidgetSettings; +import java.util.Map; +import java.util.TreeMap; + public class WorldMapWidgetSettings { @@ -75,6 +78,33 @@ public class WorldMapWidgetSettings public static final String PROJ4_AEQD = "+proj=aeqd +lat_0=%1$s +lon_0=%2$s +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"; public static final String PROJ4_AEQD1 = "+proj=aeqd +lat_0=%1$s +lon_0=%2$s +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs"; + ////////////////////////////////////////////////// + ////////////////////////////////////////////////// + + public static final String[] ALL_KEYS = new String[] { + WidgetSettings.PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_WIDGETMODE_WORLDMAP + }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + for (String key : ALL_KEYS) { // all others are type String + for (String tag : MAPTAGS) { + if (!types.containsKey(key + tag)) { + types.put(key + tag, String.class); + } + } + } + } + return types; + } + + ////////////////////////////////////////////////// + ////////////////////////////////////////////////// + /** * WorldMapWidgetMode */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 692e0ad41..2f77d5ec0 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -32,6 +32,7 @@ import com.forrestguice.suntimeswidget.ExportTask; import com.forrestguice.suntimeswidget.calendar.CalendarSettings; +import com.forrestguice.suntimeswidget.map.WorldMapWidgetSettings; import org.json.JSONObject; @@ -394,7 +395,7 @@ public static boolean importValue(SharedPreferences.Editor prefs, Class type, St } else retValue = false; if (retValue) { - Log.i("WidgetSettings", "imported: added " + key + " as type " + type.getSimpleName()); + Log.i("WidgetSettings", "import: added " + key + " as type " + type.getSimpleName()); } else Log.w("WidgetSettings", "import: skipping " + key + "... unrecognized type " + type.getSimpleName()); return retValue; @@ -405,7 +406,7 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va Map prefTypes = WidgetSettings.getPrefTypes(); prefTypes.putAll(CalendarSettings.getPrefTypes()); prefTypes.putAll(WidgetActions.getPrefTypes()); - + prefTypes.putAll(WorldMapWidgetSettings.getPrefTypes()); for (String key : values.keySet()) { From 00369f9f3c5b49feaa0dbd638e9702a507ee75b9 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 20:04:07 -0700 Subject: [PATCH 08/69] WidgetMetaData --- .../ClockWidget0ConfigActivity.java | 9 ++- .../ClockWidget0ConfigActivity_3x1.java | 5 ++ .../MoonWidget0ConfigActivity.java | 7 ++- .../MoonWidget0ConfigActivity_2x1.java | 7 ++- .../MoonWidget0ConfigActivity_3x1.java | 7 ++- .../MoonWidget0ConfigActivity_3x2.java | 5 ++ .../SolsticeWidget0ConfigActivity.java | 7 ++- .../SuntimesConfigActivity0.java | 13 +++- .../SuntimesConfigActivity0_2x1.java | 7 ++- .../SuntimesConfigActivity0_3x1.java | 7 ++- .../SuntimesConfigActivity2.java | 7 ++- .../SuntimesConfigActivity2_3x1.java | 7 ++- .../SuntimesConfigActivity2_3x2.java | 7 ++- .../SuntimesConfigActivity2_3x3.java | 7 ++- .../settings/WidgetSettings.java | 61 +++++++++++++++++++ .../widgets/DateWidget0ConfigActivity.java | 14 ++--- 16 files changed, 155 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity.java index d227f142b..20bafaa39 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity.java @@ -26,8 +26,6 @@ import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.themes.WidgetThemeConfigActivity; -import static com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity.PICK_THEME_REQUEST; - /** * Clock widget config activity. */ @@ -39,6 +37,11 @@ public ClockWidget0ConfigActivity() super(); } + @Override + protected Class getWidgetClass() { + return ClockWidget0.class; + } + @Override protected void initViews( Context context ) { @@ -73,7 +76,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, ClockWidget0.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity_3x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity_3x1.java index 823401daa..aad4fef86 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity_3x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/ClockWidget0ConfigActivity_3x1.java @@ -33,6 +33,11 @@ public ClockWidget0ConfigActivity_3x1() super(); } + @Override + protected Class getWidgetClass() { + return ClockWidget0_3x1.class; + } + @Override protected void initViews( Context context ) { super.initViews(context); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity.java index 3c995b7b4..263c66439 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity.java @@ -37,6 +37,11 @@ public MoonWidget0ConfigActivity() super(); } + @Override + protected Class getWidgetClass() { + return MoonWidget0.class; + } + @Override protected void initViews( Context context ) { @@ -60,7 +65,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, MoonWidget0.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_2x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_2x1.java index 5a33c981e..420eb1b2f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_2x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_2x1.java @@ -33,6 +33,11 @@ public MoonWidget0ConfigActivity_2x1() super(); } + @Override + protected Class getWidgetClass() { + return MoonWidget0_2x1.class; + } + @Override protected void initViews( Context context ) { @@ -46,7 +51,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, MoonWidget0_2x1.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x1.java index 723914409..1ed889c38 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x1.java @@ -37,6 +37,11 @@ public MoonWidget0ConfigActivity_3x1() super(); } + @Override + protected Class getWidgetClass() { + return MoonWidget0_3x1.class; + } + @Override protected void initViews( Context context ) { @@ -50,7 +55,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, MoonWidget0_3x1.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x2.java b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x2.java index 10a880355..b66a41f51 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x2.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/MoonWidget0ConfigActivity_3x2.java @@ -34,6 +34,11 @@ public MoonWidget0ConfigActivity_3x2() super(); } + @Override + protected Class getWidgetClass() { + return MoonWidget0_3x2.class; + } + @Override protected void initViews( Context context ) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0ConfigActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0ConfigActivity.java index 79e0c2d8d..b92de24e4 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0ConfigActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SolsticeWidget0ConfigActivity.java @@ -46,6 +46,11 @@ public SolsticeWidget0ConfigActivity() super(); } + @Override + protected Class getWidgetClass() { + return SolsticeWidget0.class; + } + @Override protected void initViews( Context context ) { @@ -72,7 +77,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SolsticeWidget0.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 5db12bcba..3865283b1 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -294,6 +294,7 @@ protected void saveSettings(Context context) saveTimezoneSettings(context); saveAppearanceSettings(context); saveActionSettings(context); + saveMetadata(context); } /** @@ -1896,6 +1897,12 @@ protected WidgetSettings.TimezoneMode getDefaultTimezoneMode() return WidgetSettings.PREF_DEF_TIMEZONE_MODE; } + protected void saveMetadata(Context context) + { + WidgetSettings.WidgetMetaData metadata = new WidgetSettings.WidgetMetaData(getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE); + WidgetSettings.saveMetaData(context, appWidgetId, metadata); + } + /** * Save UI state to settings (action group). * @@ -2126,13 +2133,17 @@ protected void addWidget() } } + protected Class getWidgetClass() { + return SuntimesWidget0.class; + } + /** * Update all widgets of this type (direct update, no broadcast). * @param context a context used to access resources */ protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget0.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_2x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_2x1.java index 55113d948..7b0bc1013 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_2x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_2x1.java @@ -36,6 +36,11 @@ public SuntimesConfigActivity0_2x1() super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget0_2x1.class; + } + @Override protected void initViews( Context context ) { @@ -49,7 +54,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget0_2x1.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_3x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_3x1.java index d7817b496..914d82d53 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_3x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0_3x1.java @@ -33,10 +33,15 @@ public SuntimesConfigActivity0_3x1() { super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget0_3x1.class; + } + @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget0_3x1.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2.java index 348cd2d30..220e8a564 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2.java @@ -43,6 +43,11 @@ public SuntimesConfigActivity2() super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget2.class; + } + @Override protected void initViews( Context context ) { @@ -90,7 +95,7 @@ protected SuntimesCalculatorDescriptor[] supportingCalculators() @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget2.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x1.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x1.java index dc2e482da..412acfb0e 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x1.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x1.java @@ -37,6 +37,11 @@ public SuntimesConfigActivity2_3x1() super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget2_3x1.class; + } + @Override protected void initViews( Context context ) { @@ -50,7 +55,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget2_3x1.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x2.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x2.java index a36725fbb..451fadc36 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x2.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x2.java @@ -37,6 +37,11 @@ public SuntimesConfigActivity2_3x2() super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget2_3x2.class; + } + @Override protected void initViews( Context context ) { @@ -50,7 +55,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget2_3x2.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x3.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x3.java index 653a6b76b..589e703b9 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x3.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity2_3x3.java @@ -37,6 +37,11 @@ public SuntimesConfigActivity2_3x3() super(); } + @Override + protected Class getWidgetClass() { + return SuntimesWidget2_3x3.class; + } + @Override protected void initViews( Context context ) { @@ -51,7 +56,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, SuntimesWidget2_3x3.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index 3e34f9528..ef45ccfa4 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -22,11 +22,13 @@ import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; +import com.forrestguice.suntimeswidget.BuildConfig; import com.forrestguice.suntimeswidget.R; import com.forrestguice.suntimeswidget.calculator.core.Location; import com.forrestguice.suntimeswidget.calculator.SuntimesCalculatorDescriptor; @@ -81,6 +83,7 @@ public class WidgetSettings public static final String PREF_PREFIX_KEY_TIMEZONE = "_timezone_"; public static final String PREF_PREFIX_KEY_DATE = "_date_"; public static final String PREF_PREFIX_KEY_ACTION = "_action_"; + public static final String PREF_PREFIX_KEY_META = "_meta_"; public static final String PREF_KEY_GENERAL_CALCULATOR = "calculator"; public static final String PREF_DEF_GENERAL_CALCULATOR = "time4a-time4j"; @@ -247,11 +250,17 @@ public class WidgetSettings public static final String PREF_KEY_NEXTUPDATE = "nextUpdate"; public static final long PREF_DEF_NEXTUPDATE = -1L; + public static final String PREF_KEY_META_CLASSNAME = "className"; + public static final String PREF_KEY_META_VERSIONCODE = "versionCode"; + /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// public static String[] ALL_KEYS = new String[] { + PREF_PREFIX_KEY_META + PREF_KEY_META_CLASSNAME, + PREF_PREFIX_KEY_META + PREF_KEY_META_VERSIONCODE, + PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_THEME, PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_SHOWTITLE, PREF_PREFIX_KEY_APPEARANCE + PREF_KEY_APPEARANCE_TITLETEXT, @@ -1689,6 +1698,57 @@ public static void initDisplayStrings( Context context ) /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// + /** + * WidgetMetaData + */ + public static class WidgetMetaData + { + private String className = null; + private int versionCode = -1; + + public WidgetMetaData(String widgetClassName, int versionCode) { + this.className = widgetClassName; + } + + public String getWidgetClassName() { + return className; + } + + public int getVersionCode() { + return versionCode; + } + } + + public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); + String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + prefs.putString(prefs_prefix + PREF_KEY_META_CLASSNAME, metadata.getWidgetClassName()); + prefs.putInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, metadata.getVersionCode()); + prefs.apply(); + } + + public static WidgetMetaData loadMetaData(Context context, int appWidgetId) + { + SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); + String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + String className = prefs.getString(prefs_prefix + PREF_KEY_META_CLASSNAME, null); + int versionCode = prefs.getInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, -1); + return new WidgetMetaData(className, versionCode); + } + + public static void deleteMetaData(Context context, int appWidgetId) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); + String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + prefs.remove(prefs_prefix + PREF_KEY_META_CLASSNAME); + prefs.remove(prefs_prefix + PREF_KEY_META_VERSIONCODE); + prefs.apply(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + public static long getNextSuggestedUpdate(Context context, int appWidgetId) { SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); @@ -3386,6 +3446,7 @@ public static void deletePrefs(Context context, int appWidgetId) deleteTimeNoteSetPref(context, appWidgetId); WidgetActions.deletePrefs(context, appWidgetId); + deleteMetaData(context, appWidgetId); } public static void initDefaults( Context context ) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/widgets/DateWidget0ConfigActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/widgets/DateWidget0ConfigActivity.java index 9cca68865..e41a8be67 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/widgets/DateWidget0ConfigActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/widgets/DateWidget0ConfigActivity.java @@ -24,18 +24,11 @@ import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.Spinner; import android.widget.SpinnerAdapter; -import android.widget.TextView; import com.forrestguice.suntimeswidget.HelpDialog; import com.forrestguice.suntimeswidget.R; @@ -61,6 +54,11 @@ public DateWidget0ConfigActivity() { super(); } + @Override + protected Class getWidgetClass() { + return DateWidget0.class; + } + @Override protected void initViews( Context context ) { @@ -95,7 +93,7 @@ protected void initViews( Context context ) @Override protected void updateWidgets(Context context, int[] appWidgetIds) { - Intent updateIntent = new Intent(context, DateWidget0.class); + Intent updateIntent = new Intent(context, getWidgetClass()); updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); sendBroadcast(updateIntent); From 9a048976d0249d9540745946b49441bd6f686f78 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 21:16:49 -0700 Subject: [PATCH 09/69] WidgetMetadata verify widget class before import --- .../SuntimesConfigActivity0.java | 24 +++++++++++++------ .../settings/WidgetSettings.java | 18 ++++++++++++++ .../settings/WidgetSettingsImportTask.java | 24 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 3865283b1..d265003aa 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2080,16 +2080,26 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) dismissProgress(); if (result.getResult() && result.numResults() > 0) { - Toast.makeText(context, "TODO: found " + result.numResults() + " items.", Toast.LENGTH_SHORT).show(); // TODO - Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + ContentValues values = result.getItems()[0]; + WidgetSettings.WidgetMetaData metadata = WidgetSettings.WidgetMetaData.getMetaDataFromValues(values); + String values_widgetClassName = ((metadata != null) ? metadata.getWidgetClassName() : null); + + if (values_widgetClassName == null || getWidgetClass().getSimpleName().equals(values_widgetClassName)) + { + Log.d("DEBUG", "importing settings for widget type " + values_widgetClassName); + Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); + loadSettings(context); // reload - SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); - WidgetSettingsImportTask.importValues(prefs, result.getItems()[0], appWidgetId); - - loadSettings(context); // reload + } else { + // TODO: show warning "types don't match, import anyway?" + Log.w("ImportSettings", "widget class names do not match! Expected " + getWidgetClass().getSimpleName() + ", found " + values_widgetClassName); + Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + } } else { - Toast.makeText(context, context.getString(R.string.msg_import_failure), Toast.LENGTH_SHORT).show(); + Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); } } }); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java index ef45ccfa4..8c85e8c53 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettings.java @@ -1717,6 +1717,24 @@ public String getWidgetClassName() { public int getVersionCode() { return versionCode; } + + public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values) + { + Long values_id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); + return WidgetSettings.WidgetMetaData.getMetaDataFromValues(values, values_id); + } + + public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values, @Nullable Long appWidgetId) + { + if (appWidgetId != null) + { + String key_className = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_CLASSNAME; + String key_versionCode = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_VERSIONCODE; + String widgetClassName = (values.containsKey(key_className) ? values.getAsString(key_className) : null); + int versionCode = (values.containsKey(key_versionCode) ? values.getAsInteger( key_versionCode) : -1); + return new WidgetMetaData(widgetClassName, versionCode); + } else return null; + } } public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 2f77d5ec0..0e9cc31f7 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -19,6 +19,7 @@ package com.forrestguice.suntimeswidget.settings; import android.annotation.TargetApi; +import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; @@ -477,4 +478,27 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va prefs.apply(); } + public static String findWidgetClassName(ContentValues values, @Nullable Long appWidgetId) + { + if (appWidgetId != null) { + return values.getAsString( WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_CLASSNAME); + } else return null; + } + + public static Long findAppWidgetIdFromFirstKey(ContentValues values) + { + String[] keys = values.keySet().toArray(new String[0]); + if (keys.length > 0) + { + try { + String[] parts = keys[0].split("_"); + return Long.parseLong(parts[1]); + + } catch (NumberFormatException e) { + Log.w("WidgetSettings", "failed to find widget id from keys.. " + e); + } + } + return null; + } + } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 32b7d3ac4..9e010f86b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2123,6 +2123,7 @@ Imported %s. Import from %s failed! Import skipped; no new themes were found. + file %d theme From 1755a7b8bc7444183ebc26e226c03ac28218cebc Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 22:28:57 -0700 Subject: [PATCH 10/69] importSettings --- .../SuntimesConfigActivity0.java | 51 +++++++++++++++---- app/src/main/res/values/strings.xml | 1 + 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index d265003aa..3d0cf709a 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -24,6 +24,7 @@ import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; @@ -39,6 +40,7 @@ import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.Toolbar; import android.util.Log; @@ -2080,22 +2082,41 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) dismissProgress(); if (result.getResult() && result.numResults() > 0) { - ContentValues values = result.getItems()[0]; - WidgetSettings.WidgetMetaData metadata = WidgetSettings.WidgetMetaData.getMetaDataFromValues(values); - String values_widgetClassName = ((metadata != null) ? metadata.getWidgetClassName() : null); - - if (values_widgetClassName == null || getWidgetClass().getSimpleName().equals(values_widgetClassName)) + ContentValues v = null; + for (int i=0; iExporting widget settings to file. Importing Importing widget settings to file. + Found saved settings but they are for another type of widget.\n\nImport anyway? Places From 3c17e61149d939cc1076af18d00bb812d5dfc5ae Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 17 Jan 2024 22:38:39 -0700 Subject: [PATCH 11/69] exportTarget --- .../forrestguice/suntimeswidget/SuntimesConfigActivity0.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 3d0cf709a..0441761dd 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1974,7 +1974,7 @@ protected void exportSettings(Context context) { saveSettings(context); - String exportTarget = "SuntimesWidget_" + appWidgetId; + String exportTarget = getWidgetClass().getSimpleName() + "_" + appWidgetId; if (Build.VERSION.SDK_INT >= 19) { String filename = exportTarget + WidgetSettingsExportTask.FILEEXT; From 22550ef89311756b954a1f23561c3d75f55ada81 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 00:05:55 -0700 Subject: [PATCH 12/69] offerUndoImport --- .../SuntimesConfigActivity0.java | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 0441761dd..6e599dcc4 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -37,6 +37,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; @@ -2128,13 +2129,47 @@ public void onClick(DialogInterface dialog, int whichButton) { return true; } - protected void importSettings(Context context, ContentValues values) + protected void importSettings(Context context, ContentValues values) { + importSettings(context, values, true); + } + protected void importSettings(Context context, ContentValues values, boolean offerUndo) { - SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + SharedPreferences prefs0 = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); + ContentValues previousValues = null; + if (offerUndo) { + previousValues = WidgetSettingsExportTask.toContentValues(prefs0, appWidgetId); + } + + SharedPreferences.Editor prefs = prefs0.edit(); WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); - loadSettings(context); // reload - Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + loadSettings(context); // reload ui + + if (offerUndo) { + Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + offerUndoImport(context, previousValues); + } + } + + protected void offerUndoImport(final Context context, final ContentValues previous) + { + View view = getWindow().getDecorView(); + if (context != null && view != null) + { + CharSequence message = context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)); + Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() + { + @Override + public void onClick(View v) { + importSettings(context, previous, false); + } + }); + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.setDuration(UNDO_IMPORT_MILLIS); + snackbar.show(); + } } + public static final int UNDO_IMPORT_MILLIS = 12000; //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// From 606d84ba0a17552b7c6058d5065034a6dfd12a65 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 00:06:14 -0700 Subject: [PATCH 13/69] layout progressView --- .../suntimeswidget/SuntimesConfigActivity0.java | 1 + app/src/main/res/layout/layout_settings.xml | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 6e599dcc4..e60bb0980 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -424,6 +424,7 @@ protected void initViews(final Context context) initToolbar(context); scrollView = (ScrollView) findViewById(R.id.scrollView); + progressView = findViewById(R.id.progress); text_appWidgetID = (TextView) findViewById(R.id.text_appwidgetid); if (text_appWidgetID != null) diff --git a/app/src/main/res/layout/layout_settings.xml b/app/src/main/res/layout/layout_settings.xml index f6f4612e4..22a052738 100644 --- a/app/src/main/res/layout/layout_settings.xml +++ b/app/src/main/res/layout/layout_settings.xml @@ -36,7 +36,13 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="@dimen/widgetActivity_padding"> + android:padding="@dimen/widgetActivity_padding" + android:animateLayoutChanges="true"> + + From 7ff1be1236cf431827124cf83fa4f97429188681 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 01:51:48 -0700 Subject: [PATCH 14/69] importSettings offer selection if file contains multiple widgets --- .../SuntimesConfigActivity0.java | 43 +++++++++++-------- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index e60bb0980..d36bcc8a9 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2084,37 +2084,42 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) dismissProgress(); if (result.getResult() && result.numResults() > 0) { - ContentValues v = null; + ContentValues values = null; + CharSequence[] items = new CharSequence[result.numResults()]; + for (int i=0; i= 0 && p < allValues.length)) { + importSettings(context, allValues[p]); + } } }) .setNegativeButton(context.getString(R.string.dialog_cancel), null); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37f82909d..c66f81c85 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1199,7 +1199,7 @@ Exporting widget settings to file. Importing Importing widget settings to file. - Found saved settings but they are for another type of widget.\n\nImport anyway? + Settings found,\nbut they are for other widgets… Places From da11cf82656601bd709876a252b183f299277018 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 11:50:29 -0700 Subject: [PATCH 15/69] export --- .../settings/WidgetSettingsExportTask.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java index 78e3664eb..d0bd1b515 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java @@ -25,12 +25,9 @@ import com.forrestguice.suntimeswidget.ExportTask; -import org.json.JSONObject; - import java.io.BufferedOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -64,16 +61,23 @@ private void initTask() @Override public boolean export( Context context, BufferedOutputStream out ) throws IOException { - for (int i=0; i Date: Thu, 18 Jan 2024 11:50:36 -0700 Subject: [PATCH 16/69] refactor --- .../SuntimesConfigActivity0.java | 7 +- .../suntimeswidget/SuntimesWidget0.java | 2 + .../settings/WidgetSettings.java | 80 +--------- .../settings/WidgetSettingsImportTask.java | 9 +- .../settings/WidgetSettingsMetadata.java | 141 ++++++++++++++++++ 5 files changed, 150 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index d36bcc8a9..ca0d3c1a6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -86,6 +86,7 @@ import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.settings.WidgetTimezones; import com.forrestguice.suntimeswidget.settings.WidgetThemes; @@ -1903,8 +1904,8 @@ protected WidgetSettings.TimezoneMode getDefaultTimezoneMode() protected void saveMetadata(Context context) { - WidgetSettings.WidgetMetaData metadata = new WidgetSettings.WidgetMetaData(getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE); - WidgetSettings.saveMetaData(context, appWidgetId, metadata); + WidgetSettingsMetadata.WidgetMetaData metadata = new WidgetSettingsMetadata.WidgetMetaData(getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE); + WidgetSettingsMetadata.saveMetaData(context, appWidgetId, metadata); } /** @@ -2090,7 +2091,7 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) for (int i=0; i getPrefTypes() } return types; } - private static void putType(Map map, Class type, String... keys) { + public static void putType(Map map, Class type, String... keys) { for (String key : keys) { map.put(key, type); } @@ -1698,75 +1691,6 @@ public static void initDisplayStrings( Context context ) /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// - /** - * WidgetMetaData - */ - public static class WidgetMetaData - { - private String className = null; - private int versionCode = -1; - - public WidgetMetaData(String widgetClassName, int versionCode) { - this.className = widgetClassName; - } - - public String getWidgetClassName() { - return className; - } - - public int getVersionCode() { - return versionCode; - } - - public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values) - { - Long values_id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); - return WidgetSettings.WidgetMetaData.getMetaDataFromValues(values, values_id); - } - - public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values, @Nullable Long appWidgetId) - { - if (appWidgetId != null) - { - String key_className = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_CLASSNAME; - String key_versionCode = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_VERSIONCODE; - String widgetClassName = (values.containsKey(key_className) ? values.getAsString(key_className) : null); - int versionCode = (values.containsKey(key_versionCode) ? values.getAsInteger( key_versionCode) : -1); - return new WidgetMetaData(widgetClassName, versionCode); - } else return null; - } - } - - public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) - { - SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); - String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; - prefs.putString(prefs_prefix + PREF_KEY_META_CLASSNAME, metadata.getWidgetClassName()); - prefs.putInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, metadata.getVersionCode()); - prefs.apply(); - } - - public static WidgetMetaData loadMetaData(Context context, int appWidgetId) - { - SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); - String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; - String className = prefs.getString(prefs_prefix + PREF_KEY_META_CLASSNAME, null); - int versionCode = prefs.getInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, -1); - return new WidgetMetaData(className, versionCode); - } - - public static void deleteMetaData(Context context, int appWidgetId) - { - SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_WIDGET, 0).edit(); - String prefs_prefix = PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; - prefs.remove(prefs_prefix + PREF_KEY_META_CLASSNAME); - prefs.remove(prefs_prefix + PREF_KEY_META_VERSIONCODE); - prefs.apply(); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////// - public static long getNextSuggestedUpdate(Context context, int appWidgetId) { SharedPreferences prefs = context.getSharedPreferences(PREFS_WIDGET, 0); @@ -3464,7 +3388,7 @@ public static void deletePrefs(Context context, int appWidgetId) deleteTimeNoteSetPref(context, appWidgetId); WidgetActions.deletePrefs(context, appWidgetId); - deleteMetaData(context, appWidgetId); + WidgetSettingsMetadata.deleteMetaData(context, appWidgetId); } public static void initDefaults( Context context ) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 0e9cc31f7..70b0abafe 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -408,6 +408,7 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va prefTypes.putAll(CalendarSettings.getPrefTypes()); prefTypes.putAll(WidgetActions.getPrefTypes()); prefTypes.putAll(WorldMapWidgetSettings.getPrefTypes()); + prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); for (String key : values.keySet()) { @@ -451,7 +452,6 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va } } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); - } else if (expectedType.equals(Long.class)) { if (valueType.equals(String.class)) { // long as String try { @@ -478,13 +478,6 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va prefs.apply(); } - public static String findWidgetClassName(ContentValues values, @Nullable Long appWidgetId) - { - if (appWidgetId != null) { - return values.getAsString( WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettings.PREF_PREFIX_KEY_META + WidgetSettings.PREF_KEY_META_CLASSNAME); - } else return null; - } - public static Long findAppWidgetIdFromFirstKey(ContentValues values) { String[] keys = values.keySet().toArray(new String[0]); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java new file mode 100644 index 000000000..f9745d99a --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -0,0 +1,141 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.Map; +import java.util.TreeMap; + +public class WidgetSettingsMetadata +{ + public static final String PREF_PREFIX_KEY_META = "_meta_"; + + public static final String PREF_KEY_META_CLASSNAME = "className"; + public static final String PREF_KEY_META_VERSIONCODE = "versionCode"; + + public static String[] ALL_KEYS = new String[] + { + PREF_PREFIX_KEY_META + PREF_KEY_META_CLASSNAME, + PREF_PREFIX_KEY_META + PREF_KEY_META_VERSIONCODE, + }; + public static String[] BOOL_KEYS = new String[] { }; + public static String[] FLOAT_KEYS = new String[] { }; + public static String[] LONG_KEYS = new String[] { }; + public static String[] INT_KEYS = new String[] { }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + putType(types, Long.class, LONG_KEYS); + putType(types, Float.class, FLOAT_KEYS); + putType(types, Integer.class, INT_KEYS); + putType(types, Boolean.class, BOOL_KEYS); + + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } + private static void putType(Map map, Class type, String... keys) { + for (String key : keys) { + map.put(key, type); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * WidgetMetaData + */ + public static class WidgetMetaData + { + private String className = null; + private int versionCode = -1; + + public WidgetMetaData(String widgetClassName, int versionCode) { + this.className = widgetClassName; + } + + public String getWidgetClassName() { + return className; + } + + public int getVersionCode() { + return versionCode; + } + + public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values) + { + Long values_id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); + return WidgetSettingsMetadata.WidgetMetaData.getMetaDataFromValues(values, values_id); + } + + public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values, @Nullable Long appWidgetId) + { + if (appWidgetId != null) + { + String key_className = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME; + String key_versionCode = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE; + String widgetClassName = (values.containsKey(key_className) ? values.getAsString(key_className) : null); + int versionCode = (values.containsKey(key_versionCode) ? values.getAsInteger( key_versionCode) : -1); + return new WidgetMetaData(widgetClassName, versionCode); + } else return null; + } + } + + public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + prefs.putString(prefs_prefix + PREF_KEY_META_CLASSNAME, metadata.getWidgetClassName()); + prefs.putInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, metadata.getVersionCode()); + prefs.apply(); + } + + public static WidgetMetaData loadMetaData(Context context, int appWidgetId) + { + SharedPreferences prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); + String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + String className = prefs.getString(prefs_prefix + PREF_KEY_META_CLASSNAME, null); + int versionCode = prefs.getInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, -1); + return new WidgetMetaData(className, versionCode); + } + + public static void deleteMetaData(Context context, int appWidgetId) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + prefs.remove(prefs_prefix + PREF_KEY_META_CLASSNAME); + prefs.remove(prefs_prefix + PREF_KEY_META_VERSIONCODE); + prefs.apply(); + } + +} From 2ac7ca78b49851d81de1b9bb3b8ee835c02ed06d Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 13:37:50 -0700 Subject: [PATCH 17/69] metadata persist widget options bundle as metadata --- .../SuntimesConfigActivity0.java | 5 +- .../suntimeswidget/SuntimesWidget0.java | 3 +- .../settings/WidgetSettingsMetadata.java | 164 ++++++++++++++++-- 3 files changed, 158 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index ca0d3c1a6..bcf62c150 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1904,7 +1904,10 @@ protected WidgetSettings.TimezoneMode getDefaultTimezoneMode() protected void saveMetadata(Context context) { - WidgetSettingsMetadata.WidgetMetaData metadata = new WidgetSettingsMetadata.WidgetMetaData(getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE); + WidgetSettingsMetadata.WidgetMetaData metadata = new WidgetSettingsMetadata.WidgetMetaData( + getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE, + WidgetSettingsMetadata.loadMetaData(context, appWidgetId) + ); WidgetSettingsMetadata.saveMetaData(context, appWidgetId, metadata); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java index 3fe940ccc..917be597b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java @@ -97,7 +97,8 @@ protected void initMinSize(Context context) public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); - // TODO + newOptions.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, getClass().getSimpleName()); + WidgetSettingsMetadata.saveMetaData(context, appWidgetId, newOptions); initLocale(context); updateWidget(context, appWidgetManager, appWidgetId); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index f9745d99a..81523d02f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -21,6 +21,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; +import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -31,18 +32,45 @@ public class WidgetSettingsMetadata { public static final String PREF_PREFIX_KEY_META = "_meta_"; - public static final String PREF_KEY_META_CLASSNAME = "className"; - public static final String PREF_KEY_META_VERSIONCODE = "versionCode"; + public static final String PREF_KEY_META_CLASSNAME = "appWidgetClassName"; + public static final String PREF_KEY_META_VERSIONCODE = "appWidgetVersionCode"; + + public static final String PREF_KEY_META_CATEGORY = "appWidgetCategory"; + // AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN; 1; widget can be displayed on the home screen + // AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD; 2; widget can be displayed on the keyguard + // AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX; 4; widget can be displayed within a space reserved for the search box + + + public static final String PREF_KEY_META_WIDTH_MIN = "appWidgetMinWidth"; + public static final String PREF_KEY_META_WIDTH_MAX = "appWidgetMaxWidth"; + + public static final String PREF_KEY_META_HEIGHT_MIN = "appWidgetMinHeight"; + public static final String PREF_KEY_META_HEIGHT_MAX = "appWidgetMaxHeight"; public static String[] ALL_KEYS = new String[] { PREF_PREFIX_KEY_META + PREF_KEY_META_CLASSNAME, PREF_PREFIX_KEY_META + PREF_KEY_META_VERSIONCODE, + PREF_PREFIX_KEY_META + PREF_KEY_META_CATEGORY, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MAX, + PREF_PREFIX_KEY_META + PREF_KEY_META_HEIGHT_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_HEIGHT_MAX, + }; + //public static String[] BOOL_KEYS = new String[] { }; + //public static String[] FLOAT_KEYS = new String[] { }; + //public static String[] LONG_KEYS = new String[] { }; + public static String[] INT_KEYS = new String[] + { + PREF_PREFIX_KEY_META + PREF_KEY_META_VERSIONCODE, + PREF_PREFIX_KEY_META + PREF_KEY_META_CATEGORY, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_WIDTH_MAX, + PREF_PREFIX_KEY_META + PREF_KEY_META_HEIGHT_MIN, + PREF_PREFIX_KEY_META + PREF_KEY_META_HEIGHT_MAX, }; - public static String[] BOOL_KEYS = new String[] { }; - public static String[] FLOAT_KEYS = new String[] { }; - public static String[] LONG_KEYS = new String[] { }; - public static String[] INT_KEYS = new String[] { }; private static Map types = null; public static Map getPrefTypes() @@ -50,10 +78,10 @@ public static Map getPrefTypes() if (types == null) { types = new TreeMap<>(); - putType(types, Long.class, LONG_KEYS); - putType(types, Float.class, FLOAT_KEYS); putType(types, Integer.class, INT_KEYS); - putType(types, Boolean.class, BOOL_KEYS); + //putType(types, Long.class, LONG_KEYS); + //putType(types, Float.class, FLOAT_KEYS); + //putType(types, Boolean.class, BOOL_KEYS); for (String key : ALL_KEYS) { // all others are type String if (!types.containsKey(key)) { @@ -79,19 +107,67 @@ public static class WidgetMetaData { private String className = null; private int versionCode = -1; + private int category = -1; + private int[] minDimens = new int[] {-1, -1}; + private int[] maxDimens = new int[] {-1, -1}; - public WidgetMetaData(String widgetClassName, int versionCode) { + public WidgetMetaData(String widgetClassName, int versionCode, int category, + int minWidth, int minHeight, int maxWidth, int maxHeight) + { this.className = widgetClassName; + this.versionCode = versionCode; + this.category = category; + this.minDimens[0] = minWidth; + this.minDimens[1] = minHeight; + this.maxDimens[0] = maxWidth; + this.maxDimens[1] = maxHeight; } + public WidgetMetaData(String widgetClassName, int versionCode, WidgetMetaData other) + { + this.className = widgetClassName; + this.versionCode = versionCode; + this.category = other.category; + this.minDimens[0] = other.minDimens[0]; + this.minDimens[1] = other.minDimens[1]; + this.maxDimens[0] = other.maxDimens[0]; + this.maxDimens[1] = other.maxDimens[1]; + } + + /** + * @return widget class name + */ public String getWidgetClassName() { return className; } + /** + * @return appVersionCode + */ public int getVersionCode() { return versionCode; } + /** + * @return category + */ + public int getCategory() { + return category; + } + + /** + * @return [width, height] + */ + public int[] getMinDimensions() { + return minDimens; + } + /** + * @return [width, height] + */ + public int[] getMaxDimensions() { + return maxDimens; + } + public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values) { Long values_id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); @@ -104,19 +180,72 @@ public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values { String key_className = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME; String key_versionCode = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE; + String key_category = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_CATEGORY; + String key_width_min = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_WIDTH_MIN; + String key_width_max = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_WIDTH_MAX; + String key_height_min = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_HEIGHT_MIN; + String key_height_max = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_HEIGHT_MAX; + String widgetClassName = (values.containsKey(key_className) ? values.getAsString(key_className) : null); int versionCode = (values.containsKey(key_versionCode) ? values.getAsInteger( key_versionCode) : -1); - return new WidgetMetaData(widgetClassName, versionCode); + int category = (values.containsKey(key_category) ? values.getAsInteger(key_category) : -1); + int minWidth = (values.containsKey(key_width_min) ? values.getAsInteger(key_width_min) : -1); + int minHeight = (values.containsKey(key_height_min) ? values.getAsInteger(key_height_min) : -1); + int maxWidth = (values.containsKey(key_width_max) ? values.getAsInteger(key_width_max) : -1); + int maxHeight = (values.containsKey(key_height_max) ? values.getAsInteger(key_height_max) : -1); + + return new WidgetMetaData(widgetClassName, versionCode, category, + minWidth, minHeight, maxWidth, maxHeight); + } else return null; } } public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) { + int[] minSize = metadata.getMinDimensions(); + int[] maxSize = metadata.getMaxDimensions(); + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; prefs.putString(prefs_prefix + PREF_KEY_META_CLASSNAME, metadata.getWidgetClassName()); prefs.putInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, metadata.getVersionCode()); + prefs.putInt(prefs_prefix + PREF_KEY_META_WIDTH_MIN, minSize[0]); + prefs.putInt(prefs_prefix + PREF_KEY_META_HEIGHT_MIN, minSize[1]); + prefs.putInt(prefs_prefix + PREF_KEY_META_WIDTH_MAX, maxSize[0]); + prefs.putInt(prefs_prefix + PREF_KEY_META_HEIGHT_MAX, maxSize[1]); + prefs.putInt(prefs_prefix + PREF_KEY_META_CATEGORY, metadata.getCategory()); + prefs.apply(); + } + + public static void saveMetaData(Context context, int appWidgetId, Bundle bundle) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; + + if (bundle.containsKey(PREF_KEY_META_CLASSNAME)) { + prefs.putString(prefs_prefix + PREF_KEY_META_CLASSNAME, bundle.getString(PREF_KEY_META_CLASSNAME)); + } + if (bundle.containsKey(PREF_KEY_META_VERSIONCODE)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, bundle.getInt(PREF_KEY_META_VERSIONCODE)); + } + + if (bundle.containsKey(PREF_KEY_META_CATEGORY)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_CATEGORY, bundle.getInt(PREF_KEY_META_CATEGORY)); + } + if (bundle.containsKey(PREF_KEY_META_WIDTH_MIN)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_WIDTH_MIN, bundle.getInt(PREF_KEY_META_WIDTH_MIN)); + } + if (bundle.containsKey(PREF_KEY_META_HEIGHT_MIN)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_HEIGHT_MIN, bundle.getInt(PREF_KEY_META_HEIGHT_MIN)); + } + if (bundle.containsKey(PREF_KEY_META_WIDTH_MAX)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_WIDTH_MAX, bundle.getInt(PREF_KEY_META_WIDTH_MAX)); + } + if (bundle.containsKey(PREF_KEY_META_HEIGHT_MAX)) { + prefs.putInt(prefs_prefix + PREF_KEY_META_HEIGHT_MAX, bundle.getInt(PREF_KEY_META_HEIGHT_MAX)); + } + prefs.apply(); } @@ -126,7 +255,13 @@ public static WidgetMetaData loadMetaData(Context context, int appWidgetId) String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; String className = prefs.getString(prefs_prefix + PREF_KEY_META_CLASSNAME, null); int versionCode = prefs.getInt(prefs_prefix + PREF_KEY_META_VERSIONCODE, -1); - return new WidgetMetaData(className, versionCode); + int category = prefs.getInt(prefs_prefix + PREF_KEY_META_CATEGORY, -1); + int minWidth = prefs.getInt(prefs_prefix + PREF_KEY_META_WIDTH_MIN, -1); + int minHeight = prefs.getInt(prefs_prefix + PREF_KEY_META_HEIGHT_MIN, -1); + int maxWidth = prefs.getInt(prefs_prefix + PREF_KEY_META_WIDTH_MAX, -1); + int maxHeight = prefs.getInt(prefs_prefix + PREF_KEY_META_HEIGHT_MAX, -1); + return new WidgetMetaData(className, versionCode, category, + minWidth, minHeight, maxWidth, maxHeight); } public static void deleteMetaData(Context context, int appWidgetId) @@ -135,6 +270,11 @@ public static void deleteMetaData(Context context, int appWidgetId) String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; prefs.remove(prefs_prefix + PREF_KEY_META_CLASSNAME); prefs.remove(prefs_prefix + PREF_KEY_META_VERSIONCODE); + prefs.remove(prefs_prefix + PREF_KEY_META_CATEGORY); + prefs.remove(prefs_prefix + PREF_KEY_META_WIDTH_MIN); + prefs.remove(prefs_prefix + PREF_KEY_META_WIDTH_MAX); + prefs.remove(prefs_prefix + PREF_KEY_META_HEIGHT_MIN); + prefs.remove(prefs_prefix + PREF_KEY_META_HEIGHT_MAX); prefs.apply(); } From 685c4e53aa014cd1eeca522f98f796f7962fb44e Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 15:33:53 -0700 Subject: [PATCH 18/69] onReceive ACTION_APPWIDGET_RESTORED --- .../suntimeswidget/SuntimesWidget0.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java index 917be597b..9ef5f5f3f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java @@ -140,6 +140,21 @@ public void onReceive(@NonNull Context context, @NonNull Intent intent) } else if (action != null && action.equals(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED)) { Log.d(TAG, "onReceive: ACTION_APPWIDGET_OPTIONS_CHANGED :: " + getClass()); + } else if (action != null && action.equals(AppWidgetManager.ACTION_APPWIDGET_RESTORED)) { + Log.d(TAG, "onReceive: ACTION_APPWIDGET_RESTORED :: " + getClass()); + if (Build.VERSION.SDK_INT >= 21) + { + int[] oldAppWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS); // old (now invalid) appWidgetIds + int[] newAppWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); // new (valid) appWidgetIds + if (oldAppWidgetIds != null && newAppWidgetIds != null) + { + for (int i=0; i " + newAppWidgetIds[i]); + } + // TODO: restore widget + } + } + } else if (action != null && action.equals(SUNTIMES_THEME_UPDATE)) { String themeName = (intent.hasExtra(KEY_THEME) ? intent.getStringExtra(KEY_THEME) : null); Log.d(TAG, "onReceive: SUNTIMES_THEME_UPDATE :: " + getClass() + " :: " + themeName); From fdf4df37dd94677a1065188b27eb7d8578808320 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 15:34:17 -0700 Subject: [PATCH 19/69] importValues override prefix --- .../suntimeswidget/SuntimesConfigActivity0.java | 2 +- .../suntimeswidget/settings/WidgetSettingsImportTask.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index bcf62c150..4628b7e70 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2151,7 +2151,7 @@ protected void importSettings(Context context, ContentValues values, boolean off } SharedPreferences.Editor prefs = prefs0.edit(); - WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); + WidgetSettingsImportTask.importValues(prefs, values, WidgetSettings.PREF_PREFIX_KEY, appWidgetId); loadSettings(context); // reload ui if (offerUndo) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 70b0abafe..ad93eb4ac 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -402,7 +402,10 @@ public static boolean importValue(SharedPreferences.Editor prefs, Class type, St return retValue; } - public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) { + importValues(prefs, values, null, appWidgetId); + } + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, long appWidgetId) { Map prefTypes = WidgetSettings.getPrefTypes(); prefTypes.putAll(CalendarSettings.getPrefTypes()); @@ -419,6 +422,9 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va } String[] keyParts = key.split("_"); + if (toPrefix != null) { + keyParts[0] = toPrefix; + } keyParts[1] = appWidgetId + ""; String k = TextUtils.join("_", keyParts); // replacement key String k0 = k.replaceFirst(WidgetSettings.PREF_PREFIX_KEY + appWidgetId, ""); From d744196a91937c7ca85c1dda796e67f35b35d16c Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 15:34:45 -0700 Subject: [PATCH 20/69] copyValues copy widget settings from one prefix/id to another --- .../settings/WidgetSettingsImportTask.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index ad93eb4ac..2d1f2667c 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class WidgetSettingsImportTask extends AsyncTask { @@ -376,6 +377,44 @@ public static ContentValues replaceKeyPrefix(ContentValues values, int replaceme return v; } + public static void copyValues(SharedPreferences prefs, int fromAppWidgetId, int toAppWidgetId) { + copyValues(prefs, WidgetSettings.PREF_PREFIX_KEY, fromAppWidgetId, WidgetSettings.PREF_PREFIX_KEY, toAppWidgetId); + } + public static void copyValues(SharedPreferences prefs, String fromPrefix, int fromAppWidgetId, String toPrefix, int toAppWidgetId) + { + Map map = prefs.getAll(); + Set keys = map.keySet(); + SharedPreferences.Editor editor = prefs.edit(); + + for (String key : keys) + { + if (key.startsWith(fromPrefix + fromAppWidgetId)) + { + String[] keyParts = key.split("_"); + keyParts[0] = toPrefix; + keyParts[1] = toAppWidgetId + ""; + String toKey = TextUtils.join("_", keyParts); + + if (map.get(key).getClass().equals(String.class)) { + editor.putString(toKey, (String) map.get(key)); + + } else if (map.get(key).getClass().equals(Integer.class)) { + editor.putInt(toKey, (Integer) map.get(key)); + + } else if (map.get(key).getClass().equals(Long.class)) { + editor.putLong(toKey, (Long) map.get(key)); + + } else if (map.get(key).getClass().equals(Float.class)) { + editor.putFloat(toKey, (Float) map.get(key)); + + } else if (map.get(key).getClass().equals(Boolean.class)) { + editor.putBoolean(toKey, (Boolean) map.get(key)); + } + } + } + editor.apply(); + } + public static boolean importValue(SharedPreferences.Editor prefs, Class type, String key, Object value) { boolean retValue = true; From a658a1407a366b08396ee5f85217054b59bd1049 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 16:13:20 -0700 Subject: [PATCH 21/69] layout progres --- .../suntimeswidget/SuntimesWidgetListActivity.java | 2 ++ app/src/main/res/layout/layout_activity_widgetlist.xml | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 857b56081..f81cb828e 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -259,6 +259,8 @@ protected void initViews(Context context) actionBar.setDisplayHomeAsUpEnabled(true); } + progressView = findViewById(R.id.progress); + widgetList = (ListView)findViewById(R.id.widgetList); widgetList.setOnItemClickListener(new AdapterView.OnItemClickListener() { diff --git a/app/src/main/res/layout/layout_activity_widgetlist.xml b/app/src/main/res/layout/layout_activity_widgetlist.xml index f5ce96d0e..8c23c8612 100644 --- a/app/src/main/res/layout/layout_activity_widgetlist.xml +++ b/app/src/main/res/layout/layout_activity_widgetlist.xml @@ -22,7 +22,7 @@ + android:orientation="vertical" android:animateLayoutChanges="true"> + + + android:layout_width="match_parent" android:layout_height="wrap_content" /> From 1494f3e2569baf2e99207a5e84e37fbc0dc50063 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 16:14:01 -0700 Subject: [PATCH 22/69] importSettings fix npe --- .../forrestguice/suntimeswidget/SuntimesConfigActivity0.java | 4 ++-- app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 4628b7e70..26b0f9eae 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2096,13 +2096,13 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) ContentValues v = result.getItems()[i]; WidgetSettingsMetadata.WidgetMetaData metadata = WidgetSettingsMetadata.WidgetMetaData.getMetaDataFromValues(v); String values_widgetClassName = ((metadata != null) ? metadata.getWidgetClassName() : null); - items[i] = values_widgetClassName; + items[i] = context.getString(R.string.importwidget_dialog_item, (values_widgetClassName != null) + ? values_widgetClassName : context.getString(R.string.importwidget_dialog_item_unknown)); if (getWidgetClass().getSimpleName().equals(values_widgetClassName)) { Log.d("ImportSettings", "found settings for widget type " + values_widgetClassName + " at index " + i); values = v; - break; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c66f81c85..0e244795a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1200,6 +1200,8 @@ Importing Importing widget settings to file. Settings found,\nbut they are for other widgets… + %1$s + (unknown) Places From c6b446411dfd1a4b3fdd35d753492683a5b86be1 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 16:20:15 -0700 Subject: [PATCH 23/69] importValues skip metadata keys; avoid overwriting existing metadata --- .../suntimeswidget/settings/WidgetSettingsImportTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 2d1f2667c..b6c7b46a1 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -450,7 +450,7 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va prefTypes.putAll(CalendarSettings.getPrefTypes()); prefTypes.putAll(WidgetActions.getPrefTypes()); prefTypes.putAll(WorldMapWidgetSettings.getPrefTypes()); - prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); + //prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); // skip these keys, avoid overwriting existing metadata for (String key : values.keySet()) { From 6a8e3ddd6b567b3614c23afffe91c6420ad584d2 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 18 Jan 2024 20:27:26 -0700 Subject: [PATCH 24/69] saveBasicWidgetMetadata --- .../SuntimesWidgetListActivity.java | 20 +++++++++++++++++++ .../settings/WidgetSettingsMetadata.java | 3 +++ 2 files changed, 23 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index f81cb828e..94f1bd54b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -69,6 +69,7 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.views.Toast; import com.forrestguice.suntimeswidget.widgets.DateWidget0; @@ -371,6 +372,23 @@ protected static ArrayList getAllWidgetIds(Context context) return ids; } + public static void saveBasicWidgetMetadata(Context context) + { + AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); + String packageName = context.getPackageName(); + for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) + { + Bundle bundle = new Bundle(); + bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, widgetClass.getSimpleName()); + bundle.putInt(WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE, BuildConfig.VERSION_CODE); + + int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); + for (int id : widgetIds) { + WidgetSettingsMetadata.saveMetaData(context, id, bundle); + } + } + } + /** * exportSettings * @param context Context @@ -392,6 +410,7 @@ protected void exportSettings(Context context) } WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, exportTarget, true, true); // export to external cache + saveBasicWidgetMetadata(context); task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); @@ -399,6 +418,7 @@ protected void exportSettings(Context context) public void exportSettings(Context context, @NonNull Uri uri) { Log.i("ExportSettings", "Starting export task: " + uri); + saveBasicWidgetMetadata(context); WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, uri); task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index 81523d02f..7bd3a7218 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -201,6 +201,9 @@ public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values } } + /////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////// + public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) { int[] minSize = metadata.getMinDimensions(); From 588ec2e5dd1344dd9537877d9c0ce6da2698b5d5 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Fri, 19 Jan 2024 00:25:45 -0700 Subject: [PATCH 25/69] importSettings --- .../SuntimesWidgetListActivity.java | 43 ++++++++++++++++--- .../settings/WidgetSettingsImportTask.java | 9 ++-- .../settings/WidgetSettingsMetadata.java | 1 + app/src/main/res/values/strings.xml | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 94f1bd54b..a70498534 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -24,9 +24,11 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -68,7 +70,9 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.views.Toast; @@ -475,20 +479,47 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// - public boolean importSettings(Context context) + public void importSettings(Context context) { if (context != null) { startActivityForResult(ExportTask.getOpenFileIntent("text/*"), IMPORT_REQUEST); - return true; } - return false; } - public boolean importSettings(Context context, @NonNull Uri uri) + public void importSettings(final Context context, @NonNull Uri uri) { Log.i("ImportSettings", "Starting import task: " + uri); - // TODO - return true; + WidgetSettingsImportTask task = new WidgetSettingsImportTask(context); + task.setTaskListener(new WidgetSettingsImportTask.TaskListener() + { + @Override + public void onStarted() { + showProgress(context, context.getString(R.string.importwidget_dialog_title), context.getString(R.string.importwidget_dialog_message)); + } + + @Override + public void onFinished(WidgetSettingsImportTask.TaskResult result) + { + dismissProgress(); + if (result.getResult() && result.numResults() > 0) { + importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, result.getItems()); // switch imported items to backup prefix, retain appWidgetIds + //importSettings(context, WidgetSettings.PREF_PREFIX_KEY, result.getItems()); // TODO: selection between these + + } else { + Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + } + } + }); + task.execute(uri); + } + + protected void importSettings(Context context, String prefix, ContentValues... contentValues) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + for (ContentValues values : contentValues) { + WidgetSettingsImportTask.importValues(prefs, values, prefix, null); + } + Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index b6c7b46a1..4312f4722 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -444,7 +444,7 @@ public static boolean importValue(SharedPreferences.Editor prefs, Class type, St public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) { importValues(prefs, values, null, appWidgetId); } - public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, long appWidgetId) + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, @Nullable Long appWidgetId) { Map prefTypes = WidgetSettings.getPrefTypes(); prefTypes.putAll(CalendarSettings.getPrefTypes()); @@ -464,9 +464,12 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va if (toPrefix != null) { keyParts[0] = toPrefix; } - keyParts[1] = appWidgetId + ""; + if (appWidgetId != null) { + keyParts[1] = appWidgetId + ""; + } + String k = TextUtils.join("_", keyParts); // replacement key - String k0 = k.replaceFirst(WidgetSettings.PREF_PREFIX_KEY + appWidgetId, ""); + String k0 = k.replaceFirst(WidgetSettings.PREF_PREFIX_KEY + keyParts[1], ""); if (prefTypes.containsKey(k0)) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index 7bd3a7218..e56bcb1de 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -30,6 +30,7 @@ public class WidgetSettingsMetadata { + public static final String BACKUP_PREFIX_KEY = "bckwidget_"; public static final String PREF_PREFIX_KEY_META = "_meta_"; public static final String PREF_KEY_META_CLASSNAME = "appWidgetClassName"; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0e244795a..8ade35e14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1198,7 +1198,7 @@ Exporting Exporting widget settings to file. Importing - Importing widget settings to file. + Importing widget settings from file. Settings found,\nbut they are for other widgets… %1$s (unknown) From c22bd649e5114249cafd44ee54da1e1e28044b78 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Fri, 19 Jan 2024 01:47:54 -0700 Subject: [PATCH 26/69] importSettings --- .../SuntimesConfigActivity0.java | 2 +- .../SuntimesWidgetListActivity.java | 47 +++++++++++++++++-- app/src/main/res/values/strings.xml | 6 ++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 26b0f9eae..ed30b0f56 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2151,7 +2151,7 @@ protected void importSettings(Context context, ContentValues values, boolean off } SharedPreferences.Editor prefs = prefs0.edit(); - WidgetSettingsImportTask.importValues(prefs, values, WidgetSettings.PREF_PREFIX_KEY, appWidgetId); + WidgetSettingsImportTask.importValues(prefs, values, WidgetSettings.PREF_PREFIX_KEY, (long) appWidgetId); loadSettings(context); // reload ui if (offerUndo) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index a70498534..2a00a2642 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -26,6 +26,7 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -45,6 +46,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; @@ -501,9 +503,48 @@ public void onStarted() { public void onFinished(WidgetSettingsImportTask.TaskResult result) { dismissProgress(); - if (result.getResult() && result.numResults() > 0) { - importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, result.getItems()); // switch imported items to backup prefix, retain appWidgetIds - //importSettings(context, WidgetSettings.PREF_PREFIX_KEY, result.getItems()); // TODO: selection between these + if (result.getResult() && result.numResults() > 0) + { + final ContentValues[] allValues = result.getItems(); + final CharSequence[] items = new CharSequence[] { + SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_restorebackup)), // 0 + SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_bestguess)), // 1 + SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_direct)), // 2 + }; + String title = context.getString(R.string.importwidget_dialog_title); + AlertDialog.Builder confirm = new AlertDialog.Builder(context).setTitle(title).setIcon(android.R.drawable.ic_dialog_alert) + .setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { /* EMPTY */ } + }) + .setPositiveButton(context.getString(R.string.configAction_import), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + int p = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + switch (p) + { + case 2: // direct import + importSettings(context, null, allValues); + break; + + case 1: // best guess + Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show(); // TODO + break; + + case 0: + default: + // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) + importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); + break; + } + + if ((p >= 0 && p < items.length)) { + importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); // switch imported items to backup prefix, retain appWidgetIds + } + } + }) + .setNegativeButton(context.getString(R.string.dialog_cancel), null); + confirm.show(); } else { Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ade35e14..837b0f623 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1197,12 +1197,16 @@ Exporting Exporting widget settings to file. - Importing + Import Settings Importing widget settings from file. Settings found,\nbut they are for other widgets… %1$s (unknown) + Restore Backup. Imported settings will be cached for now and restored later when requested by the launcher.
]]>
+ Best Guess. Imported settings will be applied to existing widgets by type (and other indicators).
]]>
+ Direct Import. Imported settings will be applied directly. This only works correctly if the widget ids are unchanged.
]]>
+ Places Manage Places From fb2841e45f76f883a4136816780bebf3d9009a55 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Fri, 19 Jan 2024 12:28:26 -0700 Subject: [PATCH 27/69] restoreBackup --- .../suntimeswidget/SuntimesWidget0.java | 9 ++- .../settings/WidgetSettingsImportTask.java | 59 +++++++++++++++---- app/src/main/res/values/strings.xml | 4 +- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java index 9ef5f5f3f..79571a3c8 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidget0.java @@ -40,6 +40,7 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetDataset; import com.forrestguice.suntimeswidget.calculator.core.Location; import com.forrestguice.suntimeswidget.getfix.GetFixHelper; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.widgets.layouts.SunLayout; import com.forrestguice.suntimeswidget.widgets.layouts.SunLayout_2x1_0; @@ -148,10 +149,12 @@ public void onReceive(@NonNull Context context, @NonNull Intent intent) int[] newAppWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); // new (valid) appWidgetIds if (oldAppWidgetIds != null && newAppWidgetIds != null) { - for (int i=0; i " + newAppWidgetIds[i]); + boolean[] backupRestored = WidgetSettingsImportTask.restoreFromBackup(context, oldAppWidgetIds, newAppWidgetIds); + for (int i=0; i map = prefs.getAll(); + Map map = fromPrefs.getAll(); Set keys = map.keySet(); - SharedPreferences.Editor editor = prefs.edit(); + boolean result = false; for (String key : keys) { if (key.startsWith(fromPrefix + fromAppWidgetId)) @@ -396,23 +396,29 @@ public static void copyValues(SharedPreferences prefs, String fromPrefix, int fr String toKey = TextUtils.join("_", keyParts); if (map.get(key).getClass().equals(String.class)) { - editor.putString(toKey, (String) map.get(key)); + toPrefs.putString(toKey, (String) map.get(key)); + result = true; } else if (map.get(key).getClass().equals(Integer.class)) { - editor.putInt(toKey, (Integer) map.get(key)); + toPrefs.putInt(toKey, (Integer) map.get(key)); + result = true; } else if (map.get(key).getClass().equals(Long.class)) { - editor.putLong(toKey, (Long) map.get(key)); + toPrefs.putLong(toKey, (Long) map.get(key)); + result = true; } else if (map.get(key).getClass().equals(Float.class)) { - editor.putFloat(toKey, (Float) map.get(key)); + toPrefs.putFloat(toKey, (Float) map.get(key)); + result = true; } else if (map.get(key).getClass().equals(Boolean.class)) { - editor.putBoolean(toKey, (Boolean) map.get(key)); + toPrefs.putBoolean(toKey, (Boolean) map.get(key)); + result = true; } } } - editor.apply(); + toPrefs.apply(); + return result; } public static boolean importValue(SharedPreferences.Editor prefs, Class type, String key, Object value) @@ -542,4 +548,35 @@ public static Long findAppWidgetIdFromFirstKey(ContentValues values) return null; } + /** + * @param oldAppWidgetIds array of old widget ids + * @param newAppWidgetIds array of new replacement widget ids + * @return array of true/false results for each old/new pair of old/new ids + */ + public static boolean[] restoreFromBackup(Context context, int[] oldAppWidgetIds, int[] newAppWidgetIds) { + SharedPreferences prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); + return restoreFromBackup(prefs, oldAppWidgetIds, newAppWidgetIds); + } + public static boolean[] restoreFromBackup(SharedPreferences prefs, int[] oldAppWidgetIds, int[] newAppWidgetIds) { + return restoreFromBackup(prefs, prefs.edit(), oldAppWidgetIds, newAppWidgetIds); + } + public static boolean[] restoreFromBackup(SharedPreferences fromPrefs, SharedPreferences.Editor toPrefs, int[] oldAppWidgetIds, int[] newAppWidgetIds) + { + if (oldAppWidgetIds != null && newAppWidgetIds != null + && oldAppWidgetIds.length == newAppWidgetIds.length) + { + boolean[] results = new boolean[oldAppWidgetIds.length]; + for (int i=0; i " + newAppWidgetIds[i]); + results[i] = copyValues(fromPrefs, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, oldAppWidgetIds[i], toPrefs, WidgetSettings.PREF_PREFIX_KEY, newAppWidgetIds[i]); + } + return results; + + } else { + Log.e("WidgetSettings", "restoreFromBackup: arrays must be non-null with matching length! ignoring request..."); + return new boolean[] { false }; + } + } + } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 837b0f623..aa2a6332e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1191,9 +1191,9 @@ No Widgets @string/configAction_export - Export Settings + Export Settings @string/configAction_import - Restore Settings + Import Settings Exporting Exporting widget settings to file. From 103c3a89669211e32e3a3778872b088568edb7ce Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Fri, 19 Jan 2024 13:58:09 -0700 Subject: [PATCH 28/69] importSettingsBestGuess --- .../SuntimesWidgetListActivity.java | 75 ++++++++++++++++--- .../settings/WidgetSettingsMetadata.java | 23 ++++++ 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 2a00a2642..8c6e73a35 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -83,7 +83,10 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import static com.forrestguice.suntimeswidget.SuntimesConfigActivity0.EXTRA_RECONFIGURE; @@ -364,16 +367,21 @@ public void dismissProgress() } protected static ArrayList getAllWidgetIds(Context context) + { + ArrayList ids = new ArrayList<>(); + for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) { + ids.addAll(getAllWidgetIds(context, widgetClass)); + } + return ids; + } + protected static ArrayList getAllWidgetIds(Context context, Class widgetClass) { AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); String packageName = context.getPackageName(); ArrayList ids = new ArrayList<>(); - for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) - { - int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); - for (int id : widgetIds) { - ids.add(id); - } + int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); + for (int id : widgetIds) { + ids.add(id); } return ids; } @@ -528,12 +536,11 @@ public void onClick(DialogInterface dialog, int whichButton) break; case 1: // best guess - Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show(); // TODO + importSettingsBestGuess(context, allValues); break; case 0: - default: - // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) + default: // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); break; } @@ -563,6 +570,56 @@ protected void importSettings(Context context, String prefix, ContentValues... c Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); } + /** + * Tries to match contentValues to existing widgetIds based on available metadata. + * @return suggested appWidget:ContentValues mapping + */ + + protected Map makeBestGuess(Context context, ContentValues... contentValues) + { + Map unused = new HashMap<>(); + Map used = new HashMap<>(); + for (ContentValues values : contentValues) { + unused.put(WidgetSettingsMetadata.WidgetMetaData.getMetaDataFromValues(values), values); + } + + Map suggested = new HashMap<>(); + for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) + { + ArrayList widgetIds = getAllWidgetIds(context, widgetClass); + for (Integer appWidgetId : widgetIds) + { + WidgetSettingsMetadata.WidgetMetaData metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); + if (unused.containsKey(metadata)) + { + ContentValues values = unused.remove(metadata); + used.put(metadata, values); + suggested.put(appWidgetId, values); + } + } + } + return suggested; + } + + protected void importSettingsBestGuess(Context context, ContentValues... contentValues) + { + Map suggested = makeBestGuess(context, contentValues); + int numMatches = suggested.size(); + if (numMatches > 0) // matched some + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + for (Integer appWidgetId : suggested.keySet()) + { + ContentValues values = suggested.get(appWidgetId); + WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); + } + Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + + } else { // matched none + Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + } + } + //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index e56bcb1de..60487d636 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -200,6 +200,29 @@ public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values } else return null; } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } else if (o == null) { + return false; + } else if (getClass() != o.getClass()) { + return false; + } + + WidgetMetaData other = (WidgetMetaData) o; + String widgetClassName1 = other.getWidgetClassName(); + if (widgetClassName1 == null) { + widgetClassName1 = ""; + } + String widgetClassName = getWidgetClassName(); + if (widgetClassName == null) { + widgetClassName = ""; + } + return widgetClassName.equals(widgetClassName1) && getCategory() == other.getCategory(); + } } /////////////////////////////////////////////////////////////////////////////////////////////// From b1b7558ab03f56c57f5e3a4b65e8956f5e3c4a3a Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 01:38:20 -0700 Subject: [PATCH 29/69] equals --- .../suntimeswidget/SuntimesWidgetListActivity.java | 4 ---- .../settings/WidgetSettingsMetadata.java | 12 +++++++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 8c6e73a35..6d1ff4764 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -544,10 +544,6 @@ public void onClick(DialogInterface dialog, int whichButton) importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); break; } - - if ((p >= 0 && p < items.length)) { - importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); // switch imported items to backup prefix, retain appWidgetIds - } } }) .setNegativeButton(context.getString(R.string.dialog_cancel), null); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index 60487d636..ebc46a0d2 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; import java.util.Map; import java.util.TreeMap; @@ -201,6 +202,11 @@ public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values } else return null; } + @Override + public int hashCode() { + return className.hashCode(); + } + @Override public boolean equals(Object o) { @@ -213,15 +219,15 @@ public boolean equals(Object o) } WidgetMetaData other = (WidgetMetaData) o; - String widgetClassName1 = other.getWidgetClassName(); + String widgetClassName1 = other.className; if (widgetClassName1 == null) { widgetClassName1 = ""; } - String widgetClassName = getWidgetClassName(); + String widgetClassName = className; if (widgetClassName == null) { widgetClassName = ""; } - return widgetClassName.equals(widgetClassName1) && getCategory() == other.getCategory(); + return widgetClassName.equals(widgetClassName1); } } From dc12d90ce434f8823d879eb0065c8440cb201182 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 01:39:56 -0700 Subject: [PATCH 30/69] deleteValues --- .../settings/WidgetSettingsImportTask.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index a830b6152..0d7d4fda4 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -377,6 +377,31 @@ public static ContentValues replaceKeyPrefix(ContentValues values, int replaceme return v; } + /** + * @param prefs SharedPreferences + * @param prefix prefix string + * @param appWidgetId appWidgetId; null to delete all ids + * @return true items were removed, false otherwise + */ + public static boolean deleteValues(SharedPreferences prefs, String prefix, Integer appWidgetId) + { + Map map = prefs.getAll(); + Set keys = map.keySet(); + + String keyPrefix = (appWidgetId != null ? prefix + appWidgetId : prefix); + SharedPreferences.Editor editor = prefs.edit(); + boolean result = false; + for (String key : keys) + { + if (key.startsWith(keyPrefix)) { + editor.remove(key); + result = true; + } + } + editor.apply(); + return result; + } + public static boolean copyValues(SharedPreferences prefs, int fromAppWidgetId, int toAppWidgetId) { return copyValues(prefs, WidgetSettings.PREF_PREFIX_KEY, fromAppWidgetId, prefs.edit(), WidgetSettings.PREF_PREFIX_KEY, toAppWidgetId); } @@ -570,6 +595,9 @@ public static boolean[] restoreFromBackup(SharedPreferences fromPrefs, SharedPre { Log.i("WidgetSettings", "restoreFromBackup: " + oldAppWidgetIds[i] + " -> " + newAppWidgetIds[i]); results[i] = copyValues(fromPrefs, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, oldAppWidgetIds[i], toPrefs, WidgetSettings.PREF_PREFIX_KEY, newAppWidgetIds[i]); + //if (results[i]) { + // deleteValues(fromPrefs, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, oldAppWidgetIds[i]); + //} } return results; @@ -579,4 +607,8 @@ public static boolean[] restoreFromBackup(SharedPreferences fromPrefs, SharedPre } } + public static void clearBackup(Context context, SharedPreferences prefs ) { + deleteValues(prefs, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, null); + } + } \ No newline at end of file From a6cbd5faa0d318ef0bd8f6c2ade71d88e296328e Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 02:16:59 -0700 Subject: [PATCH 31/69] snackbar --- .../SuntimesWidgetListActivity.java | 43 +++++++++++++++---- app/src/main/res/values/strings.xml | 7 +++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 6d1ff4764..cb35216c6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -45,6 +45,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -77,7 +78,6 @@ import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; -import com.forrestguice.suntimeswidget.views.Toast; import com.forrestguice.suntimeswidget.widgets.DateWidget0; import java.io.File; @@ -86,7 +86,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import static com.forrestguice.suntimeswidget.SuntimesConfigActivity0.EXTRA_RECONFIGURE; @@ -465,7 +464,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) { //if (isAdded()) { String successMessage = context.getString(R.string.msg_export_success, path); - Toast.makeText(context, successMessage, Toast.LENGTH_LONG).show(); + showIOResultSnackbar(context, true, successMessage); //} if (Build.VERSION.SDK_INT >= 19) { @@ -480,7 +479,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) //if (isAdded()) { String failureMessage = context.getString(R.string.msg_export_failure, path); - Toast.makeText(context, failureMessage, Toast.LENGTH_LONG).show(); + showIOResultSnackbar(context, false, failureMessage); //} } } @@ -550,7 +549,7 @@ public void onClick(DialogInterface dialog, int whichButton) confirm.show(); } else { - Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + showIOResultSnackbar(context, false, 0); } } }); @@ -560,10 +559,12 @@ public void onClick(DialogInterface dialog, int whichButton) protected void importSettings(Context context, String prefix, ContentValues... contentValues) { SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + int c = 0; for (ContentValues values : contentValues) { WidgetSettingsImportTask.importValues(prefs, values, prefix, null); + c++; } - Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + showIOResultSnackbar(context, true, c); } /** @@ -601,6 +602,7 @@ protected void importSettingsBestGuess(Context context, ContentValues... content { Map suggested = makeBestGuess(context, contentValues); int numMatches = suggested.size(); + Log.d("DEBUG", "bestGuess: " + numMatches + " matches"); if (numMatches > 0) // matched some { SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); @@ -609,13 +611,38 @@ protected void importSettingsBestGuess(Context context, ContentValues... content ContentValues values = suggested.get(appWidgetId); WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); } - Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + showIOResultSnackbar(context, true, numMatches); } else { // matched none - Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + showIOResultSnackbar(context, false, numMatches); } } + protected void showIOResultSnackbar(final Context context, boolean result, CharSequence message) + { + View view = getWindow().getDecorView(); + if (context != null && view != null) + { + Snackbar snackbar = Snackbar.make(view, message, (result ? 7000 : Snackbar.LENGTH_LONG)); + /*snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { + @Override + public void onClick(View v) { + } + });*/ + SuntimesUtils.themeSnackbar(context, snackbar, null); + snackbar.show(); + } + } + + protected void showIOResultSnackbar(final Context context, boolean result, int numResults) + { + //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + //Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + CharSequence message = (result ? context.getString(R.string.msg_import_success, context.getResources().getQuantityString(R.plurals.widgetPlural, numResults, numResults)) + : context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file))); + showIOResultSnackbar(context, result, message); + } + //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa2a6332e..5647147c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1207,6 +1207,13 @@ Best Guess. Imported settings will be applied to existing widgets by type (and other indicators).
]]>
Direct Import. Imported settings will be applied directly. This only works correctly if the widget ids are unchanged.
]]>
+ + %d widget + %d widgets + %d widgets + %d widgets + + Places Manage Places From 84bdf58008524ac1384403f427955818b51b60ef Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 10:15:07 -0700 Subject: [PATCH 32/69] importValues --- .../suntimeswidget/SuntimesConfigActivity0.java | 2 +- .../suntimeswidget/SuntimesWidgetListActivity.java | 8 ++++---- .../settings/WidgetSettingsImportTask.java | 13 +++++++++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index ed30b0f56..f3f884438 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2155,7 +2155,7 @@ protected void importSettings(Context context, ContentValues values, boolean off loadSettings(context); // reload ui if (offerUndo) { - Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); offerUndoImport(context, previousValues); } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index cb35216c6..7b308e7bc 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -531,7 +531,7 @@ public void onClick(DialogInterface dialog, int whichButton) switch (p) { case 2: // direct import - importSettings(context, null, allValues); + importSettings(context, null, false, allValues); break; case 1: // best guess @@ -540,7 +540,7 @@ public void onClick(DialogInterface dialog, int whichButton) case 0: default: // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) - importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, allValues); + importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, true, allValues); break; } } @@ -556,12 +556,12 @@ public void onClick(DialogInterface dialog, int whichButton) task.execute(uri); } - protected void importSettings(Context context, String prefix, ContentValues... contentValues) + protected void importSettings(Context context, String prefix, boolean includeMetadata, ContentValues... contentValues) { SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); int c = 0; for (ContentValues values : contentValues) { - WidgetSettingsImportTask.importValues(prefs, values, prefix, null); + WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); c++; } showIOResultSnackbar(context, true, c); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 0d7d4fda4..5d0982fcd 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -475,13 +475,18 @@ public static boolean importValue(SharedPreferences.Editor prefs, Class type, St public static void importValues(SharedPreferences.Editor prefs, ContentValues values, long appWidgetId) { importValues(prefs, values, null, appWidgetId); } - public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, @Nullable Long appWidgetId) + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, @Nullable Long appWidgetId) { + importValues(prefs, values, toPrefix, appWidgetId, false); + } + public static void importValues(SharedPreferences.Editor prefs, ContentValues values, @Nullable String toPrefix, @Nullable Long appWidgetId, boolean includeMetadata) { Map prefTypes = WidgetSettings.getPrefTypes(); prefTypes.putAll(CalendarSettings.getPrefTypes()); prefTypes.putAll(WidgetActions.getPrefTypes()); prefTypes.putAll(WorldMapWidgetSettings.getPrefTypes()); - //prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); // skip these keys, avoid overwriting existing metadata + if (includeMetadata) { + prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); + } for (String key : values.keySet()) { @@ -493,14 +498,14 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va String[] keyParts = key.split("_"); if (toPrefix != null) { - keyParts[0] = toPrefix; + keyParts[0] = toPrefix.endsWith("_") ? toPrefix.substring(0, toPrefix.length() - 1) : toPrefix; } if (appWidgetId != null) { keyParts[1] = appWidgetId + ""; } String k = TextUtils.join("_", keyParts); // replacement key - String k0 = k.replaceFirst(WidgetSettings.PREF_PREFIX_KEY + keyParts[1], ""); + String k0 = k.replaceFirst(toPrefix + keyParts[1], ""); if (prefTypes.containsKey(k0)) { From 2f876c406c240ed54bc38163dbf481c79d725ef1 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 12:05:24 -0700 Subject: [PATCH 33/69] refactor --- .../SuntimesConfigActivity0.java | 4 ++-- .../SuntimesWidgetListActivity.java | 8 +++---- .../settings/WidgetSettingsMetadata.java | 23 +++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index f3f884438..af629aee4 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -1904,7 +1904,7 @@ protected WidgetSettings.TimezoneMode getDefaultTimezoneMode() protected void saveMetadata(Context context) { - WidgetSettingsMetadata.WidgetMetaData metadata = new WidgetSettingsMetadata.WidgetMetaData( + WidgetSettingsMetadata.WidgetMetadata metadata = new WidgetSettingsMetadata.WidgetMetadata( getWidgetClass().getSimpleName(), BuildConfig.VERSION_CODE, WidgetSettingsMetadata.loadMetaData(context, appWidgetId) ); @@ -2094,7 +2094,7 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) for (int i=0; i makeBestGuess(Context context, ContentValues... contentValues) { - Map unused = new HashMap<>(); - Map used = new HashMap<>(); + Map unused = new HashMap<>(); + Map used = new HashMap<>(); for (ContentValues values : contentValues) { - unused.put(WidgetSettingsMetadata.WidgetMetaData.getMetaDataFromValues(values), values); + unused.put(WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values), values); } Map suggested = new HashMap<>(); @@ -586,7 +586,7 @@ protected Map makeBestGuess(Context context, ContentValue ArrayList widgetIds = getAllWidgetIds(context, widgetClass); for (Integer appWidgetId : widgetIds) { - WidgetSettingsMetadata.WidgetMetaData metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); + WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); if (unused.containsKey(metadata)) { ContentValues values = unused.remove(metadata); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index ebc46a0d2..5b0049608 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -24,7 +24,6 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import java.util.Map; import java.util.TreeMap; @@ -105,7 +104,7 @@ private static void putType(Map map, Class type, String... keys) { /** * WidgetMetaData */ - public static class WidgetMetaData + public static class WidgetMetadata { private String className = null; private int versionCode = -1; @@ -113,7 +112,7 @@ public static class WidgetMetaData private int[] minDimens = new int[] {-1, -1}; private int[] maxDimens = new int[] {-1, -1}; - public WidgetMetaData(String widgetClassName, int versionCode, int category, + public WidgetMetadata(String widgetClassName, int versionCode, int category, int minWidth, int minHeight, int maxWidth, int maxHeight) { this.className = widgetClassName; @@ -125,7 +124,7 @@ public WidgetMetaData(String widgetClassName, int versionCode, int category, this.maxDimens[1] = maxHeight; } - public WidgetMetaData(String widgetClassName, int versionCode, WidgetMetaData other) + public WidgetMetadata(String widgetClassName, int versionCode, WidgetMetadata other) { this.className = widgetClassName; this.versionCode = versionCode; @@ -170,13 +169,13 @@ public int[] getMaxDimensions() { return maxDimens; } - public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values) + public static WidgetMetadata getMetaDataFromValues(@NonNull ContentValues values) { Long values_id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); - return WidgetSettingsMetadata.WidgetMetaData.getMetaDataFromValues(values, values_id); + return WidgetMetadata.getMetaDataFromValues(values, values_id); } - public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values, @Nullable Long appWidgetId) + public static WidgetMetadata getMetaDataFromValues(@NonNull ContentValues values, @Nullable Long appWidgetId) { if (appWidgetId != null) { @@ -196,7 +195,7 @@ public static WidgetMetaData getMetaDataFromValues(@NonNull ContentValues values int maxWidth = (values.containsKey(key_width_max) ? values.getAsInteger(key_width_max) : -1); int maxHeight = (values.containsKey(key_height_max) ? values.getAsInteger(key_height_max) : -1); - return new WidgetMetaData(widgetClassName, versionCode, category, + return new WidgetMetadata(widgetClassName, versionCode, category, minWidth, minHeight, maxWidth, maxHeight); } else return null; @@ -218,7 +217,7 @@ public boolean equals(Object o) return false; } - WidgetMetaData other = (WidgetMetaData) o; + WidgetMetadata other = (WidgetMetadata) o; String widgetClassName1 = other.className; if (widgetClassName1 == null) { widgetClassName1 = ""; @@ -234,7 +233,7 @@ public boolean equals(Object o) /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// - public static void saveMetaData(Context context, int appWidgetId, WidgetMetaData metadata) + public static void saveMetaData(Context context, int appWidgetId, WidgetMetadata metadata) { int[] minSize = metadata.getMinDimensions(); int[] maxSize = metadata.getMaxDimensions(); @@ -282,7 +281,7 @@ public static void saveMetaData(Context context, int appWidgetId, Bundle bundle) prefs.apply(); } - public static WidgetMetaData loadMetaData(Context context, int appWidgetId) + public static WidgetMetadata loadMetaData(Context context, int appWidgetId) { SharedPreferences prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); String prefs_prefix = WidgetSettings.PREF_PREFIX_KEY + appWidgetId + PREF_PREFIX_KEY_META; @@ -293,7 +292,7 @@ public static WidgetMetaData loadMetaData(Context context, int appWidgetId) int minHeight = prefs.getInt(prefs_prefix + PREF_KEY_META_HEIGHT_MIN, -1); int maxWidth = prefs.getInt(prefs_prefix + PREF_KEY_META_WIDTH_MAX, -1); int maxHeight = prefs.getInt(prefs_prefix + PREF_KEY_META_HEIGHT_MAX, -1); - return new WidgetMetaData(className, versionCode, category, + return new WidgetMetadata(className, versionCode, category, minWidth, minHeight, maxWidth, maxHeight); } From 49cba29e079b2119169f1bbe943df54421ac1049 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 12:58:19 -0700 Subject: [PATCH 34/69] test --- .../settings/WidgetSettingsMetadataTest.java | 100 ++++++++++++++++++ .../settings/WidgetSettingsMetadata.java | 11 ++ .../settings/WidgetSettingsMetadataTest0.java | 99 +++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java create mode 100644 app/src/test/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest0.java diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java new file mode 100644 index 000000000..dda6061f0 --- /dev/null +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java @@ -0,0 +1,100 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.content.ContentValues; + +import org.junit.Test; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static org.junit.Assert.assertNotEquals; + +public class WidgetSettingsMetadataTest +{ + private static final String className = "class0"; + private static final int versionCode = 100; + private static final int category = 200; + private static final int minWidth = 300; + private static final int minHeight = 400; + private static final int maxWidth = 500; + private static final int maxHeight = 600; + + private static final String className1 = "class1"; + private static final int versionCode1 = 1000; + private static final int category1 = 1200; + private static final int minWidth1 = 1300; + private static final int minHeight1 = 1400; + private static final int maxWidth1 = 1500; + private static final int maxHeight1 = 1600; + + + protected ContentValues createContentValues0(Long appWidgetId0) + { + ContentValues values0 = new ContentValues(); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, className); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE, versionCode); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_CATEGORY, category); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_WIDTH_MIN, minWidth); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_HEIGHT_MIN, minHeight); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_WIDTH_MAX, maxWidth); + values0.put(WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + WidgetSettingsMetadata.PREF_KEY_META_HEIGHT_MAX, maxHeight); + return values0; + } + + @Test + public void test_WidgetMetadata_getMetaDataFromValues() + { + long appWidgetId0 = 100; + ContentValues values0 = createContentValues0(appWidgetId0); + WidgetSettingsMetadata.WidgetMetadata d0 = WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values0, appWidgetId0); + assertEquals(d0.getWidgetClassName(), className); + assertEquals(d0.getVersionCode(), versionCode); + assertEquals(d0.getCategory(), category); + assertEquals(d0.getMinDimensions()[0], minWidth); + assertEquals(d0.getMinDimensions()[1], minHeight); + assertEquals(d0.getMaxDimensions()[0], maxWidth); + assertEquals(d0.getMaxDimensions()[1], maxHeight); + + WidgetSettingsMetadata.WidgetMetadata d1 = WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values0); + assertEquals(d1.getWidgetClassName(), className); + assertEquals(d1.getVersionCode(), versionCode); + assertEquals(d1.getCategory(), category); + assertEquals(d1.getMinDimensions()[0], minWidth); + assertEquals(d1.getMinDimensions()[1], minHeight); + assertEquals(d1.getMaxDimensions()[0], maxWidth); + assertEquals(d1.getMaxDimensions()[1], maxHeight); + } + + @Test + public void test_WidgetMetadata_findAppWidgetIdFromFirstKey() + { + Long appWidgetId0 = 100L; + ContentValues values0 = createContentValues0(appWidgetId0); + Long appWidgetId1 = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values0); + assertNotNull(appWidgetId1); + assertEquals(appWidgetId1, appWidgetId0); + + ContentValues values2 = new ContentValues(); + Long appWidgetId3 = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values2); + assertNull(appWidgetId3); + } + +} diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index 5b0049608..e58ad2a93 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -112,6 +112,17 @@ public static class WidgetMetadata private int[] minDimens = new int[] {-1, -1}; private int[] maxDimens = new int[] {-1, -1}; + public WidgetMetadata(WidgetMetadata other) + { + this.className = other.className; + this.versionCode = other.versionCode; + this.category = other.category; + this.minDimens[0] = other.minDimens[0]; + this.minDimens[1] = other.minDimens[1]; + this.maxDimens[0] = other.maxDimens[0]; + this.maxDimens[1] = other.maxDimens[1]; + } + public WidgetMetadata(String widgetClassName, int versionCode, int category, int minWidth, int minHeight, int maxWidth, int maxHeight) { diff --git a/app/src/test/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest0.java b/app/src/test/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest0.java new file mode 100644 index 000000000..92d90b7a1 --- /dev/null +++ b/app/src/test/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest0.java @@ -0,0 +1,99 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.content.ContentValues; +import android.support.annotation.NonNull; + +import org.junit.Test; + +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class WidgetSettingsMetadataTest0 +{ + private static final String className = "class0"; + private static final int versionCode = 100; + private static final int category = 200; + private static final int minWidth = 300; + private static final int minHeight = 400; + private static final int maxWidth = 500; + private static final int maxHeight = 600; + + private static final String className1 = "class1"; + private static final int versionCode1 = 1000; + private static final int category1 = 1200; + private static final int minWidth1 = 1300; + private static final int minHeight1 = 1400; + private static final int maxWidth1 = 1500; + private static final int maxHeight1 = 1600; + + + @Test + public void test_WidgetMetadata_new() + { + WidgetSettingsMetadata.WidgetMetadata d0 = new WidgetSettingsMetadata.WidgetMetadata(className, versionCode, category, minWidth, minHeight, maxWidth, maxHeight); + assertEquals(d0.getWidgetClassName(), className); + assertEquals(d0.getVersionCode(), versionCode); + assertEquals(d0.getCategory(), category); + assertEquals(d0.getMinDimensions()[0], minWidth); + assertEquals(d0.getMinDimensions()[1], minHeight); + assertEquals(d0.getMaxDimensions()[0], maxWidth); + assertEquals(d0.getMaxDimensions()[1], maxHeight); + + WidgetSettingsMetadata.WidgetMetadata d1 = new WidgetSettingsMetadata.WidgetMetadata(className1, versionCode1, d0); + assertEquals(d1.getWidgetClassName(), className1); + assertEquals(d1.getVersionCode(), versionCode1); + assertEquals(d1.getCategory(), category); + assertEquals(d1.getMinDimensions()[0], minWidth); + assertEquals(d1.getMinDimensions()[1], minHeight); + assertEquals(d1.getMaxDimensions()[0], maxWidth); + assertEquals(d1.getMaxDimensions()[1], maxHeight); + + WidgetSettingsMetadata.WidgetMetadata d2 = new WidgetSettingsMetadata.WidgetMetadata(d1); + assertEquals(d2.getWidgetClassName(), className1); + assertEquals(d2.getVersionCode(), versionCode1); + assertEquals(d2.getCategory(), category); + assertEquals(d2.getMinDimensions()[0], minWidth); + assertEquals(d2.getMinDimensions()[1], minHeight); + assertEquals(d2.getMaxDimensions()[0], maxWidth); + assertEquals(d2.getMaxDimensions()[1], maxHeight); + } + + @Test + public void test_WidgetMetadata_equals() + { + WidgetSettingsMetadata.WidgetMetadata d0 = new WidgetSettingsMetadata.WidgetMetadata(className, versionCode, category, minWidth, minHeight, maxWidth, maxHeight); + WidgetSettingsMetadata.WidgetMetadata d00 = new WidgetSettingsMetadata.WidgetMetadata(d0); + WidgetSettingsMetadata.WidgetMetadata d1 = new WidgetSettingsMetadata.WidgetMetadata(className1, versionCode1, category1, minWidth1, minHeight1, maxWidth1, maxHeight1); + assertEquals(d0, d0); // == .. self + assertEquals(d00, d0); // == .. copy + assertNotEquals(d1, d0); // != .. different className + assertEquals(d00.hashCode(), d0.hashCode()); + assertNotEquals(d1.hashCode(), d0.hashCode()); + + WidgetSettingsMetadata.WidgetMetadata d2 = new WidgetSettingsMetadata.WidgetMetadata(className, versionCode1, d0); + WidgetSettingsMetadata.WidgetMetadata d3 = new WidgetSettingsMetadata.WidgetMetadata(className1, versionCode1, d0); + assertEquals(d2, d0); // == .. versionCode ignored by equality + assertNotEquals(d3, d0); // != .. different className + assertEquals(d2.hashCode(), d0.hashCode()); + assertNotEquals(d3.hashCode(), d0.hashCode()); + } + +} From 9bb4b059540dcea5a370f9c40cd990b654befd78 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 15:23:41 -0700 Subject: [PATCH 35/69] export include ids for quick settings tiles and app config --- .../SuntimesWidgetListActivity.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 681d87d90..4d8d1cb5f 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -78,6 +78,8 @@ import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; +import com.forrestguice.suntimeswidget.tiles.ClockTileService; +import com.forrestguice.suntimeswidget.tiles.NextEventTileService; import com.forrestguice.suntimeswidget.widgets.DateWidget0; import java.io.File; @@ -371,6 +373,9 @@ protected static ArrayList getAllWidgetIds(Context context) for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) { ids.addAll(getAllWidgetIds(context, widgetClass)); } + ids.add(0); // include app config and quick settings tiles + ids.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); + ids.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); return ids; } protected static ArrayList getAllWidgetIds(Context context, Class widgetClass) @@ -385,7 +390,7 @@ protected static ArrayList getAllWidgetIds(Context context, Class widge return ids; } - public static void saveBasicWidgetMetadata(Context context) + public static void addMetadata(Context context) { AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); String packageName = context.getPackageName(); @@ -400,6 +405,17 @@ public static void saveBasicWidgetMetadata(Context context) WidgetSettingsMetadata.saveMetaData(context, id, bundle); } } + + Bundle bundle = new Bundle(); + bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, "SuntimesActivity"); + bundle.putInt(WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE, BuildConfig.VERSION_CODE); + WidgetSettingsMetadata.saveMetaData(context, 0, bundle); + + bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, ClockTileService.class.getSimpleName()); + WidgetSettingsMetadata.saveMetaData(context, ClockTileService.CLOCKTILE_APPWIDGET_ID, bundle); + + bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, NextEventTileService.class.getSimpleName()); + WidgetSettingsMetadata.saveMetaData(context, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID, bundle); } /** @@ -423,7 +439,7 @@ protected void exportSettings(Context context) } WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, exportTarget, true, true); // export to external cache - saveBasicWidgetMetadata(context); + addMetadata(context); task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); task.execute(); @@ -431,7 +447,7 @@ protected void exportSettings(Context context) public void exportSettings(Context context, @NonNull Uri uri) { Log.i("ExportSettings", "Starting export task: " + uri); - saveBasicWidgetMetadata(context); + addMetadata(context); WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, uri); task.setTaskListener(exportSettingsListener); task.setAppWidgetIds(getAllWidgetIds(context)); From b57d2c6db23d45f0e037c1208e69fd205c225d91 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 15:53:15 -0700 Subject: [PATCH 36/69] import include ids for quick settings tiles and app config --- .../SuntimesWidgetListActivity.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 4d8d1cb5f..bbe0961f5 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -557,6 +557,9 @@ public void onClick(DialogInterface dialog, int whichButton) case 0: default: // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) importSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, true, allValues); + WidgetSettingsImportTask.restoreFromBackup(context, + new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}, // these lines should be the same + new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}); // because the ids are unchanged break; } } @@ -596,19 +599,24 @@ protected Map makeBestGuess(Context context, ContentValue unused.put(WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values), values); } + ArrayList widgetIds = new ArrayList<>(); + for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) { + widgetIds.addAll(getAllWidgetIds(context, widgetClass)); + } + widgetIds.add(0); + widgetIds.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); + widgetIds.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); + Map suggested = new HashMap<>(); - for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) + for (Integer appWidgetId : widgetIds) { - ArrayList widgetIds = getAllWidgetIds(context, widgetClass); - for (Integer appWidgetId : widgetIds) + WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); + if (unused.containsKey(metadata)) { - WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); - if (unused.containsKey(metadata)) - { - ContentValues values = unused.remove(metadata); - used.put(metadata, values); - suggested.put(appWidgetId, values); - } + Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); + ContentValues values = unused.remove(metadata); + used.put(metadata, values); + suggested.put(appWidgetId, values); } } return suggested; @@ -616,6 +624,7 @@ protected Map makeBestGuess(Context context, ContentValue protected void importSettingsBestGuess(Context context, ContentValues... contentValues) { + addMetadata(context); Map suggested = makeBestGuess(context, contentValues); int numMatches = suggested.size(); Log.d("DEBUG", "bestGuess: " + numMatches + " matches"); From 285d625f186eeb3bd4329dc3c929db6517c4def0 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 18:41:01 -0700 Subject: [PATCH 37/69] chooseImportValues --- .../SuntimesConfigActivity0.java | 85 +++++++++++++------ app/src/main/res/values/strings.xml | 5 +- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index af629aee4..56b9436b8 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2088,46 +2088,32 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) dismissProgress(); if (result.getResult() && result.numResults() > 0) { - ContentValues values = null; - CharSequence[] items = new CharSequence[result.numResults()]; + ArrayList values = new ArrayList<>(); + CharSequence[] labels = new CharSequence[result.numResults()]; for (int i=0; i= 0 && p < allValues.length)) { - importSettings(context, allValues[p]); - } - } - }) - .setNegativeButton(context.getString(R.string.dialog_cancel), null); - confirm.show(); + } else if (values.size() > 1) { // multiple matches; choose one + chooseImportValuesOfSameType(context, values); + + } else { // no matches; choose any + chooseImportValuesOfDifferentType(context, result.getItems(), labels); } } else { @@ -2139,6 +2125,55 @@ public void onClick(DialogInterface dialog, int whichButton) return true; } + protected void chooseImportValuesOfSameType(final Context context, ArrayList values) + { + final ContentValues[] matchingValues = values.toArray(new ContentValues[0]); + CharSequence[] labels = new CharSequence[matchingValues.length]; + for (int i=0; i= 0 && p < matchingValues.length)) { + Log.d("ImportSettings", "user selected " + p + " of " + (matchingValues.length-1)); + importSettings(context, matchingValues[p]); + } + } + }) + .setNegativeButton(context.getString(R.string.dialog_cancel), null); + confirm.show(); + } + + protected void chooseImportValuesOfDifferentType(final Context context, final ContentValues[] values, final CharSequence[] labels) + { + String title = context.getString(R.string.importwidget_dialog_title1); + AlertDialog.Builder confirm = new AlertDialog.Builder(context).setTitle(title).setIcon(android.R.drawable.ic_dialog_alert) + .setSingleChoiceItems(labels, 0, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { /* EMPTY */ } + }) + .setPositiveButton(context.getString(R.string.configAction_import), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + int p = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + if ((p >= 0 && p < values.length)) { + Log.d("ImportSettings", "user selected " + p + " of " + (values.length-1) + " (" + labels[p] + ")"); + importSettings(context, values[p]); + } + } + }) + .setNegativeButton(context.getString(R.string.dialog_cancel), null); + confirm.show(); + } + protected void importSettings(Context context, ContentValues values) { importSettings(context, values, true); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5647147c3..00ceec48c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1191,7 +1191,7 @@ No Widgets @string/configAction_export - Export Settings + Backup Settings @string/configAction_import Import Settings @@ -1200,11 +1200,12 @@ Import Settings Importing widget settings from file. Settings found,\nbut they are for other widgets… + Found multiple settings… %1$s (unknown) Restore Backup. Imported settings will be cached for now and restored later when requested by the launcher.
]]>
- Best Guess. Imported settings will be applied to existing widgets by type (and other indicators).
]]>
+ Best Guess. Imported settings will be applied to existing widgets by type.
]]>
Direct Import. Imported settings will be applied directly. This only works correctly if the widget ids are unchanged.
]]>
From 86b5e2859b914eabfdc2e88e8ae20ec9c512d988 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 20 Jan 2024 19:04:44 -0700 Subject: [PATCH 38/69] makeBestGuess --- .../SuntimesWidgetListActivity.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index bbe0961f5..fab08411a 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -593,10 +593,14 @@ protected void importSettings(Context context, String prefix, boolean includeMet protected Map makeBestGuess(Context context, ContentValues... contentValues) { - Map unused = new HashMap<>(); - Map used = new HashMap<>(); - for (ContentValues values : contentValues) { - unused.put(WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values), values); + ArrayList unusedKeys = new ArrayList<>(); + ArrayList unusedValues = new ArrayList<>(); + for (int i=0; i widgetIds = new ArrayList<>(); @@ -611,11 +615,12 @@ protected Map makeBestGuess(Context context, ContentValue for (Integer appWidgetId : widgetIds) { WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); - if (unused.containsKey(metadata)) + if (unusedKeys.contains(metadata)) { Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); - ContentValues values = unused.remove(metadata); - used.put(metadata, values); + int i = unusedKeys.indexOf(metadata); + unusedKeys.remove(i); + ContentValues values = unusedValues.remove(i); suggested.put(appWidgetId, values); } } From e1bbae54ee7edc2384d418cb394c80079471a578 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 21 Jan 2024 00:32:11 -0700 Subject: [PATCH 39/69] importValues --- .../suntimeswidget/settings/WidgetSettingsImportTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 5d0982fcd..c7153eac3 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -505,7 +505,7 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va } String k = TextUtils.join("_", keyParts); // replacement key - String k0 = k.replaceFirst(toPrefix + keyParts[1], ""); + String k0 = k.replaceFirst(keyParts[0] + "_" + keyParts[1], ""); if (prefTypes.containsKey(k0)) { From 155d7c6d48d17315b3b456cf0238211d47a5b6d0 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 21 Jan 2024 01:37:13 -0700 Subject: [PATCH 40/69] snackbar --- .../SuntimesWidgetListActivity.java | 49 +++++++++++++------ app/src/main/res/values/strings.xml | 2 + 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index fab08411a..824c6c700 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -480,7 +480,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) { //if (isAdded()) { String successMessage = context.getString(R.string.msg_export_success, path); - showIOResultSnackbar(context, true, successMessage); + showIOResultSnackbar(context, true, successMessage, null); //} if (Build.VERSION.SDK_INT >= 19) { @@ -495,7 +495,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) //if (isAdded()) { String failureMessage = context.getString(R.string.msg_export_failure, path); - showIOResultSnackbar(context, false, failureMessage); + showIOResultSnackbar(context, false, failureMessage, null); //} } } @@ -568,7 +568,7 @@ public void onClick(DialogInterface dialog, int whichButton) confirm.show(); } else { - showIOResultSnackbar(context, false, 0); + showIOResultSnackbar(context, false, 0, null); } } }); @@ -579,11 +579,17 @@ protected void importSettings(Context context, String prefix, boolean includeMet { SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); int c = 0; - for (ContentValues values : contentValues) { + StringBuilder report = new StringBuilder(); + for (ContentValues values : contentValues) + { + Long id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); + WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values); WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); + report.append(context.getString(R.string.importwidget_dialog_report_format, id + "", metadata.getWidgetClassName())); + report.append("\n"); c++; } - showIOResultSnackbar(context, true, c); + showIOResultSnackbar(context, true, c, report.toString()); } /** @@ -633,6 +639,7 @@ protected void importSettingsBestGuess(Context context, ContentValues... content Map suggested = makeBestGuess(context, contentValues); int numMatches = suggested.size(); Log.d("DEBUG", "bestGuess: " + numMatches + " matches"); + StringBuilder report = new StringBuilder(); if (numMatches > 0) // matched some { SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); @@ -640,37 +647,49 @@ protected void importSettingsBestGuess(Context context, ContentValues... content { ContentValues values = suggested.get(appWidgetId); WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); + + String widgetClassName = WidgetSettingsMetadata.loadMetaData(context, appWidgetId).getWidgetClassName(); + report.append(context.getString(R.string.importwidget_dialog_report_format, appWidgetId + "", widgetClassName)); + report.append("\n"); } - showIOResultSnackbar(context, true, numMatches); + showIOResultSnackbar(context, true, numMatches, report.toString()); } else { // matched none - showIOResultSnackbar(context, false, numMatches); + showIOResultSnackbar(context, false, numMatches, null); } } - protected void showIOResultSnackbar(final Context context, boolean result, CharSequence message) + protected void showIOResultSnackbar(final Context context, boolean result, final CharSequence message, @Nullable final CharSequence report) { View view = getWindow().getDecorView(); if (context != null && view != null) { Snackbar snackbar = Snackbar.make(view, message, (result ? 7000 : Snackbar.LENGTH_LONG)); - /*snackbar.setAction(context.getString(R.string.configAction_undo), new View.OnClickListener() { - @Override - public void onClick(View v) { - } - });*/ + if (report != null) + { + snackbar.setAction(context.getString(R.string.configAction_info), new View.OnClickListener() + { + @Override + public void onClick(View v) { + AlertDialog.Builder dialog = new AlertDialog.Builder(context).setTitle(message) + .setIcon(android.R.drawable.ic_dialog_info) + .setMessage(report); + dialog.show(); + } + }); + } SuntimesUtils.themeSnackbar(context, snackbar, null); snackbar.show(); } } - protected void showIOResultSnackbar(final Context context, boolean result, int numResults) + protected void showIOResultSnackbar(final Context context, boolean result, int numResults, @Nullable CharSequence report) { //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); //Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); CharSequence message = (result ? context.getString(R.string.msg_import_success, context.getResources().getQuantityString(R.plurals.widgetPlural, numResults, numResults)) : context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file))); - showIOResultSnackbar(context, result, message); + showIOResultSnackbar(context, result, message, report); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 00ceec48c..eb3a012f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ Share Export Import + More Info Sun Position Solstice / Equinox World Map @@ -1203,6 +1204,7 @@ Found multiple settings… %1$s (unknown) + %1$s, %2$s Restore Backup. Imported settings will be cached for now and restored later when requested by the launcher.
]]>
Best Guess. Imported settings will be applied to existing widgets by type.
]]>
From ed715349f0c631b9f38bd196873ae78dc552c309 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 21 Jan 2024 02:16:10 -0700 Subject: [PATCH 41/69] log --- .../suntimeswidget/SuntimesConfigActivity0.java | 6 +++--- .../suntimeswidget/SuntimesWidgetListActivity.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java index 56b9436b8..d6b97bfb1 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesConfigActivity0.java @@ -2101,7 +2101,7 @@ public void onFinished(WidgetSettingsImportTask.TaskResult result) if (getWidgetClass().getSimpleName().equals(values_widgetClassName)) { - Log.d("ImportSettings", "found settings for widget type " + values_widgetClassName + " at index " + i); + //Log.d("ImportSettings", "found settings for widget type " + values_widgetClassName + " at index " + i); values.add(v); } } @@ -2143,7 +2143,7 @@ public void onClick(DialogInterface dialog, int whichButton) { int p = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); if ((p >= 0 && p < matchingValues.length)) { - Log.d("ImportSettings", "user selected " + p + " of " + (matchingValues.length-1)); + //Log.d("ImportSettings", "user selected " + p + " of " + (matchingValues.length-1)); importSettings(context, matchingValues[p]); } } @@ -2165,7 +2165,7 @@ public void onClick(DialogInterface dialog, int whichButton) { int p = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); if ((p >= 0 && p < values.length)) { - Log.d("ImportSettings", "user selected " + p + " of " + (values.length-1) + " (" + labels[p] + ")"); + //Log.d("ImportSettings", "user selected " + p + " of " + (values.length-1) + " (" + labels[p] + ")"); importSettings(context, values[p]); } } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 824c6c700..29da89fef 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -623,7 +623,7 @@ protected Map makeBestGuess(Context context, ContentValue WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); if (unusedKeys.contains(metadata)) { - Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); + //Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); int i = unusedKeys.indexOf(metadata); unusedKeys.remove(i); ContentValues values = unusedValues.remove(i); @@ -638,10 +638,9 @@ protected void importSettingsBestGuess(Context context, ContentValues... content addMetadata(context); Map suggested = makeBestGuess(context, contentValues); int numMatches = suggested.size(); - Log.d("DEBUG", "bestGuess: " + numMatches + " matches"); - StringBuilder report = new StringBuilder(); if (numMatches > 0) // matched some { + StringBuilder report = new StringBuilder(); SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); for (Integer appWidgetId : suggested.keySet()) { From d6270c3bbf57f2b3e8eb6fc596fb76b0c26a9eb1 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 21 Jan 2024 13:06:33 -0700 Subject: [PATCH 42/69] test --- .../settings/WidgetSettingsMetadataTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java index dda6061f0..2c1d9e2d8 100644 --- a/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java @@ -97,4 +97,21 @@ public void test_WidgetMetadata_findAppWidgetIdFromFirstKey() assertNull(appWidgetId3); } + @Test + public void test_WidgetMetadata_replaceKeyPrefix() + { + Long appWidgetId0 = 100L; + ContentValues values0 = createContentValues0(appWidgetId0); + String key0 = WidgetSettings.PREF_PREFIX_KEY + appWidgetId0 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + "testKey"; + String value0 = "testValue"; + values0.put(key0, value0); + + Long appWidgetId1 = 200L; + ContentValues values1 = WidgetSettingsImportTask.replaceKeyPrefix(values0, appWidgetId1); + String key1 = WidgetSettings.PREF_PREFIX_KEY + appWidgetId1 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + "testKey"; + assertNotNull(values1); + assertTrue(values1.containsKey(key1)); + assertEquals(values1.get(key1), value0); + } + } From f23cffd88bdcc09acd09f96eadd092fc27f80bf1 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 21 Jan 2024 13:06:39 -0700 Subject: [PATCH 43/69] lint newApi; ContentValues.keyset() --- .../settings/WidgetSettingsImportTask.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index c7153eac3..92ac725ad 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -367,12 +367,13 @@ public static ContentValues putValueInto(ContentValues values, String key, Objec public static ContentValues replaceKeyPrefix(ContentValues values, int replacementId) { ContentValues v = new ContentValues(); - for (String key : values.keySet()) + Set> entries = values.valueSet(); + for (Map.Entry entry : entries) { - String[] parts = key.split("_"); + String[] parts = entry.getKey().split("_"); parts[1] = Integer.toString(replacementId); String k = TextUtils.join("_", parts); - v = putValueInto(v, k, values.get(key)); + v = putValueInto(v, k, entry.getValue()); } return v; } @@ -488,9 +489,11 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va prefTypes.putAll(WidgetSettingsMetadata.getPrefTypes()); } - for (String key : values.keySet()) + Set> entries = values.valueSet(); + for (Map.Entry entry : entries) { - Object value = values.get(key); + String key = entry.getKey(); + Object value = entry.getValue(); if (value == null) { Log.w("WidgetSettings", "import: skipping " + key + "... contains null value"); continue; @@ -564,15 +567,19 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va public static Long findAppWidgetIdFromFirstKey(ContentValues values) { - String[] keys = values.keySet().toArray(new String[0]); - if (keys.length > 0) + Set> entries = values.valueSet(); + if (entries.size() > 0) { - try { - String[] parts = keys[0].split("_"); - return Long.parseLong(parts[1]); + for (Map.Entry entry : entries) + { + try { + String key = entry.getKey(); + String[] parts = key.split("_"); + return Long.parseLong(parts[1]); - } catch (NumberFormatException e) { - Log.w("WidgetSettings", "failed to find widget id from keys.. " + e); + } catch (NumberFormatException | NullPointerException e) { + Log.w("WidgetSettings", "failed to find widget id from keys.. " + e); + } } } return null; From 653c07d1f4769dba2c777500e67efa6c1443f7ba Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Tue, 23 Jan 2024 14:00:59 -0700 Subject: [PATCH 44/69] CalendarSettings CALENDAR_FORMATPATTERN keys --- .../calendar/CalendarSettings.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java index 5f27e82c6..c1dadd6be 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java @@ -63,11 +63,20 @@ public class CalendarSettings /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// - public static final String[] ALL_KEYS = { - PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_SHOWDATE, - PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_MODE, - PREF_PREFIX_KEY_CALENDAR + PREF_KEY_CALENDAR_FORMATPATTERN - }; + public static final String[] ALL_KEYS; + static + { + CalendarMode[] modes = CalendarMode.values(); + ALL_KEYS = new String[modes.length + 3]; + for (int i=0; i Date: Tue, 23 Jan 2024 14:01:51 -0700 Subject: [PATCH 45/69] WidgetSettingsMetadata --- .../settings/WidgetSettingsMetadataTest.java | 3 ++- .../suntimeswidget/settings/WidgetSettingsMetadata.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java index 2c1d9e2d8..7b7a71fa4 100644 --- a/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java +++ b/app/src/androidTest/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadataTest.java @@ -25,6 +25,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertNotEquals; public class WidgetSettingsMetadataTest @@ -106,7 +107,7 @@ public void test_WidgetMetadata_replaceKeyPrefix() String value0 = "testValue"; values0.put(key0, value0); - Long appWidgetId1 = 200L; + int appWidgetId1 = 200; ContentValues values1 = WidgetSettingsImportTask.replaceKeyPrefix(values0, appWidgetId1); String key1 = WidgetSettings.PREF_PREFIX_KEY + appWidgetId1 + WidgetSettingsMetadata.PREF_PREFIX_KEY_META + "testKey"; assertNotNull(values1); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java index e58ad2a93..cac6b935b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsMetadata.java @@ -25,6 +25,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.forrestguice.suntimeswidget.BuildConfig; + import java.util.Map; import java.util.TreeMap; @@ -123,6 +125,12 @@ public WidgetMetadata(WidgetMetadata other) this.maxDimens[1] = other.maxDimens[1]; } + public WidgetMetadata(String widgetClassName) + { + this.className = widgetClassName; + this.versionCode = BuildConfig.VERSION_CODE; + } + public WidgetMetadata(String widgetClassName, int versionCode, int category, int minWidth, int minHeight, int maxWidth, int maxHeight) { From d651b9cb1d3e92784ac8a62be9acacf74c9551f6 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Tue, 23 Jan 2024 14:03:44 -0700 Subject: [PATCH 46/69] WidgetSettingsExportTask refactor --- .../settings/WidgetSettingsExportTask.java | 83 ++++++++++++------- .../settings/WidgetSettingsImportTask.java | 30 +++++-- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java index d0bd1b515..bb85ce091 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsExportTask.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; +import android.support.annotation.Nullable; import com.forrestguice.suntimeswidget.ExportTask; @@ -52,25 +53,37 @@ public WidgetSettingsExportTask(Context context, Uri uri) initTask(); } - private void initTask() + protected void initTask() { ext = FILEEXT; mimeType = MIMETYPE; } + /** + * writes + * [{ ContentValues }, ...] + */ @Override - public boolean export( Context context, BufferedOutputStream out ) throws IOException + public boolean export( Context context, BufferedOutputStream out ) throws IOException { + writeWidgetSettingsJSONArray(context, out); + return true; + } + + /** + * writes + * [{ ContentValues }, ...] + */ + protected void writeWidgetSettingsJSONArray(Context context, BufferedOutputStream out) throws IOException { - SharedPreferences prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); + SharedPreferences widgetPrefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); int n = appWidgetIds.size(); - - out.write("[".getBytes()); + out.write("[".getBytes()); // writes a json array for (int i=0; i values) } protected ArrayList appWidgetIds = new ArrayList<>(); - public static ContentValues toContentValues(SharedPreferences prefs, int appWidgetId) + public static ContentValues toContentValues(SharedPreferences prefs) { + return toContentValues(prefs, null); + } + + /** + * @param prefs SharedPreferences + * @param appWidgetId keys for appWidgetId, or null for all keys + * @return ContentValues + */ + public static ContentValues toContentValues(SharedPreferences prefs, @Nullable Integer appWidgetId) { Map map = prefs.getAll(); Set keys = map.keySet(); @@ -105,29 +126,31 @@ public static ContentValues toContentValues(SharedPreferences prefs, int appWidg ContentValues values = new ContentValues(); for (String key : keys) { - if (key.startsWith(WidgetSettings.PREF_PREFIX_KEY + appWidgetId)) + boolean isMatch = (appWidgetId == null || key.startsWith(WidgetSettings.PREF_PREFIX_KEY + appWidgetId)); + if (!isMatch) { + continue; + } + + if (map.get(key).getClass().equals(String.class)) { - if (map.get(key).getClass().equals(String.class)) - { - //Log.d("DEBUG", key + " is String"); - values.put(key, prefs.getString(key, null)); - - } else if (map.get(key).getClass().equals(Integer.class)) { - //Log.d("DEBUG", key + " is Integer"); - values.put(key, prefs.getInt(key, -1)); - - } else if (map.get(key).getClass().equals(Long.class)) { - //Log.d("DEBUG", key + " is Long"); - values.put(key, prefs.getLong(key, -1)); - - } else if (map.get(key).getClass().equals(Float.class)) { - //Log.d("DEBUG", key + " is Long"); - values.put(key, prefs.getFloat(key, -1)); - - } else if (map.get(key).getClass().equals(Boolean.class)) { - //Log.d("DEBUG", key + " is boolean"); - values.put(key, prefs.getBoolean(key, false)); - } + //Log.d("DEBUG", key + " is String"); + values.put(key, prefs.getString(key, null)); + + } else if (map.get(key).getClass().equals(Integer.class)) { + //Log.d("DEBUG", key + " is Integer"); + values.put(key, prefs.getInt(key, -1)); + + } else if (map.get(key).getClass().equals(Long.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getLong(key, -1)); + + } else if (map.get(key).getClass().equals(Float.class)) { + //Log.d("DEBUG", key + " is Long"); + values.put(key, prefs.getFloat(key, -1)); + + } else if (map.get(key).getClass().equals(Boolean.class)) { + //Log.d("DEBUG", key + " is boolean"); + values.put(key, prefs.getBoolean(key, false)); } } return values; diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 92ac725ad..bde437721 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -19,7 +19,6 @@ package com.forrestguice.suntimeswidget.settings; import android.annotation.TargetApi; -import android.app.PendingIntent; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; @@ -51,7 +50,7 @@ public class WidgetSettingsImportTask extends AsyncTask contextRef; + protected final WeakReference contextRef; protected boolean isPaused = false; public void pauseTask() { @@ -99,7 +98,7 @@ protected TaskResult doInBackground(Uri... params) if (in != null) { Log.d(getClass().getSimpleName(), "doInBackground: reading"); - WidgetSettingsJson.readItems(context, in, items); + readData(context, in, items); result = true; error = null; @@ -126,6 +125,10 @@ protected TaskResult doInBackground(Uri... params) return new TaskResult(result, uri, (items != null ? items.toArray(new ContentValues[0]) : null), error); } + protected void readData(Context context, InputStream in, ArrayList items) throws IOException { + ContentValuesJson.readItems(context, in, items); + } + @Override protected void onProgressUpdate(ContentValues... progressItems) { super.onProgressUpdate(progressItems); @@ -199,12 +202,17 @@ public void clearTaskListener() { } /** - * WidgetSettingsJson + * ContentValuesJson */ - public static class WidgetSettingsJson + public static class ContentValuesJson { - public static final String TAG = "WidgetSettingsJson"; + public static final String TAG = "ContentValuesJson"; + /** + * Currently reads.. + * [{ ContentValues }, ...] + * { ContentValues } + */ public static void readItems(Context context, InputStream in, ArrayList items) throws IOException { if (Build.VERSION.SDK_INT >= 11) @@ -324,6 +332,16 @@ protected static void skipJsonArray(JsonReader reader) throws IOException reader.endArray(); } + @TargetApi(11) + protected static void skipJsonItem(JsonReader reader) throws IOException + { + switch (reader.peek()) { + case BEGIN_ARRAY: skipJsonArray(reader); break; + case BEGIN_OBJECT: skipJsonObject(reader); break; + default: reader.skipValue(); break; + } + } + public static String toJson(ContentValues values) { HashMap map = ExportTask.toMap(values); From 440ad94612d25a95ffc9c5db3beb698d80e74848 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Tue, 23 Jan 2024 14:08:55 -0700 Subject: [PATCH 47/69] CalendarSettings --- .../forrestguice/suntimeswidget/calendar/CalendarSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java index c1dadd6be..b41d93a8c 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/calendar/CalendarSettings.java @@ -69,7 +69,7 @@ public class CalendarSettings CalendarMode[] modes = CalendarMode.values(); ALL_KEYS = new String[modes.length + 3]; for (int i=0; i Date: Tue, 23 Jan 2024 23:34:51 -0700 Subject: [PATCH 48/69] SuntimesBackupTask --- .../SuntimesWidgetListActivity.java | 272 ++--------- .../settings/SuntimesBackupRestoreTask.java | 446 ++++++++++++++++++ .../settings/SuntimesBackupTask.java | 309 ++++++++++++ .../settings/WidgetSettingsExportTask.java | 35 ++ .../settings/WidgetSettingsImportTask.java | 46 ++ app/src/main/res/menu/widgetlist.xml | 4 +- app/src/main/res/values/strings.xml | 23 +- 7 files changed, 902 insertions(+), 233 deletions(-) create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index 29da89fef..ef46f829b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -43,6 +43,7 @@ import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.Snackbar; @@ -73,10 +74,13 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SuntimesBackupRestoreTask; +import com.forrestguice.suntimeswidget.settings.SuntimesBackupTask; import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; +import com.forrestguice.suntimeswidget.themes.ImportThemesTask; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; @@ -88,8 +92,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import static com.forrestguice.suntimeswidget.SuntimesConfigActivity0.EXTRA_RECONFIGURE; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_DIRECTIMPORT; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_MAKEBESTGUESS; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_RESTOREBACKUP; public class SuntimesWidgetListActivity extends AppCompatActivity { @@ -157,7 +166,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { Uri uri = (data != null ? data.getData() : null); if (uri != null) { - exportSettings(SuntimesWidgetListActivity.this, uri); + SuntimesBackupTask.exportSettings(SuntimesWidgetListActivity.this, uri, exportSettingsListener); } } break; @@ -367,67 +376,15 @@ public void dismissProgress() } } - protected static ArrayList getAllWidgetIds(Context context) - { - ArrayList ids = new ArrayList<>(); - for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) { - ids.addAll(getAllWidgetIds(context, widgetClass)); - } - ids.add(0); // include app config and quick settings tiles - ids.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); - ids.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); - return ids; - } - protected static ArrayList getAllWidgetIds(Context context, Class widgetClass) - { - AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); - String packageName = context.getPackageName(); - ArrayList ids = new ArrayList<>(); - int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); - for (int id : widgetIds) { - ids.add(id); - } - return ids; - } - - public static void addMetadata(Context context) - { - AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); - String packageName = context.getPackageName(); - for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) - { - Bundle bundle = new Bundle(); - bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, widgetClass.getSimpleName()); - bundle.putInt(WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE, BuildConfig.VERSION_CODE); - - int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); - for (int id : widgetIds) { - WidgetSettingsMetadata.saveMetaData(context, id, bundle); - } - } - - Bundle bundle = new Bundle(); - bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, "SuntimesActivity"); - bundle.putInt(WidgetSettingsMetadata.PREF_KEY_META_VERSIONCODE, BuildConfig.VERSION_CODE); - WidgetSettingsMetadata.saveMetaData(context, 0, bundle); - - bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, ClockTileService.class.getSimpleName()); - WidgetSettingsMetadata.saveMetaData(context, ClockTileService.CLOCKTILE_APPWIDGET_ID, bundle); - - bundle.putString(WidgetSettingsMetadata.PREF_KEY_META_CLASSNAME, NextEventTileService.class.getSimpleName()); - WidgetSettingsMetadata.saveMetaData(context, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID, bundle); - } - /** * exportSettings * @param context Context */ protected void exportSettings(Context context) { - String exportTarget = "SuntimesWidgets"; if (Build.VERSION.SDK_INT >= 19) { - String filename = exportTarget + WidgetSettingsExportTask.FILEEXT; + String filename = SuntimesBackupTask.DEF_EXPORT_TARGET + WidgetSettingsExportTask.FILEEXT; Intent intent = ExportTask.getCreateFileIntent(filename, WidgetSettingsExportTask.MIMETYPE); try { startActivityForResult(intent, EXPORT_REQUEST); @@ -437,22 +394,9 @@ protected void exportSettings(Context context) Log.e("ExportSettings", "SAF is unavailable? (" + e + ").. falling back to legacy export method."); } } - - WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, exportTarget, true, true); // export to external cache - addMetadata(context); - task.setTaskListener(exportSettingsListener); - task.setAppWidgetIds(getAllWidgetIds(context)); - task.execute(); - } - public void exportSettings(Context context, @NonNull Uri uri) - { - Log.i("ExportSettings", "Starting export task: " + uri); - addMetadata(context); - WidgetSettingsExportTask task = new WidgetSettingsExportTask(context, uri); - task.setTaskListener(exportSettingsListener); - task.setAppWidgetIds(getAllWidgetIds(context)); - task.execute(); + SuntimesBackupTask.exportSettings(context, null, exportSettingsListener); } + private final WidgetSettingsExportTask.TaskListener exportSettingsListener = new WidgetSettingsExportTask.TaskListener() { @Override @@ -460,7 +404,7 @@ public void onStarted() { //setRetainInstance(true); Context context = SuntimesWidgetListActivity.this; - showProgress(context, context.getString(R.string.exportwidget_dialog_title), context.getString(R.string.exportwidget_dialog_message)); + showProgress(context, context.getString(R.string.configAction_createBackup), context.getString(R.string.configAction_createBackup)); } @Override @@ -480,7 +424,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) { //if (isAdded()) { String successMessage = context.getString(R.string.msg_export_success, path); - showIOResultSnackbar(context, true, successMessage, null); + SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), true, successMessage, null); //} if (Build.VERSION.SDK_INT >= 19) { @@ -495,7 +439,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) //if (isAdded()) { String failureMessage = context.getString(R.string.msg_export_failure, path); - showIOResultSnackbar(context, false, failureMessage, null); + SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), false, failureMessage, null); //} } } @@ -514,181 +458,59 @@ public void importSettings(Context context) public void importSettings(final Context context, @NonNull Uri uri) { Log.i("ImportSettings", "Starting import task: " + uri); - WidgetSettingsImportTask task = new WidgetSettingsImportTask(context); - task.setTaskListener(new WidgetSettingsImportTask.TaskListener() + SuntimesBackupRestoreTask task = new SuntimesBackupRestoreTask(context); + task.setTaskListener(new SuntimesBackupRestoreTask.TaskListener() { @Override public void onStarted() { - showProgress(context, context.getString(R.string.importwidget_dialog_title), context.getString(R.string.importwidget_dialog_message)); + showProgress(context, context.getString(R.string.configAction_restoreBackup), context.getString(R.string.configAction_restoreBackup)); } @Override - public void onFinished(WidgetSettingsImportTask.TaskResult result) + public void onFinished(final SuntimesBackupRestoreTask.TaskResult result) { dismissProgress(); if (result.getResult() && result.numResults() > 0) { - final ContentValues[] allValues = result.getItems(); - final CharSequence[] items = new CharSequence[] { - SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_restorebackup)), // 0 - SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_bestguess)), // 1 - SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_direct)), // 2 - }; - String title = context.getString(R.string.importwidget_dialog_title); - AlertDialog.Builder confirm = new AlertDialog.Builder(context).setTitle(title).setIcon(android.R.drawable.ic_dialog_alert) - .setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { /* EMPTY */ } - }) - .setPositiveButton(context.getString(R.string.configAction_import), new DialogInterface.OnClickListener() + final Map allValues = result.getItems(); + SuntimesBackupTask.chooseBackupContent(context, allValues.keySet(), true, new SuntimesBackupTask.ChooseBackupDialogListener() + { + @Override + public void onClick(DialogInterface dialog, int which, String[] keys, boolean[] checked) + { + final Set includeKeys = new TreeSet<>(); + for (int i=0; i keys, Map allValues) { - SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); - int c = 0; StringBuilder report = new StringBuilder(); - for (ContentValues values : contentValues) - { - Long id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); - WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values); - WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); - report.append(context.getString(R.string.importwidget_dialog_report_format, id + "", metadata.getWidgetClassName())); - report.append("\n"); - c++; - } - showIOResultSnackbar(context, true, c, report.toString()); - } - - /** - * Tries to match contentValues to existing widgetIds based on available metadata. - * @return suggested appWidget:ContentValues mapping - */ - - protected Map makeBestGuess(Context context, ContentValues... contentValues) - { - ArrayList unusedKeys = new ArrayList<>(); - ArrayList unusedValues = new ArrayList<>(); - for (int i=0; i widgetIds = new ArrayList<>(); - for (Class widgetClass : WidgetListAdapter.ALL_WIDGETS) { - widgetIds.addAll(getAllWidgetIds(context, widgetClass)); - } - widgetIds.add(0); - widgetIds.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); - widgetIds.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); - - Map suggested = new HashMap<>(); - for (Integer appWidgetId : widgetIds) - { - WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); - if (unusedKeys.contains(metadata)) - { - //Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); - int i = unusedKeys.indexOf(metadata); - unusedKeys.remove(i); - ContentValues values = unusedValues.remove(i); - suggested.put(appWidgetId, values); - } - } - return suggested; - } - - protected void importSettingsBestGuess(Context context, ContentValues... contentValues) - { - addMetadata(context); - Map suggested = makeBestGuess(context, contentValues); - int numMatches = suggested.size(); - if (numMatches > 0) // matched some - { - StringBuilder report = new StringBuilder(); - SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); - for (Integer appWidgetId : suggested.keySet()) - { - ContentValues values = suggested.get(appWidgetId); - WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); - - String widgetClassName = WidgetSettingsMetadata.loadMetaData(context, appWidgetId).getWidgetClassName(); - report.append(context.getString(R.string.importwidget_dialog_report_format, appWidgetId + "", widgetClassName)); - report.append("\n"); - } - showIOResultSnackbar(context, true, numMatches, report.toString()); - - } else { // matched none - showIOResultSnackbar(context, false, numMatches, null); - } - } - - protected void showIOResultSnackbar(final Context context, boolean result, final CharSequence message, @Nullable final CharSequence report) - { - View view = getWindow().getDecorView(); - if (context != null && view != null) - { - Snackbar snackbar = Snackbar.make(view, message, (result ? 7000 : Snackbar.LENGTH_LONG)); - if (report != null) - { - snackbar.setAction(context.getString(R.string.configAction_info), new View.OnClickListener() - { - @Override - public void onClick(View v) { - AlertDialog.Builder dialog = new AlertDialog.Builder(context).setTitle(message) - .setIcon(android.R.drawable.ic_dialog_info) - .setMessage(report); - dialog.show(); - } - }); - } - SuntimesUtils.themeSnackbar(context, snackbar, null); - snackbar.show(); - } - } - - protected void showIOResultSnackbar(final Context context, boolean result, int numResults, @Nullable CharSequence report) - { - //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); - //Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); - CharSequence message = (result ? context.getString(R.string.msg_import_success, context.getResources().getQuantityString(R.plurals.widgetPlural, numResults, numResults)) - : context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file))); - showIOResultSnackbar(context, result, message, report); + int c = SuntimesBackupRestoreTask.importSettings(context, method, keys, report, allValues); + SuntimesBackupRestoreTask.showIOResultSnackbar(context, getWindow().getDecorView(), (c > 0), c, ((c > 0) ? report.toString() : null)); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java new file mode 100644 index 000000000..abce951d0 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -0,0 +1,446 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.annotation.TargetApi; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.util.JsonReader; +import android.util.JsonToken; +import android.util.Log; +import android.view.View; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.ContentValuesJson; +import com.forrestguice.suntimeswidget.tiles.ClockTileService; +import com.forrestguice.suntimeswidget.tiles.NextEventTileService; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_DIRECTIMPORT; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_MAKEBESTGUESS; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_RESTOREBACKUP; + +public class SuntimesBackupRestoreTask extends AsyncTask +{ + public static final String TAG = "RestoreBackup"; + public static final long MIN_WAIT_TIME = 2000; + + protected final WeakReference contextRef; + + protected boolean isPaused = false; + public void pauseTask() { + isPaused = true; + } + public void resumeTask() { + isPaused = false; + } + public boolean isPaused() { + return isPaused; + } + + public SuntimesBackupRestoreTask(Context context) { + contextRef = new WeakReference<>(context); + } + + @Override + protected void onPreExecute() + { + Log.d(getClass().getSimpleName(), "onPreExecute"); + if (taskListener != null) { + taskListener.onStarted(); + } + } + + @Override + protected TaskResult doInBackground(Uri... params) + { + Log.d(TAG, "doInBackground: starting"); + Uri uri = null; + if (params.length > 0) { + uri = params[0]; + } + + long startTime = System.currentTimeMillis(); + boolean result = false; + Map data = new HashMap<>(); + Exception error = null; + + Context context = contextRef.get(); + if (context != null && uri != null) + { + try { + InputStream in = context.getContentResolver().openInputStream(uri); + if (in != null) + { + Log.d(TAG, "doInBackground: reading"); + readData(context, in, data); + result = true; + error = null; + + } else { + Log.e(TAG, "Failed to import from " + uri + ": null input stream!"); + result = false; + error = null; + } + } catch (IOException e) { + Log.e(TAG, "Failed to import from " + uri + ": " + e); + result = false; + data = null; + error = e; + } + } + + Log.d(TAG, "doInBackground: waiting"); + long endTime = System.currentTimeMillis(); + while ((endTime - startTime) < MIN_WAIT_TIME || isPaused) { + endTime = System.currentTimeMillis(); + } + + Log.d(TAG, "doInBackground: finishing"); + return new TaskResult(result, uri, data, error); + } + + protected void readData(Context context, InputStream in, Map data) throws IOException + { + if (Build.VERSION.SDK_INT >= 11) + { + //noinspection CharsetObjectCanBeUsed + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + reader.setLenient(true); + try { + readBackupItem(context, reader, data); + + } finally { + reader.close(); + in.close(); + } + } else { + Log.w("ImportSettings", "Unsupported; skipping import"); + in.close(); + } + } + + @TargetApi(11) + protected void readBackupItem(Context context, JsonReader reader, Map data) throws IOException + { + if (reader.peek() == JsonToken.BEGIN_OBJECT) + { + reader.beginObject(); + while (reader.hasNext()) + { + String key = reader.nextName(); + if (reader.hasNext()) + { + switch (reader.peek()) + { + case BEGIN_ARRAY: + case BEGIN_OBJECT: + ArrayList items = new ArrayList<>(); + ContentValuesJson.readItems(context, reader, items); + data.put(key, items.toArray(new ContentValues[0])); + break; + + default: + reader.skipValue(); + break; + } + } + } + reader.endObject(); + + } else { + ContentValuesJson.skipJsonItem(reader); + } + } + + @Override + protected void onProgressUpdate(Void... progressItems) { + super.onProgressUpdate(progressItems); + } + + @Override + protected void onPostExecute( TaskResult result ) + { + Log.d(TAG, "onPostExecute: " + result.getResult()); + if (taskListener != null) { + taskListener.onFinished(result); + } + } + + /** + * TaskResult + */ + public static class TaskResult + { + public TaskResult(boolean result, Uri uri, @Nullable Map items, Exception e) + { + this.result = result; + this.items = items; + this.uri = uri; + this.e = e; + } + + private final boolean result; + public boolean getResult() { + return result; + } + + private final Map items; + public Map getItems() { + return items; + } + + private final Uri uri; + public Uri getUri() { + return uri; + } + + public int numResults() { + return (items != null ? items.size() : 0); + } + + private final Exception e; + public Exception getException() { + return e; + } + } + + /** + * TaskListener + */ + public static abstract class TaskListener + { + public void onStarted() {} + public void onFinished( TaskResult result ) {} + } + protected TaskListener taskListener = null; + public void setTaskListener( TaskListener listener ) { + taskListener = listener; + } + public void clearTaskListener() { + taskListener = null; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * @param context Context context + * @param keys the set of backup keys within allValues that should be restored + * @param method 2:directImport (copy widget ids as-is), 1:bestGuess (reassign widget ids (best guess)), 0:restoreBackup (import as backup for later (when the launcher initiates restoration)) + * @param allValues a map containing backupKey:ContentValue[]; e.g. "AppSettings":ContentValues[], "WidgetSettings":ContentValues[], ... + * @return number of items imported + */ + public static int importSettings(Context context, int method, Set keys, StringBuilder report, Map allValues) + { + int c = 0; + + if (keys.contains(SuntimesBackupTask.KEY_APPSETTINGS)) { + c += (SuntimesBackupRestoreTask.importAppSettings(context, report, allValues.get(SuntimesBackupTask.KEY_APPSETTINGS)) ? 1 : 0); + } + + if (keys.contains(SuntimesBackupTask.KEY_WIDGETSETTINGS)) + { + switch (method) + { + case IMPORT_METHOD_DIRECTIMPORT: // direct import + c += SuntimesBackupRestoreTask.importWidgetSettings(context, null, false, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + break; + + case IMPORT_METHOD_MAKEBESTGUESS: // best guess + c += SuntimesBackupRestoreTask.importWidgetSettingsBestGuess(context, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + break; + + case IMPORT_METHOD_RESTOREBACKUP: + default: // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) + c += SuntimesBackupRestoreTask.importWidgetSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, true, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + WidgetSettingsImportTask.restoreFromBackup(context, + new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}, // these lines should be the same + new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}); // because the ids are unchanged + break; + } + } + + if (keys.contains(SuntimesBackupTask.KEY_ALARMITEMS)) { + c += SuntimesBackupRestoreTask.importAlarmItems(context, report, allValues.get(SuntimesBackupTask.KEY_ALARMITEMS)); + } + + if (keys.contains(SuntimesBackupTask.KEY_EVENTITEMS)) { + c += SuntimesBackupRestoreTask.importEventItems(context, report, allValues.get(SuntimesBackupTask.KEY_EVENTITEMS)); + } + + return c; + } + + /** + * importAppSettings + */ + public static boolean importAppSettings(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + { + if (contentValues != null) + { + SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); + for (ContentValues values : contentValues) + { + if (values != null) { + //WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); // TODO + report.append(context.getString(R.string.restorebackup_dialog_report_format, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_APPSETTINGS))); + report.append("\n"); + return true; + } + } + } + return false; + } + + /** + * importAlarmItems + */ + public static int importAlarmItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { + return 0; // TODO + } + + /** + * importEventItems + */ + public static int importEventItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { + return 0; // TODO + } + + /** + * importPlaceItems + */ + public static int importPlaceItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { + return 0; // TODO + } + + /** + * importWidgetSettings + */ + public static int importWidgetSettings(Context context, String prefix, boolean includeMetadata, StringBuilder report, @Nullable ContentValues... contentValues) + { + if (contentValues != null) + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + int c = 0; + for (ContentValues values : contentValues) + { + Long id = WidgetSettingsImportTask.findAppWidgetIdFromFirstKey(values); + WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.WidgetMetadata.getMetaDataFromValues(values); + WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); + report.append(context.getString(R.string.importwidget_dialog_report_format, id + "", metadata.getWidgetClassName())); + report.append("\n"); + c++; + } + return c; + } else return 0; + } + + /** + * Tries to match contentValues to existing widgetIds based on available metadata. + * @return suggested appWidget:ContentValues mapping + */ + protected static Map makeBestGuess(Context context, ContentValues... contentValues) + { + ArrayList unusedKeys = new ArrayList<>(); + ArrayList unusedValues = new ArrayList<>(); + for (int i=0; i widgetIds = new ArrayList<>(); + for (Class widgetClass : SuntimesWidgetListActivity.WidgetListAdapter.ALL_WIDGETS) { + widgetIds.addAll(SuntimesBackupTask.getAllWidgetIds(context, widgetClass)); + } + widgetIds.add(0); + widgetIds.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); + widgetIds.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); + + Map suggested = new HashMap<>(); + for (Integer appWidgetId : widgetIds) + { + WidgetSettingsMetadata.WidgetMetadata metadata = WidgetSettingsMetadata.loadMetaData(context, appWidgetId); + if (unusedKeys.contains(metadata)) + { + //Log.d("DEBUG", "makeBestGuess: " + appWidgetId + " :: " + metadata.getWidgetClassName()); + int i = unusedKeys.indexOf(metadata); + unusedKeys.remove(i); + ContentValues values = unusedValues.remove(i); + suggested.put(appWidgetId, values); + } + } + return suggested; + } + + public static int importWidgetSettingsBestGuess(Context context, StringBuilder report, ContentValues... contentValues) + { + WidgetSettingsExportTask.addWidgetMetadata(context); + Map suggested = makeBestGuess(context, contentValues); + int numMatches = suggested.size(); + if (numMatches > 0) // matched some + { + SharedPreferences.Editor prefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0).edit(); + for (Integer appWidgetId : suggested.keySet()) + { + ContentValues values = suggested.get(appWidgetId); + WidgetSettingsImportTask.importValues(prefs, values, appWidgetId); + + String widgetClassName = WidgetSettingsMetadata.loadMetaData(context, appWidgetId).getWidgetClassName(); + report.append(context.getString(R.string.importwidget_dialog_report_format, appWidgetId + "", widgetClassName)); + report.append("\n"); + } + return numMatches; + + } else { // matched none + return 0; + } + } + + /** + * showIOResultSnackbar + */ + public static void showIOResultSnackbar(final Context context, View view, boolean result, int numResults, @Nullable CharSequence report) + { + //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + //Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + CharSequence message = (result ? context.getString(R.string.msg_import_success, context.getResources().getQuantityString(R.plurals.itemsPlural, numResults, numResults)) + : context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file))); + SuntimesBackupTask.showIOResultSnackbar(context, view, result, message, report); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java new file mode 100644 index 000000000..a83117c73 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -0,0 +1,309 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.net.Uri; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.View; + +import com.forrestguice.suntimeswidget.ExportTask; +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; +import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.tiles.ClockTileService; +import com.forrestguice.suntimeswidget.tiles.NextEventTileService; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Backup all Suntimes settings to json backup file. + * Backup contents may include: AppSettings, WidgetSettings, AlarmClockItems + */ +public class SuntimesBackupTask extends WidgetSettingsExportTask +{ + public static final String KEY_APPSETTINGS = "AppSettings"; + public static final String KEY_WIDGETSETTINGS = "WidgetSettings"; + public static final String KEY_ALARMITEMS = "AlarmItems"; + public static final String KEY_EVENTITEMS = "EventItems"; + public static final String KEY_PLACEITEMS = "PlaceItems"; + + public static final String[] ALL_KEYS = new String[] { + KEY_APPSETTINGS, KEY_WIDGETSETTINGS + //, KEY_ALARMITEMS // TODO: implement + //, KEY_EVENTITEMS // TODO: implement + //, KEY_PLACEITEMS // TODO: implement + }; + + public static final String DEF_EXPORT_TARGET = "SuntimesBackup"; + + public SuntimesBackupTask(Context context, String exportTarget) { + super(context, exportTarget); + } + public SuntimesBackupTask(Context context, String exportTarget, boolean useExternalStorage, boolean saveToCache) { + super(context, exportTarget, useExternalStorage, saveToCache); + } + public SuntimesBackupTask(Context context, Uri uri) { + super(context, uri); + } + + /** + * @param key KEY_APPSETTINGS, KEY_WIDGETSETTINGS, KEY_ALARMITEMS + * @param value true, false + */ + public void includeInBackup(String key, boolean value) { + includedKeys.put(key, value); + } + public void includeInBackup(String... keys) { + for (String key : keys) { + includeInBackup(key, true); + } + } + public void includeAll() { + includeInBackup(ALL_KEYS); + } + protected Map includedKeys = new HashMap<>(); + + /** + * writes + * { + * "AppSettings": { ContentValues } + * "WidgetSettings": [{ ContentValues }, ...] + * "AlarmItems": [{ AlarmClockItem }, ...] + * } + */ + @Override + public boolean export( Context context, BufferedOutputStream out ) throws IOException + { + out.write("{".getBytes()); + int c = 0; // keys written + + if (includedKeys.containsKey(KEY_APPSETTINGS) && includedKeys.get(KEY_APPSETTINGS)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_APPSETTINGS + "\": ").getBytes()); // include AppSettings + writeAppSettingsJSONObject(context, out); + c++; + } + + if (includedKeys.containsKey(KEY_WIDGETSETTINGS) && includedKeys.get(KEY_WIDGETSETTINGS) && appWidgetIds.size() > 0) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_WIDGETSETTINGS + "\": ").getBytes()); // include WidgetSettings + writeWidgetSettingsJSONArray(context, out); + c++; + } + + if (includedKeys.containsKey(KEY_ALARMITEMS) && includedKeys.get(KEY_ALARMITEMS)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_ALARMITEMS + "\": ").getBytes()); // include AlarmItems + writeAlarmItemsJSONArray(context, out); + c++; + } + + out.write("}".getBytes()); + out.flush(); + return true; + } + + /** + * writes + * { ContentValues } + */ + protected void writeAppSettingsJSONObject(Context context, BufferedOutputStream out) throws IOException + { + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(context); // AppSettings are stored in default shared prefs + String json = WidgetSettingsImportTask.ContentValuesJson.toJson(toContentValues(appPrefs)); + out.write(json.getBytes()); + out.flush(); + } + + /** + * writes + * [{ AlarmClockItem}, ...] + */ + protected void writeAlarmItemsJSONArray(Context context, BufferedOutputStream out) throws IOException + { + out.write("{}".getBytes()); // TODO: write alarm items + out.flush(); + } + + public static void exportSettings(final Context context, @Nullable final Uri uri, final ExportTask.TaskListener exportListener) + { + Log.i("ExportSettings", "Starting export task: " + uri); + SuntimesBackupTask.chooseBackupContent(context, SuntimesBackupTask.ALL_KEYS, false, new SuntimesBackupTask.ChooseBackupDialogListener() + { + @Override + public void onClick(DialogInterface dialog, int which, String[] keys, boolean[] checked) + { + WidgetSettingsExportTask.addWidgetMetadata(context); + SuntimesBackupTask task = (uri != null ? new SuntimesBackupTask(context, uri) + : new SuntimesBackupTask(context, SuntimesBackupTask.DEF_EXPORT_TARGET, true, true)); // export to external cache; + task.setTaskListener(exportListener); + task.includeInBackup(keys); + task.setAppWidgetIds(getAllWidgetIds(context)); + task.execute(); + } + }); + } + + protected static ArrayList getAllWidgetIds(Context context) + { + ArrayList ids = new ArrayList<>(); + for (Class widgetClass : SuntimesWidgetListActivity.WidgetListAdapter.ALL_WIDGETS) { + ids.addAll(getAllWidgetIds(context, widgetClass)); + } + ids.add(0); // include app config and quick settings tiles + ids.add(ClockTileService.CLOCKTILE_APPWIDGET_ID); + ids.add(NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID); + return ids; + } + protected static ArrayList getAllWidgetIds(Context context, Class widgetClass) + { + AppWidgetManager widgetManager = AppWidgetManager.getInstance(context); + String packageName = context.getPackageName(); + ArrayList ids = new ArrayList<>(); + int[] widgetIds = widgetManager.getAppWidgetIds(new ComponentName(packageName, widgetClass.getName())); + for (int id : widgetIds) { + ids.add(id); + } + return ids; + } + + /** + * displayStringForBackupKey + * @param context Context + * @param key backupKey, e.g. KEY_APPSETTINGS + * @return display string (or the key itself if unrecognized) + */ + public static CharSequence displayStringForBackupKey(Context context, String key) + { + if (SuntimesBackupTask.KEY_APPSETTINGS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_appsettings)); + } + if (SuntimesBackupTask.KEY_WIDGETSETTINGS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_widgetsettings)); + } + if (SuntimesBackupTask.KEY_ALARMITEMS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_alarmitems)); + } + if (SuntimesBackupTask.KEY_EVENTITEMS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_eventitems)); + } + if (SuntimesBackupTask.KEY_PLACEITEMS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_placeitems)); + } + return key; + } + + /** + * ChooseBackupDialogListener + */ + public interface ChooseBackupDialogListener { + void onClick(DialogInterface dialog, int which, String[] keys, boolean[] checked); + } + + /** + * chooseBackupContent + * @param context Context + * @param keys key to choose from + * @param isImport true importing content, false exporting content + * @param onClickListener dialog listener + */ + public static void chooseBackupContent(final Context context, Set keys, boolean isImport, @NonNull final ChooseBackupDialogListener onClickListener) { + chooseBackupContent(context, keys.toArray(new String[0]), isImport, onClickListener); + } + public static void chooseBackupContent(final Context context, final String[] keys, boolean isImport, @NonNull final ChooseBackupDialogListener onClickListener) + { + final CharSequence[] items = new CharSequence[keys.length]; + final boolean[] checked = new boolean[keys.length]; + for (int i=0; i Show widget help No Widgets + Create Backup + Restore Backup + Alarms + App Configuration + Widget Settings + Places + Custom Events + %1$s + @string/configAction_export - Backup Settings @string/configAction_import - Import Settings - - Exporting + Export Settings Exporting widget settings to file. Import Settings Importing widget settings from file. @@ -1204,12 +1210,17 @@ Found multiple settings… %1$s (unknown) - %1$s, %2$s - Restore Backup. Imported settings will be cached for now and restored later when requested by the launcher.
]]>
Best Guess. Imported settings will be applied to existing widgets by type.
]]>
Direct Import. Imported settings will be applied directly. This only works correctly if the widget ids are unchanged.
]]>
+ %1$s, %2$s + + %d item + %d items + %d items + %d items + %d widget %d widgets From e3f0534063da3242604fd9259ac4439a3b910d5e Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 24 Jan 2024 01:28:11 -0700 Subject: [PATCH 49/69] includeInBackup --- .../suntimeswidget/settings/SuntimesBackupTask.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java index a83117c73..616ec6512 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -89,6 +89,11 @@ public void includeInBackup(String... keys) { includeInBackup(key, true); } } + public void includeInBackup(String[] keys, boolean[] include) { + for (int i=0; i Date: Wed, 24 Jan 2024 13:24:36 -0700 Subject: [PATCH 50/69] EventExportTask refactor --- .../events/EventExportTask.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/events/EventExportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/events/EventExportTask.java index ba2d615ca..ef79fee9e 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/events/EventExportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/events/EventExportTask.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2022 Forrest Guice + Copyright (C) 2022-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -65,26 +65,31 @@ public EventSettings.EventAlias[] getItems() { return items; } - @Override protected boolean export(Context context, BufferedOutputStream out) throws IOException { if (items != null) { numEntries = items.length; - out.write("[".getBytes()); - for (int i=0; i Date: Wed, 24 Jan 2024 13:25:03 -0700 Subject: [PATCH 51/69] AlarmClockItemExportTask refactor --- .../alarmclock/AlarmClockItemExportTask.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItemExportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItemExportTask.java index afbaf2b59..621308d9e 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItemExportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmClockItemExportTask.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2022 Forrest Guice + Copyright (C) 2022-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -71,20 +71,28 @@ protected boolean export(Context context, BufferedOutputStream out) throws IOExc if (items != null) { numEntries = items.length; - out.write("[".getBytes()); - for (int i=0; i Date: Wed, 24 Jan 2024 19:22:25 -0700 Subject: [PATCH 52/69] snoozeLimit fixes bug where snooze limit setting isn't correctly applied --- .../forrestguice/suntimeswidget/alarmclock/AlarmSettings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java index 7e4f042dc..ef79423cc 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java @@ -269,7 +269,7 @@ public static long loadPrefAlarmSnoozeLimit(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (Build.VERSION.SDK_INT >= 11) { - return prefs.getInt(PREF_KEY_ALARM_SNOOZE, PREF_DEF_ALARM_SNOOZE_LIMIT); + return prefs.getInt(PREF_KEY_ALARM_SNOOZE_LIMIT, PREF_DEF_ALARM_SNOOZE_LIMIT); } else return loadStringPrefAsLong(prefs, PREF_KEY_ALARM_SNOOZE_LIMIT, PREF_DEF_ALARM_SNOOZE_LIMIT); } From 31ed7ff1c01fb2e12535a8e111962bd992cb7c0a Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 24 Jan 2024 19:30:35 -0700 Subject: [PATCH 53/69] getPrefTypes --- .../alarmclock/AlarmSettings.java | 69 ++++++++++++++++++- .../suntimeswidget/settings/AppSettings.java | 59 +++++++++++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java index ef79423cc..3e4487e4a 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/alarmclock/AlarmSettings.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2018-2022 Forrest Guice + Copyright (C) 2018-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -49,7 +49,9 @@ import com.forrestguice.suntimeswidget.settings.WidgetActions; import java.lang.ref.WeakReference; +import java.util.Map; import java.util.TimeZone; +import java.util.TreeMap; import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; @@ -136,6 +138,71 @@ public class AlarmSettings public static final String PREF_KEY_ALARM_DISMISS_CHALLENGE = "app_alarms_dismiss_challenge"; public static final DismissChallenge PREF_DEF_ALARM_DISMISS_CHALLENGE = DismissChallenge.NONE; + //////////////////////////////////////////////////////////////////////////////////////////////// + + public static final String[] ALL_KEYS = new String[] + { + PREF_KEY_ALARM_CATEGORY, PREF_KEY_ALARM_AUTOSTART, + PREF_KEY_ALARM_BATTERYOPT, PREF_KEY_ALARM_NOTIFICATIONS, + PREF_KEY_ALARM_VOLUMES, PREF_KEY_ALARM_HARDAREBUTTON_ACTION, + PREF_KEY_ALARM_SILENCEAFTER, PREF_KEY_ALARM_TIMEOUT, + PREF_KEY_ALARM_SNOOZE, PREF_KEY_ALARM_SNOOZE_LIMIT, + PREF_KEY_ALARM_UPCOMING, PREF_KEY_ALARM_AUTODISMISS, + PREF_KEY_ALARM_AUTOENABLE, PREF_KEY_ALARM_AUTOVIBRATE, + PREF_KEY_ALARM_RINGTONE_URI_ALARM, PREF_KEY_ALARM_RINGTONE_NAME_ALARM, + PREF_KEY_ALARM_RINGTONE_URI_NOTIFICATION, PREF_KEY_ALARM_RINGTONE_NAME_NOTIFICATION, + PREF_KEY_ALARM_ALLRINGTONES, PREF_KEY_ALARM_SHOWLAUNCHER, + PREF_KEY_ALARM_POWEROFFALARMS, PREF_KEY_ALARM_UPCOMING_ALARMID, + PREF_KEY_ALARM_SYSTEM_TIMEZONE_ID, PREF_KEY_ALARM_SYSTEM_TIMEZONE_OFFSET, + PREF_KEY_ALARM_FADEIN, PREF_KEY_ALARM_DISMISS_CHALLENGE, + PREF_KEY_ALARM_SORT, PREF_KEY_ALARM_SORT_ENABLED_FIRST, PREF_KEY_ALARM_SORT_SHOW_OFFSET, + PREF_KEY_ALARM_BOOTCOMPLETED, PREF_KEY_ALARM_BOOTCOMPLETED_ATELAPSED, PREF_KEY_ALARM_BOOTCOMPLETED_DURATION, PREF_KEY_ALARM_BOOTCOMPLETED_RESULT, + }; + public static final String[] LONG_KEYS = new String[] { + PREF_KEY_ALARM_UPCOMING_ALARMID, + PREF_KEY_ALARM_SYSTEM_TIMEZONE_OFFSET, + PREF_KEY_ALARM_BOOTCOMPLETED, PREF_KEY_ALARM_BOOTCOMPLETED_ATELAPSED, PREF_KEY_ALARM_BOOTCOMPLETED_DURATION, + }; + public static final String[] INT_KEYS = new String[] { + PREF_KEY_ALARM_SILENCEAFTER, PREF_KEY_ALARM_TIMEOUT, + PREF_KEY_ALARM_SNOOZE, PREF_KEY_ALARM_SNOOZE_LIMIT, + PREF_KEY_ALARM_UPCOMING, PREF_KEY_ALARM_AUTODISMISS, + PREF_KEY_ALARM_FADEIN, PREF_KEY_ALARM_SORT, + }; + public static final String[] BOOL_KEYS = new String[] + { + PREF_KEY_ALARM_AUTOENABLE, PREF_KEY_ALARM_AUTOVIBRATE, + PREF_KEY_ALARM_ALLRINGTONES, PREF_KEY_ALARM_SHOWLAUNCHER, PREF_KEY_ALARM_POWEROFFALARMS, + PREF_KEY_ALARM_SORT_ENABLED_FIRST, PREF_KEY_ALARM_SORT_SHOW_OFFSET, + PREF_KEY_ALARM_BOOTCOMPLETED_RESULT + }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + for (String key : LONG_KEYS) { + types.put(key, Long.class); + } + for (String key : INT_KEYS) { + types.put(key, Integer.class); + } + for (String key : BOOL_KEYS) { + types.put(key, Boolean.class); + } + + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// public static boolean hasAlarmSupport(Context context) { return !AppSettings.isTelevision(context); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java index 2f40517dd..34f73b78b 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/AppSettings.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2014-2022 Forrest Guice + Copyright (C) 2014-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -55,6 +55,8 @@ import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; /** * Shared preferences used by the app; uses getDefaultSharedPreferences (stored in com.forrestguice.suntimeswidget_preferences.xml). @@ -178,6 +180,61 @@ public static void setFirstLaunch( Context context, boolean value ) { public static final String PREF_KEY_DIALOG = "dialog"; public static final String PREF_KEY_DIALOG_DONOTSHOWAGAIN = "donotshowagain"; + //////////////////////////////////////////////////////////////////////////////////////////////// + + public static final String[] ALL_KEYS = new String[] + { + PREF_KEY_APPEARANCE_THEME, PREF_KEY_APPEARANCE_THEME_LIGHT, PREF_KEY_APPEARANCE_THEME_DARK, PREF_KEY_APPEARANCE_TEXTSIZE, + PREF_KEY_LOCALE_MODE, PREF_KEY_LOCALE, PREF_KEY_UI_SHOWWARNINGS, + PREF_KEY_UI_DATETAPACTION, PREF_KEY_UI_DATETAPACTION1, PREF_KEY_UI_CLOCKTAPACTION, PREF_KEY_UI_NOTETAPACTION, + PREF_KEY_UI_SHOWLIGHTMAP, PREF_KEY_UI_SHOWEQUINOX, PREF_KEY_UI_SHOWCROSSQUARTER, PREF_KEY_UI_SHOWMOON, PREF_KEY_UI_SHOWLUNARNOON, + PREF_KEY_UI_SHOWMAPBUTTON, PREF_KEY_UI_SHOWDATASOURCE, PREF_KEY_UI_SHOWHEADER_ICON, PREF_KEY_UI_SHOWHEADER_TEXT, + PREF_KEY_UI_EMPHASIZEFIELD, PREF_KEY_UI_SHOWFIELDS, PREF_KEY_ACCESSIBILITY_VERBOSE, PREF_KEY_UI_TIMEZONESORT, + PREF_KEY_GETFIX_MINELAPSED, PREF_KEY_GETFIX_MAXELAPSED, PREF_KEY_GETFIX_MAXAGE, PREF_KEY_GETFIX_TIME, PREF_KEY_GETFIX_PASSIVE, + PREF_KEY_PLUGINS_ENABLESCAN, PREF_KEY_FIRST_LAUNCH, PREF_KEY_DIALOG, PREF_KEY_DIALOG_DONOTSHOWAGAIN + }; + public static final String[] INT_KEYS = new String[] { + PREF_KEY_UI_SHOWFIELDS + }; + public static final String[] LONG_KEYS = new String[] { + PREF_KEY_GETFIX_TIME + }; + public static final String[] BOOL_KEYS = new String[] + { + PREF_KEY_UI_SHOWWARNINGS, PREF_KEY_UI_SHOWMAPBUTTON, PREF_KEY_UI_SHOWDATASOURCE, PREF_KEY_UI_SHOWHEADER_ICON, + PREF_KEY_UI_SHOWLIGHTMAP, PREF_KEY_UI_SHOWEQUINOX, PREF_KEY_UI_SHOWCROSSQUARTER, PREF_KEY_UI_SHOWMOON, PREF_KEY_UI_SHOWLUNARNOON, + PREF_KEY_ACCESSIBILITY_VERBOSE, PREF_KEY_GETFIX_PASSIVE, PREF_KEY_PLUGINS_ENABLESCAN, PREF_KEY_FIRST_LAUNCH, PREF_KEY_DIALOG_DONOTSHOWAGAIN + }; + + private static Map types = null; + public static Map getPrefTypes() + { + if (types == null) + { + types = new TreeMap<>(); + for (String key : INT_KEYS) { + types.put(key, Integer.class); + } + for (String key : BOOL_KEYS) { + types.put(key, Boolean.class); + } + + types.put(PREF_KEY_UI_SHOWHEADER_TEXT, String.class); // int as String + types.put(PREF_KEY_GETFIX_MINELAPSED, String.class); // int as String + types.put(PREF_KEY_GETFIX_MAXELAPSED, String.class); // int as String + types.put(PREF_KEY_GETFIX_MAXAGE, String.class); // int as String + + for (String key : ALL_KEYS) { // all others are type String + if (!types.containsKey(key)) { + types.put(key, String.class); + } + } + } + return types; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + /** * Text sizes */ From b38496e412148f6a21b7a8e92d0a595c4d78302f Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 24 Jan 2024 21:22:30 -0700 Subject: [PATCH 54/69] WidgetActions contentValues --- .../settings/WidgetActions.java | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java index 45697e44f..4ef6dd4f6 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetActions.java @@ -1,5 +1,5 @@ /** - Copyright (C) 2019-2022 Forrest Guice + Copyright (C) 2019-2024 Forrest Guice This file is part of SuntimesWidget. SuntimesWidget is free software: you can redistribute it and/or modify @@ -20,13 +20,13 @@ import android.appwidget.AppWidgetManager; import android.content.ActivityNotFoundException; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Color; import android.net.Uri; import android.os.Build; -import android.provider.CalendarContract; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; @@ -107,6 +107,8 @@ public class WidgetActions public static final LaunchType PREF_DEF_ACTION_LAUNCH_TYPE = LaunchType.ACTIVITY; public static final String PREF_KEY_ACTION_LAUNCH_LIST = "list"; + public static final String PREF_KEY_ACTION_LAUNCH_ID = "id"; + public static final String PREF_KEY_ACTION_LAUNCH_APPWIDGETID = "appWidgetId"; /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// @@ -124,7 +126,9 @@ public class WidgetActions PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_DATA, PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_DATATYPE, PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_TYPE, - PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_LIST + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_LIST, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_ID, + PREF_PREFIX_KEY_ACTION + PREF_KEY_ACTION_LAUNCH + "_0_" + PREF_KEY_ACTION_LAUNCH_APPWIDGETID, }; public static final String[] INT_KEYS = new String[] { @@ -249,6 +253,34 @@ public static void saveActionLaunchPref(Context context, @Nullable String titleS } } + public static boolean saveActionLaunchPref(Context context, @Nullable ContentValues values, int appWidgetId) + { + if (values != null) + { + String id = values.getAsString(PREF_KEY_ACTION_LAUNCH_ID); + if (id != null) + { + String tagString = values.getAsString(PREF_KEY_ACTION_LAUNCH_TAGS); + String[] tags = (tagString != null ? tagString.split("\\|") : new String[0]); + + saveActionLaunchPref(context, + values.getAsString(PREF_KEY_ACTION_LAUNCH_TITLE), + values.getAsString(PREF_KEY_ACTION_LAUNCH_DESC), + values.getAsInteger(PREF_KEY_ACTION_LAUNCH_COLOR), + tags, appWidgetId, id, + values.getAsString(PREF_KEY_ACTION_LAUNCH), + values.getAsString(PREF_KEY_ACTION_LAUNCH_PACKAGE), + values.getAsString(PREF_KEY_ACTION_LAUNCH_TYPE), + values.getAsString(PREF_KEY_ACTION_LAUNCH_ACTION), + values.getAsString(PREF_KEY_ACTION_LAUNCH_DATA), + values.getAsString(PREF_KEY_ACTION_LAUNCH_DATATYPE), + values.getAsString(PREF_KEY_ACTION_LAUNCH_EXTRAS), true); + return true; + } + } + return false; + } + public static Set loadActionTags(Context context, int appWidgetId, @Nullable String id) { SharedPreferences prefs = context.getSharedPreferences(PREFS_ACTIONS, 0); @@ -263,6 +295,23 @@ public static Set loadActionLaunchList(Context context, int appWidgetId) Set actionList = getStringSet(prefs, listKey, null); return (actionList != null) ? new TreeSet(actionList) : new TreeSet(); } + public static ContentValues loadActionLaunchPref(Context context, int appWidgetId, @Nullable String id) + { + ContentValues values = new ContentValues(); + values.put(PREF_KEY_ACTION_LAUNCH_ID, id); + values.put(PREF_KEY_ACTION_LAUNCH, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH)); + values.put(PREF_KEY_ACTION_LAUNCH_PACKAGE, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_PACKAGE)); + values.put(PREF_KEY_ACTION_LAUNCH_TYPE, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_TYPE)); + values.put(PREF_KEY_ACTION_LAUNCH_ACTION, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_ACTION)); + values.put(PREF_KEY_ACTION_LAUNCH_DATA, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_DATA)); + values.put(PREF_KEY_ACTION_LAUNCH_DATATYPE, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_DATATYPE)); + values.put(PREF_KEY_ACTION_LAUNCH_EXTRAS, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_EXTRAS)); + values.put(PREF_KEY_ACTION_LAUNCH_TITLE, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_TITLE)); + values.put(PREF_KEY_ACTION_LAUNCH_DESC, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_DESC)); + values.put(PREF_KEY_ACTION_LAUNCH_COLOR, loadActionLaunchPref(context, appWidgetId, id, PREF_KEY_ACTION_LAUNCH_COLOR)); + values.put(PREF_KEY_ACTION_LAUNCH_TAGS, stringSetToString(loadActionTags(context, appWidgetId, id))); + return values; + } public static String loadActionLaunchPref(Context context, int appWidgetId, @Nullable String id, @Nullable String key) { if (id == null) { From 5d03d0a7ada95ac5ab94fdfb2a92ee030b6fe6f4 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 24 Jan 2024 21:23:01 -0700 Subject: [PATCH 55/69] GetFixDatabaseAdapter addPlace --- .../suntimeswidget/getfix/GetFixDatabaseAdapter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/getfix/GetFixDatabaseAdapter.java b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/GetFixDatabaseAdapter.java index 017acb038..527ef3fd9 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/getfix/GetFixDatabaseAdapter.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/getfix/GetFixDatabaseAdapter.java @@ -180,6 +180,10 @@ public long addPlace( Location place, String comment ) values.put(KEY_PLACE_LONGITUDE, place.getLongitude()); values.put(KEY_PLACE_ALTITUDE, place.getAltitude()); values.put(KEY_PLACE_COMMENT, comment); + return addPlace(values); + } + + public long addPlace(ContentValues values) { // TODO: verify contents before calling insert return database.insert(TABLE_PLACES, null, values); } From dede3df3e3d2214c159f9bb8f61d71c4ef0b5f99 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Wed, 24 Jan 2024 23:34:15 -0700 Subject: [PATCH 56/69] SuntimesBackupTask --- .../settings/SuntimesBackupRestoreTask.java | 91 ++++++++- .../settings/SuntimesBackupTask.java | 189 ++++++++++++++++-- .../settings/WidgetSettingsExportTask.java | 10 +- .../settings/WidgetSettingsImportTask.java | 47 +++-- app/src/main/res/values/strings.xml | 6 +- 5 files changed, 288 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index abce951d0..2be187832 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -34,6 +34,10 @@ import com.forrestguice.suntimeswidget.R; import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.events.EventSettings; +import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.ContentValuesJson; import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; @@ -300,21 +304,32 @@ public static int importSettings(Context context, int method, Set keys, c += SuntimesBackupRestoreTask.importEventItems(context, report, allValues.get(SuntimesBackupTask.KEY_EVENTITEMS)); } + if (keys.contains(SuntimesBackupTask.KEY_PLACEITEMS)) { + c += SuntimesBackupRestoreTask.importPlaceItems(context, report, allValues.get(SuntimesBackupTask.KEY_PLACEITEMS)); + } + + if (keys.contains(SuntimesBackupTask.KEY_ACTIONS)) { + c += (SuntimesBackupRestoreTask.importActions(context, report, allValues.get(SuntimesBackupTask.KEY_ACTIONS)) ? 1 : 0); + } + return c; } /** * importAppSettings */ - public static boolean importAppSettings(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + protected static boolean importAppSettings(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { + Map prefTypes = AppSettings.getPrefTypes(); + prefTypes.putAll(AlarmSettings.getPrefTypes()); + if (contentValues != null) { SharedPreferences.Editor prefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); for (ContentValues values : contentValues) { if (values != null) { - //WidgetSettingsImportTask.importValues(prefs, values, prefix, null, includeMetadata); // TODO + WidgetSettingsImportTask.importValues(prefs, prefTypes, values, false, null, null, "AppSettings"); report.append(context.getString(R.string.restorebackup_dialog_report_format, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_APPSETTINGS))); report.append("\n"); return true; @@ -327,28 +342,86 @@ public static boolean importAppSettings(Context context, StringBuilder report, @ /** * importAlarmItems */ - public static int importAlarmItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { - return 0; // TODO + protected static int importAlarmItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + { + int c = 0; + AlarmDatabaseAdapter db = new AlarmDatabaseAdapter(context); + db.open(); + for (ContentValues values : contentValues) + { + if (values != null) { + db.addAlarm(values); + c++; + } + } + db.close(); + report.append(context.getString(R.string.restorebackup_dialog_report_format1, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_ALARMITEMS), c+"")); + report.append("\n"); + return c; } /** * importEventItems */ - public static int importEventItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { - return 0; // TODO + protected static int importEventItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + { + int c = 0; + for (ContentValues values : contentValues) + { + if (values != null) { + EventSettings.saveEvent(context, new EventSettings.EventAlias(values)); + c++; + } + } + report.append(context.getString(R.string.restorebackup_dialog_report_format1, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_EVENTITEMS), c+"")); + report.append("\n"); + return c; } /** * importPlaceItems */ - public static int importPlaceItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) { - return 0; // TODO + protected static int importPlaceItems(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + { + int c = 0; + GetFixDatabaseAdapter db = new GetFixDatabaseAdapter(context); + db.open(); + for (ContentValues values : contentValues) { + if (values != null && + db.addPlace(values) >= 0) { + c++; + } + } + db.close(); + report.append(context.getString(R.string.restorebackup_dialog_report_format1, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_PLACEITEMS), c+"")); + report.append("\n"); + return c; + } + + /** + * importActions + */ + protected static boolean importActions(Context context, StringBuilder report, @Nullable ContentValues... contentValues) + { + if (contentValues != null) + { + int c = 0; + for (ContentValues values : contentValues) { + if (WidgetActions.saveActionLaunchPref(context, values, 0)) { + c++; + } + } + report.append(context.getString(R.string.restorebackup_dialog_report_format1, SuntimesBackupTask.displayStringForBackupKey(context, SuntimesBackupTask.KEY_ACTIONS), c+"")); + report.append("\n"); + return true; + } + return false; } /** * importWidgetSettings */ - public static int importWidgetSettings(Context context, String prefix, boolean includeMetadata, StringBuilder report, @Nullable ContentValues... contentValues) + protected static int importWidgetSettings(Context context, String prefix, boolean includeMetadata, StringBuilder report, @Nullable ContentValues... contentValues) { if (contentValues != null) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java index 616ec6512..8027a433c 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -20,9 +20,12 @@ import android.appwidget.AppWidgetManager; import android.content.ComponentName; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; +import android.database.Cursor; +import android.database.DatabaseUtils; import android.net.Uri; import android.preference.PreferenceManager; import android.support.annotation.NonNull; @@ -30,19 +33,31 @@ import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.util.Log; +import android.util.Pair; import android.view.View; import com.forrestguice.suntimeswidget.ExportTask; import com.forrestguice.suntimeswidget.R; import com.forrestguice.suntimeswidget.SuntimesUtils; import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItem; +import com.forrestguice.suntimeswidget.alarmclock.AlarmClockItemExportTask; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmEventProvider; +import com.forrestguice.suntimeswidget.events.EventExportTask; +import com.forrestguice.suntimeswidget.events.EventSettings; +import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; import java.io.BufferedOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -57,12 +72,10 @@ public class SuntimesBackupTask extends WidgetSettingsExportTask public static final String KEY_ALARMITEMS = "AlarmItems"; public static final String KEY_EVENTITEMS = "EventItems"; public static final String KEY_PLACEITEMS = "PlaceItems"; + public static final String KEY_ACTIONS = "Actions"; public static final String[] ALL_KEYS = new String[] { - KEY_APPSETTINGS, KEY_WIDGETSETTINGS - //, KEY_ALARMITEMS // TODO: implement - //, KEY_EVENTITEMS // TODO: implement - //, KEY_PLACEITEMS // TODO: implement + KEY_APPSETTINGS, KEY_WIDGETSETTINGS, KEY_ALARMITEMS, KEY_EVENTITEMS, KEY_PLACEITEMS, KEY_ACTIONS }; public static final String DEF_EXPORT_TARGET = "SuntimesBackup"; @@ -119,7 +132,8 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce out.write(",\n".getBytes()); } out.write(("\"" + KEY_APPSETTINGS + "\": ").getBytes()); // include AppSettings - writeAppSettingsJSONObject(context, out); + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(context); + writeAppSettingsJSONObject(context, appPrefs, out); c++; } @@ -129,7 +143,8 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce out.write(",\n".getBytes()); } out.write(("\"" + KEY_WIDGETSETTINGS + "\": ").getBytes()); // include WidgetSettings - writeWidgetSettingsJSONArray(context, out); + SharedPreferences widgetPrefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); + writeWidgetSettingsJSONArray(context, widgetPrefs, getAllWidgetIds(context), out); c++; } @@ -139,7 +154,41 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce out.write(",\n".getBytes()); } out.write(("\"" + KEY_ALARMITEMS + "\": ").getBytes()); // include AlarmItems - writeAlarmItemsJSONArray(context, out); + AlarmDatabaseAdapter alarmDb = new AlarmDatabaseAdapter(context); + writeAlarmItemsJSONArray(context, alarmDb, out); + c++; + } + + if (includedKeys.containsKey(KEY_EVENTITEMS) && includedKeys.get(KEY_EVENTITEMS)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_EVENTITEMS + "\": ").getBytes()); // include EventItems + List events = EventSettings.loadEvents(context, AlarmEventProvider.EventType.SUN_ELEVATION); + EventExportTask.writeEventItemsJSONArray(context, events.toArray(new EventSettings.EventAlias[0]), out); + c++; + } + + if (includedKeys.containsKey(KEY_PLACEITEMS) && includedKeys.get(KEY_PLACEITEMS)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_PLACEITEMS + "\": ").getBytes()); // include PlacesItems + GetFixDatabaseAdapter placesDb = new GetFixDatabaseAdapter(context); + writePlaceItemsJSONArray(context, placesDb, out); + c++; + } + + if (includedKeys.containsKey(KEY_ACTIONS) && includedKeys.get(KEY_ACTIONS)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_ACTIONS + "\": ").getBytes()); // include ActionItems + String[] actions = WidgetActions.loadActionLaunchList(context, 0).toArray(new String[0]); + writeActionsJSONArray(context, actions, out); c++; } @@ -152,9 +201,8 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce * writes * { ContentValues } */ - protected void writeAppSettingsJSONObject(Context context, BufferedOutputStream out) throws IOException + public static void writeAppSettingsJSONObject(Context context, SharedPreferences appPrefs, BufferedOutputStream out) throws IOException { - SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(context); // AppSettings are stored in default shared prefs String json = WidgetSettingsImportTask.ContentValuesJson.toJson(toContentValues(appPrefs)); out.write(json.getBytes()); out.flush(); @@ -162,14 +210,90 @@ protected void writeAppSettingsJSONObject(Context context, BufferedOutputStream /** * writes - * [{ AlarmClockItem}, ...] + * { ContentValues } + */ + public static void writeActionsJSONArray(Context context, String[] actions, BufferedOutputStream out) throws IOException + { + out.write("[".getBytes()); + + for (int i=0; i values = new ArrayList<>(); + db.open(); + Cursor cursor = db.getAllPlaces(0, true); + while (!cursor.isAfterLast()) + { + ContentValues placeValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, placeValues); + values.add(placeValues); + cursor.moveToNext(); + } + db.close(); + writeContentValuesJSONArray(context, values.toArray(new ContentValues[0]), out); + } + + /** + * writes + * [{ ContentValues }, ...] */ - protected void writeAlarmItemsJSONArray(Context context, BufferedOutputStream out) throws IOException + public static void writeContentValuesJSONArray(Context context, ContentValues[] items, BufferedOutputStream out) throws IOException { - out.write("{}".getBytes()); // TODO: write alarm items + out.write("[".getBytes()); + for (int i=0; i items = new ArrayList<>(); + db.open(); + Cursor cursor = db.getAllAlarms(0, true); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + ContentValues entryValues = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(cursor, entryValues); + items.add(new AlarmClockItem(context, entryValues)); + cursor.moveToNext(); + } + db.close(); + AlarmClockItemExportTask.writeAlarmItemsJSONArray(context, items.toArray(new AlarmClockItem[0]), out); + } + + /** + * exportSettings to uri + * Displays an AlertDialog (chooser), then creates and starts a SuntimesBackupTask. + */ public static void exportSettings(final Context context, @Nullable final Uri uri, final ExportTask.TaskListener exportListener) { Log.i("ExportSettings", "Starting export task: " + uri); @@ -235,6 +359,9 @@ public static CharSequence displayStringForBackupKey(Context context, String key if (SuntimesBackupTask.KEY_PLACEITEMS.equals(key)) { return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_placeitems)); } + if (SuntimesBackupTask.KEY_ACTIONS.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_actions)); + } return key; } @@ -257,28 +384,48 @@ public static void chooseBackupContent(final Context context, Set keys, } public static void chooseBackupContent(final Context context, final String[] keys, boolean isImport, @NonNull final ChooseBackupDialogListener onClickListener) { - final CharSequence[] items = new CharSequence[keys.length]; + final ArrayList> items = new ArrayList<>(); final boolean[] checked = new boolean[keys.length]; - for (int i=0; i(i, SuntimesBackupTask.displayStringForBackupKey(context, keys[i]))); + } + items.sort(new Comparator>() + { + @Override + public int compare(Pair o1, Pair o2) + { + if (o1 == null) { + return -1; + } else if (o2 == null) { + return 1; + } else return o1.second.toString().compareTo(o2.second.toString()); + } + }); + + CharSequence[] displayStrings = new CharSequence[items.size()]; + for (int i=0; i appWidgetIds, BufferedOutputStream out) throws IOException { - SharedPreferences widgetPrefs = context.getSharedPreferences(WidgetSettings.PREFS_WIDGET, 0); int n = appWidgetIds.size(); out.write("[".getBytes()); // writes a json array for (int i=0; i prefTypes, ContentValues values, boolean hasPrefix, @Nullable String toPrefix, @Nullable Long appWidgetId, String tag) + { Set> entries = values.valueSet(); for (Map.Entry entry : entries) { String key = entry.getKey(); Object value = entry.getValue(); if (value == null) { - Log.w("WidgetSettings", "import: skipping " + key + "... contains null value"); + Log.w(tag, "import: skipping " + key + "... contains null value"); continue; } - String[] keyParts = key.split("_"); - if (toPrefix != null) { - keyParts[0] = toPrefix.endsWith("_") ? toPrefix.substring(0, toPrefix.length() - 1) : toPrefix; - } - if (appWidgetId != null) { - keyParts[1] = appWidgetId + ""; - } + String k = key; + String k0 = k; + if (hasPrefix) + { + String[] keyParts = key.split("_"); + if (toPrefix != null) { + keyParts[0] = toPrefix.endsWith("_") ? toPrefix.substring(0, toPrefix.length() - 1) : toPrefix; + } + if (appWidgetId != null) { + keyParts[1] = appWidgetId + ""; + } - String k = TextUtils.join("_", keyParts); // replacement key - String k0 = k.replaceFirst(keyParts[0] + "_" + keyParts[1], ""); + k = TextUtils.join("_", keyParts); // full replacement key + k0 = k.replaceFirst(keyParts[0] + "_" + keyParts[1], ""); // replacement key w/out prefix + } if (prefTypes.containsKey(k0)) { @@ -550,39 +559,39 @@ public static void importValues(SharedPreferences.Editor prefs, ContentValues va String s = (String) value; if (s.toLowerCase().equals("true") || s.toLowerCase().equals("false")) { importValue(prefs, Boolean.class, k, Boolean.parseBoolean(s)); - } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + s + " (String)"); - } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } else Log.w(tag, "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + s + " (String)"); + } else Log.w(tag, "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } else if (expectedType.equals(Integer.class)) { if (valueType.equals(String.class)) { // int as String try { importValue(prefs, Integer.class, k, Integer.parseInt((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "import: skipping " + k + "... " + e); + Log.w(tag, "import: skipping " + k + "... " + e); } - } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } else Log.w(tag, "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } else if (expectedType.equals(Long.class)) { if (valueType.equals(String.class)) { // long as String try { importValue(prefs, Long.class, k, Long.parseLong((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "import: skipping " + k + "... " + e); + Log.w(tag, "import: skipping " + k + "... " + e); } - } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } else Log.w(tag, "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } else if (expectedType.equals(Double.class)) { if (valueType.equals(String.class)) { // double as String try { importValue(prefs, Double.class, k, Double.parseDouble((String) value)); } catch (NumberFormatException e) { - Log.w("WidgetSettings", "import: skipping " + k + "... " + e); + Log.w(tag, "import: skipping " + k + "... " + e); } - } else Log.w("WidgetSettings", "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); + } else Log.w(tag, "import: skipping " + k + "... expected " + expectedType.getSimpleName() + ", found " + valueType.getSimpleName()); } } } else { - Log.w("WidgetSettings", "import: skipping " + k0 + "... unrecognized key"); + Log.w(tag, "import: skipping " + k0 + "... unrecognized key"); } } prefs.apply(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 256220043..79a0462a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1194,11 +1194,13 @@ Create Backup Restore Backup Alarms - App Configuration + App Settings Widget Settings Places - Custom Events + Events + Actions %1$s + %1$s (%2$s) @string/configAction_export @string/configAction_import From e780a87d5ecbaa92f0d37956be66a4bd51bf2816 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 25 Jan 2024 22:33:56 -0700 Subject: [PATCH 57/69] findAppWidgetIdFromFirstKey --- .../suntimeswidget/settings/WidgetSettingsImportTask.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index ec9a812eb..e316ab9c5 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -597,6 +597,7 @@ public static void importValues(SharedPreferences.Editor prefs, Map> entries = values.valueSet(); @@ -606,8 +607,10 @@ public static Long findAppWidgetIdFromFirstKey(ContentValues values) { try { String key = entry.getKey(); - String[] parts = key.split("_"); - return Long.parseLong(parts[1]); + String[] parts = ((key != null) ? key.split("_") : new String[0]); + if (parts.length > 2) { + return Long.parseLong(parts[1]); + } } catch (NumberFormatException | NullPointerException e) { Log.w("WidgetSettings", "failed to find widget id from keys.. " + e); From 0e086481f3221813fe203a6b4e5a9f73590c40ae Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 25 Jan 2024 22:38:19 -0700 Subject: [PATCH 58/69] SuntimesBackupTask backup files contain filetype and version as first two keys --- .../settings/SuntimesBackupRestoreTask.java | 52 ++++++++++++++++++- .../settings/SuntimesBackupTask.java | 22 ++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index 2be187832..2244fb345 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -42,6 +42,7 @@ import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -139,8 +140,13 @@ protected void readData(Context context, InputStream in, Map= 11) { + BufferedInputStream bufferedIn = new BufferedInputStream(in); + if (!containsBackupItem(bufferedIn)) { + Log.w(TAG, "This does not look like a valid backup file; trying to load it anyway..."); + } + //noinspection CharsetObjectCanBeUsed - JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + JsonReader reader = new JsonReader(new InputStreamReader(bufferedIn, "UTF-8")); reader.setLenient(true); try { readBackupItem(context, reader, data); @@ -149,8 +155,9 @@ protected void readData(Context context, InputStream in, Map includedKeys = new HashMap<>(); + @Override + public boolean export( Context context, BufferedOutputStream out ) throws IOException + { + writeBackupJSONObject(context, out); + return true; + } + /** * writes * { + * "Type": "SuntimesBackup" + * "Version": "107" * "AppSettings": { ContentValues } * "WidgetSettings": [{ ContentValues }, ...] * "AlarmItems": [{ AlarmClockItem }, ...] * } */ - @Override - public boolean export( Context context, BufferedOutputStream out ) throws IOException + protected void writeBackupJSONObject( Context context, BufferedOutputStream out ) throws IOException { out.write("{".getBytes()); - int c = 0; // keys written + out.write(("\"" + KEY_CLASS + "\": \"" + KEY_BACKUPFILE + "\",\n").getBytes()); // declare type (expected to be the first item) + out.write(("\"" + KEY_VERSION + "\": " + BuildConfig.VERSION_CODE).getBytes()); // declare version (expected to be the second item) + int c = 2; // keys written if (includedKeys.containsKey(KEY_APPSETTINGS) && includedKeys.get(KEY_APPSETTINGS)) { @@ -194,7 +209,6 @@ public boolean export( Context context, BufferedOutputStream out ) throws IOExce out.write("}".getBytes()); out.flush(); - return true; } /** From 77b5e851aa6442794bd925d8455c1f8ed0a27a9d Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Thu, 25 Jan 2024 23:01:19 -0700 Subject: [PATCH 59/69] readData --- .../settings/SuntimesBackupRestoreTask.java | 2 +- .../settings/WidgetSettingsImportTask.java | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index 2244fb345..033e26947 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -163,7 +163,7 @@ protected void readData(Context context, InputStream in, Map data) throws IOException + public static void readBackupItem(Context context, JsonReader reader, Map data) throws IOException { if (reader.peek() == JsonToken.BEGIN_OBJECT) { diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index e316ab9c5..54992c4e2 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -41,12 +41,14 @@ import org.json.JSONObject; +import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -130,8 +132,30 @@ protected TaskResult doInBackground(Uri... params) return new TaskResult(result, uri, (items != null ? items.toArray(new ContentValues[0]) : null), error); } - protected void readData(Context context, InputStream in, ArrayList items) throws IOException { - ContentValuesJson.readItems(context, in, items); + protected void readData(Context context, InputStream in, ArrayList items) throws IOException + { + BufferedInputStream bufferedIn = new BufferedInputStream(in); + if (SuntimesBackupRestoreTask.containsBackupItem(bufferedIn)) { + readItemsFromBackup(context, bufferedIn, items); + + } else { + ContentValuesJson.readItems(context, bufferedIn, items); + } + } + + protected static void readItemsFromBackup(Context context, BufferedInputStream in, ArrayList items) throws IOException + { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + reader.setLenient(true); + try { + Map data = new HashMap<>(); + SuntimesBackupRestoreTask.readBackupItem(context, reader, data); + items.addAll(Arrays.asList(data.get(SuntimesBackupTask.KEY_WIDGETSETTINGS))); + + } finally { + reader.close(); + in.close(); + } } @Override From 2a6ed7b8edfa0b552875b06ae00f7d02fc9e9538 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 27 Jan 2024 14:52:40 -0700 Subject: [PATCH 60/69] SuntimesBackupRestoreTask refactor; split into BackupLoadTask and BackupRestoreTask --- .../settings/SuntimesBackupLoadTask.java | 329 ++++++++++++++++ .../settings/SuntimesBackupRestoreTask.java | 354 ++++++++---------- 2 files changed, 487 insertions(+), 196 deletions(-) create mode 100644 app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java new file mode 100644 index 000000000..09275df78 --- /dev/null +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java @@ -0,0 +1,329 @@ +/** + Copyright (C) 2024 Forrest Guice + This file is part of SuntimesWidget. + + SuntimesWidget is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SuntimesWidget is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SuntimesWidget. If not, see . +*/ + +package com.forrestguice.suntimeswidget.settings; + +import android.annotation.TargetApi; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.AlertDialog; +import android.util.JsonReader; +import android.util.JsonToken; +import android.util.Log; +import android.view.View; + +import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; +import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; +import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; +import com.forrestguice.suntimeswidget.events.EventSettings; +import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; +import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.ContentValuesJson; +import com.forrestguice.suntimeswidget.tiles.ClockTileService; +import com.forrestguice.suntimeswidget.tiles.NextEventTileService; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_DIRECTIMPORT; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_MAKEBESTGUESS; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_RESTOREBACKUP; + +public class SuntimesBackupLoadTask extends AsyncTask +{ + public static final String TAG = "RestoreBackup"; + public static final long MIN_WAIT_TIME = 2000; + + protected final WeakReference contextRef; + + protected boolean isPaused = false; + public void pauseTask() { + isPaused = true; + } + public void resumeTask() { + isPaused = false; + } + public boolean isPaused() { + return isPaused; + } + + public SuntimesBackupLoadTask(Context context) { + contextRef = new WeakReference<>(context); + } + + @Override + protected void onPreExecute() + { + Log.d(getClass().getSimpleName(), "onPreExecute"); + if (taskListener != null) { + taskListener.onStarted(); + } + } + + @Override + protected TaskResult doInBackground(Uri... params) + { + Log.d(TAG, "doInBackground: starting"); + Uri uri = null; + if (params.length > 0) { + uri = params[0]; + } + + long startTime = System.currentTimeMillis(); + boolean result = false; + Map data = new HashMap<>(); + Exception error = null; + + Context context = contextRef.get(); + if (context != null && uri != null) + { + try { + InputStream in = context.getContentResolver().openInputStream(uri); + if (in != null) + { + Log.d(TAG, "doInBackground: reading"); + readData(context, in, data); + result = true; + error = null; + + } else { + Log.e(TAG, "Failed to import from " + uri + ": null input stream!"); + result = false; + error = null; + } + } catch (IOException e) { + Log.e(TAG, "Failed to import from " + uri + ": " + e); + result = false; + data = null; + error = e; + } + } + + Log.d(TAG, "doInBackground: waiting"); + long endTime = System.currentTimeMillis(); + while ((endTime - startTime) < MIN_WAIT_TIME || isPaused) { + endTime = System.currentTimeMillis(); + } + + Log.d(TAG, "doInBackground: finishing"); + return new TaskResult(result, uri, data, error); + } + + protected void readData(Context context, InputStream in, Map data) throws IOException + { + if (Build.VERSION.SDK_INT >= 11) + { + BufferedInputStream bufferedIn = new BufferedInputStream(in); + if (!containsBackupItem(bufferedIn)) { + Log.w(TAG, "This does not look like a valid backup file; trying to load it anyway..."); + } + + //noinspection CharsetObjectCanBeUsed + JsonReader reader = new JsonReader(new InputStreamReader(bufferedIn, "UTF-8")); + reader.setLenient(true); + try { + readBackupItem(context, reader, data); + + } finally { + reader.close(); + in.close(); + } + + } else { + Log.w(TAG, "Unsupported; skipping import"); + in.close(); + } + } + + @TargetApi(11) + public static void readBackupItem(Context context, JsonReader reader, Map data) throws IOException + { + if (reader.peek() == JsonToken.BEGIN_OBJECT) + { + reader.beginObject(); + while (reader.hasNext()) + { + String key = reader.nextName(); + if (reader.hasNext()) + { + switch (reader.peek()) + { + case BEGIN_ARRAY: + case BEGIN_OBJECT: + ArrayList items = new ArrayList<>(); + ContentValuesJson.readItems(context, reader, items); + data.put(key, items.toArray(new ContentValues[0])); + break; + + default: + reader.skipValue(); + break; + } + } + } + reader.endObject(); + + } else { + ContentValuesJson.skipJsonItem(reader); + } + } + + /** + * @return true if beginning of stream indicates it contains a backup json object; marks/resets the stream + */ + @TargetApi(11) + public static boolean containsBackupItem(BufferedInputStream in) throws IOException + { + in.mark(Integer.MAX_VALUE); // mark starting position + JsonReader reader = new JsonReader(new InputStreamReader(in)); + reader.setLenient(true); + + boolean retValue = false; + if (reader.peek() == JsonToken.BEGIN_OBJECT) + { + reader.beginObject(); + if (reader.peek() == JsonToken.NAME) + { + String key = reader.nextName(); + if (SuntimesBackupTask.KEY_CLASS.equals(key)) + { + if (reader.peek() == JsonToken.STRING) + { + String type = reader.nextString(); + retValue = (SuntimesBackupTask.KEY_BACKUPFILE.equals(type)); + + if (!retValue) { + Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be " + SuntimesBackupTask.KEY_BACKUPFILE + " (found " + type + ")"); + } + } else { + Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " expects a String (found " + reader.peek() + ")"); + } + } else { + Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be first item but it is missing!"); + } + } else { + Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be first item but it is missing!"); + } + } + in.reset(); // reset to starting mark + return retValue; + } + + @Override + protected void onProgressUpdate(Void... progressItems) { + super.onProgressUpdate(progressItems); + } + + @Override + protected void onPostExecute( TaskResult result ) + { + Log.d(TAG, "onPostExecute: " + result.getResult()); + if (taskListener != null) { + taskListener.onFinished(result); + } + } + + /** + * TaskResult + */ + public static class TaskResult + { + public TaskResult(boolean result, Uri uri, @Nullable Map items, Exception e) + { + this.result = result; + this.items = items; + this.uri = uri; + this.e = e; + } + + private final boolean result; + public boolean getResult() { + return result; + } + + private final Map items; + public Map getItems() { + return items; + } + + private final Uri uri; + public Uri getUri() { + return uri; + } + + public int numResults() { + return (items != null ? items.size() : 0); + } + + private final Exception e; + public Exception getException() { + return e; + } + } + + /** + * TaskListener + */ + public static abstract class TaskListener + { + public void onStarted() {} + public void onFinished( TaskResult result ) {} + } + protected TaskListener taskListener = null; + public void setTaskListener( TaskListener listener ) { + taskListener = listener; + } + public void clearTaskListener() { + taskListener = null; + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + /** + /** + * showIOResultSnackbar + */ + public static void showIOResultSnackbar(@Nullable final Context context, View view, boolean result, int numResults, @Nullable CharSequence report) + { + if (context == null) { + return; + } + //Toast.makeText(context, context.getString(R.string.msg_import_success, context.getString(R.string.configAction_settings)), Toast.LENGTH_SHORT).show(); + //Toast.makeText(context, context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file)), Toast.LENGTH_SHORT).show(); + CharSequence message = (result ? context.getString(R.string.msg_import_success, context.getResources().getQuantityString(R.plurals.itemsPlural, numResults, numResults)) + : context.getString(R.string.msg_import_failure, context.getString(R.string.msg_import_label_file))); + SuntimesBackupTask.showIOResultSnackbar(context, view, null, result, message, report); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index 033e26947..42e410c51 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -18,222 +18,111 @@ package com.forrestguice.suntimeswidget.settings; -import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; +import android.content.DialogInterface; import android.content.SharedPreferences; -import android.net.Uri; import android.os.AsyncTask; -import android.os.Build; import android.preference.PreferenceManager; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.JsonReader; -import android.util.JsonToken; +import android.support.v7.app.AlertDialog; import android.util.Log; -import android.view.View; import com.forrestguice.suntimeswidget.R; +import com.forrestguice.suntimeswidget.SuntimesUtils; import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; import com.forrestguice.suntimeswidget.events.EventSettings; import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; -import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.ContentValuesJson; import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.TreeSet; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_DIRECTIMPORT; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_MAKEBESTGUESS; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_RESTOREBACKUP; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_DIRECTIMPORT; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_MAKEBESTGUESS; +import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_RESTOREBACKUP; -public class SuntimesBackupRestoreTask extends AsyncTask +public class SuntimesBackupRestoreTask extends AsyncTask { public static final String TAG = "RestoreBackup"; - public static final long MIN_WAIT_TIME = 2000; protected final WeakReference contextRef; - - protected boolean isPaused = false; - public void pauseTask() { - isPaused = true; + public SuntimesBackupRestoreTask(Context context) { + contextRef = new WeakReference<>(context); } - public void resumeTask() { - isPaused = false; + + protected Map data = new HashMap<>(); // all backup data + protected Set keys = new TreeSet<>(); // keys to restore + + public void setData(Map d) { + data = d; } - public boolean isPaused() { - return isPaused; + public void setKeys(Set included) { + keys = included; } - public SuntimesBackupRestoreTask(Context context) { - contextRef = new WeakReference<>(context); + protected Map methods = new HashMap<>(); + public void setMethod(String key, int method) { + methods.put(key, method); + } + public void setMethods(Map values) { + methods = values; } @Override protected void onPreExecute() { - Log.d(getClass().getSimpleName(), "onPreExecute"); + //Log.d(TAG, "onPreExecute"); if (taskListener != null) { taskListener.onStarted(); } } @Override - protected TaskResult doInBackground(Uri... params) + protected TaskResult doInBackground(Void... params) { Log.d(TAG, "doInBackground: starting"); - Uri uri = null; - if (params.length > 0) { - uri = params[0]; - } - long startTime = System.currentTimeMillis(); + + int c = 0; boolean result = false; - Map data = new HashMap<>(); Exception error = null; + StringBuilder report = new StringBuilder(); Context context = contextRef.get(); - if (context != null && uri != null) + if (context != null && data != null && keys != null) { try { - InputStream in = context.getContentResolver().openInputStream(uri); - if (in != null) - { - Log.d(TAG, "doInBackground: reading"); - readData(context, in, data); - result = true; - error = null; - - } else { - Log.e(TAG, "Failed to import from " + uri + ": null input stream!"); - result = false; - error = null; - } - } catch (IOException e) { - Log.e(TAG, "Failed to import from " + uri + ": " + e); + c = importSettings(context, keys, methods, report, data); + result = true; + error = null; + + } catch (Exception e) { + Log.e(TAG, "Failed to restore backup: " + e); result = false; - data = null; error = e; } + } else { + result = false; + error = null; } Log.d(TAG, "doInBackground: waiting"); long endTime = System.currentTimeMillis(); - while ((endTime - startTime) < MIN_WAIT_TIME || isPaused) { + while ((endTime - startTime) < SuntimesBackupLoadTask.MIN_WAIT_TIME) { endTime = System.currentTimeMillis(); } Log.d(TAG, "doInBackground: finishing"); - return new TaskResult(result, uri, data, error); - } - - protected void readData(Context context, InputStream in, Map data) throws IOException - { - if (Build.VERSION.SDK_INT >= 11) - { - BufferedInputStream bufferedIn = new BufferedInputStream(in); - if (!containsBackupItem(bufferedIn)) { - Log.w(TAG, "This does not look like a valid backup file; trying to load it anyway..."); - } - - //noinspection CharsetObjectCanBeUsed - JsonReader reader = new JsonReader(new InputStreamReader(bufferedIn, "UTF-8")); - reader.setLenient(true); - try { - readBackupItem(context, reader, data); - - } finally { - reader.close(); - in.close(); - } - - } else { - Log.w(TAG, "Unsupported; skipping import"); - in.close(); - } - } - - @TargetApi(11) - public static void readBackupItem(Context context, JsonReader reader, Map data) throws IOException - { - if (reader.peek() == JsonToken.BEGIN_OBJECT) - { - reader.beginObject(); - while (reader.hasNext()) - { - String key = reader.nextName(); - if (reader.hasNext()) - { - switch (reader.peek()) - { - case BEGIN_ARRAY: - case BEGIN_OBJECT: - ArrayList items = new ArrayList<>(); - ContentValuesJson.readItems(context, reader, items); - data.put(key, items.toArray(new ContentValues[0])); - break; - - default: - reader.skipValue(); - break; - } - } - } - reader.endObject(); - - } else { - ContentValuesJson.skipJsonItem(reader); - } - } - - /** - * @return true if beginning of stream indicates it contains a backup json object; marks/resets the stream - */ - @TargetApi(11) - public static boolean containsBackupItem(BufferedInputStream in) throws IOException - { - in.mark(Integer.MAX_VALUE); // mark starting position - JsonReader reader = new JsonReader(new InputStreamReader(in)); - reader.setLenient(true); - - boolean retValue = false; - if (reader.peek() == JsonToken.BEGIN_OBJECT) - { - reader.beginObject(); - if (reader.peek() == JsonToken.NAME) - { - String key = reader.nextName(); - if (SuntimesBackupTask.KEY_CLASS.equals(key)) - { - if (reader.peek() == JsonToken.STRING) - { - String type = reader.nextString(); - retValue = (SuntimesBackupTask.KEY_BACKUPFILE.equals(type)); - - if (!retValue) { - Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be " + SuntimesBackupTask.KEY_BACKUPFILE + " (found " + type + ")"); - } - } else { - Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " expects a String (found " + reader.peek() + ")"); - } - } else { - Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be first item but it is missing!"); - } - } else { - Log.w(TAG, "containsBackupItem: " + SuntimesBackupTask.KEY_CLASS + " should be first item but it is missing!"); - } - } - in.reset(); // reset to starting mark - return retValue; + return new TaskResult(result, report.toString(), c, error); } @Override @@ -244,7 +133,7 @@ protected void onProgressUpdate(Void... progressItems) { @Override protected void onPostExecute( TaskResult result ) { - Log.d(TAG, "onPostExecute: " + result.getResult()); + //Log.d(TAG, "onPostExecute: " + result.getResult()); if (taskListener != null) { taskListener.onFinished(result); } @@ -255,11 +144,11 @@ protected void onPostExecute( TaskResult result ) */ public static class TaskResult { - public TaskResult(boolean result, Uri uri, @Nullable Map items, Exception e) + public TaskResult(boolean result, String report, int numResults, Exception e) { this.result = result; - this.items = items; - this.uri = uri; + this.report = report; + this.numResults = numResults; this.e = e; } @@ -268,18 +157,14 @@ public boolean getResult() { return result; } - private final Map items; - public Map getItems() { - return items; - } - - private final Uri uri; - public Uri getUri() { - return uri; + private final String report; + public String getReport() { + return report; } - public int numResults() { - return (items != null ? items.size() : 0); + private final int numResults; + public int getNumResults() { + return numResults; } private final Exception e; @@ -310,33 +195,36 @@ public void clearTaskListener() { /** * @param context Context context * @param keys the set of backup keys within allValues that should be restored - * @param method 2:directImport (copy widget ids as-is), 1:bestGuess (reassign widget ids (best guess)), 0:restoreBackup (import as backup for later (when the launcher initiates restoration)) - * @param allValues a map containing backupKey:ContentValue[]; e.g. "AppSettings":ContentValues[], "WidgetSettings":ContentValues[], ... - * @return number of items imported + * @param methods 2:directImport (copy widget ids as-is), 1:bestGuess (reassign widget ids (best guess)), 0:restoreBackup (import as backup for later (when the launcher initiates restoration)) + * @param allValues a map containing backupKey:ContentValue[]; e.g. "AppSettings":ContentValues[], "WidgetSettings":ContentValues[], ... + * @return number of items imported */ - public static int importSettings(Context context, int method, Set keys, StringBuilder report, Map allValues) + public static int importSettings(Context context, Set keys, Map methods, StringBuilder report, Map allValues) { int c = 0; if (keys.contains(SuntimesBackupTask.KEY_APPSETTINGS)) { - c += (SuntimesBackupRestoreTask.importAppSettings(context, report, allValues.get(SuntimesBackupTask.KEY_APPSETTINGS)) ? 1 : 0); + c += (importAppSettings(context, report, allValues.get(SuntimesBackupTask.KEY_APPSETTINGS)) ? 1 : 0); } if (keys.contains(SuntimesBackupTask.KEY_WIDGETSETTINGS)) { + int method = (methods.containsKey(SuntimesBackupTask.KEY_WIDGETSETTINGS)) + ? methods.get(SuntimesBackupTask.KEY_WIDGETSETTINGS) : IMPORT_WIDGETS_METHOD_RESTOREBACKUP; + switch (method) { - case IMPORT_METHOD_DIRECTIMPORT: // direct import - c += SuntimesBackupRestoreTask.importWidgetSettings(context, null, false, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + case IMPORT_WIDGETS_METHOD_DIRECTIMPORT: // direct import + c += importWidgetSettings(context, null, false, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); break; - case IMPORT_METHOD_MAKEBESTGUESS: // best guess - c += SuntimesBackupRestoreTask.importWidgetSettingsBestGuess(context, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + case IMPORT_WIDGETS_METHOD_MAKEBESTGUESS: // best guess + c += importWidgetSettingsBestGuess(context, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); break; - case IMPORT_METHOD_RESTOREBACKUP: + case IMPORT_WIDGETS_METHOD_RESTOREBACKUP: default: // backup import (writes to backup prefix, individual widgets restore themselves later when triggered) - c += SuntimesBackupRestoreTask.importWidgetSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, true, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + c += importWidgetSettings(context, WidgetSettingsMetadata.BACKUP_PREFIX_KEY, true, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); WidgetSettingsImportTask.restoreFromBackup(context, new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}, // these lines should be the same new int[] {0, ClockTileService.CLOCKTILE_APPWIDGET_ID, NextEventTileService.NEXTEVENTTILE_APPWIDGET_ID}); // because the ids are unchanged @@ -345,19 +233,19 @@ public static int importSettings(Context context, int method, Set keys, } if (keys.contains(SuntimesBackupTask.KEY_ALARMITEMS)) { - c += SuntimesBackupRestoreTask.importAlarmItems(context, report, allValues.get(SuntimesBackupTask.KEY_ALARMITEMS)); + c += importAlarmItems(context, report, allValues.get(SuntimesBackupTask.KEY_ALARMITEMS)); } if (keys.contains(SuntimesBackupTask.KEY_EVENTITEMS)) { - c += SuntimesBackupRestoreTask.importEventItems(context, report, allValues.get(SuntimesBackupTask.KEY_EVENTITEMS)); + c += importEventItems(context, report, allValues.get(SuntimesBackupTask.KEY_EVENTITEMS)); } if (keys.contains(SuntimesBackupTask.KEY_PLACEITEMS)) { - c += SuntimesBackupRestoreTask.importPlaceItems(context, report, allValues.get(SuntimesBackupTask.KEY_PLACEITEMS)); + c += importPlaceItems(context, report, allValues.get(SuntimesBackupTask.KEY_PLACEITEMS)); } if (keys.contains(SuntimesBackupTask.KEY_ACTIONS)) { - c += (SuntimesBackupRestoreTask.importActions(context, report, allValues.get(SuntimesBackupTask.KEY_ACTIONS)) ? 1 : 0); + c += (importActions(context, report, allValues.get(SuntimesBackupTask.KEY_ACTIONS)) ? 1 : 0); } return c; @@ -395,9 +283,14 @@ protected static int importAlarmItems(Context context, StringBuilder report, @Nu int c = 0; AlarmDatabaseAdapter db = new AlarmDatabaseAdapter(context); db.open(); + // db.clearAlarms(); // TODO: alarm import options; "clear first" for (ContentValues values : contentValues) { - if (values != null) { + if (values != null) + { + if (values.containsKey(AlarmDatabaseAdapter.KEY_ROWID)) { + values.remove(AlarmDatabaseAdapter.KEY_ROWID); // clear rowID (insert as new items) + } db.addAlarm(values); c++; } @@ -434,10 +327,17 @@ protected static int importPlaceItems(Context context, StringBuilder report, @Nu int c = 0; GetFixDatabaseAdapter db = new GetFixDatabaseAdapter(context); db.open(); - for (ContentValues values : contentValues) { - if (values != null && - db.addPlace(values) >= 0) { - c++; + // db.clearPlaces(); // TODO: place import options; "clear first" + for (ContentValues values : contentValues) + { + if (values != null) + { + if (values.containsKey(GetFixDatabaseAdapter.KEY_ROWID)) { + values.remove(GetFixDatabaseAdapter.KEY_ROWID); // clear rowID (insert as new items) + } + if (db.addPlace(values) >= 0) { + c++; + } } } db.close(); @@ -552,16 +452,78 @@ public static int importWidgetSettingsBestGuess(Context context, StringBuilder r } } - /** - * showIOResultSnackbar - */ - public static void showIOResultSnackbar(final Context context, View view, boolean result, int numResults, @Nullable CharSequence report) + //////////////////////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////////////////////// + + public static final int IMPORT_WIDGETS_METHOD_RESTOREBACKUP = WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_RESTOREBACKUP; // 0 + public static final int IMPORT_WIDGETS_METHOD_MAKEBESTGUESS = WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_MAKEBESTGUESS; // 1 + public static final int IMPORT_WIDGETS_METHOD_DIRECTIMPORT = WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_DIRECTIMPORT; // 2 + public static final int[] IMPORT_WIDGETS_METHODS = WidgetSettingsImportTask.IMPORT_WIDGETS_METHODS; + + public static final int IMPORT_PLACES_METHOD_CLEAR = 10; // clear all, then insert + public static final int IMPORT_PLACES_METHOD_ADDALL = 20; // insert all (may result in duplicates) + public static final int IMPORT_PLACES_METHOD_IGNORE = 30; // insert values (ignore if existing) + public static final int IMPORT_PLACES_METHOD_OVERWRITE = 40; // insert values (update if existing) + public static final int[] IMPORT_PLACES_METHODS = new int[] { IMPORT_PLACES_METHOD_ADDALL, IMPORT_PLACES_METHOD_CLEAR, IMPORT_PLACES_METHOD_IGNORE, IMPORT_PLACES_METHOD_OVERWRITE }; + + public static final int IMPORT_ALARMS_METHOD_CLEAR = 100; // clear all, then insert + public static final int IMPORT_ALARMS_METHOD_ADDALL = 200; // insert all (may result in duplicates) + public static final int[] IMPORT_ALARMS_METHODS = new int[] { IMPORT_ALARMS_METHOD_ADDALL, IMPORT_ALARMS_METHOD_CLEAR }; + + public static void chooseImportMethod(final Context context, final String key, final int[] methods, @NonNull final DialogInterface.OnClickListener onClickListener) + { + final CharSequence[] items = new CharSequence[methods.length]; + for (int i=0; i Date: Sat, 27 Jan 2024 21:32:36 -0700 Subject: [PATCH 61/69] strings --- app/src/main/res/values/strings.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 79a0462a7..96d0410a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1217,6 +1217,14 @@ Direct Import. Imported settings will be applied directly. This only works correctly if the widget ids are unchanged.
]]>
%1$s, %2$s + Clear. Alarms will be cleared before importing from backup.
]]>
+ Keep Existing. Existing alarms will be preserved (may result in duplicate).
]]>
+ + Clear. Places will be cleared before importing from backup.
]]>
+ Keep Existing. Existing places will be preserved (may result in duplicates).
]]>
+ Ignore Existing. Places will be added if they do not already exist (ignores existing places).
]]>
+ Overwrite Existing. Places will be overwritten if they already exist.
]]>
+ %d item %d items From fa64e371d17558c879014189ae1a91847c4abf2e Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 27 Jan 2024 21:34:10 -0700 Subject: [PATCH 62/69] SuntimesBackupRestoreTask --- .../SuntimesWidgetListActivity.java | 99 +++++++---- .../settings/SuntimesBackupLoadTask.java | 18 -- .../settings/SuntimesBackupRestoreTask.java | 155 +++++++++++++----- .../settings/SuntimesBackupTask.java | 48 ++++-- .../settings/WidgetSettingsImportTask.java | 20 +-- 5 files changed, 231 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java index ef46f829b..5c1dbea57 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/SuntimesWidgetListActivity.java @@ -29,7 +29,6 @@ import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -43,12 +42,9 @@ import android.os.Build; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.design.widget.Snackbar; import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; @@ -74,16 +70,12 @@ import com.forrestguice.suntimeswidget.calculator.SuntimesMoonData; import com.forrestguice.suntimeswidget.calculator.SuntimesRiseSetData; import com.forrestguice.suntimeswidget.settings.AppSettings; +import com.forrestguice.suntimeswidget.settings.SuntimesBackupLoadTask; import com.forrestguice.suntimeswidget.settings.SuntimesBackupRestoreTask; import com.forrestguice.suntimeswidget.settings.SuntimesBackupTask; -import com.forrestguice.suntimeswidget.settings.WidgetSettings; import com.forrestguice.suntimeswidget.settings.WidgetSettingsExportTask; import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask; -import com.forrestguice.suntimeswidget.settings.WidgetSettingsMetadata; -import com.forrestguice.suntimeswidget.themes.ImportThemesTask; import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; -import com.forrestguice.suntimeswidget.tiles.ClockTileService; -import com.forrestguice.suntimeswidget.tiles.NextEventTileService; import com.forrestguice.suntimeswidget.widgets.DateWidget0; import java.io.File; @@ -96,9 +88,6 @@ import java.util.TreeSet; import static com.forrestguice.suntimeswidget.SuntimesConfigActivity0.EXTRA_RECONFIGURE; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_DIRECTIMPORT; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_MAKEBESTGUESS; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_METHOD_RESTOREBACKUP; public class SuntimesWidgetListActivity extends AppCompatActivity { @@ -424,7 +413,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) { //if (isAdded()) { String successMessage = context.getString(R.string.msg_export_success, path); - SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), true, successMessage, null); + SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), results.getExportUri(), true, successMessage, null); //} if (Build.VERSION.SDK_INT >= 19) { @@ -439,7 +428,7 @@ public void onFinished(WidgetSettingsExportTask.ExportResult results) //if (isAdded()) { String failureMessage = context.getString(R.string.msg_export_failure, path); - SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), false, failureMessage, null); + SuntimesBackupTask.showIOResultSnackbar(context, getWindow().getDecorView(), results.getExportUri(), false, failureMessage, null); //} } } @@ -458,8 +447,8 @@ public void importSettings(Context context) public void importSettings(final Context context, @NonNull Uri uri) { Log.i("ImportSettings", "Starting import task: " + uri); - SuntimesBackupRestoreTask task = new SuntimesBackupRestoreTask(context); - task.setTaskListener(new SuntimesBackupRestoreTask.TaskListener() + SuntimesBackupLoadTask task = new SuntimesBackupLoadTask(context); + task.setTaskListener(new SuntimesBackupLoadTask.TaskListener() { @Override public void onStarted() { @@ -467,7 +456,7 @@ public void onStarted() { } @Override - public void onFinished(final SuntimesBackupRestoreTask.TaskResult result) + public void onFinished(final SuntimesBackupLoadTask.TaskResult result) { dismissProgress(); if (result.getResult() && result.numResults() > 0) @@ -485,32 +474,86 @@ public void onClick(DialogInterface dialog, int which, String[] keys, boolean[] } } - if (includeKeys.contains(SuntimesBackupTask.KEY_WIDGETSETTINGS)) + final String[] keysThatWantMethods = new String[] { SuntimesBackupTask.KEY_WIDGETSETTINGS, SuntimesBackupTask.KEY_PLACEITEMS, SuntimesBackupTask.KEY_ALARMITEMS }; + final Map methodsForKeysThatWantMethods = new HashMap<>(); + methodsForKeysThatWantMethods.put(SuntimesBackupTask.KEY_ALARMITEMS, SuntimesBackupRestoreTask.IMPORT_ALARMS_METHODS); + methodsForKeysThatWantMethods.put(SuntimesBackupTask.KEY_PLACEITEMS, SuntimesBackupRestoreTask.IMPORT_PLACES_METHODS); + methodsForKeysThatWantMethods.put(SuntimesBackupTask.KEY_WIDGETSETTINGS, SuntimesBackupRestoreTask.IMPORT_WIDGETS_METHODS); + + final Map methods = new HashMap<>(); // choose methods for key each; import after observing all + final SuntimesBackupRestoreTask.BackupKeyObserver observer = new SuntimesBackupRestoreTask.BackupKeyObserver(keysThatWantMethods, new SuntimesBackupRestoreTask.BackupKeyObserver.ObserverListener() + { + @Override + public void onObservingItem(final SuntimesBackupRestoreTask.BackupKeyObserver observer, final String key ) + { + if (includeKeys.contains(key)) + { + SuntimesBackupRestoreTask.chooseImportMethod(context, key, methodsForKeysThatWantMethods.get(key), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int importMethod) { + methods.put(key, importMethod); + observer.notify(key); // trigger observeNext + } + }); + } else observer.notify(key); + } + public void onObservedAll(SuntimesBackupRestoreTask.BackupKeyObserver observer) { + importSettings(context, includeKeys, methods, allValues); + } + }); + observer.observeNext(); + + /*if (includeKeys.contains(SuntimesBackupTask.KEY_WIDGETSETTINGS)) { - WidgetSettingsImportTask.chooseWidgetSettingsImportMethod(context, WidgetSettingsImportTask.ALL_IMPORT_METHODS, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int method) { - importSettings(context, method, includeKeys, allValues); + SuntimesBackupRestoreTask.chooseImportMethod(context, SuntimesBackupTask.KEY_WIDGETSETTINGS, SuntimesBackupRestoreTask.IMPORT_WIDGETS_METHODS, new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int widgetImportMethod) { + methods.put(SuntimesBackupTask.KEY_WIDGETSETTINGS, widgetImportMethod); + importSettings(context, includeKeys, methods, allValues); } }); } else { - importSettings(context, IMPORT_METHOD_RESTOREBACKUP, includeKeys, allValues); - } + importSettings(context, includeKeys, methods, allValues); + }*/ } }); } else { - SuntimesBackupRestoreTask.showIOResultSnackbar(context, getWindow().getDecorView(), false, 0, null); + SuntimesBackupLoadTask.showIOResultSnackbar(context, getWindow().getDecorView(), false, 0, null); } } }); task.execute(uri); } - protected void importSettings(Context context, int method, Set keys, Map allValues) + protected void importSettings(final Context context, final Set keys, final Map methods, final Map allValues) { - StringBuilder report = new StringBuilder(); - int c = SuntimesBackupRestoreTask.importSettings(context, method, keys, report, allValues); - SuntimesBackupRestoreTask.showIOResultSnackbar(context, getWindow().getDecorView(), (c > 0), c, ((c > 0) ? report.toString() : null)); + SuntimesBackupRestoreTask task = new SuntimesBackupRestoreTask(context); + task.setData(allValues); + task.setKeys(keys); + task.setMethods(methods); + task.setTaskListener(new SuntimesBackupRestoreTask.TaskListener() + { + @Override + public void onStarted() { + showProgress(context, context.getString(R.string.configAction_import), context.getString(R.string.configAction_import)); + } + + @Override + public void onFinished(SuntimesBackupRestoreTask.TaskResult result) + { + dismissProgress(); + if (result.getResult()) + { + int c = result.getNumResults(); + SuntimesBackupLoadTask.showIOResultSnackbar(context, getWindow().getDecorView(), (c > 0), c, ((c > 0) ? result.getReport() : null)); + + } else { + SuntimesBackupLoadTask.showIOResultSnackbar(context, getWindow().getDecorView(), false, result.getNumResults(), result.getReport()); + } + } + }); + task.execute(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java index 09275df78..66b25ab86 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupLoadTask.java @@ -21,29 +21,17 @@ import android.annotation.TargetApi; import android.content.ContentValues; import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; import android.util.JsonReader; import android.util.JsonToken; import android.util.Log; import android.view.View; import com.forrestguice.suntimeswidget.R; -import com.forrestguice.suntimeswidget.SuntimesWidgetListActivity; -import com.forrestguice.suntimeswidget.alarmclock.AlarmDatabaseAdapter; -import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; -import com.forrestguice.suntimeswidget.events.EventSettings; -import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; import com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.ContentValuesJson; -import com.forrestguice.suntimeswidget.tiles.ClockTileService; -import com.forrestguice.suntimeswidget.tiles.NextEventTileService; import java.io.BufferedInputStream; import java.io.IOException; @@ -53,11 +41,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.Set; - -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_DIRECTIMPORT; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_MAKEBESTGUESS; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_RESTOREBACKUP; public class SuntimesBackupLoadTask extends AsyncTask { @@ -310,7 +293,6 @@ public void clearTaskListener() { //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// - /** /** * showIOResultSnackbar */ diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index 42e410c51..11c04fb34 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -18,6 +18,7 @@ package com.forrestguice.suntimeswidget.settings; +import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; @@ -46,10 +47,6 @@ import java.util.Set; import java.util.TreeSet; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_DIRECTIMPORT; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_MAKEBESTGUESS; -import static com.forrestguice.suntimeswidget.settings.WidgetSettingsImportTask.IMPORT_WIDGETS_METHOD_RESTOREBACKUP; - public class SuntimesBackupRestoreTask extends AsyncTask { public static final String TAG = "RestoreBackup"; @@ -211,37 +208,25 @@ public static int importSettings(Context context, Set keys, Map items = new HashMap<>(); + private final Set remainingKeys = new TreeSet(); + + @SuppressLint("UseSparseArrays") + public BackupKeyObserver(String[] keys, ObserverListener listener) + { + this.observerListener = listener; + for (String key : keys) { + items.put(key, false); + remainingKeys.add(key); + } + } + + public void observeNext() + { + for (String key : remainingKeys) + { + remainingKeys.remove(key); + if (observerListener != null) { + observerListener.onObservingItem(this, key); + } + break; + } + } + + public void notify(String key) + { + items.put(key, true); + if (observerListener != null) + { + observerListener.onObservedItem(this, key); + if (observedAll()) { + observerListener.onObservedAll(this); + } else observeNext(); + } + } + + public boolean observedAll() + { + boolean retValue = true; + for (Boolean value : items.values()) { + retValue = retValue && value; + } + return retValue; + } + + private final ObserverListener observerListener; + public static abstract class ObserverListener + { + public void onObservingItem(BackupKeyObserver observer, String key) {} + public void onObservedItem(BackupKeyObserver observer, String key) {} + public void onObservedAll(BackupKeyObserver observer) {} + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java index eb8a0f5b0..c2fdc5f21 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -424,7 +424,7 @@ public int compare(Pair o1, Pair o2) AlertDialog.Builder confirm = new AlertDialog.Builder(context) .setTitle(context.getString(isImport ? R.string.configAction_restoreBackup : R.string.configAction_createBackup)) - .setIcon(android.R.drawable.ic_dialog_info) + .setIcon(isImport ? R.drawable.ic_action_copy : R.drawable.ic_action_save) .setMultiChoiceItems(displayStrings, Arrays.copyOf(checked, checked.length), new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { @@ -449,27 +449,47 @@ public void onClick(DialogInterface dialog, int whichButton) { /** * showIOResultSnackbar */ - public static void showIOResultSnackbar(final Context context, final View view, boolean result, final CharSequence message, @Nullable final CharSequence report) + public static void showIOResultSnackbar(final Context context, final View view, @Nullable Uri shareUri, boolean result, final CharSequence message, @Nullable final CharSequence report) { if (context != null && view != null) { Snackbar snackbar = Snackbar.make(view, message, (result ? 7000 : Snackbar.LENGTH_LONG)); - if (report != null) - { - snackbar.setAction(context.getString(R.string.configAction_info), new View.OnClickListener() - { - @Override - public void onClick(View v) { - AlertDialog.Builder dialog = new AlertDialog.Builder(context).setTitle(message) - .setIcon(android.R.drawable.ic_dialog_info) - .setMessage(report); - dialog.show(); - } - }); + + if (report != null) { + snackbar.setAction(context.getString(R.string.configAction_info), onClickShowReport(context, message, report)); + + } else if (result && shareUri != null) { + snackbar.setAction(context.getString(R.string.configAction_share), onClickShareUri(context, shareUri)); } + SuntimesUtils.themeSnackbar(context, snackbar, null); snackbar.show(); } } + @Override + public void onClick(View v) + { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_STREAM, uri); + context.startActivity(Intent.createChooser(intent, context.getString(R.string.configAction_share))); + } + }; + } + + private static View.OnClickListener onClickShowReport(final Context context, final CharSequence message, final CharSequence report) + { + return new View.OnClickListener() + { + @Override + public void onClick(View v) { + AlertDialog.Builder dialog = new AlertDialog.Builder(context).setTitle(message) + .setIcon(android.R.drawable.ic_dialog_info) + .setMessage(report); + dialog.show(); + } + }; + } + } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 54992c4e2..2ed823697 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -135,7 +135,7 @@ protected TaskResult doInBackground(Uri... params) protected void readData(Context context, InputStream in, ArrayList items) throws IOException { BufferedInputStream bufferedIn = new BufferedInputStream(in); - if (SuntimesBackupRestoreTask.containsBackupItem(bufferedIn)) { + if (SuntimesBackupLoadTask.containsBackupItem(bufferedIn)) { readItemsFromBackup(context, bufferedIn, items); } else { @@ -149,7 +149,7 @@ protected static void readItemsFromBackup(Context context, BufferedInputStream i reader.setLenient(true); try { Map data = new HashMap<>(); - SuntimesBackupRestoreTask.readBackupItem(context, reader, data); + SuntimesBackupLoadTask.readBackupItem(context, reader, data); items.addAll(Arrays.asList(data.get(SuntimesBackupTask.KEY_WIDGETSETTINGS))); } finally { @@ -685,10 +685,10 @@ public static void clearBackup(Context context, SharedPreferences prefs ) { //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// - public static final int IMPORT_METHOD_RESTOREBACKUP = 0; - public static final int IMPORT_METHOD_MAKEBESTGUESS = 1; - public static final int IMPORT_METHOD_DIRECTIMPORT = 2; - public static final int[] ALL_IMPORT_METHODS = new int[] { IMPORT_METHOD_RESTOREBACKUP, IMPORT_METHOD_MAKEBESTGUESS, IMPORT_METHOD_DIRECTIMPORT }; + public static final int IMPORT_WIDGETS_METHOD_RESTOREBACKUP = 0; + public static final int IMPORT_WIDGETS_METHOD_MAKEBESTGUESS = 1; + public static final int IMPORT_WIDGETS_METHOD_DIRECTIMPORT = 2; + public static final int[] IMPORT_WIDGETS_METHODS = new int[] {IMPORT_WIDGETS_METHOD_RESTOREBACKUP, IMPORT_WIDGETS_METHOD_MAKEBESTGUESS, IMPORT_WIDGETS_METHOD_DIRECTIMPORT}; public static void chooseWidgetSettingsImportMethod(final Context context, final int[] methods, @NonNull final DialogInterface.OnClickListener onClickListener) { @@ -698,7 +698,7 @@ public static void chooseWidgetSettingsImportMethod(final Context context, final } AlertDialog.Builder confirm = new AlertDialog.Builder(context) .setTitle(context.getString(R.string.restorebackup_dialog_item_widgetsettings)) - .setIcon(android.R.drawable.ic_dialog_info) + .setIcon(R.drawable.ic_action_widget) .setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { /* EMPTY */ } }) @@ -716,9 +716,9 @@ public void onClick(DialogInterface dialog, int whichButton) protected static CharSequence displayStringForImportMethod(Context context, int method) { switch (method) { - case IMPORT_METHOD_DIRECTIMPORT: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_direct)); - case IMPORT_METHOD_MAKEBESTGUESS: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_bestguess)); - case IMPORT_METHOD_RESTOREBACKUP: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_restorebackup)); + case IMPORT_WIDGETS_METHOD_DIRECTIMPORT: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_direct)); + case IMPORT_WIDGETS_METHOD_MAKEBESTGUESS: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_bestguess)); + case IMPORT_WIDGETS_METHOD_RESTOREBACKUP: return SuntimesUtils.fromHtml(context.getString(R.string.importwidget_dialog_item_restorebackup)); default: return method + ""; } } From a053434507c03c51198c3286720e6bdd9e28904d Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sat, 27 Jan 2024 21:34:48 -0700 Subject: [PATCH 63/69] SuntimesBackupTask --- .../settings/SuntimesBackupTask.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java index c2fdc5f21..db7632e61 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -23,6 +23,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.DatabaseUtils; @@ -74,13 +75,14 @@ public class SuntimesBackupTask extends WidgetSettingsExportTask public static final String KEY_APPSETTINGS = "AppSettings"; public static final String KEY_WIDGETSETTINGS = "WidgetSettings"; + public static final String KEY_WIDGETTHEMES = "WidgetThemes"; public static final String KEY_ALARMITEMS = "AlarmItems"; public static final String KEY_EVENTITEMS = "EventItems"; public static final String KEY_PLACEITEMS = "PlaceItems"; public static final String KEY_ACTIONS = "Actions"; public static final String[] ALL_KEYS = new String[] { - KEY_APPSETTINGS, KEY_WIDGETSETTINGS, KEY_ALARMITEMS, KEY_EVENTITEMS, KEY_PLACEITEMS, KEY_ACTIONS + KEY_APPSETTINGS, KEY_WIDGETSETTINGS, KEY_ALARMITEMS, KEY_EVENTITEMS, KEY_PLACEITEMS, KEY_ACTIONS //, KEY_WIDGETTHEMES // TODO: themes }; public static final String DEF_EXPORT_TARGET = "SuntimesBackup"; @@ -207,6 +209,16 @@ protected void writeBackupJSONObject( Context context, BufferedOutputStream out c++; } + if (includedKeys.containsKey(KEY_WIDGETTHEMES) && includedKeys.get(KEY_WIDGETTHEMES)) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + out.write(("\"" + KEY_WIDGETTHEMES + "\": ").getBytes()); // include Widget Themes + writeWidgetThemesJSONArray(context, out); // TODO + c++; + } + out.write("}".getBytes()); out.flush(); } @@ -304,6 +316,12 @@ public static void writeAlarmItemsJSONArray(Context context, AlarmDatabaseAdapte AlarmClockItemExportTask.writeAlarmItemsJSONArray(context, items.toArray(new AlarmClockItem[0]), out); } + public static void writeWidgetThemesJSONArray(Context context, BufferedOutputStream out) throws IOException + { + out.write("[]".getBytes()); // TODO + out.flush(); + } + /** * exportSettings to uri * Displays an AlertDialog (chooser), then creates and starts a SuntimesBackupTask. @@ -467,6 +485,10 @@ public static void showIOResultSnackbar(final Context context, final View view, } } + private static View.OnClickListener onClickShareUri(final Context context, final Uri uri) + { + return new View.OnClickListener() + { @Override public void onClick(View v) { From 66db87a19c9b8a9abb825c1babcbb0a2099267da Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 28 Jan 2024 10:16:01 -0700 Subject: [PATCH 64/69] SuntimesBackupTask include widget themes --- .../settings/SuntimesBackupRestoreTask.java | 63 ++++++++---- .../settings/SuntimesBackupTask.java | 26 ++++- .../suntimeswidget/themes/SuntimesTheme.java | 95 +++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + 4 files changed, 155 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java index 11c04fb34..113d4f565 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupRestoreTask.java @@ -37,6 +37,8 @@ import com.forrestguice.suntimeswidget.alarmclock.AlarmSettings; import com.forrestguice.suntimeswidget.events.EventSettings; import com.forrestguice.suntimeswidget.getfix.GetFixDatabaseAdapter; +import com.forrestguice.suntimeswidget.themes.SuntimesTheme; +import com.forrestguice.suntimeswidget.themes.WidgetThemeListActivity; import com.forrestguice.suntimeswidget.tiles.ClockTileService; import com.forrestguice.suntimeswidget.tiles.NextEventTileService; @@ -193,22 +195,30 @@ public void clearTaskListener() { * @param context Context context * @param keys the set of backup keys within allValues that should be restored * @param methods 2:directImport (copy widget ids as-is), 1:bestGuess (reassign widget ids (best guess)), 0:restoreBackup (import as backup for later (when the launcher initiates restoration)) - * @param allValues a map containing backupKey:ContentValue[]; e.g. "AppSettings":ContentValues[], "WidgetSettings":ContentValues[], ... - * @return number of items imported + * @param allValues a map containing backupKey:ContentValue[]; e.g. "AppSettings":ContentValues[], "WidgetSettings":ContentValues[], ... + * @return number of items imported */ public static int importSettings(Context context, Set keys, Map methods, StringBuilder report, Map allValues) { int c = 0; - if (keys.contains(SuntimesBackupTask.KEY_APPSETTINGS)) { - c += (importAppSettings(context, report, allValues.get(SuntimesBackupTask.KEY_APPSETTINGS)) ? 1 : 0); + if (keys.contains(SuntimesBackupTask.KEY_ACTIONS)) { + c += (importActions(context, report, allValues.get(SuntimesBackupTask.KEY_ACTIONS)) ? 1 : 0); } - if (keys.contains(SuntimesBackupTask.KEY_WIDGETSETTINGS)) + if (keys.contains(SuntimesBackupTask.KEY_EVENTITEMS)) { + c += importEventItems(context, report, allValues.get(SuntimesBackupTask.KEY_EVENTITEMS)); + } + + if (keys.contains(SuntimesBackupTask.KEY_PLACEITEMS)) { - int method = (methods.containsKey(SuntimesBackupTask.KEY_WIDGETSETTINGS)) - ? methods.get(SuntimesBackupTask.KEY_WIDGETSETTINGS) : IMPORT_WIDGETS_METHOD_RESTOREBACKUP; - c += importWidgetSettings(context, method, report, allValues.get(SuntimesBackupTask.KEY_WIDGETSETTINGS)); + int method = (methods.containsKey(SuntimesBackupTask.KEY_PLACEITEMS)) + ? methods.get(SuntimesBackupTask.KEY_PLACEITEMS) : IMPORT_PLACES_METHOD_ADDALL; + c += importPlaceItems(context, method, report, allValues.get(SuntimesBackupTask.KEY_PLACEITEMS)); + } + + if (keys.contains(SuntimesBackupTask.KEY_APPSETTINGS)) { + c += (importAppSettings(context, report, allValues.get(SuntimesBackupTask.KEY_APPSETTINGS)) ? 1 : 0); } if (keys.contains(SuntimesBackupTask.KEY_ALARMITEMS)) @@ -218,19 +228,15 @@ public static int importSettings(Context context, Set keys, Map themes = WidgetThemes.loadInstalledList(prefs); + for (String themeName : themes) + { + if (c > 0) { + out.write(",\n".getBytes()); + } + SuntimesTheme theme = WidgetThemes.loadTheme(context, themeName); + String jsonString = WidgetSettingsImportTask.ContentValuesJson.toJson(theme.toContentValues()); + out.write(jsonString.getBytes()); + c++; + } + out.write("]".getBytes()); out.flush(); } @@ -382,6 +397,9 @@ public static CharSequence displayStringForBackupKey(Context context, String key if (SuntimesBackupTask.KEY_WIDGETSETTINGS.equals(key)) { return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_widgetsettings)); } + if (SuntimesBackupTask.KEY_WIDGETTHEMES.equals(key)) { + return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_widgetthemes)); + } if (SuntimesBackupTask.KEY_ALARMITEMS.equals(key)) { return SuntimesUtils.fromHtml(context.getString(R.string.restorebackup_dialog_item_alarmitems)); } diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/themes/SuntimesTheme.java b/app/src/main/java/com/forrestguice/suntimeswidget/themes/SuntimesTheme.java index b865ce124..f9ff09619 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/themes/SuntimesTheme.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/themes/SuntimesTheme.java @@ -282,6 +282,85 @@ public SuntimesTheme(SuntimesTheme otherTheme) this.themeTimeBold = otherTheme.themeTimeBold; } + public SuntimesTheme(ContentValues values) // TODO: test + { + this.themeVersion = values.getAsInteger(THEME_VERSION); + this.themeName = values.getAsString(THEME_NAME); + this.themeIsDefault = values.getAsBoolean(THEME_ISDEFAULT); + this.themeDisplayString = values.getAsString(THEME_DISPLAYSTRING); + + this.themeBackground = ThemeBackground.valueOf(values.getAsString(THEME_BACKGROUND)); + this.themeBackgroundColor = values.getAsInteger(THEME_BACKGROUND_COLOR); + + this.themePadding[0] = values.getAsInteger(THEME_PADDING_LEFT); + this.themePadding[1] = values.getAsInteger(THEME_PADDING_TOP); + this.themePadding[2] = values.getAsInteger(THEME_PADDING_RIGHT); + this.themePadding[3] = values.getAsInteger(THEME_PADDING_BOTTOM); + + this.themeTextColor = values.getAsInteger(THEME_TEXTCOLOR); + this.themeTitleColor = values.getAsInteger(THEME_TITLECOLOR); + this.themeTimeColor = values.getAsInteger(THEME_TIMECOLOR); + this.themeTimeSuffixColor = values.getAsInteger(THEME_TIMESUFFIXCOLOR); + this.themeActionColor = values.getAsInteger(THEME_ACTIONCOLOR); + this.themeAccentColor = values.getAsInteger(THEME_ACCENTCOLOR); + + this.themeSunriseTextColor = values.getAsInteger(THEME_SUNRISECOLOR); + this.themeSunriseIconColor = values.getAsInteger(THEME_RISEICON_FILL_COLOR); + this.themeSunriseIconStrokeColor = values.getAsInteger(THEME_RISEICON_STROKE_COLOR); + this.themeSunriseIconStrokeWidth = values.getAsInteger(THEME_RISEICON_STROKE_WIDTH); + + this.themeNoonTextColor = values.getAsInteger(THEME_NOONCOLOR); + this.themeNoonIconColor = values.getAsInteger(THEME_NOONICON_FILL_COLOR); + this.themeNoonIconStrokeColor = values.getAsInteger(THEME_NOONICON_STROKE_COLOR); + this.themeNoonIconStrokeWidth = values.getAsInteger(THEME_NOONICON_STROKE_WIDTH); + + this.themeSunsetTextColor = values.getAsInteger(THEME_SUNSETCOLOR); + this.themeSunsetIconColor = values.getAsInteger(THEME_SETICON_FILL_COLOR); + this.themeSunsetIconStrokeColor = values.getAsInteger(THEME_SETICON_STROKE_COLOR); + this.themeSunsetIconStrokeWidth = values.getAsInteger(THEME_SETICON_STROKE_WIDTH); + + this.themeMoonriseTextColor = values.getAsInteger(THEME_MOONRISECOLOR); + this.themeMoonsetTextColor = values.getAsInteger(THEME_MOONSETCOLOR); + this.themeMoonWaningColor = values.getAsInteger(THEME_MOONWANINGCOLOR); + this.themeMoonNewColor = values.getAsInteger(THEME_MOONNEWCOLOR); + this.themeMoonWaxingColor = values.getAsInteger(THEME_MOONWAXINGCOLOR); + this.themeMoonFullColor = values.getAsInteger(THEME_MOONFULLCOLOR); + + this.themeMoonWaningTextColor = values.getAsInteger(THEME_MOONWANINGCOLOR_TEXT); + this.themeMoonNewTextColor = values.getAsInteger(THEME_MOONNEWCOLOR_TEXT); + this.themeMoonWaxingTextColor = values.getAsInteger(THEME_MOONWAXINGCOLOR_TEXT); + this.themeMoonFullTextColor = values.getAsInteger(THEME_MOONFULLCOLOR_TEXT); + + this.themeMoonFullStroke = values.getAsInteger(THEME_MOONFULL_STROKE_WIDTH); + this.themeMoonNewStroke = values.getAsInteger(THEME_MOONNEW_STROKE_WIDTH); + + this.themeDayColor = values.getAsInteger(THEME_DAYCOLOR); + this.themeCivilColor = values.getAsInteger(THEME_CIVILCOLOR); + this.themeNauticalColor = values.getAsInteger(THEME_NAUTICALCOLOR); + this.themeAstroColor = values.getAsInteger(THEME_ASTROCOLOR); + this.themeNightColor = values.getAsInteger(THEME_NIGHTCOLOR); + this.themeGraphPointFillColor = values.getAsInteger(THEME_GRAPH_POINT_FILL_COLOR); + this.themeGraphPointStrokeColor = values.getAsInteger(THEME_GRAPH_POINT_STROKE_COLOR); + + this.themeSpringColor = values.getAsInteger(THEME_SPRINGCOLOR); + this.themeSummerColor = values.getAsInteger(THEME_SUMMERCOLOR); + this.themeFallColor = values.getAsInteger(THEME_FALLCOLOR); + this.themeWinterColor = values.getAsInteger(THEME_WINTERCOLOR); + + this.themeMapBackgroundColor = values.getAsInteger(THEME_MAP_BACKGROUNDCOLOR); + this.themeMapForegroundColor = values.getAsInteger(THEME_MAP_FOREGROUNDCOLOR); + this.themeMapShadowColor = values.getAsInteger(THEME_MAP_SHADOWCOLOR); + this.themeMapHighlightColor = values.getAsInteger(THEME_MAP_HIGHLIGHTCOLOR); + + this.themeTitleSize = values.getAsFloat(THEME_TITLESIZE); + this.themeTextSize = values.getAsFloat(THEME_TEXTSIZE); + this.themeTimeSize = values.getAsFloat(THEME_TIMESIZE); + this.themeTimeSuffixSize = values.getAsFloat(THEME_TIMESUFFIXSIZE); + + this.themeTitleBold = values.getAsBoolean(THEME_TITLEBOLD); + this.themeTimeBold = values.getAsBoolean(THEME_TIMEBOLD); + } + public boolean initTheme( Context context, String themesPrefix, String themeName, SuntimesTheme defaultTheme ) { long bench_start = System.nanoTime(); @@ -492,15 +571,15 @@ public ContentValues toContentValues() values.put(THEME_BACKGROUND, this.themeBackground.name()); values.put(THEME_BACKGROUND_COLOR, this.themeBackgroundColor); - values.put(THEME_PADDING_LEFT, this.themePaddingPixels[0]); - values.put(THEME_PADDING_TOP, this.themePaddingPixels[1]); - values.put(THEME_PADDING_RIGHT, this.themePaddingPixels[2]); - values.put(THEME_PADDING_BOTTOM, this.themePaddingPixels[3]); + values.put(THEME_PADDING_LEFT, this.themePadding[0]); + values.put(THEME_PADDING_TOP, this.themePadding[1]); + values.put(THEME_PADDING_RIGHT, this.themePadding[2]); + values.put(THEME_PADDING_BOTTOM, this.themePadding[3]); - values.put(THEME_PADDING_LEFT + THEME_PADDING_PIXELS, this.themePadding[0]); - values.put(THEME_PADDING_TOP + THEME_PADDING_PIXELS, this.themePadding[1]); - values.put(THEME_PADDING_RIGHT + THEME_PADDING_PIXELS, this.themePadding[2]); - values.put(THEME_PADDING_BOTTOM + THEME_PADDING_PIXELS, this.themePadding[3]); + values.put(THEME_PADDING_LEFT + THEME_PADDING_PIXELS, this.themePaddingPixels[0]); + values.put(THEME_PADDING_TOP + THEME_PADDING_PIXELS, this.themePaddingPixels[1]); + values.put(THEME_PADDING_RIGHT + THEME_PADDING_PIXELS, this.themePaddingPixels[2]); + values.put(THEME_PADDING_BOTTOM + THEME_PADDING_PIXELS, this.themePaddingPixels[3]); values.put(THEME_TEXTCOLOR, this.themeTextColor); values.put(THEME_TITLECOLOR, this.themeTitleColor); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 96d0410a1..23407a1ac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1196,6 +1196,7 @@ Alarms App Settings Widget Settings + Widget Themes Places Events Actions From b600f5138d5265a3b6653a5a991ab10f97f8d667 Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 28 Jan 2024 10:22:37 -0700 Subject: [PATCH 65/69] lint newApi --- .../settings/WidgetSettingsImportTask.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java index 2ed823697..7e54f1c9c 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/WidgetSettingsImportTask.java @@ -145,15 +145,22 @@ protected void readData(Context context, InputStream in, ArrayList items) throws IOException { - JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); - reader.setLenient(true); - try { - Map data = new HashMap<>(); - SuntimesBackupLoadTask.readBackupItem(context, reader, data); - items.addAll(Arrays.asList(data.get(SuntimesBackupTask.KEY_WIDGETSETTINGS))); - - } finally { - reader.close(); + if (Build.VERSION.SDK_INT >= 11) + { + JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); + reader.setLenient(true); + try { + Map data = new HashMap<>(); + SuntimesBackupLoadTask.readBackupItem(context, reader, data); + items.addAll(Arrays.asList(data.get(SuntimesBackupTask.KEY_WIDGETSETTINGS))); + + } finally { + reader.close(); + in.close(); + } + + } else { + Log.w("ImportSettings", "Unsupported; skipping import"); in.close(); } } From 7ff94ae8345eed9e01a5421f99c7c411ea1a844e Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 28 Jan 2024 10:26:47 -0700 Subject: [PATCH 66/69] lint newApi --- .../suntimeswidget/settings/SuntimesBackupTask.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java index 7b20077c7..6deca5a49 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/settings/SuntimesBackupTask.java @@ -440,7 +440,8 @@ public static void chooseBackupContent(final Context context, final String[] key checked[i] = true; items.add(new Pair(i, SuntimesBackupTask.displayStringForBackupKey(context, keys[i]))); } - items.sort(new Comparator>() + + Collections.sort(items, new Comparator>() { @Override public int compare(Pair o1, Pair o2) From cb295b5dc7b6a96797e04d527ac311cbd5b0299c Mon Sep 17 00:00:00 2001 From: Forrest Guice Date: Sun, 28 Jan 2024 19:24:57 -0700 Subject: [PATCH 67/69] about adds a donate link to about; https://forrestguice.github.io/SuntimesWidget/donate --- .../suntimeswidget/AboutActivity.java | 6 ++ .../suntimeswidget/WelcomeActivity.java | 7 ++- .../res/drawable/ic_action_favorite_24.xml | 5 ++ .../main/res/layout-land/layout_about_app.xml | 23 +++++++ app/src/main/res/layout/layout_about_app.xml | 26 +++++++- .../main/res/layout/layout_welcome_legal.xml | 60 +++++++++++++++++-- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 4 ++ 8 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/drawable/ic_action_favorite_24.xml diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/AboutActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/AboutActivity.java index 0a5d8b883..119241471 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/AboutActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/AboutActivity.java @@ -248,6 +248,12 @@ public void onClick(View v) { supportView.setText(SuntimesUtils.fromHtml(context.getString(R.string.app_support_url))); } + TextView donateView = (TextView) dialogContent.findViewById(R.id.txt_donate_url); + if (donateView != null) { + donateView.setMovementMethod(LinkMovementMethod.getInstance()); + donateView.setText(SuntimesUtils.fromHtml(context.getString(R.string.app_donate_url, context.getString(R.string.app_name)))); + } + TextView legalView1 = (TextView) dialogContent.findViewById(R.id.txt_about_legal1); if (legalView1 != null) { legalView1.setMovementMethod(LinkMovementMethod.getInstance()); diff --git a/app/src/main/java/com/forrestguice/suntimeswidget/WelcomeActivity.java b/app/src/main/java/com/forrestguice/suntimeswidget/WelcomeActivity.java index aa1af6d91..626bf1fa0 100644 --- a/app/src/main/java/com/forrestguice/suntimeswidget/WelcomeActivity.java +++ b/app/src/main/java/com/forrestguice/suntimeswidget/WelcomeActivity.java @@ -441,7 +441,7 @@ public void initViews(Context context, View view) } } - textViews = new int[] { R.id.link0, R.id.link1, R.id.link2, R.id.link3 }; + textViews = new int[] { R.id.link0, R.id.link1, R.id.link2, R.id.link3, R.id.link4 }; for (int resID : textViews) { TextView text = (TextView) view.findViewById(resID); if (text != null) { @@ -449,6 +449,11 @@ public void initViews(Context context, View view) text.setMovementMethod(LinkMovementMethod.getInstance()); } } + + TextView donateLink = (TextView) view.findViewById(R.id.link4); + if (donateLink != null) { + donateLink.setText(SuntimesUtils.fromHtml(context.getString(R.string.app_donate_url, context.getString(R.string.app_name)))); + } } } diff --git a/app/src/main/res/drawable/ic_action_favorite_24.xml b/app/src/main/res/drawable/ic_action_favorite_24.xml new file mode 100644 index 000000000..122796b28 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_favorite_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-land/layout_about_app.xml b/app/src/main/res/layout-land/layout_about_app.xml index 3dd03de0b..7c0cd0a1d 100644 --- a/app/src/main/res/layout-land/layout_about_app.xml +++ b/app/src/main/res/layout-land/layout_about_app.xml @@ -80,6 +80,29 @@ android:text="@string/app_url" android:autoLink="web" /> + + + + + + + + + +
\ No newline at end of file diff --git a/app/src/main/res/layout/layout_about_app.xml b/app/src/main/res/layout/layout_about_app.xml index 8ce8d0da7..6d975c955 100644 --- a/app/src/main/res/layout/layout_about_app.xml +++ b/app/src/main/res/layout/layout_about_app.xml @@ -1,5 +1,6 @@ - @@ -78,5 +79,28 @@ android:text="@string/app_url" android:autoLink="web" /> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_welcome_legal.xml b/app/src/main/res/layout/layout_welcome_legal.xml index 9b6394abe..def25dfd7 100644 --- a/app/src/main/res/layout/layout_welcome_legal.xml +++ b/app/src/main/res/layout/layout_welcome_legal.xml @@ -42,11 +42,30 @@ - + + + + +