package com.example.meinwald.ui.map; import android.Manifest; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.location.Location; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.example.meinwald.BuildConfig; import com.example.meinwald.R; import com.example.meinwald.ui.area.AreaReference; import com.example.meinwald.ui.area.OwnArea; import com.example.meinwald.ui.task.OwnTask; import com.google.android.gms.common.util.Hex; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.PolygonOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.maps.android.SphericalUtil; import org.json.JSONArray; import org.json.JSONException; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; import static com.example.meinwald.R.layout.fragment_map; public class MapsFragment extends Fragment implements OnMapReadyCallback { /** * ______________________________________________________________________________________________ * MAPS */ // A default location and default zoom to use when location permission is not granted. private static final LatLng M_DEFAULT_LOCATION = new LatLng(49.5, 11.0); //Nuremberg private static final int DEFAULT_ZOOM = 1; private static final float MAX_ZOOM = (float) 20.0; private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1; private static final String TAG = MapsFragment.class.getSimpleName(); // Keys for storing activity state. private static final String KEY_CAMERA_POSITION = "camera_position"; private static final String KEY_LOCATION = "location"; private GoogleMap mMap; private CameraPosition mCameraPosition; /** * The entry point to the Fused Location Provider. */ private FusedLocationProviderClient mFusedLocationProviderClient; private boolean mLocationPermissionGranted; /** * The geographical location where the device is currently located. * That is the last-known location retrieved by the Fused Location Provider. */ private Location mLastKnownLocation; /** * ______________________________________________________________________________________________ * TASKS */ private List allTasks; private static final String KEY_ALL_TASKS = "allTasks"; /** * ______________________________________________________________________________________________ * STORAGEE */ private static final int READ_EXTERNAL_STORAGE_CODE = 200; private static final int WRITE_EXTERNAL_STORAGE_CODE = 201; private boolean mReadStorageGranted; private boolean mWriteStorageGranted; /** * ______________________________________________________________________________________________ * AREA */ private List allAreas; private static final String KEY_AREA_REFERENCES = "areaReferences"; private List areaReferences; @Override public void onCreate (Bundle savedInstanceState) { super.onCreate(savedInstanceState); areaReferences = new ArrayList<>(); areaReferences = readAreaReferencesFromPreferences(); } /** * Is called when the MapsFragment view is created. Initializes all service connections, the location client and map itself. * @param inflater Calling LayoutInflater * @param container ViewGroup * @param savedInstanceState Applications savedInstanceState * @return */ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //get view to inflate View root = inflater.inflate(fragment_map, container, false); //get storage permission getReadStoragePermission(); //get tasks allTasks = readTasksFromPreferences(); //get areas //instantiate area lists allAreas = new ArrayList<>(); //get saved areas allAreas = loadAreasFromPreferences(); //display map fragment SupportMapFragment mapFragment = new SupportMapFragment(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); transaction.add(R.id.map, mapFragment).commit(); //construct a FusedLocationProviderClient. mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(getActivity()); //wait for map is ready mapFragment.getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap googleMap) { //save map instance locally mMap = googleMap; //set max zoom level mMap.setMaxZoomPreference(MAX_ZOOM); mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { if (mMap.getCameraPosition().zoom < 17.5) { mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(marker.getPosition().latitude, marker.getPosition().longitude),(float)17.5)); } else { mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(marker.getPosition().latitude, marker.getPosition().longitude),mMap.getCameraPosition().zoom)); } return false; } }); //retrieve saved instance state mCameraPosition = parseCameraPosition(PreferenceManager.getDefaultSharedPreferences(getContext()).getString(KEY_CAMERA_POSITION, new CameraPosition(new LatLng(45.0,11.0), (float) 1.0, (float) 0.0,(float) 0.0).toString())); mLastKnownLocation = new Location(PreferenceManager.getDefaultSharedPreferences(getContext()).getString(KEY_LOCATION, new LatLng(45.0,11.0).toString())); mCameraPosition = parseCameraPosition(PreferenceManager.getDefaultSharedPreferences(getContext()).getString(KEY_CAMERA_POSITION, new CameraPosition(new LatLng(45.0,11.0), (float) 1.0, (float) 0.0,(float) 0.0).toString())); //move camera to last position mMap.moveCamera(CameraUpdateFactory.newCameraPosition(mCameraPosition)); //get permission getLocationPermission(); //turn on the My Location layer and the related control on the map. updateLocationUI(); // Get the current location of the device and set the position of the map. getDeviceLocation(); } }); return root; } @Override public void onPause() { //save current location and camera position to shared preferences if (mMap.getCameraPosition() != null && mLastKnownLocation != null) { PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putString(KEY_CAMERA_POSITION, mMap.getCameraPosition().toString()).commit(); PreferenceManager.getDefaultSharedPreferences(getContext()).edit().putString(KEY_LOCATION, mLastKnownLocation.toString()).commit(); } super.onPause(); } private void drawAreas() { for (OwnArea area: allAreas) { if (area.getLocations() != null && area.getLocations().size()>0) { PolygonOptions options = new PolygonOptions().clickable(true); if (BuildConfig.DEBUG) { Log.d(TAG, "Color: " + getAreaColor(area.getLastChecked())); Log.d(TAG, "Color: " + String.valueOf(getAreaColor(area.getLastChecked() & 0x00FFFFFF) | 0x60000000)); Log.d(TAG, "Area: " + SphericalUtil.computeArea(area.getLocations())); } options.strokeColor(getAreaColor(area.getLastChecked())); options.fillColor((options.getStrokeColor() & 0x00FFFFFF) | 0x60000000); for (LatLng location: area.getLocations()) { options.add(location); if (BuildConfig.DEBUG) { Log.d(TAG, "Position: " + location.toString()); } } options.add(area.getLocations().get(0)); mMap.addPolygon(options); } } } /** * Reads areaReferences saved in preferences as jsonStrings * * @return list of AreaReferences */ private List readAreaReferencesFromPreferences() { if (BuildConfig.DEBUG) { Log.d(TAG, "readAreaReferencesFromPreferences()"); } List areas = new ArrayList<>(); try { String jsonString = PreferenceManager.getDefaultSharedPreferences(getContext()).getString(KEY_AREA_REFERENCES, "error"); if(!"error".equalsIgnoreCase(jsonString)) { JSONArray jsonObjects = new JSONArray(jsonString); for (int i = 0; i loadAreasFromPreferences() { if (BuildConfig.DEBUG) { Log.d(TAG, "loadAreasFromPreferences() reference size: " + areaReferences.size()); } List areas = new ArrayList<>(); Integer i = 0; for (AreaReference reference: areaReferences) { String jsonString = readAreaFromExternalStorage(reference.getPath()); areas.add(new OwnArea(jsonString)); if (BuildConfig.DEBUG) { Log.d(TAG, "load area from preferences: " + areas.get(i).toString()); } i++; } return areas; } /** * Read OwnArea from file in external storage and return as a string. * * @param path File path where the OwnArea is saved. * @return */ private String readAreaFromExternalStorage(String path) { if (BuildConfig.DEBUG) { Log.d(TAG, "readAreaFromExternalStorage()"); } //Get the text file File file = new File(path); //Read text from file StringBuilder text = new StringBuilder(); try { BufferedReader br = new BufferedReader(new FileReader(file)); String line; while ((line = br.readLine()) != null) { text.append(line); } br.close(); } catch (IOException e) { if (BuildConfig.DEBUG) { Log.e(TAG, "Failed read from external storage: " + e.toString()); } } return text.toString(); } @Override public void onResume() { super.onResume(); } /** * Parses a string and creates a new CameraPosition from it. * Use CameraPosition.toString() to get corresponding string. * @param posString String which includes a camera position. * @return The parsed position of type CameraPosition. */ private CameraPosition parseCameraPosition(String posString) { //parse string from shared preferences (CameraPosition.toString()) String[] stringsplit = posString.split(" "); float lat = Float.parseFloat(stringsplit[1].split(",")[0].substring(1)); float lng = Float.parseFloat(stringsplit[1].split(",")[1].substring(0,stringsplit[1].split(",")[1].length()-1)); float zoom = Float.parseFloat(stringsplit[2].split("=")[1].substring(0,stringsplit[2].split("=")[1].length()-1)); float tilt = Float.parseFloat(stringsplit[3].split("=")[1].substring(0,stringsplit[3].split("=")[1].length()-1)); float bearing = Float.parseFloat(stringsplit[4].split("=")[1].substring(0,stringsplit[4].split("=")[1].length()-1)); return new CameraPosition(new LatLng(lat,lng),zoom,tilt,bearing); } /** * Request location permission, so that we can get the location of the device. * The result of the permission request is handled by a callback, * onRequestPermissionsResult. * */ private void getLocationPermission() { if (ContextCompat.checkSelfPermission(getActivity().getApplicationContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { mLocationPermissionGranted = true; } else { ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION); } } /** * DOCU ME! * TOOD: What are grant results etc for? * @param requestCode * @param permissions * @param grantResults */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { mLocationPermissionGranted = false; switch (requestCode) { case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { updateLocationUI(); mLocationPermissionGranted = true; } } case READ_EXTERNAL_STORAGE_CODE: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { mReadStorageGranted = true; } } } } /** * Get the best and most recent location of the device, which may be null in rare * cases when a location is not available. * */ private void getDeviceLocation() { try { if (mLocationPermissionGranted) { Task locationResult = mFusedLocationProviderClient.getLastLocation(); locationResult.addOnCompleteListener(getActivity(), new OnCompleteListener() { @Override public void onComplete(@NonNull Task task) { if (task.isSuccessful()) { // Set the last known position to the current location of the device. mLastKnownLocation = task.getResult(); if (BuildConfig.DEBUG) { if (task.getResult() != null) { Log.d(TAG, "getDeviceLocation " + task.getResult().toString()); } } } else { if (BuildConfig.DEBUG) { Log.d(TAG, "Current location is null. Using defaults."); Log.e(TAG, "Exception: %s", task.getException()); } mMap.moveCamera(CameraUpdateFactory .newLatLngZoom(M_DEFAULT_LOCATION, DEFAULT_ZOOM)); //mMap.getUiSettings().setMyLocationButtonEnabled(false); if (BuildConfig.DEBUG) { Log.d(TAG, "getDeviceLocation: task not successful"); } } } }); } } catch (SecurityException e) { if (BuildConfig.DEBUG) { Log.e(TAG, "Exception occurred: "); e.printStackTrace(); } } } private void updateLocationUI() { if (mMap == null) { if (BuildConfig.DEBUG) { Log.d(TAG, "Permission: Map not ready!"); } return; } try { if (mLocationPermissionGranted) { mMap.setMyLocationEnabled(true); mMap.getUiSettings().setMapToolbarEnabled(true); mMap.getUiSettings().setMyLocationButtonEnabled(true); mMap.getUiSettings().setCompassEnabled(true); mMap.getUiSettings().setZoomControlsEnabled(true); if (BuildConfig.DEBUG) { Log.d(TAG, "Permission: Granted!"); } } else { mMap.setMyLocationEnabled(false); mMap.getUiSettings().setMyLocationButtonEnabled(false); mMap.getUiSettings().setZoomControlsEnabled(false); mMap.getUiSettings().setCompassEnabled(false); mMap.getUiSettings().setMapToolbarEnabled(false); mLastKnownLocation = null; if (BuildConfig.DEBUG) { Log.d(TAG, "Permission: Denied!"); } getLocationPermission(); } } catch (SecurityException e) { if (BuildConfig.DEBUG) { Log.e(TAG, "Exception occurred: "); e.printStackTrace(); } } for (OwnTask task: allTasks) { MarkerOptions marker = new MarkerOptions(); //set options marker.position(new LatLng(task.getLocation().getLatitude(), task.getLocation().getLongitude())); marker.title(task.getTitle()); mMap.addMarker(marker); } drawAreas(); } /** * Decides color of area based on the time passed since last ckeck update. * * @param time difference in milliseconds between last check and now * @return color number */ private int getAreaColor(long time) { //set transparency related to time difference if(time > -1) { long timeDiff = new Date().getTime() - time; //less than a week if (timeDiff < 7*24*60*60*1000L) { return Color.parseColor("#57a639"); } //less than two weeks else if (timeDiff < 14*24*60*60*1000L) { return Color.parseColor("#819b45"); } //less than three weeks else if (timeDiff < 21*24*60*60*1000L) { return Color.parseColor("#d3d30c"); } //less than four weeks else if (timeDiff < 28*24*60*60*1000L) { return Color.parseColor("#df9600"); } //less than 6 weeks else if (timeDiff < 42*24*60*60*1000L) { return Color.parseColor("#df3d00"); } //longer than four weeks else { return Color.parseColor("#f70000"); } } else { return Color.parseColor("#f70000"); } } /** * Request read external storage permission, so that we can get saved images from external storage. * The result of the permission request is handled by a callback, * onRequestPermissionsResult. * */ private void getReadStoragePermission() { if (ContextCompat.checkSelfPermission(getActivity().getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { mReadStorageGranted = true; } else { ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_CODE); } } /** * Reads tasks saved in preferences as jsonStrings * * @return list of OwnTask */ private List readTasksFromPreferences() { List tasks = new ArrayList<>(); try { String jsonString = PreferenceManager.getDefaultSharedPreferences(getContext()).getString(KEY_ALL_TASKS, "error"); if(!"error".equalsIgnoreCase(jsonString)) { JSONArray jsonObjects = new JSONArray(jsonString); for (int i = 0; i