Skip to content

Commit

Permalink
Only allow Map engines that are available
Browse files Browse the repository at this point in the history
Fallback to available ones on app start
  • Loading branch information
seadowg committed Aug 8, 2023
1 parent 6490c16 commit 0e93ab0
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,36 @@ 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) {
false
}
}

fun createMapboxMapFragment(): MapFragment? {
return createClassInstance<MapFragment>("org.odk.collect.mapbox.MapboxMapFragment")
fun createMapboxMapFragment(): MapFragment {
return createClassInstance(MAP_FRAGMENT)
}

@JvmStatic
fun createMapBoxInitializationFragment(): Fragment? {
return createClassInstance<Fragment>("org.odk.collect.mapbox.MapBoxInitializationFragment")
fun createMapBoxInitializationFragment(): Fragment {
return createClassInstance("org.odk.collect.mapbox.MapBoxInitializationFragment")
}

@JvmStatic
fun createMapboxMapConfigurator(): MapConfigurator? {
return createClassInstance<MapConfigurator>("org.odk.collect.mapbox.MapboxMapConfigurator")
fun createMapboxMapConfigurator(): MapConfigurator {
return createClassInstance("org.odk.collect.mapbox.MapboxMapConfigurator")
}

private fun <T> 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 <T> createClassInstance(className: String): T {
return getClass(className).newInstance() as T
}

private fun getClass(className: String): Class<*> = Class.forName(className)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,8 +42,8 @@ class ApplicationInitializer(
) {
fun initialize() {
runInitializers()
initializeFrameworks()
initializeLocale()
initializeFrameworks()
}

private fun runInitializers() {
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";
Expand All @@ -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<SourceOption> 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,
Expand Down Expand Up @@ -115,7 +126,7 @@ private static SourceOption[] initOptions() {
)
));

return sourceOptions.toArray(new SourceOption[]{});
MapConfiguratorProvider.sourceOptions = sourceOptions.toArray(new SourceOption[]{});
}

/** Gets the currently selected MapConfigurator. */
Expand All @@ -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;
}
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class MapsPreferencesFragment : BaseProjectPreferencesFragment() {
MapConfiguratorProvider.getIds(),
settingsProvider.getUnprotectedSettings()
)

basemapSourcePref.setIconSpaceReserved(false)
onBasemapSourceChanged(MapConfiguratorProvider.getConfigurator())
basemapSourcePref.setOnPreferenceChangeListener { _: Preference?, value: Any ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<Preference> createPrefs(Context context, Settings settings) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 0e93ab0

Please sign in to comment.