package de.jeisfeld.augendiagnoselib.fragments; import android.app.Activity; import android.app.DialogFragment; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceFragment; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import com.android.vending.billing.Purchase; import com.android.vending.billing.PurchasedSku; import com.android.vending.billing.SkuDetails; import com.immersion.hapticmediasdk.utils.Log; import java.io.File; import java.util.List; import de.jeisfeld.augendiagnoselib.Application; import de.jeisfeld.augendiagnoselib.Application.AuthorizationLevel; import de.jeisfeld.augendiagnoselib.R; import de.jeisfeld.augendiagnoselib.components.DirectorySelectionPreference; import de.jeisfeld.augendiagnoselib.components.DirectorySelectionPreference.OnDialogClosedListener; import de.jeisfeld.augendiagnoselib.components.OverlayPinchImageView; import de.jeisfeld.augendiagnoselib.components.PinchImageView; import de.jeisfeld.augendiagnoselib.util.DialogUtil; import de.jeisfeld.augendiagnoselib.util.DialogUtil.DisplayMessageDialogFragment.MessageDialogListener; import de.jeisfeld.augendiagnoselib.util.GoogleBillingHelper; import de.jeisfeld.augendiagnoselib.util.GoogleBillingHelper.OnInventoryFinishedListener; import de.jeisfeld.augendiagnoselib.util.GoogleBillingHelper.OnPurchaseSuccessListener; import de.jeisfeld.augendiagnoselib.util.PreferenceUtil; import de.jeisfeld.augendiagnoselib.util.SystemUtil; import de.jeisfeld.augendiagnoselib.util.imagefile.FileUtil; import de.jeisfeld.augendiagnoselib.util.imagefile.JpegSynchronizationUtil; /** * Fragment for displaying the settings. */ public class SettingsFragment extends PreferenceFragment { /** * The requestCode for starting the permisson request. */ public static final int REQUEST_CODE_PERMISSION = 5; /** * The requestCode with which the storage access framework is triggered for photo folder. */ private static final int REQUEST_CODE_STORAGE_ACCESS_PHOTOS = 3; /** * The requestCode with which the storage access framework is triggered for input folder. */ private static final int REQUEST_CODE_STORAGE_ACCESS_INPUT = 4; /** * The resource key of the flag indicating the type of settings to be shown. * This key is used as well in pref_header.xml. */ public static final String STRING_PREF_TYPE = "prefType"; /** * A prefix put before the productId to define the according preference key. */ private static final String SKU_KEY_PREFIX = "sku_"; /** * A listener handling the change of preferences. */ private final CustomOnPreferenceChangeListener mOnPreferenceChangeListener = new CustomOnPreferenceChangeListener(); /** * Field holding the value of the input folder preference, in order to detect a real change. */ @Nullable private String mFolderInput; /** * Field holding the value of the photo folder preference, in order to detect a real change. */ @Nullable private String mFolderPhotos; /** * The type of fragment to be shown. */ @Nullable private String mType = null; /** * Field for temporarily storing the folder used for Storage Access Framework. */ @Nullable private File mCurrentFolder; /** * Initialize the fragment with onlyPacks flag. * * @param prefType The type of preferences to be shown. */ public final void setParameters(final String prefType) { Bundle args = new Bundle(); args.putString(STRING_PREF_TYPE, prefType); setArguments(args); } @Override public final void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() == null) { Log.e(Application.TAG, "Illegal call of SettingsFragment without parameters"); return; } mType = getArguments().getString(STRING_PREF_TYPE); if (mType.equals(getActivity().getString(R.string.key_dummy_screen_input_settings))) { addPreferencesFromResource(R.xml.prefs_input); mFolderInput = PreferenceUtil.getSharedPreferenceString(R.string.key_folder_input); bindPreferenceSummaryToValue(R.string.key_folder_input); } else if (mType.equals(getActivity().getString(R.string.key_folder_input))) { // Special fragment for displaying only input folder. addPreferencesFromResource(R.xml.prefs_input); mFolderInput = PreferenceUtil.getSharedPreferenceString(R.string.key_folder_input); bindPreferenceSummaryToValue(R.string.key_folder_input); PreferenceScreen screen = getPreferenceScreen(); for (int i = 0; i < screen.getPreferenceCount(); i++) { Preference pref = screen.getPreference(i); if (pref.getKey().equals(getActivity().getString(R.string.key_folder_input))) { DirectorySelectionPreference preference = (DirectorySelectionPreference) pref; preference.setOnDialogClosedListener(new OnDialogClosedListener() { @Override public void onDialogClosed() { getActivity().finish(); } }); preference.showDialog(); } else { screen.removePreference(pref); } } } else if (mType.equals(getActivity().getString(R.string.key_dummy_screen_display_settings))) { addPreferencesFromResource(R.xml.prefs_display); bindPreferenceSummaryToValue(R.string.key_language); addHintButtonListener(R.string.key_dummy_show_hints, false); addHintButtonListener(R.string.key_dummy_hide_hints, true); } else if (mType.equals(getActivity().getString(R.string.key_dummy_screen_storage_settings))) { addPreferencesFromResource(R.xml.prefs_storage); mFolderPhotos = PreferenceUtil.getSharedPreferenceString(R.string.key_folder_photos); bindPreferenceSummaryToValue(R.string.key_folder_photos); bindPreferenceSummaryToValue(R.string.key_max_bitmap_size); bindPreferenceSummaryToValue(R.string.key_store_option); bindPreferenceSummaryToValue(R.string.key_full_resolution); } else if (mType.equals(getActivity().getString(R.string.key_dummy_screen_camera_settings))) { addPreferencesFromResource(R.xml.prefs_camera); bindPreferenceSummaryToValue(R.string.key_camera_api_version); bindPreferenceSummaryToValue(R.string.key_camera_screen_position); if (getString(R.string.pref_title_folder_input).length() > 0) { // Eye photo sequence is in input settings and can be hidden here. getPreferenceScreen().removePreference(findPreference(getString(R.string.key_eye_sequence_choice))); } if (!SystemUtil.isAndroid5()) { getPreferenceScreen().removePreference(findPreference(getString(R.string.key_camera_api_version))); } if (!SystemUtil.hasFlashlight()) { getPreferenceScreen().removePreference(findPreference(getString(R.string.key_enable_flash))); } } else if (mType.equals(getActivity().getString(R.string.key_dummy_screen_overlay_settings))) { PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity()); setPreferenceScreen(preferenceScreen); String[] buttonStrings = getResources().getStringArray(R.array.overlay_button_strings); for (int btnIndex = 1; btnIndex < DisplayImageFragment.OVERLAY_BUTTON_COUNT; btnIndex++) { ListPreference preference = new ListPreference(getActivity()); preference.setTitle(String.format(getString(R.string.pref_title_overlay_button), buttonStrings[btnIndex])); updateOverlayPreferenceEntries(btnIndex, preference); preference.setKey(PreferenceUtil.getIndexedPreferenceKey(R.string.key_indexed_overlaytype, btnIndex)); bindPreferenceSummaryToValue(preference); preferenceScreen.addPreference(preference); } } else if (mType.equals(getActivity().getString(R.string.key_dummy_screen_premium_settings))) { addPreferencesFromResource(R.xml.prefs_premium); addDeveloperContactButtonListener(); addUnlockerAppButtonListener(); int permission = ContextCompat.checkSelfPermission(getActivity(), "com.android.vending.BILLING"); if (permission == PackageManager.PERMISSION_GRANTED) { initializeGoogleBilling(); } else { ActivityCompat.requestPermissions(getActivity(), new String[] {"com.android.vending.BILLING"}, REQUEST_CODE_PERMISSION); } } } /** * Initialize Google Billing (after having permission). */ public final void initializeGoogleBilling() { GoogleBillingHelper.initialize(getActivity(), mOnInventoryFinishedListener); } /** * Add the listener for a "hints" button. * * @param preferenceId The id of the button. * @param hintPreferenceValue The value to be set to all the hints preferences. */ private void addHintButtonListener(final int preferenceId, final boolean hintPreferenceValue) { Preference showPreference = findPreference(getString(preferenceId)); showPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { PreferenceUtil.setAllHints(hintPreferenceValue); DialogUtil.displayInfo(getActivity(), null, hintPreferenceValue ? R.string.message_dialog_no_hints_will_be_shown : R.string.message_dialog_hints_will_be_shown); return true; } }); } /** * Add the listener for unlocker app. */ private void addUnlockerAppButtonListener() { Preference unlockPreference = findPreference(getString(R.string.key_dummy_unlocker_app)); unlockPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { Intent googlePlayIntent = new Intent(Intent.ACTION_VIEW); googlePlayIntent.setData(Uri.parse("market://details?id=de.jeisfeld.augendiagnoseunlocker")); try { startActivity(googlePlayIntent); } catch (Exception e) { DialogUtil.displayError(getActivity(), R.string.message_dialog_failed_to_open_google_play, false); } return true; } }); unlockPreference.setEnabled(!SystemUtil.isAppInstalled("de.jeisfeld.augendiagnoseunlocker")); } /** * Add the listener for developer contact. */ private void addDeveloperContactButtonListener() { Preference contactPreference = findPreference(getString(R.string.key_dummy_contact_developer)); contactPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference preference) { Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts( "mailto", getString(R.string.menu_email_contact_developer), null)); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.menu_subject_contact_developer)); startActivity(intent); return true; } }); } /** * Set the choice entries for all overlay button preferences. */ private void updateOverlayPreferenceEntries() { for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) { ListPreference preference = (ListPreference) getPreferenceScreen().getPreference(i); Integer buttonIndex = PreferenceUtil.getIndexFromPreferenceKey(preference.getKey()); if (buttonIndex != null) { updateOverlayPreferenceEntries(buttonIndex, preference); } } } /** * Set the choice entries for an overlay button preference. * * @param buttonIndex The index of the button. * @param preference The preference. */ private void updateOverlayPreferenceEntries(final int buttonIndex, @NonNull final ListPreference preference) { int highestOverlayButtonIndex = DisplayImageFragment.getHighestOverlayButtonIndex(); if (buttonIndex > highestOverlayButtonIndex + 1) { preference.setEnabled(false); return; } else { preference.setEnabled(true); } String[] buttonStrings = getResources().getStringArray(R.array.overlay_button_strings); String[] overlayNames = getResources().getStringArray(R.array.overlay_names); boolean showUnused = buttonIndex >= highestOverlayButtonIndex; int numberOfEntries = showUnused ? OverlayPinchImageView.OVERLAY_COUNT - 1 : OverlayPinchImageView.OVERLAY_COUNT - 2; CharSequence[] entries = new CharSequence[numberOfEntries]; CharSequence[] entryValues = new CharSequence[numberOfEntries]; for (int i = 0; i < OverlayPinchImageView.OVERLAY_COUNT - 2; i++) { int overlayIndex = i + 1; entryValues[i] = Integer.toString(overlayIndex); Integer currentButton = DisplayImageFragment.buttonForOverlayWithIndex(overlayIndex); SpannableString entry; if (currentButton == null) { entry = new SpannableString(overlayNames[overlayIndex]); } else { entry = new SpannableString(buttonStrings[currentButton] + " " + overlayNames[overlayIndex]); if (currentButton == buttonIndex) { entry.setSpan(new StyleSpan(Typeface.BOLD), 0, entry.length(), 0); } else { entry.setSpan(new ForegroundColorSpan(Color.LTGRAY), 0, entry.length(), 0); } } entries[i] = entry; } if (showUnused) { SpannableString entry = new SpannableString(getString(R.string.pref_value_overlay_button_empty)); entry.setSpan(new StyleSpan(Typeface.ITALIC), 0, entry.length(), 0); entries[OverlayPinchImageView.OVERLAY_COUNT - 2] = entry; entryValues[OverlayPinchImageView.OVERLAY_COUNT - 2] = "-1"; } preference.setEntries(entries); preference.setEntryValues(entryValues); } /** * Binds a preference's summary to its value. More specifically, when the preference's value is changed, its summary * (line of text below the preference title) is updated to reflect the value. The summary is also immediately * updated upon calling this method. The exact display format is dependent on the type of preference. * * @param preference The preference to be bound. */ private void bindPreferenceSummaryToValue(@Nullable final Preference preference) { if (preference != null) { // Set the listener to watch for value changes. preference.setOnPreferenceChangeListener(mOnPreferenceChangeListener); // Trigger the listener immediately to set the summary mOnPreferenceChangeListener.setSummary(preference, PreferenceManager .getDefaultSharedPreferences(preference.getContext()).getString(preference.getKey(), "")); } } /** * Helper method for easier call of {@link #bindPreferenceSummaryToValue(android.preference.Preference)}. * * @param preferenceKey The key of the preference. */ private void bindPreferenceSummaryToValue(final int preferenceKey) { bindPreferenceSummaryToValue(findPreference(getString(preferenceKey))); } /** * A listener handling the response after reading the in-add purchase inventory. */ private final OnInventoryFinishedListener mOnInventoryFinishedListener = new OnInventoryFinishedListener() { @Override public void handleProducts(@NonNull final List<PurchasedSku> purchases, @NonNull final List<SkuDetails> availableProducts, final boolean isPremium) { // List inventory items. for (PurchasedSku purchase : purchases) { Preference purchasePreference = new Preference(getActivity()); String title = String.format(getString(R.string.button_purchased_item), purchase.getSkuDetails().getDisplayTitle(getActivity())); purchasePreference.setTitle(title); purchasePreference.setSummary(purchase.getSkuDetails().getDescription()); purchasePreference.setEnabled(false); getPreferenceScreen().addPreference(purchasePreference); } for (SkuDetails skuDetails : availableProducts) { Preference skuPreference = new Preference(getActivity()); skuPreference.setTitle(skuDetails.getDisplayTitle(getActivity())); skuPreference.setKey(SKU_KEY_PREFIX + skuDetails.getSku()); skuPreference.setSummary(skuDetails.getDescription()); skuPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(@NonNull final Preference preference) { String productId = preference.getKey().substring(SKU_KEY_PREFIX.length()); GoogleBillingHelper.launchPurchaseFlow(productId, mOnPurchaseSuccessListener); return false; } }); getPreferenceScreen().addPreference(skuPreference); } } }; /** * A listener handling the response after purchasing a product. */ private final OnPurchaseSuccessListener mOnPurchaseSuccessListener = new OnPurchaseSuccessListener() { @Override public void handlePurchase(final Purchase purchase, final boolean addedPremiumProduct) { if (addedPremiumProduct) { PreferenceUtil.setSharedPreferenceBoolean(R.string.key_internal_has_premium_pack, true); } int messageResource = addedPremiumProduct ? R.string.message_dialog_purchase_thanks_premium : R.string.message_dialog_purchase_thanks; MessageDialogListener listener = new MessageDialogListener() { private static final long serialVersionUID = 1L; @Override public void onDialogClick(final DialogFragment dialog) { getActivity().finish(); Application.startApplication(getActivity()); } @Override public void onDialogCancel(final DialogFragment dialog) { getActivity().finish(); Application.startApplication(getActivity()); } }; DialogUtil.displayInfo(getActivity(), listener, messageResource); } @Override public void handleFailure() { // Do nothing. } }; @Override public final void onActivityResult(final int requestCode, final int resultCode, @NonNull final Intent data) { super.onActivityResult(requestCode, resultCode, data); if (SystemUtil.isAndroid5()) { onActivityResultLollipop(requestCode, resultCode, data); } } /** * After triggering the Storage Access Framework, ensure that folder is really writable. Set preferences * accordingly. * * @param requestCode The integer request code originally supplied to startActivityForResult(), allowing you to identify who * this result came from. * @param resultCode The integer result code returned by the child activity through its setResult(). * @param data An Intent, which can return result data to the caller (various data can be attached to Intent * "extras"). */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private void onActivityResultLollipop(final int requestCode, final int resultCode, @NonNull final Intent data) { int preferenceKeyUri; int preferenceKeyFolder; String oldFolder; if (requestCode == REQUEST_CODE_STORAGE_ACCESS_PHOTOS) { preferenceKeyUri = R.string.key_internal_uri_extsdcard_photos; preferenceKeyFolder = R.string.key_folder_photos; oldFolder = mFolderPhotos; } else if (requestCode == REQUEST_CODE_STORAGE_ACCESS_INPUT) { preferenceKeyUri = R.string.key_internal_uri_extsdcard_input; preferenceKeyFolder = R.string.key_folder_input; oldFolder = mFolderInput; } else { return; } Uri oldUri = PreferenceUtil.getSharedPreferenceUri(preferenceKeyUri); if (oldUri == null) { // Backward compatibility to the version where only the base key existed oldUri = PreferenceUtil.getSharedPreferenceUri(R.string.key_internal_uri_extsdcard_photos); } Uri treeUri = null; if (resultCode == Activity.RESULT_OK) { // Get Uri from Storage Access Framework. treeUri = data.getData(); // Persist URI - this is required for verification of writability. PreferenceUtil.setSharedPreferenceUri(preferenceKeyUri, treeUri); } // If not confirmed SAF, or if still not writable, then revert settings. if (resultCode != Activity.RESULT_OK || !FileUtil.isWritableNormalOrSaf(mCurrentFolder)) { DialogUtil.displayError(getActivity(), R.string.message_dialog_cannot_write_to_folder_saf, false, mCurrentFolder); PreferenceUtil.setSharedPreferenceString(preferenceKeyFolder, oldFolder); findPreference(getString(preferenceKeyFolder)).setSummary(oldFolder); mCurrentFolder = null; PreferenceUtil.setSharedPreferenceUri(preferenceKeyUri, oldUri); return; } // After confirmation, update stored value of folder. if (preferenceKeyFolder == R.string.key_folder_photos) { mFolderPhotos = mCurrentFolder.getAbsolutePath(); } if (preferenceKeyFolder == R.string.key_folder_input) { mFolderInput = mCurrentFolder.getAbsolutePath(); } PreferenceUtil.setSharedPreferenceString(preferenceKeyFolder, mCurrentFolder.getAbsolutePath()); findPreference(getString(preferenceKeyFolder)).setSummary(mCurrentFolder.getAbsolutePath()); // Persist access permissions. final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // noinspection ResourceType getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags); } @Override public final void onDestroy() { super.onDestroy(); if (mType != null && mType.equals(getActivity().getString(R.string.key_dummy_screen_premium_settings))) { GoogleBillingHelper.dispose(); } } /** * A preference value change listener that updates the preference's summary to reflect its new value. */ private class CustomOnPreferenceChangeListener implements OnPreferenceChangeListener { @Override public boolean onPreferenceChange(@NonNull final Preference preference, @NonNull final Object value) { String stringValue = value.toString(); // For maxBitmapSize, inform PinchImageView if (preference.getKey().equals(preference.getContext().getString(R.string.key_max_bitmap_size))) { PinchImageView.setMaxBitmapSize(Integer.parseInt(stringValue)); } // For folder choices, if not writable on Android 5, then trigger Storage Access Framework. else if (preference.getKey().equals(preference.getContext().getString(R.string.key_folder_photos))) { if (mFolderPhotos == null || !mFolderPhotos.equals(value)) { mCurrentFolder = new File(stringValue); if (checkFolder(mCurrentFolder, REQUEST_CODE_STORAGE_ACCESS_PHOTOS)) { mFolderPhotos = mCurrentFolder.getAbsolutePath(); } else { // Do not accept change. return false; } } } else if (preference.getKey().equals(preference.getContext().getString(R.string.key_folder_input))) { if (mFolderInput == null || !mFolderInput.equals(value)) { mCurrentFolder = new File(stringValue); if (checkFolder(mCurrentFolder, REQUEST_CODE_STORAGE_ACCESS_INPUT)) { mFolderInput = mCurrentFolder.getAbsolutePath(); } else { // Do not accept change. return false; } } } // Apply change of language else if (preference.getKey().equals(preference.getContext().getString(R.string.key_language))) { String oldLanguageString = PreferenceUtil.getSharedPreferenceString(R.string.key_language); if (oldLanguageString == null || !oldLanguageString.equals(value)) { Application.setLanguage(); PreferenceUtil.setSharedPreferenceString(R.string.key_language, stringValue); // Workaround to get rid of all kinds of cashing if (!JpegSynchronizationUtil.isSaving()) { Application.startApplication(getActivity()); System.exit(0); } } } else if (preference.getKey().startsWith(getString(R.string.key_indexed_overlaytype))) { int overlayIndex = Integer.parseInt(stringValue); Integer buttonPosition = PreferenceUtil.getIndexFromPreferenceKey(preference.getKey()); if (buttonPosition == null) { buttonPosition = 0; } Integer oldButtonPosition = DisplayImageFragment.buttonForOverlayWithIndex(overlayIndex); int oldOverlayIndex = PreferenceUtil.getIndexedSharedPreferenceIntString(R.string.key_indexed_overlaytype, buttonPosition, -1); if (oldOverlayIndex != overlayIndex) { if (Application.getAuthorizationLevel() == AuthorizationLevel.TRIAL_ACCESS && overlayIndex >= Integer.parseInt(Application.getResourceString(R.string.overlay_trial_count))) { DialogUtil.displayAuthorizationError(getActivity(), R.string.message_dialog_trial_overlays); return false; } if (oldButtonPosition != null && !oldButtonPosition.equals(buttonPosition)) { // If the same overlay is already used, switch overlays PreferenceUtil.setIndexedSharedPreferenceIntString(R.string.key_indexed_overlaytype, oldButtonPosition, oldOverlayIndex); ListPreference oldButtonPreference = (ListPreference) findPreference( PreferenceUtil.getIndexedPreferenceKey(R.string.key_indexed_overlaytype, oldButtonPosition)); updateSummaryForOverlayPreference(oldButtonPreference); } PreferenceUtil.setIndexedSharedPreferenceString(R.string.key_indexed_overlaytype, buttonPosition, stringValue); updateOverlayPreferenceEntries(); } } setSummary(preference, stringValue); return true; } /** * Update the summary for a preference. * * @param preference The preference. * @param value the new value of the preference. */ private void setSummary(@NonNull final Preference preference, final String value) { if (preference.getClass().equals(ListPreference.class)) { // For list preferences (except customized ones), look up the correct display value in the preference's 'entries' list. ListPreference listPreference = (ListPreference) preference; int index = listPreference.findIndexOfValue(value); if (preference.getKey().startsWith(getString(R.string.key_indexed_overlaytype))) { updateSummaryForOverlayPreference(listPreference); } else if (index < 0) { preference.setSummary(null); } else { String entryValue = listPreference.getEntries()[index].toString(); preference.setSummary(entryValue); } } else { // For all other preferences, set the summary to the value's simple string representation. preference.setSummary(value); } } /** * Set the summary for an overlay preference. * * @param preference The overlay preference. */ private void updateSummaryForOverlayPreference(@Nullable final ListPreference preference) { if (preference != null && preference.getKey().startsWith(getString(R.string.key_indexed_overlaytype))) { Integer buttonIndex = PreferenceUtil.getIndexFromPreferenceKey(preference.getKey()); if (buttonIndex == null) { buttonIndex = 0; } int overlayIndex = PreferenceUtil.getIndexedSharedPreferenceIntString(R.string.key_indexed_overlaytype, buttonIndex, -1); if (overlayIndex > 0) { String overlayName = getResources().getStringArray(R.array.overlay_names)[overlayIndex]; preference.setSummary(overlayName); } else { preference.setSummary(R.string.pref_value_overlay_button_empty); } } } /** * Check the folder for writeability. If not, then on Android 5 retrieve Uri for extsdcard via Storage * Access Framework. * * @param folder The folder to be checked. * @param code The request code of the type of folder check. * @return true if the check was successful or if SAF has been triggered. */ private boolean checkFolder(@NonNull final File folder, final int code) { if (SystemUtil.isAndroid5() && FileUtil.isOnExtSdCard(folder)) { if (!folder.exists() || !folder.isDirectory()) { return false; } // On Android 5, trigger storage access framework. if (!FileUtil.isWritableNormalOrSaf(folder)) { // Ensure via listener that storage access framework is called only after information // message. MessageDialogListener listener = new MessageDialogListener() { /** * Default serial version id. */ private static final long serialVersionUID = 1L; @Override @RequiresApi(api = VERSION_CODES.LOLLIPOP) public void onDialogClick(final DialogFragment dialog) { triggerStorageAccessFramework(code); } @Override public void onDialogCancel(final DialogFragment dialog) { // do nothing. } }; DialogUtil.displayInfo(getActivity(), listener, R.string.message_dialog_select_extsdcard); return false; } // Only accept after SAF stuff is done. return true; } else if (SystemUtil.isKitkat() && FileUtil.isOnExtSdCard(folder)) { // Assume that Kitkat workaround works return true; } else if (FileUtil.isWritable(new File(folder, "DummyFile"))) { return true; } else { DialogUtil.displayError(getActivity(), R.string.message_dialog_cannot_write_to_folder, false, mCurrentFolder); mCurrentFolder = null; return false; } } /** * Trigger the storage access framework to access the base folder of the ext sd card. * * @param code The request code to be used. */ @RequiresApi(Build.VERSION_CODES.LOLLIPOP) private void triggerStorageAccessFramework(final int code) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, code); } } }