Scoped storage was a feature introduced in Android Q(10). This is now mandatory from Android 11 that all apps targeting this version have scoped storage implemented, these new improvements have privacy in mind.
In preview versions of android before Android 10, the runtime permission to read or write was required to access almost every file on disk, the Scoped storage feature is introduced mainly to avoid this situation. You can also check the official documentation: Click Here
Manage device storage
From Android 11, the apps can use the scoped storage model can access only their own app-specific cache files.
If your app needs to manage device storage check the below action to free the space.
1. Check for free space by invoking the ACTION_MANAGE_STORAGE intent action.
2. If there isn’t enough free space on the device, prompt the user to give your app consent to clear all caches.
To do so, invoke the ACTION_CLEAR_APP_CACHE intent action(It might affect device battery life and might remove a large number of files from the device.).
Let’s write a sample code to access storage.
AndroidManifest.xml for permission:
1 2 3 4 5 6 7 |
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> |
ConstantHelpers.kt:
1 2 3 4 5 6 7 8 9 10 |
object ConstantHelpers { const val OPEN_FILE_REQUEST_CODE = 2001 const val OPEN_FOLDER_REQUEST_CODE = 2002 const val CHOOSE_FILE = 2003 const val READ_EXTERNAL_STORAGE_PERMISSION = 2004 const val CREATE_FILE_REQUEST_CODE = 2005 var downloadUrl = "https://mobikuldemo.webkul.com/mobikulmp/pub/media/mobikul/splash/1080x2160/default/black_mp.jpg" } |
MainActivity.kt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
/* This code is for Android 10 and above, if you are using the below android devices then you have to Handle the below version as per the older runtime permission required. */ class MainActivity : AppCompatActivity() { lateinit var mContentViewBinding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mContentViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) clickListeners() } private fun clickListeners() { mContentViewBinding.createFileBt.setOnClickListener { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (permissionToReadWrite) { createFile() } else { permissionForReadWrite() } } else { createFile() } } mContentViewBinding.openFileBt.setOnClickListener { openFile() } mContentViewBinding.openFolderBt.setOnClickListener { openFolder() } mContentViewBinding.downloadImageDownloadBt.setOnClickListener { downloadImage() } mContentViewBinding.downloadImageAppFolderBt.setOnClickListener { downloadImageToAppFolder() } } private fun createFile() { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.type = "text/plain" intent.putExtra(Intent.EXTRA_TITLE, "Test.txt") startActivityForResult(intent, CREATE_FILE_REQUEST_CODE) } private fun writeFileContent(uri: Uri?) { try { val file = uri?.let { this.contentResolver.openFileDescriptor(it, "w") } file?.let { val fileOutputStream = FileOutputStream( it.fileDescriptor ) val textContent = "This is the dummy text." fileOutputStream.write(textContent.toByteArray()) fileOutputStream.close() it.close() } } catch (e: FileNotFoundException) { //print logs } catch (e: IOException) { //print logs } } private fun downloadImage() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (permissionToReadWrite) { downloadImageToDownloadFolder() } else { permissionForReadWrite() } } else { downloadImageToDownloadFolder() } } private fun downloadImageToAppFolder() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (permissionToReadWrite) { downloadToAppFolder() } else { permissionForReadWrite() } } else { downloadToAppFolder() } } //Downloading file to Internal Folder private fun downloadToAppFolder() { try { val file = File( this.getExternalFilesDir( null ), "test2.png" ) if (!file.exists()) file.createNewFile() var fileOutputStream: FileOutputStream? = null fileOutputStream = FileOutputStream(file) val bitmap = (ContextCompat.getDrawable(this, R.drawable.test) as BitmapDrawable).bitmap bitmap?.compress(Bitmap.CompressFormat.PNG, 80, fileOutputStream) Toast.makeText( applicationContext, getString(R.string.download_successful) + file.absolutePath, Toast.LENGTH_LONG ).show() } catch (e: Exception) { e.printStackTrace() } } private var permissionToReadWrite: Boolean = false get() { val permissionGrantedResult: Int = ContextCompat.checkSelfPermission( this, android.Manifest.permission.READ_EXTERNAL_STORAGE ) return permissionGrantedResult == PackageManager.PERMISSION_GRANTED } //Request Permission For Read Storage private fun permissionForReadWrite() { ActivityCompat.requestPermissions( this, arrayOf( android.Manifest.permission.READ_EXTERNAL_STORAGE ), READ_EXTERNAL_STORAGE_PERMISSION ) } private fun downloadImageToDownloadFolder() { val mgr = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val downloadUri = Uri.parse(downloadUrl) val request = DownloadManager.Request( downloadUri ) request.setAllowedNetworkTypes( DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE ) .setAllowedOverRoaming(false).setTitle("Image Sample") .setDescription("Testing") .setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, "test1.jpg" ) Toast.makeText( applicationContext, "Downloaded successfully to ${downloadUri?.path}", Toast.LENGTH_LONG ).show() mgr.enqueue(request) } private fun openFolder() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION } startActivityForResult(intent, OPEN_FOLDER_REQUEST_CODE) } private fun openFile() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { //if you want to open PDF file type = "application/pdf" addCategory(Intent.CATEGORY_OPENABLE) //Adding Read URI permission flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION } startActivityForResult(intent, OPEN_FILE_REQUEST_CODE) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK) { if (requestCode == OPEN_FILE_REQUEST_CODE) { data?.data?.also { documentUri -> //Permission needed if you want to retain access even after reboot contentResolver.takePersistableUriPermission( documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) Toast.makeText(this, documentUri.path.toString(), Toast.LENGTH_LONG).show() } } else if (requestCode == OPEN_FOLDER_REQUEST_CODE) { val directoryUri = data?.data ?: return //Taking permission to retain access contentResolver.takePersistableUriPermission( directoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) //Now you have access to the folder, you can easily view the content or do whatever you want. val documentsTree = DocumentFile.fromTreeUri(application, directoryUri) ?: return val childDocuments = documentsTree.listFiles().asList() Toast.makeText( this, "Total Items Under this folder =" + childDocuments.size.toString(), Toast.LENGTH_LONG ).show() } else if (requestCode == CHOOSE_FILE) else if (requestCode == CREATE_FILE_REQUEST_CODE) { if (data != null) { writeFileContent(data.data) } } } } } |
activity_main.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<?xml version="1.0" encoding="utf-8"?> <layout> <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/nested_scroll" android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <Button android:id="@+id/create_file_bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_normal" android:layout_marginTop="@dimen/spacing_huge" android:layout_marginEnd="@dimen/spacing_normal" android:padding="@dimen/spacing_normal" android:text="@string/create_a_dummy_file" /> <Button android:id="@+id/open_file_bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_normal" android:padding="@dimen/spacing_normal" android:text="@string/open_files" /> <Button android:id="@+id/open_folder_bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_normal" android:padding="@dimen/spacing_normal" android:text="@string/open_folder" /> <Button android:id="@+id/download_image_download_bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_normal" android:padding="@dimen/spacing_normal" android:text="@string/download_image_url_to_downloads" /> <Button android:id="@+id/download_image_app_folder_bt" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/spacing_normal" android:padding="@dimen/spacing_normal" android:text="@string/download_image_to_app_folder" /> </androidx.appcompat.widget.LinearLayoutCompat> </androidx.core.widget.NestedScrollView> </layout> |
string.xml:
1 2 3 4 5 6 7 8 9 10 |
<resources> <string name="app_name">Scoped Storage Example</string> <string name="file_operations_demo">File Operations Demo</string> <string name="create_a_dummy_file">Create a Dummy File</string> <string name="open_files">Open files</string> <string name="open_folder">Open Folder</string> <string name="download_image_to_app_folder">Download Image to App Folder</string> <string name="download_image_url_to_downloads">Download Image from Url to Downloads</string> <string name="download_successful">Image Download successful to: </string> </resources> |
dimens.xml:
1 2 3 4 5 6 |
<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="text_size_large">30sp</dimen> <dimen name="spacing_normal">8dp</dimen> <dimen name="spacing_huge">48dp</dimen> </resources> |
. . . . . . . . .
That’s it from my side for today, thanks for reading it until now. You can check our other android blogs Click Here.