From 754b96a319c2d6b79f5b47a1c0131bceeffc41f7 Mon Sep 17 00:00:00 2001 From: David Madl Date: Sat, 7 Mar 2026 18:48:57 +0100 Subject: [PATCH] feat: benchmark MediaStore API of Android --- app/src/main/AndroidManifest.xml | 12 +- .../java/at/lockstep/app/MainActivity.java | 8 + .../app/MediaStoreBenchmarkActivity.java | 141 ++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 6 + .../layout/activity_media_store_benchmark.xml | 13 ++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/at/lockstep/app/MediaStoreBenchmarkActivity.java create mode 100644 app/src/main/res/layout/activity_media_store_benchmark.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1932e55..7c35cd5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,9 @@ + + + - + + + + + + diff --git a/app/src/main/java/at/lockstep/app/MainActivity.java b/app/src/main/java/at/lockstep/app/MainActivity.java index a219cc3..d47abcc 100644 --- a/app/src/main/java/at/lockstep/app/MainActivity.java +++ b/app/src/main/java/at/lockstep/app/MainActivity.java @@ -1,6 +1,7 @@ package at.lockstep.app; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.widget.Button; @@ -12,6 +13,7 @@ import at.lockstep.pb.PlaybackEngine; public class MainActivity extends Activity { private Button btnStart; private Button btnStop; + private Button btnMediaStoreBenchmark; @Override protected void onCreate(Bundle savedInstanceState) { @@ -20,6 +22,7 @@ public class MainActivity extends Activity { btnStart = findViewById(R.id.btnStart); btnStop = findViewById(R.id.btnStop); + btnMediaStoreBenchmark = findViewById(R.id.btnMediaStoreBenchmark); // TODO: handle clicking START button twice btnStart.setOnClickListener(v -> @@ -39,5 +42,10 @@ public class MainActivity extends Activity { btnStop.setOnClickListener(v -> startService(LstForegroundService.stopIntent(MainActivity.this)) ); + + btnMediaStoreBenchmark.setOnClickListener(v -> { + Intent intent = new Intent(MainActivity.this, MediaStoreBenchmarkActivity.class); + startActivity(intent); + }); } } diff --git a/app/src/main/java/at/lockstep/app/MediaStoreBenchmarkActivity.java b/app/src/main/java/at/lockstep/app/MediaStoreBenchmarkActivity.java new file mode 100644 index 0000000..9331d06 --- /dev/null +++ b/app/src/main/java/at/lockstep/app/MediaStoreBenchmarkActivity.java @@ -0,0 +1,141 @@ +package at.lockstep.app; + +import static androidx.activity.result.ActivityResultCallerKt.registerForActivityResult; + +import android.app.Activity; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemClock; +import android.provider.MediaStore; +import android.widget.TextView; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.core.content.ContextCompat; + +import java.util.ArrayList; +import java.util.List; + +import at.lockstep.R; + +public class MediaStoreBenchmarkActivity extends Activity { + + private TextView resultTextView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_media_store_benchmark); + + resultTextView = findViewById(R.id.resultTextView); + + if (hasReadPermission()) { + loadMusic(); + } else { + requestReadPermission(); + } + } + + private static final int REQUEST_READ_PERMISSION = 1001; + + @Override + public void onRequestPermissionsResult( + int requestCode, + String[] permissions, + int[] grantResults + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + if (requestCode == REQUEST_READ_PERMISSION) { + boolean granted = grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED; + + if (granted) { + loadMusic(); + } else { + resultTextView.setText("Permission denied."); + } + } + } + + private boolean hasReadPermission() { + String permission; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permission = Manifest.permission.READ_MEDIA_AUDIO; + } else { + permission = Manifest.permission.READ_EXTERNAL_STORAGE; + } + + return ContextCompat.checkSelfPermission(this, permission) + == PackageManager.PERMISSION_GRANTED; + } + + private void requestReadPermission() { + String permission; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permission = Manifest.permission.READ_MEDIA_AUDIO; + } else { + permission = Manifest.permission.READ_EXTERNAL_STORAGE; + } + + //permissionLauncher.launch(permission); + requestPermissions( + new String[]{ permission }, + REQUEST_READ_PERMISSION + ); + } + + private void loadMusic() { + List musicList = new ArrayList<>(); + + android.net.Uri collection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + + String[] projection = new String[] { + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.DATA + }; + + String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0"; + + long start = SystemClock.elapsedRealtime(); + + try (android.database.Cursor cursor = getContentResolver().query( + collection, + projection, + selection, + null, + MediaStore.Audio.Media.TITLE + " ASC" + )) { + if (cursor != null) { + int titleColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE); + int dataColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DATA); + + while (cursor.moveToNext()) { + String title = cursor.getString(titleColumn); + String path = dataColumn != -1 ? cursor.getString(dataColumn) : null; + + if (path != null) { + musicList.add(title + "\n" + path); + } else { + musicList.add(title + "\n[path unavailable]"); + } + } + } + } + + long elapsedMs = SystemClock.elapsedRealtime() - start; + + StringBuilder header = new StringBuilder(); + header.append("Found ").append(musicList.size()).append(" music files\n"); + header.append("Query time: ").append(elapsedMs).append(" ms\n\n"); + + resultTextView.setText(header.toString() + String.join("\n\n", musicList)); + + // 200 music files in 32 ms + // paths like "/storage/emulated/0/Download/...mp3" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e946fd5..f8cc49a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -18,4 +18,10 @@ android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Stop collection" /> + +