diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 149e6a4544..cac342b66c 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,7 +28,7 @@ jobs: - name: Build with Gradle run: ./gradlew assembleCurrentDebug - name: Run unit tests - timeout-minutes: 10 + timeout-minutes: 15 run: ./gradlew testCurrentDebugUnitTest - name: Upload Test Results if: ${{ always() }} diff --git a/documentation/docs/help/en/Introduction.md b/documentation/docs/help/en/Introduction.md index 5b4bd37307..6ca42c5853 100644 --- a/documentation/docs/help/en/Introduction.md +++ b/documentation/docs/help/en/Introduction.md @@ -1,4 +1,4 @@ -_Before we start: most screens have links in the menu to the on-device help system giving you direct access to information relevant for the current context, you can easily navigate back to this text too. If you have a larger device, for example a tablet, you can open the help system in a separate split window. All the help texts and more (FAQs, tutorials) can be found on the [Vespucci documentation site](https://vespucci.io/) too._ +_Before we start: most screens have links in the menu to the on-device help system giving you direct access to information relevant for the current context, you can easily navigate back to this text too. If you have a larger device, for example a tablet, you can open the help system in a separate split window. All the help texts and more (FAQs, tutorials) can be found on the [Vespucci documentation site](https://vespucci.io/) too. You can further start the help viewer directly on devices that support short cuts with a long press on the app icon and selecting "Help"_ # Vespucci Introduction @@ -46,6 +46,8 @@ A long press on the lock icon will display a menu currently offering 4 options: * **Indoor** - enables Indoor mode, see [Indoor mode](#indoor). White lock icon with an "I" is displayed. * **C-Mode** - enables C-Mode, only objects that have a warning flag set will be displayed, see [C-Mode](#c-mode). White lock icon with a "C" is displayed. +If you are using Vespucci on an Android device that supports short cuts (long press on the app icon) you can start directly to _Address_ and _Indoor_ mode. + #### Single tap, double tap, and long press By default, selectable nodes and ways have an orange area around them indicating roughly where you have to touch to select an object. You have three options: @@ -90,7 +92,7 @@ On first start the app launches in "Simple mode", this can be changed in the mai Tapping the large green floating button on the map screen will show a menu. After you've selected one of the items, you will be asked to tap the screen at the location where you want to create the object, pan and zoom continues to work if you need to adjust the map view. -See [Creating new objects in simple actions mode](Simple%20actions.md) for more information. +See [Creating new objects in simple actions mode](Simple%20actions.md) for more information. Simple mode os the default for new installs. ##### Advanced (long press) mode @@ -300,10 +302,12 @@ The full description can be found here [Preferences](Preferences.md) The full description can be found here [Advanced preferences](Advanced%20preferences.md) -## Reporting Problems +## Reporting and Resolving Issues If Vespucci crashes, or it detects an inconsistent state, you will be asked to send in the crash dump. Please do so if that happens, but please only once per specific situation. If you want to give further input or open an issue for a feature request or similar, please do so here: [Vespucci issue tracker](https://github.com/MarcusWolschon/osmeditor4android/issues). The "Provide feedback" function from the main menu will open a new issue and include the relevant app and device information without extra typing. -If you want to discuss something related to Vespucci, you can either start a discussion on the [Vespucci Google group](https://groups.google.com/forum/#!forum/osmeditor4android) or on the [OpenStreetMap Android forum](http://forum.openstreetmap.org/viewforum.php?id=56) +If you are experiencing difficulties starting the app after a crash, you can try to start it in _Safe_ mode on devices that support short cuts: long press on the app icon and then select _Safe_ from the menu. + +If you want to discuss something related to Vespucci, you can either start a discussion on the [OpenStreetMap forum](https://community.openstreetmap.org). diff --git a/src/androidTest/java/de/blau/android/SafeModeTest.java b/src/androidTest/java/de/blau/android/SafeModeTest.java new file mode 100644 index 0000000000..e446e56edc --- /dev/null +++ b/src/androidTest/java/de/blau/android/SafeModeTest.java @@ -0,0 +1,95 @@ +package de.blau.android; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.app.Instrumentation; +import android.app.Instrumentation.ActivityMonitor; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; +import androidx.test.uiautomator.UiDevice; +import de.blau.android.layer.MapViewLayer; +import de.blau.android.prefs.Preferences; +import de.blau.android.resources.DataStyle; + +/** + * + * This test was originally written with ActivityScenario however that delivers the launch intent -twice- to the + * activity, the 1st time with out the intent extras + * + * @author simon + * + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class SafeModeTest { + + Instrumentation instrumentation = null; + Context context = null; + UiDevice device = null; + + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(Splash.class, false, false); + + /** + * Pre-test setup + */ + @Before + public void setup() { + instrumentation = InstrumentationRegistry.getInstrumentation(); + device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + context = instrumentation.getTargetContext(); + Intent start = Intent.makeMainActivity(new ComponentName(context, Splash.class)); + start.putExtra(Splash.SAFE, true); + Splash splash = mActivityRule.launchActivity(start); + assertNotNull(splash); + } + + /** + * Reset map style and disable all layers + */ + @Test + public void defaultOptions() { + assertTrue(TestUtils.clickText(device, false, context.getString(R.string.Continue), true)); + ActivityMonitor monitor = instrumentation.addMonitor(Main.class.getName(), null, false); + Main main = (Main) monitor.waitForActivityWithTimeout(5000); + TestUtils.grantPermissons(device); + TestUtils.dismissStartUpDialogs(device, context); + + Preferences prefs = App.getLogic().getPrefs(); + assertEquals(prefs.getDataStyle(), DataStyle.getBuiltinStyleName()); + + Map map = App.getLogic().getMap(); + for (MapViewLayer l : map.getLayers()) { + assertFalse(l.isVisible()); + } + } + + /** + * Reset map style and disable all layers + */ + @Test + public void resetState() { + TestUtils.clickResource(device, false, device.getCurrentPackageName() + ":id/safe_state_check", false); + assertTrue(TestUtils.clickText(device, false, context.getString(R.string.Continue), true)); + assertTrue(TestUtils.findText(device, false, context.getString(R.string.safe_delete_state_title), 5000)); + assertTrue(TestUtils.clickText(device, false, context.getString(R.string.safe_delete_state_text), true)); + ActivityMonitor monitor = instrumentation.addMonitor(Main.class.getName(), null, false); + Main main = (Main) monitor.waitForActivityWithTimeout(5000); + TestUtils.grantPermissons(device); + TestUtils.dismissStartUpDialogs(device, context); + assertTrue(App.getDelegator().isEmpty()); + } +} diff --git a/src/main/assets/help/en/Introduction.html b/src/main/assets/help/en/Introduction.html index 4fac470ca6..deb7c45a0c 100644 --- a/src/main/assets/help/en/Introduction.html +++ b/src/main/assets/help/en/Introduction.html @@ -5,7 +5,7 @@ -

Before we start: most screens have links in the menu to the on-device help system giving you direct access to information relevant for the current context, you can easily navigate back to this text too. If you have a larger device, for example a tablet, you can open the help system in a separate split window. All the help texts and more (FAQs, tutorials) can be found on the Vespucci documentation site too.

+

Before we start: most screens have links in the menu to the on-device help system giving you direct access to information relevant for the current context, you can easily navigate back to this text too. If you have a larger device, for example a tablet, you can open the help system in a separate split window. All the help texts and more (FAQs, tutorials) can be found on the Vespucci documentation site too. You can further start the help viewer directly on devices that support short cuts with a long press on the app icon and selecting "Help"

Vespucci Introduction

Vespucci is a full featured OpenStreetMap editor that supports most operations that desktop editors provide. It has been tested successfully on Google's Android 2.3 to 14.0 (versions prior to 4.1 are no longer supported) and various AOSP based variants. A word of caution: while mobile device capabilities have caught up with their desktop rivals, particularly older devices have very limited memory available and tend to be rather slow. You should take this in to account when using Vespucci and keep, for example, the areas you are editing to a reasonable size.

Editing with Vespucci

@@ -40,6 +40,7 @@

Lock, unlock, mode switching

  • Indoor - enables Indoor mode, see Indoor mode. White lock icon with an "I" is displayed.
  • C-Mode - enables C-Mode, only objects that have a warning flag set will be displayed, see C-Mode. White lock icon with a "C" is displayed.
  • +

    If you are using Vespucci on an Android device that supports short cuts (long press on the app icon) you can start directly to Address and Indoor mode.

    Single tap, double tap, and long press

    By default, selectable nodes and ways have an orange area around them indicating roughly where you have to touch to select an object. You have three options:

    The full description can be found here Advanced preferences

    -

    Reporting Problems

    +

    Reporting and Resolving Issues

    If Vespucci crashes, or it detects an inconsistent state, you will be asked to send in the crash dump. Please do so if that happens, but please only once per specific situation. If you want to give further input or open an issue for a feature request or similar, please do so here: Vespucci issue tracker. The "Provide feedback" function from the main menu will open a new issue and include the relevant app and device information without extra typing.

    -

    If you want to discuss something related to Vespucci, you can either start a discussion on the Vespucci Google group or on the OpenStreetMap Android forum

    +

    If you are experiencing difficulties starting the app after a crash, you can try to start it in Safe mode on devices that support short cuts: long press on the app icon and then select Safe from the menu.

    +

    If you want to discuss something related to Vespucci, you can either start a discussion on the OpenStreetMap forum.

    \ No newline at end of file diff --git a/src/main/java/de/blau/android/Logic.java b/src/main/java/de/blau/android/Logic.java index 1f33bbd701..c01efa461d 100644 --- a/src/main/java/de/blau/android/Logic.java +++ b/src/main/java/de/blau/android/Logic.java @@ -3857,7 +3857,7 @@ protected Integer doInBackground(Void v) { setBorders(mainMap); return READ_OK; } - if (getDelegator().readFromFile(activity, StorageDelegator.FILENAME + ".backup")) { + if (getDelegator().readFromFile(activity, StorageDelegator.BACKUP_FILENAME)) { getDelegator().dirty(); // we need to overwrite the saved state asap setBorders(mainMap); return READ_BACKUP; diff --git a/src/main/java/de/blau/android/Splash.java b/src/main/java/de/blau/android/Splash.java index f4cf7cd98f..f1b3155a87 100644 --- a/src/main/java/de/blau/android/Splash.java +++ b/src/main/java/de/blau/android/Splash.java @@ -5,27 +5,39 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.database.sqlite.SQLiteException; import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.RelativeLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AppCompatActivity; import androidx.core.splashscreen.SplashScreen; import androidx.preference.PreferenceManager; import de.blau.android.contract.Paths; import de.blau.android.dialogs.Progress; +import de.blau.android.layer.LayerConfig; +import de.blau.android.osm.StorageDelegator; +import de.blau.android.prefs.AdvancedPrefDatabase; import de.blau.android.resources.DataStyle; import de.blau.android.resources.KeyDatabaseHelper; import de.blau.android.resources.TileLayerDatabase; import de.blau.android.resources.TileLayerSource; import de.blau.android.util.ExecutorTask; import de.blau.android.util.FileUtil; +import de.blau.android.util.ThemeUtils; +import de.blau.android.util.Util; /** * Originally based https://www.bignerdranch.com/blog/splash-screens-the-right-way/ @@ -39,8 +51,12 @@ public class Splash extends AppCompatActivity { private static final String DEBUG_TAG = Splash.class.getSimpleName(); - static final String SHORTCUT_EXTRAS_KEY = "shortcut_extras"; - private static final String SAFE = "safe"; + static final String SHORTCUT_EXTRAS_KEY = "shortcut_extras"; + static final String SAFE = "safe"; + + private Bundle shortcutExtras; + private Object startedLock = new Object(); + private boolean started = false; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -56,92 +72,156 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } + final ExecutorTask startup = new ExecutorTask() { + + boolean newInstall; + boolean newConfig; + boolean migratePublicDirectory; + + @Override + protected Void doInBackground(Void param) { + try (TileLayerDatabase db = new TileLayerDatabase(Splash.this)) { + Log.d(DEBUG_TAG, "checking last tile source update"); + long lastDatabaseUpdate = 0; + try { + lastDatabaseUpdate = Math.max(TileLayerDatabase.getSourceUpdate(db.getReadableDatabase(), TileLayerDatabase.SOURCE_JOSM_IMAGERY), + TileLayerDatabase.getSourceUpdate(db.getReadableDatabase(), TileLayerDatabase.SOURCE_ELI)); + } catch (SQLiteException sex) { + Log.e(DEBUG_TAG, "Exception accessing tile layer database " + sex.getMessage()); + cancel(); + } + Log.d(DEBUG_TAG, "checking last package update"); + long lastUpdateTime = 0L; + try { + lastUpdateTime = Util.getPackageInfo(Splash.this.getPackageName(), getPackageManager()).lastUpdateTime; + } catch (NameNotFoundException e1) { + // can't really happen + } + newInstall = lastDatabaseUpdate == 0; + newConfig = lastUpdateTime > lastDatabaseUpdate; + if (newInstall || newConfig) { + migratePublicDirectory = !FileUtil.publicDirectoryExists(); + Progress.showDialog(Splash.this, migratePublicDirectory ? Progress.PROGRESS_MIGRATION : Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); + } + if (migratePublicDirectory) { + directoryMigration(Splash.this); + Splash.this.runOnUiThread(() -> { + Progress.dismissDialog(Splash.this, Progress.PROGRESS_MIGRATION); + Progress.showDialog(Splash.this, Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); + }); + } + if (newInstall || newConfig) { + KeyDatabaseHelper.readKeysFromAssets(Splash.this); + } + if (!isCancelled()) { + TileLayerSource.createOrUpdateCustomSource(Splash.this, db.getWritableDatabase(), true); + if (newInstall || newConfig) { + TileLayerSource.createOrUpdateFromAssetsSource(Splash.this, db.getWritableDatabase(), newConfig, true); + } + TileLayerSource.getListsLocked(Splash.this, db.getReadableDatabase(), true); + } + } + if (newInstall || newConfig) { + Progress.dismissDialog(Splash.this, Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); + } + // read Presets here to avoid reading them on UI thread on startup of Main + Progress.showDialog(Splash.this, Progress.PROGRESS_LOADING_PRESET); + App.getCurrentPresets(Splash.this); + // + Intent intent = new Intent(Splash.this, Main.class); + intent.putExtra(SHORTCUT_EXTRAS_KEY, shortcutExtras); + startActivity(intent); + return null; + } + + @Override + protected void onPostExecute(Void result) { + Log.d(DEBUG_TAG, "onPostExecute"); + Progress.dismissDialog(Splash.this, Progress.PROGRESS_LOADING_PRESET); + Splash.this.finish(); + } + }; + @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override protected void onResume() { super.onResume(); Log.d(DEBUG_TAG, "onResume"); - final Bundle shortcutExtras = getIntent().getExtras(); - if (shortcutExtras != null && shortcutExtras.getBoolean(SAFE)) { - // do anything here to make startup safe - // currently this is only setting the data style to the minimal built in version to avoid issues with Skia - Log.d(DEBUG_TAG, "Starting in safe mode!"); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - prefs.edit().putString(getString(R.string.config_mapProfile_key), DataStyle.getBuiltinStyleName()).commit(); + synchronized (startedLock) { + if (!started) { + started = true; + shortcutExtras = getIntent().getExtras(); + if (shortcutExtras != null && shortcutExtras.getBoolean(SAFE)) { + showSafeModeDialog(startup); + return; + } + startup.execute(); + } } + } - new ExecutorTask() { - - boolean newInstall; - boolean newConfig; - boolean migratePublicDirectory; - - @Override - protected Void doInBackground(Void param) { - try (TileLayerDatabase db = new TileLayerDatabase(Splash.this)) { - Log.d(DEBUG_TAG, "checking last tile source update"); - long lastDatabaseUpdate = 0; - try { - lastDatabaseUpdate = Math.max(TileLayerDatabase.getSourceUpdate(db.getReadableDatabase(), TileLayerDatabase.SOURCE_JOSM_IMAGERY), - TileLayerDatabase.getSourceUpdate(db.getReadableDatabase(), TileLayerDatabase.SOURCE_ELI)); - } catch (SQLiteException sex) { - Log.e(DEBUG_TAG, "Exception accessing tile layer database " + sex.getMessage()); - cancel(); - } - Log.d(DEBUG_TAG, "checking last package update"); - long lastUpdateTime = 0L; - try { - String packageName = Splash.this.getPackageName(); - PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 0); - lastUpdateTime = packageInfo.lastUpdateTime; - } catch (NameNotFoundException e1) { - // can't really happen - } - newInstall = lastDatabaseUpdate == 0; - newConfig = lastUpdateTime > lastDatabaseUpdate; - if (newInstall || newConfig) { - migratePublicDirectory = !FileUtil.publicDirectoryExists(); - Progress.showDialog(Splash.this, migratePublicDirectory ? Progress.PROGRESS_MIGRATION : Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); - } - if (migratePublicDirectory) { - directoryMigration(Splash.this); - Splash.this.runOnUiThread(() -> { - Progress.dismissDialog(Splash.this, Progress.PROGRESS_MIGRATION); - Progress.showDialog(Splash.this, Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); - }); - } - if (newInstall || newConfig) { - KeyDatabaseHelper.readKeysFromAssets(Splash.this); - } - if (!isCancelled()) { - TileLayerSource.createOrUpdateCustomSource(Splash.this, db.getWritableDatabase(), true); - if (newInstall || newConfig) { - TileLayerSource.createOrUpdateFromAssetsSource(Splash.this, db.getWritableDatabase(), newConfig, true); + /** + * Show a dialog with options for safe mode + * + * Note: dismissing the dialog before the task is run leads to the background vanishing + * + * @param startupTask the ExecutorTask that is run to actually start the app + */ + private void showSafeModeDialog(@NonNull ExecutorTask startupTask) { + + final LayoutInflater inflater = ThemeUtils.getLayoutInflater(this); + RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.safe_mode, null); + + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.safe_mode_dialog_title); + builder.setView(layout); + + CheckBox style = (CheckBox) layout.findViewById(R.id.safe_style_check); + CheckBox layers = (CheckBox) layout.findViewById(R.id.safe_layer_check); + CheckBox state = (CheckBox) layout.findViewById(R.id.safe_state_check); + + builder.setPositiveButton(R.string.Continue, null); + builder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> finish()); + + final AlertDialog dialog = builder.create(); + dialog.setOnShowListener((DialogInterface d) -> { + final Button positive = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + positive.setOnClickListener((View v) -> { + Log.e(DEBUG_TAG, "Starting in safe mode"); + if (style.isChecked()) { + // use minimal data style + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + prefs.edit().putString(getString(R.string.config_mapProfile_key), DataStyle.getBuiltinStyleName()).commit(); + } + if (layers.isChecked()) { + // hide all layers + try (AdvancedPrefDatabase db = new AdvancedPrefDatabase(this)) { + final LayerConfig[] layerConfigs = db.getLayers(); + for (LayerConfig config : layerConfigs) { + db.setLayerVisibility(config.getPosition(), false); } - TileLayerSource.getListsLocked(Splash.this, db.getReadableDatabase(), true); } } - if (newInstall || newConfig) { - Progress.dismissDialog(Splash.this, Progress.PROGRESS_BUILDING_IMAGERY_DATABASE); + if (state.isChecked()) { + Builder reallyBuilder = new AlertDialog.Builder(this); + reallyBuilder.setTitle(R.string.safe_delete_state_title); + reallyBuilder.setPositiveButton(R.string.safe_delete_state_text, (DialogInterface dialog2, int which2) -> { + Log.e(DEBUG_TAG, "Removing state files"); + this.deleteFile(StorageDelegator.FILENAME); + this.deleteFile(StorageDelegator.BACKUP_FILENAME); + dialog.dismiss(); + startupTask.execute(); + }); + reallyBuilder.setNegativeButton(R.string.no, null); + reallyBuilder.show(); + return; } - // read Presets here to avoid reading them on UI thread on startup of Main - Progress.showDialog(Splash.this, Progress.PROGRESS_LOADING_PRESET); - App.getCurrentPresets(Splash.this); - // - Intent intent = new Intent(Splash.this, Main.class); - intent.putExtra(SHORTCUT_EXTRAS_KEY, shortcutExtras); - startActivity(intent); - return null; - } - - @Override - protected void onPostExecute(Void result) { - Log.d(DEBUG_TAG, "onPostExecute"); - Progress.dismissDialog(Splash.this, Progress.PROGRESS_LOADING_PRESET); - Splash.this.finish(); - } - }.execute(); + dialog.dismiss(); + startupTask.execute(); + }); + }); + dialog.show(); } /** diff --git a/src/main/java/de/blau/android/osm/StorageDelegator.java b/src/main/java/de/blau/android/osm/StorageDelegator.java index 2f215f95d6..64a260ac6c 100755 --- a/src/main/java/de/blau/android/osm/StorageDelegator.java +++ b/src/main/java/de/blau/android/osm/StorageDelegator.java @@ -83,7 +83,8 @@ public class StorageDelegator implements Serializable, Exportable, DataStorage { */ private transient boolean imageryRecorded = false; - public static final String FILENAME = "lastActivity" + "." + FileExtensions.RES; + public static final String FILENAME = "lastActivity" + "." + FileExtensions.RES; + public static final String BACKUP_FILENAME = FILENAME + ".backup"; private transient SavingHelper savingHelper = new SavingHelper<>(); diff --git a/src/main/java/de/blau/android/resources/DataStyle.java b/src/main/java/de/blau/android/resources/DataStyle.java index 681432cb80..c4c88a72e2 100644 --- a/src/main/java/de/blau/android/resources/DataStyle.java +++ b/src/main/java/de/blau/android/resources/DataStyle.java @@ -67,10 +67,10 @@ import de.blau.android.util.XmlFileFilter; public final class DataStyle extends DefaultHandler { - private static final String I18N_DATASTYLE = "i18n/datastyle_"; - private static final String DEBUG_TAG = "DataStyle"; + private static final String I18N_DATASTYLE = "i18n/datastyle_"; + private static final Version CURRENT_VERSION = new Version("0.3.0"); private static final String FILE_PATH_STYLE_SUFFIX = "-profile.xml"; diff --git a/src/main/java/de/blau/android/util/Util.java b/src/main/java/de/blau/android/util/Util.java index f3e78f697e..b525499a64 100644 --- a/src/main/java/de/blau/android/util/Util.java +++ b/src/main/java/de/blau/android/util/Util.java @@ -19,7 +19,9 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; @@ -558,20 +560,33 @@ public static boolean supportsWebView(@NonNull Context ctx) { * @param packageManager a PackageManager instance * @return true if the package is installed */ - @SuppressWarnings("deprecation") + public static boolean isPackageInstalled(@NonNull String packageName, @NonNull PackageManager packageManager) { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)); - } else { - packageManager.getPackageInfo(packageName, 0); - } + getPackageInfo(packageName, packageManager); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } + /** + * Get the PackageInfo for a specific package + * + * @param packageName the name of the package + * @param packageManager a PackageManager instance + * @return a PackageInfo object if the package is installed + * @throws NameNotFoundException + */ + @SuppressWarnings("deprecation") + @NonNull + public static PackageInfo getPackageInfo(@NonNull String packageName, @NonNull PackageManager packageManager) throws NameNotFoundException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0)); + } + return packageManager.getPackageInfo(packageName, 0); + } + /** * Create a Po class from an InputStream * diff --git a/src/main/res/layout/safe_mode.xml b/src/main/res/layout/safe_mode.xml new file mode 100644 index 0000000000..67f5d25b32 --- /dev/null +++ b/src/main/res/layout/safe_mode.xml @@ -0,0 +1,64 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 01e0f801e3..a0c5e8d8d8 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -332,6 +332,13 @@ Require optional Add re-survey entry Add check entry + + Safe mode options + Set data style to minimal + Disable all layers + Remove saved state + Delete saved data state? + Yes (cannot be undone) Empty relation The %1$s relation has no members.\n\nLeaving it as is will make it non-editable in Vespucci. @@ -1411,6 +1418,7 @@ Default Regular expression State + Continue No entries Geocoder type diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml index 700c7aefa7..f15dfabc2e 100644 --- a/src/main/res/values/styles.xml +++ b/src/main/res/values/styles.xml @@ -7,28 +7,34 @@ @drawable/vespucci_splash @color/osm_green @style/SplashTheme2 + @color/material_red -