package mil.nga.dice.map;
import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseExpandableListAdapter;
import android.widget.CheckBox;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mil.nga.dice.DICEConstants;
import mil.nga.dice.R;
import mil.nga.dice.map.geopackage.GeoPackageMapData;
import mil.nga.dice.map.geopackage.GeoPackageSelected;
import mil.nga.dice.map.geopackage.GeoPackageTableMapData;
import mil.nga.dice.report.ReportUtils;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageManager;
import mil.nga.geopackage.extension.link.FeatureTileTableLinker;
import mil.nga.geopackage.factory.GeoPackageFactory;
import mil.nga.geopackage.features.index.FeatureIndexManager;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.tiles.user.TileDao;
/**
* View, enabled, disable, and delete map tile and feature overlays
*/
public class OverlaysActivity extends AppCompatActivity {
/**
* Permission request read external storage id
*/
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 100;
/**
* Overlays adapter as the expandable list adapter
*/
private OverlaysAdapter overlaysAdapter;
/**
* Expandable list view for displaying the GeoPackages and tables
*/
private ExpandableListView expandableList;
/**
* Selected GeoPackages for retrieving and updating selected
*/
private GeoPackageSelected selectedGeoPackages;
/**
* Flag to track when selection changes were made
*/
private boolean changes = false;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.overlays);
expandableList = (ExpandableListView) findViewById(R.id.overlays_list);
expandableList.setEnabled(false);
if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(this, R.style.AppCompatAlertDialogStyle)
.setTitle(R.string.overlays_access_title)
.setMessage(R.string.overlays_access_message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(OverlaysActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
})
.create()
.show();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
}
overlaysAdapter = new OverlaysAdapter(this);
expandableList.setAdapter(overlaysAdapter);
expandableList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
int itemType = ExpandableListView.getPackedPositionType(id);
if (itemType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
int childPosition = ExpandableListView.getPackedPositionChild(id);
int groupPosition = ExpandableListView.getPackedPositionGroup(id);
// Handle child row long clicks here
return true;
} else if (itemType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
int groupPosition = ExpandableListView.getPackedPositionGroup(id);
GeoPackageMapData cacheOverlay = (GeoPackageMapData) overlaysAdapter.getGroup(groupPosition);
if (!cacheOverlay.isLocked()) {
deleteCacheOverlayConfirm(cacheOverlay);
}
return true;
}
return false;
}
});
selectedGeoPackages = new GeoPackageSelected(this);
update();
}
/**
* Update the overlays table by searching for public GeoPackages and their selected state
*/
private void update() {
List<GeoPackageMapData> mapDataList = new ArrayList<>();
Map<String, Set<String>> selectedCaches = selectedGeoPackages.getSelectedMap();
GeoPackageManager manager = GeoPackageFactory.getManager(this);
File reportsDir = ReportUtils.getReportDirectory();
String like = DICEConstants.DICE_TEMP_CACHE_SUFFIX + "%";
List<String> geoPackages = manager.databasesNotLike(like);
for (String name : geoPackages) {
GeoPackageMapData mapData = new GeoPackageMapData(name);
Set<String> selectedTables = selectedCaches.get(name);
if (selectedTables != null) {
mapData.setEnabled(true);
}
mapDataList.add(mapData);
String filePath = manager.getPath(name);
boolean locked = filePath.startsWith(reportsDir.getAbsolutePath());
mapData.setLocked(locked);
GeoPackage geoPackage = manager.open(name);
// GeoPackage tile tables, build a mapping between table name and the created map overlays
Map<String, GeoPackageTableMapData> tileMapOverlays = new HashMap<>();
List<String> tileTables = geoPackage.getTileTables();
for (String tileTable : tileTables) {
TileDao tileDao = geoPackage.getTileDao(tileTable);
GeoPackageTableMapData tableMapData = new GeoPackageTableMapData(tileTable, false);
if (mapData.isEnabled() && (selectedTables.size() == 0 || selectedTables.contains(tileTable))) {
tableMapData.setEnabled(true);
}
tableMapData.setCount(tileDao.count());
tableMapData.setMinZoom(tileDao.getMinZoom());
tableMapData.setMaxZoom(tileDao.getMaxZoom());
tileMapOverlays.put(tileTable, tableMapData);
}
// Get a linker to find tile tables linked to features
FeatureTileTableLinker linker = new FeatureTileTableLinker(geoPackage);
Map<String, GeoPackageTableMapData> linkedTileMapOverlays = new HashMap<>();
List<String> featureTables = geoPackage.getFeatureTables();
for (String featureTable : featureTables) {
FeatureDao featureDao = geoPackage.getFeatureDao(featureTable);
FeatureIndexManager indexer = new FeatureIndexManager(this, geoPackage, featureDao);
boolean indexed = indexer.isIndexed();
indexer.close();
long minZoom = 0;
if (indexed) {
minZoom = featureDao.getZoomLevel() + DICEConstants.DICE_FEATURE_TILES_MIN_ZOOM_OFFSET;
minZoom = Math.max(minZoom, 0);
minZoom = Math.min(minZoom, DICEConstants.DICE_FEATURES_MAX_ZOOM);
}
GeoPackageTableMapData tableMapData = new GeoPackageTableMapData(featureTable, true);
if (mapData.isEnabled() && (selectedTables.size() == 0 || selectedTables.contains(featureTable))) {
tableMapData.setEnabled(true);
}
tableMapData.setCount(featureDao.count());
tableMapData.setMinZoom(minZoom);
tableMapData.setMaxZoom(DICEConstants.DICE_FEATURES_MAX_ZOOM);
// If indexed, check for linked tile tables
if (indexed) {
List<String> linkedTileTables = linker.getTileTablesForFeatureTable(featureTable);
for (String linkedTileTable : linkedTileTables) {
// Get the tile table cache overlay
GeoPackageTableMapData tileTableMapData = tileMapOverlays.get(linkedTileTable);
if (tileTableMapData != null) {
// Remove from tile cache overlays so the tile table is not added as stand alone, and add to the linked overlays
tileMapOverlays.remove(linkedTileTable);
linkedTileMapOverlays.put(linkedTileTable, tileTableMapData);
} else {
// Another feature table may already be linked to this table, so check the linked overlays
tileTableMapData = linkedTileMapOverlays.get(linkedTileTable);
}
// Add the linked tile table to the feature table
if (tileTableMapData != null) {
tableMapData.addLinked(tileTableMapData);
}
}
}
mapData.addTable(tableMapData);
}
// Add stand alone tile tables that were not linked to feature tables
for (GeoPackageTableMapData tileCacheOverlay : tileMapOverlays.values()) {
mapData.addTable(tileCacheOverlay);
}
geoPackage.close();
}
overlaysAdapter.setOverlays(mapDataList);
overlaysAdapter.notifyDataSetChanged();
expandableList.setEnabled(true);
}
/**
* {@inheritDoc}
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* {@inheritDoc}
*/
@Override
public void onBackPressed() {
if (changes || overlaysAdapter.isChanges()) {
selectedGeoPackages.updateSelected(getSelected());
}
finish();
}
/**
* {@inheritDoc}
*/
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// TODO
}
break;
}
}
}
/**
* Cache Overlay Expandable list adapter
*/
public static class OverlaysAdapter extends BaseExpandableListAdapter {
/**
* Context
*/
private OverlaysActivity activity;
/**
* GeoPackage Map data
*/
private List<GeoPackageMapData> overlays = new ArrayList<>();
/**
* Flag to track when selection changes were made
*/
private boolean changes = false;
/**
* Constructor
*
* @param activity
*/
public OverlaysAdapter(OverlaysActivity activity) {
this.activity = activity;
}
/**
* Set the overlays
*
* @param overlays
*/
public void setOverlays(List<GeoPackageMapData> overlays) {
this.overlays = overlays;
}
/**
* Is there changes
*
* @return true if changes
*/
public boolean isChanges() {
return changes;
}
/**
* Get the overlays
*
* @return
*/
public List<GeoPackageMapData> getOverlays() {
return overlays;
}
/**
* {@inheritDoc}
*/
@Override
public int getGroupCount() {
return overlays.size();
}
/**
* {@inheritDoc}
*/
@Override
public int getChildrenCount(int i) {
return overlays.get(i).getTables().size();
}
/**
* {@inheritDoc}
*/
@Override
public Object getGroup(int i) {
return overlays.get(i);
}
/**
* {@inheritDoc}
*/
@Override
public Object getChild(int i, int j) {
return overlays.get(i).getTables().get(j);
}
/**
* {@inheritDoc}
*/
@Override
public long getGroupId(int i) {
return i;
}
/**
* {@inheritDoc}
*/
@Override
public long getChildId(int i, int j) {
return j;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasStableIds() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public View getGroupView(int i, boolean isExpanded, View view,
ViewGroup viewGroup) {
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(activity);
view = inflater.inflate(R.layout.overlays_group, viewGroup, false);
}
ImageView imageView = (ImageView) view.findViewById(R.id.overlays_group_image);
TextView cacheName = (TextView) view.findViewById(R.id.overlays_group_name);
ImageView lockedImageView = (ImageView) view.findViewById(R.id.overlays_group_locked_image);
CheckBox checkBox = (CheckBox) view.findViewById(R.id.overlays_group_checkbox);
final GeoPackageMapData overlay = overlays.get(i);
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean checked = ((CheckBox) v).isChecked();
overlay.setEnabled(checked);
changes = true;
boolean modified = false;
for (GeoPackageTableMapData childCache : overlay.getTables()) {
if (childCache.isEnabled() != checked) {
childCache.setEnabled(checked);
modified = true;
}
}
if (modified) {
notifyDataSetChanged();
}
}
});
Integer imageResource = overlay.getIconImageResourceId();
if (imageResource != null) {
imageView.setImageResource(imageResource);
} else {
imageView.setImageResource(-1);
}
cacheName.setText(overlay.getName());
if (overlay.isLocked()) {
lockedImageView.setVisibility(View.VISIBLE);
} else {
lockedImageView.setVisibility(View.INVISIBLE);
}
checkBox.setChecked(overlay.isEnabled());
return view;
}
/**
* {@inheritDoc}
*/
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(activity);
convertView = inflater.inflate(R.layout.overlays_child, parent, false);
}
final GeoPackageMapData overlay = overlays.get(groupPosition);
final GeoPackageTableMapData childCache = overlay.getTables().get(childPosition);
ImageView imageView = (ImageView) convertView.findViewById(R.id.overlays_child_image);
TextView tableName = (TextView) convertView.findViewById(R.id.overlays_child_name);
TextView info = (TextView) convertView.findViewById(R.id.overlays_child_info);
CheckBox checkBox = (CheckBox) convertView.findViewById(R.id.overlays_child_checkbox);
convertView.findViewById(R.id.divider).setVisibility(isLastChild ? View.VISIBLE : View.INVISIBLE);
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean checked = ((CheckBox) v).isChecked();
childCache.setEnabled(checked);
changes = true;
boolean modified = false;
if (checked) {
if (!overlay.isEnabled()) {
overlay.setEnabled(true);
modified = true;
}
} else if (overlay.isEnabled()) {
modified = true;
for (GeoPackageTableMapData childCache : overlay.getTables()) {
if (childCache.isEnabled()) {
modified = false;
break;
}
}
if (modified) {
overlay.setEnabled(false);
}
}
if (modified) {
notifyDataSetChanged();
}
}
});
tableName.setText(childCache.getName());
info.setText(childCache.getInfo());
checkBox.setChecked(childCache.isEnabled());
Integer imageResource = childCache.getIconImageResourceId();
if (imageResource != null) {
imageView.setImageResource(imageResource);
} else {
imageView.setImageResource(-1);
}
return convertView;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isChildSelectable(int i, int j) {
return true;
}
}
/**
* Delete the cache overlay
*
* @param cacheOverlay
*/
private void deleteCacheOverlayConfirm(final GeoPackageMapData cacheOverlay) {
AlertDialog deleteDialog = new AlertDialog.Builder(this)
.setTitle("Delete Cache")
.setMessage("Delete " + cacheOverlay.getName() + " Cache?")
.setPositiveButton("Delete",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
deleteCacheOverlay(cacheOverlay);
}
})
.setNegativeButton(getString(R.string.button_cancel_label),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
dialog.dismiss();
}
}).create();
deleteDialog.show();
}
/**
* Delete the cache overlay
*
* @param cacheOverlay
*/
private void deleteCacheOverlay(GeoPackageMapData cacheOverlay) {
expandableList.setEnabled(false);
// Get the GeoPackage file
GeoPackageManager manager = GeoPackageFactory.getManager(this);
// Delete the cache from the GeoPackage manager
manager.delete(cacheOverlay.getName());
changes = true;
update();
}
/**
* Get the selected GeoPackages and tables
*
* @return selected map
*/
private Map<String, Set<String>> getSelected() {
Map<String, Set<String>> selected = new HashMap<>();
for (GeoPackageMapData mapData : overlaysAdapter.getOverlays()) {
if (mapData.isEnabled()) {
Set<String> selectedTables = new HashSet<>();
selected.put(mapData.getName(), selectedTables);
for (GeoPackageTableMapData tableMapData : mapData.getTables()) {
if (tableMapData.isEnabled()) {
selectedTables.add(tableMapData.getName());
}
for (GeoPackageTableMapData linkedTable : tableMapData.getLinked()) {
if (linkedTable.isEnabled()) {
selectedTables.add(linkedTable.getName());
}
}
}
}
}
return selected;
}
}