diff --git a/app/src/main/java/net/osmtracker/activity/TrackDetail.java b/app/src/main/java/net/osmtracker/activity/TrackDetail.java index 5f4eb6d9..daec6808 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackDetail.java +++ b/app/src/main/java/net/osmtracker/activity/TrackDetail.java @@ -15,12 +15,14 @@ import net.osmtracker.util.MercatorProjection; import android.Manifest; +import android.app.AlertDialog; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Intent; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Paint; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import androidx.core.app.ActivityCompat; @@ -237,32 +239,10 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivity(i); break; case R.id.trackdetail_menu_export: - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - - // Should we show an explanation? - if (ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - - // Show an expanation to the user *asynchronously* -- don't block - // this thread waiting for the user's response! After the user - // sees the explanation, try again to request the permission. - // TODO: explain why we need permission. - Log.w(TAG, "we should explain why we need write permission"); - - } else { - - // No explanation needed, we can request the permission. - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - RC_WRITE_PERMISSIONS); - break; - } - - } else { + if (writeExternalStoragePermissionGranted()) { exportTrack(); - break; } + break; case R.id.trackdetail_menu_osm_upload: i = new Intent(this, OpenStreetMapUpload.class); i.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, trackId); @@ -272,6 +252,51 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } + /** + * Checks if the external storage write permission is granted. + * If not, it requests the permission and may display a rationale dialog explaining why it is needed. + * + *

For devices running Android R (API level 30) and above, this permission is not required, + * so the method will return {@code true} immediately.

+ * + * @return {@code true} if the write permission is already granted or not required (Android R+), + * {@code false} otherwise. + */ + private boolean writeExternalStoragePermissionGranted() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return true; + } + else if (ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + // Show an expanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + new AlertDialog.Builder(this) + .setTitle(R.string.permission_required) + .setMessage(R.string.storage_permission_for_export_GPX) + .setPositiveButton(R.string.acccept, (dialog, which) -> { + // Request the permission again + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + RC_WRITE_PERMISSIONS); + }) + .setNegativeButton(R.string.menu_cancel, (dialog, which) -> dialog.dismiss()) + .show(); + } else { + // No explanation needed, we can request the permission. + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + RC_WRITE_PERMISSIONS); + } + } + + return ContextCompat.checkSelfPermission(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + /** * Invoke the export track task after external write permissions request. diff --git a/app/src/main/java/net/osmtracker/activity/TrackManager.java b/app/src/main/java/net/osmtracker/activity/TrackManager.java index c9738aa2..29518f98 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackManager.java +++ b/app/src/main/java/net/osmtracker/activity/TrackManager.java @@ -84,6 +84,9 @@ public class TrackManager extends AppCompatActivity private TrackListRVAdapter recyclerViewAdapter; + // To check if the RecyclerView already has a DividerItemDecoration added + private boolean hasDivider; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -139,7 +142,7 @@ protected void onResume() { /** - * + * Configures and initializes the RecyclerView for displaying the list of tracks. */ private void setRecyclerView() { RecyclerView recyclerView = findViewById(R.id.recyclerview); @@ -147,11 +150,13 @@ private void setRecyclerView() { LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); recyclerView.setLayoutManager(layoutManager); - - DividerItemDecoration did = new DividerItemDecoration(recyclerView.getContext(), - layoutManager.getOrientation()); - recyclerView.addItemDecoration(did); - + // adds a divider decoration if not already present + if (!hasDivider) { + DividerItemDecoration did = new DividerItemDecoration(recyclerView.getContext(), + layoutManager.getOrientation()); + recyclerView.addItemDecoration(did); + hasDivider = true; + } recyclerView.setHasFixedSize(true); Cursor cursor = getContentResolver().query( TrackContentProvider.CONTENT_URI_TRACK, null, null, null, @@ -281,7 +286,7 @@ private void tryStartTrackLogger(Intent intent){ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) { Log.i(TAG,"Should explain"); - Toast.makeText(this, "Can't continue without GPS permission", + Toast.makeText(this, R.string.gps_perms_required, Toast.LENGTH_LONG).show(); } @@ -724,7 +729,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], // functionality that depends on this permission. //TODO: add an informative message. Log.w(TAG, "we should explain why we need write permission_EXPORT_ALL"); - Toast.makeText(this, "To export the GPX trace we need to write on the storage.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.storage_permission_for_export_GPX, Toast.LENGTH_LONG).show(); } break; } @@ -742,7 +747,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], // functionality that depends on this permission. //TODO: add an informative message. Log.w(TAG, "we should explain why we need write permission_EXPORT_ONE"); - Toast.makeText(this, "To export the GPX trace we need to write on the storage.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.storage_permission_for_export_GPX, Toast.LENGTH_LONG).show(); } break; } @@ -759,7 +764,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], // functionality that depends on this permission. //TODO: add an informative message. Log.w(TAG, "Permission not granted"); - Toast.makeText(this, "To display the track properly we need access to the storage.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.storage_permission_for_display_track, Toast.LENGTH_LONG).show(); } break; } @@ -777,7 +782,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], // functionality that depends on this permission. //TODO: add an informative message. Log.w(TAG, "Permission not granted"); - Toast.makeText(this, "To share the track properly we need access to the storage.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.storage_permission_for_share_track, Toast.LENGTH_LONG).show(); } break; } @@ -794,7 +799,7 @@ public void onRequestPermissionsResult(int requestCode, String permissions[], // functionality that depends on this permission. //TODO: add an informative message. Log.w(TAG, "Permission not granted"); - Toast.makeText(this, "To upload the track to OSM we need access to the storage.", Toast.LENGTH_LONG).show(); + Toast.makeText(this, R.string.storage_permission_for_upload_to_OSM, Toast.LENGTH_LONG).show(); } break; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b155d7e4..5c8d80bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -116,6 +116,15 @@ Voice recording has failed Error while parsing XML layout file. Please revert to default layout. + + Permission required + To export the GPX trace we need to write on the storage. + To display the track properly we need access to the storage. + To share the track properly we need access to the storage. + To upload the track to OSM we need access to the storage. + Accept + Can\'t continue without GPS permission + Tracked with OSMTracker for Androidâ„¢ Warning: HDOP values aren\'t the HDOP as returned by the GPS device. They\'re approximated from the location accuracy in meters.