Skip to content
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
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@
android:exported="false"
tools:replace="android:authorities" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>

<!--
To protect user privacy, disable SafeBrowsing which could send URLs to Google servers
https://developer.android.com/reference/android/webkit/WebView
Expand Down
87 changes: 76 additions & 11 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,42 @@ import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.app.ActivityOptions
import android.appwidget.AppWidgetManager
import android.content.*
import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.*
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Message
import android.text.Editable
import android.view.*
import android.view.View.*
import android.view.ContextMenu
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.OnFocusChangeListener
import android.view.View.VISIBLE
import android.view.View.inflate
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.webkit.*
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebView.FindListener
import android.webkit.WebView.HitTestResult
import android.webkit.WebView.HitTestResult.*
import android.webkit.WebView.HitTestResult.IMAGE_TYPE
import android.webkit.WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
import android.webkit.WebView.HitTestResult.UNKNOWN_TYPE
import android.webkit.WebViewDatabase
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
Expand All @@ -52,18 +74,31 @@ import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.Observer
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion
import com.duckduckgo.app.bookmarks.ui.EditBookmarkDialogFragment
import com.duckduckgo.app.brokensite.BrokenSiteActivity
import com.duckduckgo.app.browser.BrowserTabViewModel.*
import com.duckduckgo.app.browser.BrowserTabViewModel.AutoCompleteViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.BrowserViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.Command
import com.duckduckgo.app.browser.BrowserTabViewModel.CtaViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.FindInPageViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.GlobalLayoutViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.LoadingViewState
import com.duckduckgo.app.browser.BrowserTabViewModel.OmnibarViewState
import com.duckduckgo.app.browser.autocomplete.BrowserAutoCompleteSuggestionsAdapter
import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserNavigator
import com.duckduckgo.app.browser.defaultbrowsing.TopInstructionsCard
import com.duckduckgo.app.browser.downloader.FileDownloadNotificationManager
import com.duckduckgo.app.browser.downloader.FileDownloader
import com.duckduckgo.app.browser.downloader.FileDownloader.PendingFileDownload
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.DownloadFileData
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.UserDownloadAction
import com.duckduckgo.app.browser.filechooser.FileChooserIntentBuilder
import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
Expand All @@ -76,10 +111,25 @@ import com.duckduckgo.app.browser.tabpreview.WebViewPreviewGenerator
import com.duckduckgo.app.browser.tabpreview.WebViewPreviewPersister
import com.duckduckgo.app.browser.ui.HttpAuthenticationDialogFragment
import com.duckduckgo.app.browser.useragent.UserAgentProvider
import com.duckduckgo.app.cta.ui.*
import com.duckduckgo.app.cta.ui.Cta
import com.duckduckgo.app.cta.ui.CtaViewModel
import com.duckduckgo.app.cta.ui.DaxBubbleCta
import com.duckduckgo.app.cta.ui.DaxDialogCta
import com.duckduckgo.app.cta.ui.HomePanelCta
import com.duckduckgo.app.cta.ui.SecondaryButtonCta
import com.duckduckgo.app.global.ViewModelFactory
import com.duckduckgo.app.global.device.DeviceInfo
import com.duckduckgo.app.global.view.*
import com.duckduckgo.app.global.view.NonDismissibleBehavior
import com.duckduckgo.app.global.view.TextChangedWatcher
import com.duckduckgo.app.global.view.gone
import com.duckduckgo.app.global.view.hide
import com.duckduckgo.app.global.view.hideKeyboard
import com.duckduckgo.app.global.view.isDifferent
import com.duckduckgo.app.global.view.isImmersiveModeEnabled
import com.duckduckgo.app.global.view.renderIfChanged
import com.duckduckgo.app.global.view.show
import com.duckduckgo.app.global.view.showKeyboard
import com.duckduckgo.app.global.view.toggleFullScreen
import com.duckduckgo.app.onboarding.ui.page.DefaultBrowserPage
import com.duckduckgo.app.privacy.model.PrivacyGrade
import com.duckduckgo.app.privacy.renderer.icon
Expand All @@ -102,7 +152,14 @@ import kotlinx.android.synthetic.main.include_new_browser_tab.*
import kotlinx.android.synthetic.main.include_omnibar_toolbar.*
import kotlinx.android.synthetic.main.include_omnibar_toolbar.view.*
import kotlinx.android.synthetic.main.popup_window_browser_menu.view.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.longToast
import org.jetbrains.anko.share
import timber.log.Timber
Expand Down Expand Up @@ -1086,6 +1143,13 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
pendingFileDownload = null
thread {
fileDownloader.download(pendingDownload, object : FileDownloader.FileDownloadListener {
override fun confirmDownload(downloadFileData: DownloadFileData, userDownloadAction: UserDownloadAction) {
val downloadConfirmationFragment = DownloadConfirmationFragment(downloadFileData, userDownloadAction)
fragmentManager?.let {
downloadConfirmationFragment.show(it, DOWNLAOD_CONFIRM_TAG)
}
}

override fun downloadStarted() {
fileDownloadNotificationManager.showDownloadInProgressNotification()
}
Expand Down Expand Up @@ -1167,6 +1231,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope {
private const val URL_BUNDLE_KEY = "url"

private const val AUTHENTICATION_DIALOG_TAG = "AUTH_DIALOG_TAG"
private const val DOWNLAOD_CONFIRM_TAG = "DOWNLAOD_CONFIRM_TAG"
private const val DAX_DIALOG_DIALOG_TAG = "DAX_DIALOG_TAG"

private const val MAX_PROGRESS = 100
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2019 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.FileProvider.getUriForFile
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.DownloadFileData
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.UserDownloadAction
import com.duckduckgo.app.global.view.gone
import com.duckduckgo.app.global.view.leftDrawable
import com.duckduckgo.app.global.view.show
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.download_confirmation.view.*
import timber.log.Timber

class DownloadConfirmationFragment(
private val downloadFileData: DownloadFileData,
private val userDownloadAction: UserDownloadAction
) : BottomSheetDialogFragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.download_confirmation, container, false)
setupViews(view)
return view
}

private fun setupViews(view: View) {
view.downloadMessage.text = getString(R.string.downloadConfirmationSaveFileTitle, downloadFileData.file.name)
view.openWith.setOnClickListener {
openFile()
dismiss()
}
view.replace.setOnClickListener {
userDownloadAction.acceptAndReplace()
dismiss()
}
view.continueDownload.setOnClickListener {
userDownloadAction.accept()
dismiss()
}
view.cancel.setOnClickListener {
userDownloadAction.cancel()
dismiss()
}

if (downloadFileData.alreadyDownloaded) {
view.openWith.show()
view.replace.show()
view.continueDownload.text = getString(R.string.downloadConfirmationKeepBothFilesText)
view.continueDownload.leftDrawable(R.drawable.ic_keepboth_brownish_24dp)
} else {
view.openWith.gone()
view.replace.gone()
view.continueDownload.text = getString(R.string.downloadConfirmationContinue)
view.continueDownload.leftDrawable(R.drawable.ic_file_brownish_24dp)
}
}

private fun openFile() {
val intent = context?.let { createIntentToOpenFile(it) }
activity?.packageManager?.let { packageManager ->
if (intent?.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
Timber.e("No suitable activity found")
Toast.makeText(activity, R.string.downloadConfirmationUnableToOpenFileText, Toast.LENGTH_SHORT).show()
}
}
}

private fun createIntentToOpenFile(context: Context): Intent {
val uri = getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", downloadFileData.file)
val mime = activity?.contentResolver?.getType(uri)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, mime)
return intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,27 @@ package com.duckduckgo.app.browser.downloader
import android.os.Environment
import android.webkit.URLUtil
import androidx.annotation.WorkerThread
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.DownloadFileData
import com.duckduckgo.app.browser.downloader.NetworkFileDownloadManager.UserDownloadAction
import java.io.File
import javax.inject.Inject

class FileDownloader @Inject constructor(
private val dataUriDownloader: DataUriDownloader,
private val networkDownloader: NetworkFileDownloader
private val networkFileDownloadManager: NetworkFileDownloadManager
) {

@WorkerThread
fun download(pending: PendingFileDownload?, callback: FileDownloadListener?) {
fun download(pending: PendingFileDownload?, callback: FileDownloadListener) {

if (pending == null) {
return
}

when {
URLUtil.isNetworkUrl(pending.url) -> networkDownloader.download(pending)
URLUtil.isNetworkUrl(pending.url) -> networkFileDownloadManager.download(pending, callback)
URLUtil.isDataUrl(pending.url) -> dataUriDownloader.download(pending, callback)
else -> callback?.downloadFailed("Not supported")
else -> callback.downloadFailed("Not supported")
}
}

Expand All @@ -51,6 +53,10 @@ class FileDownloader @Inject constructor(
)

interface FileDownloadListener {
fun confirmDownload(
downloadFileData: DownloadFileData,
userDownloadAction: UserDownloadAction
)
fun downloadStarted()
fun downloadFinished(file: File, mimeType: String?)
fun downloadFailed(message: String)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2019 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.app.browser.downloader

import timber.log.Timber
import java.io.File
import javax.inject.Inject


class NetworkFileDownloadManager @Inject constructor(private val networkDownloader: NetworkFileDownloader) {

fun download(
pendingDownload: FileDownloader.PendingFileDownload,
callback: FileDownloader.FileDownloadListener
) {
val guessedFileName = networkDownloader.guessFileName(pendingDownload)
val fileToDownload = File(pendingDownload.directory, guessedFileName)
val alreadyDownloaded = fileToDownload.exists()
callback.confirmDownload(
DownloadFileData(fileToDownload, alreadyDownloaded),
object : UserDownloadAction {
override fun acceptAndReplace() {
File(pendingDownload.directory, guessedFileName).delete()
networkDownloader.download(pendingDownload)
}

override fun accept() {
networkDownloader.download(pendingDownload)
}

override fun cancel() {
Timber.i("Cancelled download for url ${pendingDownload.url}")
}
})
}

interface UserDownloadAction {
fun accept()
fun acceptAndReplace()
fun cancel()
}

class DownloadFileData(val file: File, val alreadyDownloaded: Boolean)
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class NetworkFileDownloader @Inject constructor(private val context: Context) {
manager?.enqueue(request)
}

private fun guessFileName(pending: FileDownloader.PendingFileDownload): String? {
fun guessFileName(pending: FileDownloader.PendingFileDownload): String? {
val guessedFileName = URLUtil.guessFileName(pending.url, pending.contentDisposition, pending.mimeType)
Timber.i("Guessed filename of $guessedFileName for url ${pending.url}")
return guessedFileName
Expand Down
Loading