Migrate PermissionUtil to Kotlin
- Set min SDK to 16 (was 14), because READ_EXTERNAL_STORAGE is minimum 16 - Add new shortcut to Settings.kt class - Use showDialog util function
This commit is contained in:
parent
ec4f57b5b6
commit
b892b7b8d3
|
@ -1,5 +1,5 @@
|
||||||
ext.versions = [
|
ext.versions = [
|
||||||
minSdk : 14,
|
minSdk : 16,
|
||||||
targetSdk : 29,
|
targetSdk : 29,
|
||||||
compileSdk : 29,
|
compileSdk : 29,
|
||||||
// You need to run ./gradlew wrapper after updating the version
|
// You need to run ./gradlew wrapper after updating the version
|
||||||
|
|
|
@ -224,8 +224,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupCacheLocationPreference() {
|
private void setupCacheLocationPreference() {
|
||||||
cacheLocation.setSummary(settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
cacheLocation.setSummary(Settings.getCacheLocation());
|
||||||
FileUtil.getDefaultMusicDirectory().getPath()));
|
|
||||||
|
|
||||||
cacheLocation.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
cacheLocation.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -431,8 +430,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||||
sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText());
|
sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText());
|
||||||
sharingDefaultDescription.setSummary(sharingDefaultDescription.getText());
|
sharingDefaultDescription.setSummary(sharingDefaultDescription.getText());
|
||||||
sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText());
|
sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText());
|
||||||
cacheLocation.setSummary(settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
cacheLocation.setSummary(Settings.getCacheLocation());
|
||||||
FileUtil.getDefaultMusicDirectory().getPath()));
|
|
||||||
|
|
||||||
if (!mediaButtonsEnabled.isChecked()) {
|
if (!mediaButtonsEnabled.isChecked()) {
|
||||||
lockScreenEnabled.setChecked(false);
|
lockScreenEnabled.setChecked(false);
|
||||||
|
|
|
@ -1,237 +0,0 @@
|
||||||
package org.moire.ultrasonic.util;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.content.PermissionChecker;
|
|
||||||
|
|
||||||
import com.karumi.dexter.Dexter;
|
|
||||||
import com.karumi.dexter.MultiplePermissionsReport;
|
|
||||||
import com.karumi.dexter.PermissionToken;
|
|
||||||
import com.karumi.dexter.listener.DexterError;
|
|
||||||
import com.karumi.dexter.listener.PermissionRequest;
|
|
||||||
import com.karumi.dexter.listener.PermissionRequestErrorListener;
|
|
||||||
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
|
|
||||||
|
|
||||||
import org.moire.ultrasonic.R;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
import static androidx.core.content.PermissionChecker.PERMISSION_DENIED;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains static functions for Permission handling
|
|
||||||
*/
|
|
||||||
public class PermissionUtil {
|
|
||||||
|
|
||||||
private Context activityContext;
|
|
||||||
private final Context applicationContext;
|
|
||||||
|
|
||||||
public PermissionUtil(Context context) {
|
|
||||||
applicationContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface PermissionRequestFinishedCallback {
|
|
||||||
void onPermissionRequestFinished(boolean hasPermission);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForegroundApplicationStarted(Context context) {
|
|
||||||
this.activityContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ForegroundApplicationStopped() {
|
|
||||||
activityContext = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function can be used to handle file access permission failures.
|
|
||||||
*
|
|
||||||
* It will check if the failure is because the necessary permissions aren't available,
|
|
||||||
* and it will request them, if necessary.
|
|
||||||
*
|
|
||||||
* @param callback callback function to execute after the permission request is finished
|
|
||||||
*/
|
|
||||||
public void handlePermissionFailed(@Nullable final PermissionRequestFinishedCallback callback) {
|
|
||||||
String currentCachePath = Settings.getPreferences().getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, FileUtil.getDefaultMusicDirectory().getPath());
|
|
||||||
String defaultCachePath = FileUtil.getDefaultMusicDirectory().getPath();
|
|
||||||
|
|
||||||
// Ultrasonic can do nothing about this error when the Music Directory is already set to the default.
|
|
||||||
if (currentCachePath.compareTo(defaultCachePath) == 0) return;
|
|
||||||
|
|
||||||
if ((PermissionChecker.checkSelfPermission(applicationContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_DENIED) ||
|
|
||||||
(PermissionChecker.checkSelfPermission(applicationContext, Manifest.permission.READ_EXTERNAL_STORAGE) == PERMISSION_DENIED)) {
|
|
||||||
// While we request permission, the Music Directory is temporarily reset to its default location
|
|
||||||
setCacheLocation(applicationContext, FileUtil.getDefaultMusicDirectory().getPath());
|
|
||||||
// If the application is not running, we can't notify the user
|
|
||||||
if (activityContext == null) return;
|
|
||||||
requestFailedPermission(activityContext, currentCachePath, callback);
|
|
||||||
} else {
|
|
||||||
setCacheLocation(applicationContext, FileUtil.getDefaultMusicDirectory().getPath());
|
|
||||||
// If the application is not running, we can't notify the user
|
|
||||||
if (activityContext != null) {
|
|
||||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
showWarning(activityContext, activityContext.getString(R.string.permissions_message_box_title), activityContext.getString(R.string.permissions_access_error), null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (callback != null) {
|
|
||||||
callback.onPermissionRequestFinished(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function requests permission to access the filesystem.
|
|
||||||
* It can be used to request the permission initially, e.g. when the user decides to use a non-default folder for the cache
|
|
||||||
* @param context context for the operation
|
|
||||||
* @param callback callback function to execute after the permission request is finished
|
|
||||||
*/
|
|
||||||
public static void requestInitialPermission(final Context context, final PermissionRequestFinishedCallback callback) {
|
|
||||||
Dexter.withContext(context)
|
|
||||||
.withPermissions(
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
.withListener(new MultiplePermissionsListener() {
|
|
||||||
@Override
|
|
||||||
public void onPermissionsChecked(MultiplePermissionsReport report) {
|
|
||||||
if (report.areAllPermissionsGranted()) {
|
|
||||||
Timber.i("Permission granted to read / write external storage");
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (report.isAnyPermissionPermanentlyDenied()) {
|
|
||||||
Timber.i("Found permanently denied permission to read / write external storage, offering settings");
|
|
||||||
showSettingsDialog(context);
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("At least one permission is missing to read / write external storage");
|
|
||||||
showWarning(context, context.getString(R.string.permissions_message_box_title),
|
|
||||||
context.getString(R.string.permissions_rationale_description_initial), null);
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
|
|
||||||
showWarning(context, context.getString(R.string.permissions_rationale_title),
|
|
||||||
context.getString(R.string.permissions_rationale_description_initial), token);
|
|
||||||
}
|
|
||||||
}).withErrorListener(new PermissionRequestErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onError(DexterError error) {
|
|
||||||
Timber.e("An error has occurred during checking permissions with Dexter: %s", error.toString());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.check();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setCacheLocation(Context context, String cacheLocation) {
|
|
||||||
Settings.getPreferences().edit()
|
|
||||||
.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, cacheLocation)
|
|
||||||
.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void requestFailedPermission(final Context context, final String cacheLocation, final PermissionRequestFinishedCallback callback) {
|
|
||||||
Dexter.withContext(context)
|
|
||||||
.withPermissions(
|
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
.withListener(new MultiplePermissionsListener() {
|
|
||||||
@Override
|
|
||||||
public void onPermissionsChecked(MultiplePermissionsReport report) {
|
|
||||||
if (report.areAllPermissionsGranted()) {
|
|
||||||
Timber.i("Permission granted to use cache directory %s", cacheLocation);
|
|
||||||
setCacheLocation(context, cacheLocation);
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (report.isAnyPermissionPermanentlyDenied()) {
|
|
||||||
Timber.i("Found permanently denied permission to use cache directory %s, offering settings", cacheLocation);
|
|
||||||
showSettingsDialog(context);
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.i("At least one permission is missing to use directory %s ", cacheLocation);
|
|
||||||
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
|
|
||||||
showWarning(context, context.getString(R.string.permissions_message_box_title),
|
|
||||||
context.getString(R.string.permissions_permission_missing), null);
|
|
||||||
if (callback != null) callback.onPermissionRequestFinished(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
|
|
||||||
showWarning(context, context.getString(R.string.permissions_rationale_title),
|
|
||||||
context.getString(R.string.permissions_rationale_description_failed), token);
|
|
||||||
}
|
|
||||||
}).withErrorListener(new PermissionRequestErrorListener() {
|
|
||||||
@Override
|
|
||||||
public void onError(DexterError error) {
|
|
||||||
Timber.e("An error has occurred during checking permissions with Dexter: %s", error.toString());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.check();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showSettingsDialog(final Context context) {
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Dialog);
|
|
||||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
|
||||||
builder.setTitle(context.getString(R.string.permissions_permanent_denial_title));
|
|
||||||
builder.setMessage(context.getString(R.string.permissions_permanent_denial_description));
|
|
||||||
|
|
||||||
builder.setPositiveButton(context.getString(R.string.permissions_open_settings), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.cancel();
|
|
||||||
openSettings(context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(context.getString(R.string.common_cancel), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
setCacheLocation(context, FileUtil.getDefaultMusicDirectory().getPath());
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void openSettings(Context context) {
|
|
||||||
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
|
||||||
i.setData(Uri.parse("package:" + context.getPackageName()));
|
|
||||||
context.startActivity(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showWarning(Context context, String title, String text, final PermissionToken token) {
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Dialog);
|
|
||||||
builder.setIcon(android.R.drawable.ic_dialog_alert);
|
|
||||||
builder.setTitle(title);
|
|
||||||
builder.setMessage(text);
|
|
||||||
builder.setPositiveButton(context.getString(R.string.common_ok), new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.cancel();
|
|
||||||
if (token != null) token.continuePermissionRequest();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.show();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -83,7 +83,7 @@ class NavigationActivity : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setUncaughtExceptionHandler()
|
setUncaughtExceptionHandler()
|
||||||
permissionUtil.ForegroundApplicationStarted(this)
|
permissionUtil.onForegroundApplicationStarted(this)
|
||||||
Util.applyTheme(this)
|
Util.applyTheme(this)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -198,7 +198,7 @@ class NavigationActivity : AppCompatActivity() {
|
||||||
nowPlayingEventDistributor.unsubscribe(nowPlayingEventListener)
|
nowPlayingEventDistributor.unsubscribe(nowPlayingEventListener)
|
||||||
themeChangedEventDistributor.unsubscribe(themeChangedEventListener)
|
themeChangedEventDistributor.unsubscribe(themeChangedEventListener)
|
||||||
imageLoaderProvider.clearImageLoader()
|
imageLoaderProvider.clearImageLoader()
|
||||||
permissionUtil.ForegroundApplicationStopped()
|
permissionUtil.onForegroundApplicationStopped()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
@ -304,12 +304,7 @@ class NavigationActivity : AppCompatActivity() {
|
||||||
PreferenceManager.setDefaultValues(this, R.xml.settings, false)
|
PreferenceManager.setDefaultValues(this, R.xml.settings, false)
|
||||||
val preferences = Settings.preferences
|
val preferences = Settings.preferences
|
||||||
if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) {
|
if (!preferences.contains(Constants.PREFERENCES_KEY_CACHE_LOCATION)) {
|
||||||
val editor = preferences.edit()
|
Settings.cacheLocation = FileUtil.defaultMusicDirectory.path
|
||||||
editor.putString(
|
|
||||||
Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
|
||||||
FileUtil.defaultMusicDirectory.path
|
|
||||||
)
|
|
||||||
editor.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -415,10 +415,9 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
Util.showDialog(
|
Util.showDialog(
|
||||||
activity,
|
context = requireActivity(),
|
||||||
android.R.drawable.ic_dialog_info,
|
titleId = R.string.settings_testing_ok,
|
||||||
R.string.settings_testing_ok,
|
message = dialogText
|
||||||
dialogText
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,9 +253,8 @@ object FileUtil {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val musicDirectory: File
|
val musicDirectory: File
|
||||||
get() {
|
get() {
|
||||||
val path = Settings.preferences
|
val path = Settings.cacheLocation
|
||||||
.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION, defaultMusicDirectory.path)
|
val dir = File(path)
|
||||||
val dir = File(path!!)
|
|
||||||
val hasAccess = ensureDirectoryExistsAndIsReadWritable(dir)
|
val hasAccess = ensureDirectoryExistsAndIsReadWritable(dir)
|
||||||
if (!hasAccess) permissionUtil.value.handlePermissionFailed(null)
|
if (!hasAccess) permissionUtil.value.handlePermissionFailed(null)
|
||||||
return if (hasAccess) dir else defaultMusicDirectory
|
return if (hasAccess) dir else defaultMusicDirectory
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import androidx.core.content.PermissionChecker
|
||||||
|
import com.karumi.dexter.Dexter
|
||||||
|
import com.karumi.dexter.MultiplePermissionsReport
|
||||||
|
import com.karumi.dexter.PermissionToken
|
||||||
|
import com.karumi.dexter.listener.PermissionRequest
|
||||||
|
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
|
||||||
|
import org.moire.ultrasonic.R
|
||||||
|
import org.moire.ultrasonic.util.FileUtil.defaultMusicDirectory
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains static functions for Permission handling
|
||||||
|
*/
|
||||||
|
class PermissionUtil(private val applicationContext: Context) {
|
||||||
|
private var activityContext: Context? = null
|
||||||
|
|
||||||
|
interface PermissionRequestFinishedCallback {
|
||||||
|
fun onPermissionRequestFinished(hasPermission: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onForegroundApplicationStarted(context: Context?) {
|
||||||
|
activityContext = context
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onForegroundApplicationStopped() {
|
||||||
|
activityContext = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function can be used to handle file access permission failures.
|
||||||
|
*
|
||||||
|
* It will check if the failure is because the necessary permissions aren't available,
|
||||||
|
* and it will request them, if necessary.
|
||||||
|
*
|
||||||
|
* @param callback callback function to execute after the permission request is finished
|
||||||
|
*/
|
||||||
|
fun handlePermissionFailed(callback: PermissionRequestFinishedCallback?) {
|
||||||
|
val currentCachePath = Settings.cacheLocation
|
||||||
|
val defaultCachePath = defaultMusicDirectory.path
|
||||||
|
|
||||||
|
// Ultrasonic can do nothing about this error when the Music Directory is already set to the default.
|
||||||
|
if (currentCachePath.compareTo(defaultCachePath) == 0) return
|
||||||
|
|
||||||
|
if (PermissionChecker.checkSelfPermission(
|
||||||
|
applicationContext,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
) == PermissionChecker.PERMISSION_DENIED ||
|
||||||
|
PermissionChecker.checkSelfPermission(
|
||||||
|
applicationContext,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) == PermissionChecker.PERMISSION_DENIED
|
||||||
|
) {
|
||||||
|
// While we request permission, the Music Directory is temporarily reset to its default location
|
||||||
|
Settings.cacheLocation = defaultMusicDirectory.path
|
||||||
|
// If the application is not running, we can't notify the user
|
||||||
|
if (activityContext == null) return
|
||||||
|
requestFailedPermission(activityContext!!, currentCachePath, callback)
|
||||||
|
} else {
|
||||||
|
Settings.cacheLocation = defaultMusicDirectory.path
|
||||||
|
// If the application is not running, we can't notify the user
|
||||||
|
if (activityContext != null) {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
showWarning(
|
||||||
|
activityContext!!,
|
||||||
|
activityContext!!.getString(R.string.permissions_message_box_title),
|
||||||
|
activityContext!!.getString(R.string.permissions_access_error),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback?.onPermissionRequestFinished(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* This function requests permission to access the filesystem.
|
||||||
|
* It can be used to request the permission initially, e.g. when the user decides to
|
||||||
|
* use a non-default folder for the cache
|
||||||
|
* @param context context for the operation
|
||||||
|
* @param callback callback function to execute after the permission request is finished
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun requestInitialPermission(
|
||||||
|
context: Context,
|
||||||
|
callback: PermissionRequestFinishedCallback?
|
||||||
|
) {
|
||||||
|
Dexter.withContext(context)
|
||||||
|
.withPermissions(
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
.withListener(object : MultiplePermissionsListener {
|
||||||
|
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
|
||||||
|
if (report.areAllPermissionsGranted()) {
|
||||||
|
Timber.i("R/W permission granted for external storage")
|
||||||
|
callback?.onPermissionRequestFinished(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (report.isAnyPermissionPermanentlyDenied) {
|
||||||
|
Timber.i(
|
||||||
|
"R/W permission is permanently denied for external storage"
|
||||||
|
)
|
||||||
|
showSettingsDialog(context)
|
||||||
|
callback?.onPermissionRequestFinished(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.i("R/W permission is missing for external storage")
|
||||||
|
showWarning(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.permissions_message_box_title),
|
||||||
|
context.getString(R.string.permissions_rationale_description_initial),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
callback?.onPermissionRequestFinished(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionRationaleShouldBeShown(
|
||||||
|
permissions: List<PermissionRequest>,
|
||||||
|
token: PermissionToken
|
||||||
|
) {
|
||||||
|
showWarning(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.permissions_rationale_title),
|
||||||
|
context.getString(R.string.permissions_rationale_description_initial),
|
||||||
|
token
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).withErrorListener { error ->
|
||||||
|
Timber.e(
|
||||||
|
"An error has occurred during checking permissions with Dexter: %s",
|
||||||
|
error.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestFailedPermission(
|
||||||
|
context: Context,
|
||||||
|
cacheLocation: String?,
|
||||||
|
callback: PermissionRequestFinishedCallback?
|
||||||
|
) {
|
||||||
|
Dexter.withContext(context)
|
||||||
|
.withPermissions(
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
|
.withListener(object : MultiplePermissionsListener {
|
||||||
|
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
|
||||||
|
if (report.areAllPermissionsGranted()) {
|
||||||
|
Timber.i("Permission granted to use cache directory %s", cacheLocation)
|
||||||
|
|
||||||
|
if (cacheLocation != null) {
|
||||||
|
Settings.cacheLocation = cacheLocation
|
||||||
|
}
|
||||||
|
callback?.onPermissionRequestFinished(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (report.isAnyPermissionPermanentlyDenied) {
|
||||||
|
Timber.i(
|
||||||
|
"R/W permission for cache directory %s was permanently denied",
|
||||||
|
cacheLocation
|
||||||
|
)
|
||||||
|
showSettingsDialog(context)
|
||||||
|
callback?.onPermissionRequestFinished(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Timber.i(
|
||||||
|
"At least one permission is missing to use directory %s ",
|
||||||
|
cacheLocation
|
||||||
|
)
|
||||||
|
Settings.cacheLocation = defaultMusicDirectory.path
|
||||||
|
showWarning(
|
||||||
|
context, context.getString(R.string.permissions_message_box_title),
|
||||||
|
context.getString(R.string.permissions_permission_missing), null
|
||||||
|
)
|
||||||
|
callback?.onPermissionRequestFinished(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionRationaleShouldBeShown(
|
||||||
|
permissions: List<PermissionRequest>,
|
||||||
|
token: PermissionToken
|
||||||
|
) {
|
||||||
|
showWarning(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.permissions_rationale_title),
|
||||||
|
context.getString(R.string.permissions_rationale_description_failed),
|
||||||
|
token
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).withErrorListener { error ->
|
||||||
|
Timber.e(
|
||||||
|
"An error has occurred during checking permissions with Dexter: %s",
|
||||||
|
error.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSettingsDialog(ctx: Context) {
|
||||||
|
|
||||||
|
val builder = Util.createDialog(
|
||||||
|
context = ctx,
|
||||||
|
android.R.drawable.ic_dialog_alert,
|
||||||
|
ctx.getString(R.string.permissions_permanent_denial_title),
|
||||||
|
ctx.getString(R.string.permissions_permanent_denial_description)
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.setPositiveButton(ctx.getString(R.string.permissions_open_settings)) {
|
||||||
|
dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
openSettings(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setNegativeButton(ctx.getString(R.string.common_cancel)) { dialog, _ ->
|
||||||
|
Settings.cacheLocation = defaultMusicDirectory.path
|
||||||
|
dialog.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openSettings(context: Context) {
|
||||||
|
val i = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
i.addCategory(Intent.CATEGORY_DEFAULT)
|
||||||
|
i.data = Uri.parse("package:" + context.packageName)
|
||||||
|
context.startActivity(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showWarning(
|
||||||
|
context: Context,
|
||||||
|
title: String,
|
||||||
|
text: String,
|
||||||
|
token: PermissionToken?
|
||||||
|
) {
|
||||||
|
|
||||||
|
val builder = Util.createDialog(
|
||||||
|
context = context,
|
||||||
|
android.R.drawable.ic_dialog_alert,
|
||||||
|
title,
|
||||||
|
text
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.setPositiveButton(context.getString(R.string.common_ok)) { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
token?.continuePermissionRequest()
|
||||||
|
}
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,6 +102,23 @@ object Settings {
|
||||||
return if (preloadCount == -1) Int.MAX_VALUE else preloadCount
|
return if (preloadCount == -1) Int.MAX_VALUE else preloadCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
var cacheLocation: String
|
||||||
|
get() {
|
||||||
|
return preferences.getString(
|
||||||
|
Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
||||||
|
FileUtil.defaultMusicDirectory.path
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
set(location) {
|
||||||
|
val editor = preferences.edit()
|
||||||
|
editor.putString(
|
||||||
|
Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
||||||
|
location
|
||||||
|
)
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val cacheSizeMB: Int
|
val cacheSizeMB: Int
|
||||||
get() {
|
get() {
|
||||||
|
|
|
@ -393,17 +393,30 @@ object Util {
|
||||||
|
|
||||||
// The AlertDialog requires an Activity context, app context is not enough
|
// The AlertDialog requires an Activity context, app context is not enough
|
||||||
// See https://stackoverflow.com/questions/5436822/
|
// See https://stackoverflow.com/questions/5436822/
|
||||||
fun showDialog(context: Context?, icon: Int, titleId: Int, message: String?) {
|
fun createDialog(
|
||||||
AlertDialog.Builder(context)
|
context: Context?,
|
||||||
|
icon: Int = android.R.drawable.ic_dialog_info,
|
||||||
|
title: String,
|
||||||
|
message: String?
|
||||||
|
): AlertDialog.Builder {
|
||||||
|
return AlertDialog.Builder(context)
|
||||||
.setIcon(icon)
|
.setIcon(icon)
|
||||||
.setTitle(titleId)
|
.setTitle(title)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(R.string.common_ok) {
|
.setPositiveButton(R.string.common_ok) {
|
||||||
dialog: DialogInterface,
|
dialog: DialogInterface,
|
||||||
_: Int ->
|
_: Int ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
}
|
||||||
|
|
||||||
|
fun showDialog(
|
||||||
|
context: Context,
|
||||||
|
icon: Int = android.R.drawable.ic_dialog_info,
|
||||||
|
titleId: Int,
|
||||||
|
message: String?
|
||||||
|
) {
|
||||||
|
createDialog(context, icon, context.getString(titleId, ""), message).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
Loading…
Reference in New Issue