Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable downloads from private sites #10610

Merged
merged 4 commits into from
Oct 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package org.wordpress.android.networking

import android.util.Base64
import com.android.volley.Request
import com.android.volley.Request.Priority
import com.bumptech.glide.integration.volley.VolleyRequestFactory
import com.bumptech.glide.integration.volley.VolleyStreamFetcher
import com.bumptech.glide.load.data.DataFetcher.DataCallback
import org.wordpress.android.fluxc.network.HTTPAuthManager
import org.wordpress.android.fluxc.network.UserAgent
import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken
import org.wordpress.android.ui.utils.AuthenticationUtils
import org.wordpress.android.util.UrlUtils
import org.wordpress.android.util.WPUrlUtils
import java.io.InputStream
Expand All @@ -21,9 +18,7 @@ import javax.inject.Singleton
*/
@Singleton
class GlideRequestFactory @Inject constructor(
private val accessToken: AccessToken,
private val httpAuthManager: HTTPAuthManager,
private val userAgent: UserAgent
private val authenticationUtils: AuthenticationUtils
) : VolleyRequestFactory {
override fun create(
url: String,
Expand All @@ -40,20 +35,10 @@ class GlideRequestFactory @Inject constructor(
}

private fun addAuthHeaders(url: String, currentHeaders: Map<String, String>): MutableMap<String, String> {
val authenticationHeaders = authenticationUtils.getAuthHeaders(url)
val headers = currentHeaders.toMutableMap()
headers["User-Agent"] = userAgent.userAgent
if (WPUrlUtils.safeToAddWordPressComAuthToken(url)) {
if (accessToken.exists()) {
headers["Authorization"] = "Bearer " + accessToken.get()
}
} else {
// Check if we had HTTP Auth credentials for the root url
val httpAuthModel = httpAuthManager.getHTTPAuthModel(url)
if (httpAuthModel != null) {
val creds = String.format("%s:%s", httpAuthModel.username, httpAuthModel.password)
val auth = "Basic " + Base64.encodeToString(creds.toByteArray(), Base64.NO_WRAP)
headers["Authorization"] = auth
}
authenticationHeaders.entries.forEach { (key, value) ->
headers[key] = value
}
return headers
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.wordpress.android.ui.reader

import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Environment
import org.wordpress.android.ui.utils.AuthenticationUtils
import org.wordpress.android.ui.utils.DownloadManagerWrapper
import javax.inject.Inject

class ReaderFileDownloadManager
@Inject constructor(
private val authenticationUtils: AuthenticationUtils,
private val downloadManager: DownloadManagerWrapper
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE == intent.action) {
val downloadId = intent.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, 0
)
openDownloadedAttachment(context, downloadId)
}
}

fun downloadFile(fileUrl: String) {
val request = downloadManager.buildRequest(fileUrl)

for (entry in authenticationUtils.getAuthHeaders(fileUrl).entries) {
request.addRequestHeader(entry.key, entry.value)
}

val fileName = downloadManager.guessUrl(fileUrl)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setMimeType(downloadManager.getMimeType(fileUrl))
request.setTitle(fileName)
request.allowScanningByMediaScanner()

downloadManager.enqueue(request)
}

private fun openDownloadedAttachment(context: Context, downloadId: Long) {
val query = downloadManager.buildQuery()
query.setFilterById(downloadId)
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
val downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
val downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE))
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL && downloadLocalUri != null) {
downloadManager.openDownloadedAttachment(context, downloadLocalUri, downloadMimeType)
}
}
cursor.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.text.TextUtils;
import android.view.LayoutInflater;
Expand All @@ -17,7 +16,6 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.URLUtil;
import android.webkit.WebView;
import android.widget.ProgressBar;
import android.widget.TextView;
Expand Down Expand Up @@ -76,6 +74,7 @@
import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderCustomViewListener;
import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderWebViewPageFinishedListener;
import org.wordpress.android.ui.reader.views.ReaderWebView.ReaderWebViewUrlClickListener;
import org.wordpress.android.ui.utils.AuthenticationUtils;
import org.wordpress.android.util.AniUtils;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
Expand All @@ -98,7 +97,6 @@

import javax.inject.Inject;

import static android.content.Context.DOWNLOAD_SERVICE;
import static org.wordpress.android.fluxc.generated.AccountActionBuilder.newUpdateSubscriptionNotificationPostAction;
import static org.wordpress.android.util.WPPermissionUtils.READER_FILE_DOWNLOAD_PERMISSION_REQUEST_CODE;
import static org.wordpress.android.util.WPSwipeToRefreshHelper.buildSwipeToRefreshHelper;
Expand Down Expand Up @@ -160,6 +158,8 @@ public class ReaderPostDetailFragment extends Fragment
@Inject AccountStore mAccountStore;
@Inject SiteStore mSiteStore;
@Inject Dispatcher mDispatcher;
@Inject AuthenticationUtils mAuthenticationUtils;
@Inject ReaderFileDownloadManager mReaderFileDownloadManager;

public static ReaderPostDetailFragment newInstance(long blogId, long postId) {
return newInstance(false, blogId, postId, null, 0, false, null, null, false);
Expand Down Expand Up @@ -431,13 +431,22 @@ public void onStart() {
super.onStart();
mDispatcher.register(this);
EventBus.getDefault().register(this);
FragmentActivity activity = getActivity();
if (activity != null) {
activity.registerReceiver(mReaderFileDownloadManager,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}

@Override
public void onStop() {
super.onStop();
mDispatcher.unregister(this);
EventBus.getDefault().unregister(this);
FragmentActivity activity = getActivity();
if (activity != null) {
activity.unregisterReceiver(mReaderFileDownloadManager);
}
}

/*
Expand Down Expand Up @@ -1351,8 +1360,12 @@ public boolean onUrlClick(String url) {
return true;
}

OpenUrlType openUrlType = shouldOpenExternal(url) ? OpenUrlType.EXTERNAL : OpenUrlType.INTERNAL;
ReaderActivityLauncher.openUrl(getActivity(), url, openUrlType);
if (isFile(url)) {
onFileDownloadClick(url);
} else {
OpenUrlType openUrlType = shouldOpenExternal(url) ? OpenUrlType.EXTERNAL : OpenUrlType.INTERNAL;
ReaderActivityLauncher.openUrl(getActivity(), url, openUrlType);
}
return true;
}

Expand All @@ -1368,12 +1381,17 @@ private boolean shouldOpenExternal(String url) {
// if the mime type starts with "application" open it externally - this will either
// open it in the associated app or the default browser (which will enable the user
// to download it)
if (isFile(url)) return true;

// open all other urls using an AuthenticatedWebViewActivity
return false;
}

private boolean isFile(String url) {
String mimeType = UrlUtils.getUrlMimeType(url);
if (mimeType != null && mimeType.startsWith("application")) {
return true;
}

// open all other urls using an AuthenticatedWebViewActivity
return false;
}

Expand All @@ -1388,7 +1406,7 @@ public boolean onFileDownloadClick(String fileUrl) {
if (activity != null
&& fileUrl != null
&& PermissionUtils.checkAndRequestStoragePermission(this, READER_FILE_DOWNLOAD_PERMISSION_REQUEST_CODE)) {
downloadFile(fileUrl, activity);
mReaderFileDownloadManager.downloadFile(fileUrl);
return true;
} else {
mFileForDownload = fileUrl;
Expand All @@ -1403,23 +1421,13 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
if (activity != null
&& requestCode == READER_FILE_DOWNLOAD_PERMISSION_REQUEST_CODE
&& (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
downloadFile(mFileForDownload, activity);
mReaderFileDownloadManager.downloadFile(mFileForDownload);
mFileForDownload = null;
} else {
mFileForDownload = null;
}
}

private void downloadFile(String fileUrl, FragmentActivity activity) {
DownloadManager.Request r = new DownloadManager.Request(Uri.parse(fileUrl));
String fileName = URLUtil.guessUrl(fileUrl);
r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
r.allowScanningByMediaScanner();
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
DownloadManager dm = (DownloadManager) activity.getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(r);
}

private ActionBar getActionBar() {
if (isAdded() && getActivity() instanceof AppCompatActivity) {
return ((AppCompatActivity) getActivity()).getSupportActionBar();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.wordpress.android.ui.utils

import android.util.Base64
import org.wordpress.android.fluxc.network.HTTPAuthManager
import org.wordpress.android.fluxc.network.UserAgent
import org.wordpress.android.fluxc.network.rest.wpcom.auth.AccessToken
import org.wordpress.android.util.WPUrlUtils
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthenticationUtils
@Inject constructor(
private val accessToken: AccessToken,
private val httpAuthManager: HTTPAuthManager,
private val userAgent: UserAgent
) {
fun getAuthHeaders(url: String): Map<String, String> {
val headers = mutableMapOf<String, String>()
headers["User-Agent"] = userAgent.userAgent
if (WPUrlUtils.safeToAddWordPressComAuthToken(url)) {
if (accessToken.exists()) {
headers["Authorization"] = "Bearer " + accessToken.get()
}
} else {
// Check if we had HTTP Auth credentials for the root url
val httpAuthModel = httpAuthManager.getHTTPAuthModel(url)
if (httpAuthModel != null) {
val creds = String.format("%s:%s", httpAuthModel.username, httpAuthModel.password)
val auth = "Basic " + Base64.encodeToString(creds.toByteArray(), Base64.NO_WRAP)
headers["Authorization"] = auth
}
}
return headers
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.wordpress.android.ui.utils

import android.app.DownloadManager
import android.app.DownloadManager.Query
import android.app.DownloadManager.Request
import android.content.ActivityNotFoundException
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.webkit.MimeTypeMap
import android.webkit.URLUtil
import androidx.core.content.FileProvider
import org.wordpress.android.BuildConfig
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T
import java.io.File
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class DownloadManagerWrapper
@Inject constructor(private val context: Context) {
fun enqueue(request: Request): Long = downloadManager().enqueue(request)

fun buildRequest(fileUrl: String) = Request(Uri.parse(fileUrl))

fun query(query: Query): Cursor = downloadManager().query(query)

fun buildQuery() = Query()

fun guessUrl(fileUrl: String): String = URLUtil.guessUrl(fileUrl)

fun getMimeType(url: String): String? {
var type: String? = null
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
if (extension != null) {
val mime = MimeTypeMap.getSingleton()
type = mime.getMimeTypeFromExtension(extension)
}
return type
}

private fun toPublicUri(fileUrl: String): Uri {
val fileUri = Uri.parse(fileUrl)
return if (ContentResolver.SCHEME_FILE == fileUri.scheme) {
val file = File(fileUri.path)
FileProvider.getUriForFile(
context,
"${BuildConfig.APPLICATION_ID}.provider",
file
)
} else {
fileUri
}
}

fun openDownloadedAttachment(
context: Context,
fileUrl: String,
attachmentMimeType: String
) {
val attachmentUri = toPublicUri(fileUrl)

val openAttachmentIntent = Intent(Intent.ACTION_VIEW)
openAttachmentIntent.setDataAndType(attachmentUri, attachmentMimeType)
openAttachmentIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
try {
context.startActivity(openAttachmentIntent)
} catch (e: ActivityNotFoundException) {
AppLog.e(T.READER, "No browser found on the device: ${e.message}")
}
}

private fun downloadManager() =
(context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager)
}
Loading