Skip to content

Commit

Permalink
Switch to Storage Access Framework SDK > 25
Browse files Browse the repository at this point in the history
  • Loading branch information
AndInTheClouds committed Dec 21, 2022
1 parent 2251461 commit 8d5aa5d
Show file tree
Hide file tree
Showing 18 changed files with 599 additions and 241 deletions.
11 changes: 7 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.hollowbamboo.chordreader2"
android:versionCode="3"
android:versionName="2.1.1">
android:versionCode="4"
android:versionName="2.1.2">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:maxSdkVersion="25"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:icon="@mipmap/chord_reader_icon"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true">
android:requestLegacyExternalStorage="true"
android:allowBackup="true">

<activity
android:name="org.hollowbamboo.chordreader2.MainActivity"
Expand Down
137 changes: 116 additions & 21 deletions app/src/main/java/org/hollowbamboo/chordreader2/MainActivity.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
package org.hollowbamboo.chordreader2;

import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
import static android.os.Build.VERSION.SDK_INT;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.Settings;
import android.preference.PreferenceManager;
import android.provider.DocumentsContract;
import android.text.method.LinkMovementMethod;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStore;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
Expand All @@ -43,6 +52,8 @@
import org.hollowbamboo.chordreader2.helper.PreferenceHelper;
import org.hollowbamboo.chordreader2.model.DataViewModel;

import java.util.Objects;

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {

private static final int STORAGE_PERMISSION_CODE = 100;
Expand Down Expand Up @@ -77,11 +88,11 @@ protected void onCreate(Bundle savedInstanceState) {

initializeChordDictionary();

showInitialMessage();

if(!areStoragePermissionsGranted())
requestPermission();

showInitialMessage();

viewModel = new ViewModelProvider(this).get(DataViewModel.class);
}

Expand Down Expand Up @@ -152,6 +163,29 @@ else if (tag.contains("WebSearch"))

private void showInitialMessage() {

int versionCode = 0;

try {
versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}

SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

if (prefs.getInt("lastUpdate", 0) != versionCode) {
try {
PreferenceHelper.setFirstRunPreference(getApplicationContext(), true);

// Commiting in the preferences, that the update was successful.
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("lastUpdate", versionCode);
editor.apply();
} catch(Throwable t) {
// update failed, or cancelled
}
}

boolean isFirstRun = PreferenceHelper.getFirstRunPreference(getApplicationContext());
if(isFirstRun) {

Expand All @@ -171,32 +205,93 @@ private void showInitialMessage() {
}

private boolean areStoragePermissionsGranted() {
if(SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
if(SDK_INT >= Build.VERSION_CODES.O) {
Uri storageLocationSummary = PreferenceHelper.getStorageLocation(this);
int permissions = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;

try {
getContentResolver().takePersistableUriPermission(storageLocationSummary, permissions);
return true;
} catch (Exception e) {
return false;
}
} else {
int result = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}

private void requestPermission()
{
if(SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
startActivityForResult(intent, 2296);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 2296);
private void requestPermission() {

ActivityResultLauncher<Intent> directoryPickerResultLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {

if (result.getData() != null) {
Uri uri = result.getData().getData();

grantUriPermission(getPackageName(), uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

PreferenceHelper.setStorageLocation(getApplicationContext(), uri);
PreferenceHelper.clearCache();
}
}
}
});


DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {

if(SDK_INT >= Build.VERSION_CODES.O) {

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
PreferenceHelper.getStorageLocation(getApplicationContext()));

intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
directoryPickerResultLauncher.launch(intent);
} else {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
}
}
};

//check if folder chord_reader_2 already exist or first time install
DocumentFile base_folder;
String message;

try {
base_folder = Objects.requireNonNull(DocumentFile.fromTreeUri(getApplicationContext(),
PreferenceHelper.getStorageLocation(getApplicationContext())));
} catch (Exception e) {
base_folder = null;
}

if(base_folder == null || !base_folder.exists()) {
message = getString(R.string.select_storage_location);
} else {
//below android 11
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
message = getString(R.string.grant_storage_access);
}

AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setTitle(R.string.storage_access)
.setPositiveButton(android.R.string.yes, onClickListener)
.setMessage(message);

builder.show();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import org.hollowbamboo.chordreader2.R;
import org.hollowbamboo.chordreader2.chords.Chord;
import org.hollowbamboo.chordreader2.chords.NoteNaming;
import org.hollowbamboo.chordreader2.chords.regex.ChordParser;
import org.hollowbamboo.chordreader2.db.ChordReaderDBHelper;
import org.hollowbamboo.chordreader2.ui.SongViewFragment;
import org.hollowbamboo.chordreader2.util.StringUtil;

import java.io.BufferedReader;
Expand All @@ -33,7 +40,7 @@ public static void initialize(Context context) {

// load custom chord variations
try {
customChordVars = SaveFileHelper.openFile("customChordVariations_DO_NOT_EDIT.crd");
customChordVars = SaveFileHelper.openFile(context,"customChordVariations_DO_NOT_EDIT.crd");

if (!customChordVars.isEmpty()) {
loadIntoChordDictionary(context, -1, NoteNaming.English, dictionary);
Expand Down Expand Up @@ -114,17 +121,17 @@ public static List<String> getGuitarChordsForChord(Chord chord) {
return result != null ? result : Collections.emptyList();
}

public static void setGuitarChordsForChord(Chord chord, List<String> newGuitarChords) {
public static void setGuitarChordsForChord(Context context, Chord chord, List<String> newGuitarChords) {
List<String> existingValue = chordsToGuitarChords.get(chord);
if (existingValue != null) {
chordsToGuitarChords.remove(chord);
}
chordsToGuitarChords.put(chord, newGuitarChords);

saveChordDictionaryToFile();
saveChordDictionaryToFile(context);
}

private static void saveChordDictionaryToFile() {
private static void saveChordDictionaryToFile(Context context) {
StringBuilder stringBuilder = new StringBuilder();
for (Object key : chordsToGuitarChords.keySet()) {
String chord = ((Chord) key).toPrintableString(NoteNaming.English);
Expand All @@ -142,14 +149,45 @@ private static void saveChordDictionaryToFile() {
final String result = stringBuilder.substring(0, stringBuilder.length() - 1); // cut off final newline

// do in background to avoid jankiness
AsyncTask<Void,Void,Boolean> saveTask = new AsyncTask<Void, Void, Boolean>(){
HandlerThread handlerThread = new HandlerThread("SaveChordsHandlerThread");
handlerThread.start();

Handler asyncHandler = new Handler(handlerThread.getLooper()) {
@Override
protected Boolean doInBackground(Void... params) {
return SaveFileHelper.saveFile(result, "customChordVariations_DO_NOT_EDIT.crd");
public void handleMessage(Message msg) {
super.handleMessage(msg);
Boolean successfullySavedLog = (Boolean) msg.obj;

String toastMessage;

if(successfullySavedLog) {
toastMessage = context.getString(R.string.file_saved);
} else {
toastMessage = context.getString(R.string.unable_to_save_file);
}

// must be called on the main thread
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show();
}
});


handlerThread.quit();
}
};

saveTask.execute((Void)null);
Runnable runnable = () -> {
// your async code goes here.
Message message = new Message();
message.obj = SaveFileHelper.saveFile(context, result, "customChordVariations_DO_NOT_EDIT.crd");

asyncHandler.sendMessage(message);
};

asyncHandler.post(runnable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.preference.PreferenceManager;
import org.hollowbamboo.chordreader2.R;
import org.hollowbamboo.chordreader2.chords.NoteNaming;
Expand All @@ -15,8 +16,9 @@ public class PreferenceHelper {
private static ColorScheme colorScheme = null;
private static NoteNaming noteNaming = null;
private static String searchEngineURL = null;
private static String storageLocation = null;

private static UtilLogger log = new UtilLogger(org.hollowbamboo.chordreader2.helper.PreferenceHelper.class);
private static final UtilLogger log = new UtilLogger(org.hollowbamboo.chordreader2.helper.PreferenceHelper.class);

public static void clearCache() {
textSize = -1;
Expand Down Expand Up @@ -118,8 +120,25 @@ public static void setSearchEngineURL(Context context, String searchEngineURL) {
Editor editor = sharedPrefs.edit();
editor.putString(context.getString(R.string.pref_search_engine), searchEngineURL);
editor.apply();

}

public static Uri getStorageLocation(Context context) {

if(storageLocation == null) {

SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
storageLocation = sharedPrefs.getString(
context.getString(R.string.pref_storage_location),
context.getString(R.string.pref_storage_location_default));
}

return Uri.parse(storageLocation);
}

public static void setStorageLocation(Context context, Uri uri) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
Editor editor = sharedPrefs.edit();
editor.putString(context.getString(R.string.pref_storage_location), uri.toString());
editor.apply();
}
}
Loading

0 comments on commit 8d5aa5d

Please sign in to comment.