Android 9 (API level 28) introduces great new features and capabilities for users and developers. Some of the changes impacts File Write and Read. The following is a solution to the File Write / Read problem for pdf files
Code Structure
Code Structure
GenericFileProvider.java
package com.zackdawood; import android.support.v4.content.FileProvider; public class GenericFileProvider extends FileProvider { }
FileDownloader.javapackage com.zackdawood; import android.util.Log; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class FileDownloader { private static final String TAG = "FileDownloader"; private static final int MEGABYTE = 1024 * 1024; public static void downloadFile(String fileUrl, File directory) { try { Log.v(TAG, "downloadFile() invoked "); Log.v(TAG, "downloadFile() fileUrl " + fileUrl); Log.v(TAG, "downloadFile() directory " + directory); URL url = new URL(fileUrl); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.connect(); InputStream inputStream = urlConnection.getInputStream(); FileOutputStream fileOutputStream = new FileOutputStream(directory); int totalSize = urlConnection.getContentLength(); byte[] buffer = new byte[MEGABYTE]; int bufferLength = 0; while ((bufferLength = inputStream.read(buffer)) > 0) { fileOutputStream.write(buffer, 0, bufferLength); } fileOutputStream.close(); Log.v(TAG, "downloadFile() completed "); } catch (FileNotFoundException e) { e.printStackTrace(); Log.e(TAG, "downloadFile() error" + e.getMessage()); Log.e(TAG, "downloadFile() error" + e.getStackTrace()); } catch (MalformedURLException e) { e.printStackTrace(); Log.e(TAG, "downloadFile() error" + e.getMessage()); Log.e(TAG, "downloadFile() error" + e.getStackTrace()); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "downloadFile() error" + e.getMessage()); Log.e(TAG, "downloadFile() error" + e.getStackTrace()); } } }
MainActivity.javaAndroidManifest.xmlpackage com.zackdawood; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.FileProvider; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.Toast; import java.io.File; import java.io.IOException; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private static final String[] PERMISSIONS = {android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}; private static boolean hasPermissions(Context context, String... permissions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context != null && permissions != null) { for (String permission : permissions) { if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } } return true; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.v(TAG, "onCreate() Method invoked "); ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, 112); } public void request(View view) { ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, 112); } public void view(View view) { Log.v(TAG, "view() Method invoked "); if (!hasPermissions(MainActivity.this, PERMISSIONS)) { Log.v(TAG, "download() Method DON'T HAVE PERMISSIONS "); Toast t = Toast.makeText(getApplicationContext(), "You don't have read access !", Toast.LENGTH_LONG); t.show(); } else { File d = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); // -> filename = maven.pdf File pdfFile = new File(d, "maven.pdf"); Log.v(TAG, "view() Method pdfFile " + pdfFile.getAbsolutePath()); Uri path = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", pdfFile); Log.v(TAG, "view() Method path " + path); Intent pdfIntent = new Intent(Intent.ACTION_VIEW); pdfIntent.setDataAndType(path, "application/pdf"); pdfIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); pdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); try { startActivity(pdfIntent); } catch (ActivityNotFoundException e) { Toast.makeText(MainActivity.this, "No Application available to view PDF", Toast.LENGTH_SHORT).show(); } } Log.v(TAG, "view() Method completed "); } public void download(View view) { Log.v(TAG, "download() Method invoked "); if (!hasPermissions(MainActivity.this, PERMISSIONS)) { Log.v(TAG, "download() Method DON'T HAVE PERMISSIONS "); Toast t = Toast.makeText(getApplicationContext(), "You don't have write access !", Toast.LENGTH_LONG); t.show(); } else { Log.v(TAG, "download() Method HAVE PERMISSIONS "); //new DownloadFile().execute("http://maven.apache.org/maven-1.x/maven.pdf", "maven.pdf"); new DownloadFile().execute("http://www.axmag.com/download/pdfurl-guide.pdf", "maven.pdf"); } Log.v(TAG, "download() Method completed "); } private class DownloadFile extends AsyncTask<String, Void, Void> { @Override protected Void doInBackground(String... strings) { Log.v(TAG, "doInBackground() Method invoked "); String fileUrl = strings[0]; // -> http://maven.apache.org/maven-1.x/maven.pdf String fileName = strings[1]; // -> maven.pdf String extStorageDirectory = Environment.getExternalStorageDirectory().toString(); File folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File pdfFile = new File(folder, fileName); Log.v(TAG, "doInBackground() pdfFile invoked " + pdfFile.getAbsolutePath()); Log.v(TAG, "doInBackground() pdfFile invoked " + pdfFile.getAbsoluteFile()); try { pdfFile.createNewFile(); Log.v(TAG, "doInBackground() file created" + pdfFile); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "doInBackground() error" + e.getMessage()); Log.e(TAG, "doInBackground() error" + e.getStackTrace()); } FileDownloader.downloadFile(fileUrl, pdfFile); Log.v(TAG, "doInBackground() file download completed"); return null; } } }
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.zackdawood"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" android:networkSecurityConfig="@xml/network_security_config"> <provider android:name=".GenericFileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/external_files"/> </provider> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="200px" android:onClick="download" android:text="@string/file_download" /> <Button android:id="@+id/button4" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="200px" android:onClick="view" android:text="@string/file_view" /> <Button android:id="@+id/button5" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="200px" android:onClick="request" android:text="@string/file_request" /> </LinearLayout> </android.support.design.widget.CoordinatorLayout>
strings.xml<resources> <string name="app_name">DemoInternalDownload</string> <string name="action_settings">Settings</string> <string name="file_view">File View</string> <string name="file_download">File Download</string> <string name="file_request">Request Access Again</string> </resources>
external_files.xml<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths>
network_security_config.xml<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">maven.apache.org</domain> <domain includeSubdomains="true">www.axmag.com</domain> </domain-config> </network-security-config>
build.gradleapply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.zackdawood" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
Prompt ScreenOutput Screen