From 8400a519e1246388a0b2dc6ecf0f0a3b80842fc7 Mon Sep 17 00:00:00 2001 From: Gyubin Han Date: Sun, 4 Jan 2026 23:09:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=99=B8=EC=9E=A5=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EC=86=8C=20=EC=A0=91=EA=B7=BC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/file/explorer/MainActivity.java | 210 +++++++++++++++++- app/src/main/res/drawable/ic_storage.xml | 10 + app/src/main/res/layout/activity_main.xml | 21 +- app/src/main/res/menu/main_menu.xml | 9 + 4 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_storage.xml create mode 100644 app/src/main/res/menu/main_menu.xml diff --git a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java index c98f145..2bf8356 100644 --- a/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java +++ b/app/src/main/java/be/gyu/android/file/explorer/MainActivity.java @@ -1,12 +1,218 @@ package be.gyu.android.file.explorer; -import androidx.appcompat.app.AppCompatActivity; +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; +import android.provider.Settings; +import android.view.Menu; +import android.view.MenuItem; +import android.webkit.MimeTypeMap; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity implements FileAdapter.OnItemClickListener { + + private static final int REQUEST_CODE_MANAGE_EXTERNAL_STORAGE = 1; + private RecyclerView recyclerView; + private FileAdapter fileAdapter; + private List fileList; + private File currentDirectory; -public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + recyclerView = findViewById(R.id.recyclerView); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + fileList = new ArrayList<>(); + fileAdapter = new FileAdapter(fileList); + fileAdapter.setOnItemClickListener(this); + recyclerView.setAdapter(fileAdapter); + + if (checkStoragePermission()) { + loadFiles(Environment.getExternalStorageDirectory()); + } else { + requestStoragePermission(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + if (item.getItemId() == R.id.select_storage) { + showStorageSelectionDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void showStorageSelectionDialog() { + StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE); + List storageVolumes = storageManager.getStorageVolumes(); + List volumeNames = new ArrayList<>(); + List volumePaths = new ArrayList<>(); + + for (StorageVolume volume : storageVolumes) { + if (volume.getState().equals(Environment.MEDIA_MOUNTED)) { + File path = volume.getDirectory(); + if (path != null) { + volumeNames.add(volume.getDescription(this)); + volumePaths.add(path); + } + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Select Storage"); + builder.setItems(volumeNames.toArray(new String[0]), (dialog, which) -> { + loadFiles(volumePaths.get(which)); + }); + builder.show(); + } + + + private boolean checkStoragePermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Environment.isExternalStorageManager(); + } else { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; + } + } + + private void requestStoragePermission() { + if (Build.VERSION.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, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE); + } catch (Exception e) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + startActivityForResult(intent, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE); + } + } else { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_MANAGE_EXTERNAL_STORAGE); + } + } + + private void loadFiles(File directory) { + this.currentDirectory = directory; + if (directory != null) { + setTitle(directory.getName()); + fileList.clear(); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + fileList.add(new FileItem(file.getName(), file.getAbsolutePath(), file.isDirectory())); + } + } else { + Toast.makeText(this, "Cannot read this directory!", Toast.LENGTH_SHORT).show(); + } + } + fileAdapter.notifyDataSetChanged(); + } + + @Override + public void onItemClick(FileItem item) { + File file = new File(item.getPath()); + if (file.isDirectory()) { + if (file.canRead()) { + loadFiles(file); + } else { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } else { + openFile(file); + } + } + + private void openFile(File file) { + Uri uri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file); + Intent intent = new Intent(Intent.ACTION_VIEW); + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(uri.toString())); + intent.setDataAndType(uri, mime); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + startActivity(intent); + } catch (Exception e) { + Toast.makeText(this, "Cannot open file", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onBackPressed() { + // Check against all storage volume roots + StorageManager storageManager = (StorageManager) getSystemService(STORAGE_SERVICE); + List storageVolumes = storageManager.getStorageVolumes(); + boolean isAtRootOfAVolume = false; + for (StorageVolume volume : storageVolumes) { + File volumePath = volume.getDirectory(); + if (volumePath != null && currentDirectory.equals(volumePath)) { + isAtRootOfAVolume = true; + break; + } + } + + if (currentDirectory != null && !isAtRootOfAVolume) { + loadFiles(currentDirectory.getParentFile()); + } else { + super.onBackPressed(); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + loadFiles(Environment.getExternalStorageDirectory()); + } else { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_MANAGE_EXTERNAL_STORAGE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment.isExternalStorageManager()) { + loadFiles(Environment.getExternalStorageDirectory()); + } else { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_storage.xml b/app/src/main/res/drawable/ic_storage.xml new file mode 100644 index 0000000..9cd4030 --- /dev/null +++ b/app/src/main/res/drawable/ic_storage.xml @@ -0,0 +1,10 @@ + + + \ 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 93faac8..6b38c8f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,9 +6,26 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + + + + + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/appBarLayout" /> \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml new file mode 100644 index 0000000..240c3c6 --- /dev/null +++ b/app/src/main/res/menu/main_menu.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file