diff --git a/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt b/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt index b4a956d870d..4b5edc49973 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt +++ b/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt @@ -6,9 +6,12 @@ import org.odk.collect.maps.MapFragment object MapboxClassInstanceCreator { + private const val MAP_FRAGMENT = "org.odk.collect.mapbox.MapboxMapFragment" + @JvmStatic fun isMapboxAvailable(): Boolean { - return createMapboxMapFragment() != null && try { + return try { + getClass(MAP_FRAGMENT) System.loadLibrary("mapbox-common") true } catch (e: Throwable) { @@ -16,29 +19,23 @@ object MapboxClassInstanceCreator { } } - fun createMapboxMapFragment(): MapFragment? { - return createClassInstance("org.odk.collect.mapbox.MapboxMapFragment") + fun createMapboxMapFragment(): MapFragment { + return createClassInstance(MAP_FRAGMENT) } @JvmStatic - fun createMapBoxInitializationFragment(): Fragment? { - return createClassInstance("org.odk.collect.mapbox.MapBoxInitializationFragment") + fun createMapBoxInitializationFragment(): Fragment { + return createClassInstance("org.odk.collect.mapbox.MapBoxInitializationFragment") } @JvmStatic - fun createMapboxMapConfigurator(): MapConfigurator? { - return createClassInstance("org.odk.collect.mapbox.MapboxMapConfigurator") + fun createMapboxMapConfigurator(): MapConfigurator { + return createClassInstance("org.odk.collect.mapbox.MapboxMapConfigurator") } - private fun createClassInstance(className: String): T? { - return try { - Class.forName(className).newInstance() as T - } catch (e: ClassNotFoundException) { - null - } catch (e: IllegalAccessException) { - null - } catch (e: InstantiationException) { - null - } + private fun createClassInstance(className: String): T { + return getClass(className).newInstance() as T } + + private fun getClass(className: String): Class<*> = Class.forName(className) } diff --git a/collect_app/src/main/java/org/odk/collect/android/application/initialization/ApplicationInitializer.kt b/collect_app/src/main/java/org/odk/collect/android/application/initialization/ApplicationInitializer.kt index 1433d578c29..8aa064acf51 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/initialization/ApplicationInitializer.kt +++ b/collect_app/src/main/java/org/odk/collect/android/application/initialization/ApplicationInitializer.kt @@ -19,11 +19,13 @@ import org.odk.collect.analytics.Analytics import org.odk.collect.android.BuildConfig import org.odk.collect.android.application.Collect import org.odk.collect.android.application.initialization.upgrade.UpgradeInitializer +import org.odk.collect.android.geo.MapConfiguratorProvider import org.odk.collect.android.logic.actions.setgeopoint.CollectSetGeopointActionHandler import org.odk.collect.metadata.PropertyManager import org.odk.collect.osmdroid.OsmDroidInitializer import org.odk.collect.projects.ProjectsRepository import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.utilities.UserAgentProvider import timber.log.Timber import java.util.Locale @@ -40,8 +42,8 @@ class ApplicationInitializer( ) { fun initialize() { runInitializers() - initializeFrameworks() initializeLocale() + initializeFrameworks() } private fun runInitializers() { @@ -97,6 +99,18 @@ class ApplicationInitializer( } private fun initializeMapFrameworks() { + // Reset basemap setting if the currently selected is not available + MapConfiguratorProvider.initOptions(context) + val availableBaseMaps = MapConfiguratorProvider.getIds() + val baseMapSetting = + settingsProvider.getUnprotectedSettings().getString(ProjectKeys.KEY_BASEMAP_SOURCE) + if (!availableBaseMaps.contains(baseMapSetting)) { + settingsProvider.getUnprotectedSettings().save( + ProjectKeys.KEY_BASEMAP_SOURCE, + availableBaseMaps[0] + ) + } + try { MapsInitializer.initialize( context, diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index 3c659cd98e9..7bbec8189d7 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -12,6 +12,8 @@ import static org.odk.collect.settings.keys.ProjectKeys.KEY_USGS_MAP_STYLE; import static org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString; +import android.content.Context; + import androidx.annotation.NonNull; import com.google.android.gms.maps.GoogleMap; @@ -30,7 +32,7 @@ public class MapConfiguratorProvider { - private static final SourceOption[] SOURCE_OPTIONS = initOptions(); + private static SourceOption[] sourceOptions; private static final String USGS_URL_BASE = "https://basemap.nationalmap.gov/arcgis/rest/services"; private static final String OSM_COPYRIGHT = "© OpenStreetMap contributors"; @@ -48,17 +50,26 @@ private MapConfiguratorProvider() { * to make them easier to find. This defines the basemap sources and the * basemap options available under each one, in their order of appearance. */ - private static SourceOption[] initOptions() { + public static void initOptions(Context context) { + if (sourceOptions != null) { + return; + } + ArrayList sourceOptions = new ArrayList<>(); - sourceOptions.add(new SourceOption(BASEMAP_SOURCE_GOOGLE, org.odk.collect.strings.R.string.basemap_source_google, - new GoogleMapConfigurator( - KEY_GOOGLE_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_google, - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_NORMAL, org.odk.collect.strings.R.string.streets), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_TERRAIN, org.odk.collect.strings.R.string.terrain), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_HYBRID, org.odk.collect.strings.R.string.hybrid), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_SATELLITE, org.odk.collect.strings.R.string.satellite) - ) - )); + + GoogleMapConfigurator googleMapsConfigurator = new GoogleMapConfigurator( + KEY_GOOGLE_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_google, + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_NORMAL, org.odk.collect.strings.R.string.streets), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_TERRAIN, org.odk.collect.strings.R.string.terrain), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_HYBRID, org.odk.collect.strings.R.string.hybrid), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_SATELLITE, org.odk.collect.strings.R.string.satellite) + ); + + if (googleMapsConfigurator.isAvailable(context)) { + sourceOptions.add(new SourceOption(BASEMAP_SOURCE_GOOGLE, org.odk.collect.strings.R.string.basemap_source_google, + googleMapsConfigurator + )); + } if (isMapboxSupported()) { sourceOptions.add(new SourceOption(BASEMAP_SOURCE_MAPBOX, org.odk.collect.strings.R.string.basemap_source_mapbox, @@ -115,7 +126,7 @@ private static SourceOption[] initOptions() { ) )); - return sourceOptions.toArray(new SourceOption[]{}); + MapConfiguratorProvider.sourceOptions = sourceOptions.toArray(new SourceOption[]{}); } /** Gets the currently selected MapConfigurator. */ @@ -134,18 +145,18 @@ MapConfigurator getConfigurator() { /** Gets a list of the IDs of the basemap sources, in order. */ public static String[] getIds() { - String[] ids = new String[SOURCE_OPTIONS.length]; + String[] ids = new String[sourceOptions.length]; for (int i = 0; i < ids.length; i++) { - ids[i] = SOURCE_OPTIONS[i].id; + ids[i] = sourceOptions[i].id; } return ids; } /** Gets a list of the label string IDs of the basemap sources, in order. */ public static int[] getLabelIds() { - int[] labelIds = new int[SOURCE_OPTIONS.length]; + int[] labelIds = new int[sourceOptions.length]; for (int i = 0; i < labelIds.length; i++) { - labelIds[i] = SOURCE_OPTIONS[i].labelId; + labelIds[i] = sourceOptions[i].labelId; } return labelIds; } @@ -162,12 +173,13 @@ private static boolean isMapboxSupported() { if (id == null) { id = DaggerUtils.getComponent(getApplication()).settingsProvider().getUnprotectedSettings().getString(KEY_BASEMAP_SOURCE); } - for (SourceOption option : SOURCE_OPTIONS) { + for (SourceOption option : sourceOptions) { if (option.id.equals(id)) { return option; } } - return SOURCE_OPTIONS[0]; + + return sourceOptions[0]; } private static Collect getApplication() { diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt index 04140039b62..d392874cef1 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt @@ -20,7 +20,7 @@ class MapFragmentFactoryImpl(private val settingsProvider: SettingsProvider) : M return when { isBasemapOSM(settings.getString(KEY_BASEMAP_SOURCE)) -> OsmDroidMapFragment() - settings.getString(KEY_BASEMAP_SOURCE) == BASEMAP_SOURCE_MAPBOX -> MapboxClassInstanceCreator.createMapboxMapFragment() ?: GoogleMapFragment() + settings.getString(KEY_BASEMAP_SOURCE) == BASEMAP_SOURCE_MAPBOX -> MapboxClassInstanceCreator.createMapboxMapFragment() else -> GoogleMapFragment() } } diff --git a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuActivity.kt b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuActivity.kt index 5c7d82e9cef..07b84d8be9c 100644 --- a/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuActivity.kt +++ b/collect_app/src/main/java/org/odk/collect/android/mainmenu/MainMenuActivity.kt @@ -190,7 +190,7 @@ class MainMenuActivity : LocalizedActivity() { if (isMapboxAvailable()) { supportFragmentManager .beginTransaction() - .add(R.id.map_box_initialization_fragment, createMapBoxInitializationFragment()!!) + .add(R.id.map_box_initialization_fragment, createMapBoxInitializationFragment()) .commit() } } diff --git a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt index 10469f2e487..5784fc3eeac 100644 --- a/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt +++ b/collect_app/src/main/java/org/odk/collect/android/preferences/screens/MapsPreferencesFragment.kt @@ -107,6 +107,7 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment() { MapConfiguratorProvider.getIds(), settingsProvider.getUnprotectedSettings() ) + basemapSourcePref.setIconSpaceReserved(false) onBasemapSourceChanged(MapConfiguratorProvider.getConfigurator()) basemapSourcePref.setOnPreferenceChangeListener { _: Preference?, value: Any -> diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java index 89e40ae16de..1d91eaf30d1 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java @@ -3,7 +3,6 @@ import static org.odk.collect.androidshared.ui.PrefUtils.createListPref; import static org.odk.collect.androidshared.ui.PrefUtils.getInt; -import android.app.ActivityManager; import android.content.Context; import android.os.Bundle; @@ -38,28 +37,19 @@ public GoogleMapConfigurator(String prefKey, int sourceLabelId, GoogleMapTypeOpt } @Override public boolean isAvailable(Context context) { - return isGoogleMapsSdkAvailable(context) && isGooglePlayServicesAvailable(context); + return GoogleMapFragment.isAvailable(context); } @Override public void showUnavailableMessage(Context context) { - if (!isGoogleMapsSdkAvailable(context)) { + if (!GoogleMapFragment.isGoogleMapsSdkAvailable(context)) { ToastUtils.showLongToast(context, context.getString( org.odk.collect.strings.R.string.basemap_source_unavailable, context.getString(sourceLabelId))); } - if (!isGooglePlayServicesAvailable(context)) { - new PlayServicesChecker().showGooglePlayServicesAvailabilityErrorDialog(context); - } - } - private boolean isGoogleMapsSdkAvailable(Context context) { - // The Google Maps SDK for Android requires OpenGL ES version 2. - // See https://developers.google.com/maps/documentation/android-sdk/config - return ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)) - .getDeviceConfigurationInfo().reqGlEsVersion >= 0x20000; - } - - private boolean isGooglePlayServicesAvailable(Context context) { - return new PlayServicesChecker().isGooglePlayServicesAvailable(context); + PlayServicesChecker playServicesChecker = new PlayServicesChecker(); + if (!playServicesChecker.isGooglePlayServicesAvailable(context)) { + playServicesChecker.showGooglePlayServicesAvailabilityErrorDialog(context); + } } @Override public List createPrefs(Context context, Settings settings) { diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java index bf8fd7b275b..6c3012a59ad 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java @@ -18,7 +18,10 @@ import static org.odk.collect.maps.MapConsts.POLYLINE_STROKE_WIDTH; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.location.Location; import android.os.Bundle; import android.os.Handler; @@ -48,6 +51,7 @@ import com.google.android.gms.maps.model.TileOverlayOptions; import org.odk.collect.androidshared.system.ContextUtils; +import org.odk.collect.androidshared.system.PlayServicesChecker; import org.odk.collect.androidshared.ui.ToastUtils; import org.odk.collect.googlemaps.GoogleMapConfigurator.GoogleMapTypeOption; import org.odk.collect.location.LocationClient; @@ -83,6 +87,28 @@ public class GoogleMapFragment extends SupportMapFragment implements // Bundle keys understood by applyConfig(). static final String KEY_MAP_TYPE = "MAP_TYPE"; + public static boolean isAvailable(Context context) { + try { + ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + String apiKey = applicationInfo.metaData.getString("com.google.android.geo.API_KEY"); + + return isGoogleMapsSdkAvailable(context) && isGooglePlayServicesAvailable(context) && !apiKey.equals(""); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static boolean isGoogleMapsSdkAvailable(Context context) { + // The Google Maps SDK for Android requires OpenGL ES version 2. + // See https://developers.google.com/maps/documentation/android-sdk/config + return ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)) + .getDeviceConfigurationInfo().reqGlEsVersion >= 0x20000; + } + + private static boolean isGooglePlayServicesAvailable(Context context) { + return new PlayServicesChecker().isGooglePlayServicesAvailable(context); + } + @Inject ReferenceLayerRepository referenceLayerRepository;