Commit d20d0c26 authored by John Abd-El-Malek's avatar John Abd-El-Malek Committed by Commit Bot

Add system notification UI for downloads in WebLayer.

This closely matches Android Download Manager's UI with the exception of skipping the "% downloaded" part since it seemed redundant with the progress bar. The embedder can disable the UI if they choose.

The few UI strings need to be translated. The button strings can be reused from Chrome, but we need to figure out what the rest of the text should say and add translations if they're not shared.

Bug: 1025603
Change-Id: I61ba3b821c417de13191a67a85487b61b5f9c86f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2002980Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarClark DuVall <cduvall@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Commit-Queue: John Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734589}
parent 2d23c49b
......@@ -77,6 +77,7 @@ public class DownloadCallbackTest {
@Override
public void onDownloadStarted(Download download) {
mSeenStarted = true;
download.disableNotification();
}
@Override
......@@ -85,6 +86,7 @@ public class DownloadCallbackTest {
mLocation = download.getLocation().toString();
mState = download.getState();
mError = download.getError();
mMimetype = download.getMimeType();
}
@Override
......@@ -213,5 +215,6 @@ public class DownloadCallbackTest {
"org.chromium.weblayer.shell/cache/weblayer/Downloads/"));
Assert.assertEquals(DownloadState.COMPLETE, mCallback.mState);
Assert.assertEquals(DownloadError.NO_ERROR, mCallback.mError);
Assert.assertEquals("text/html", mCallback.mMimetype);
}
}
......@@ -80,6 +80,7 @@ class DownloadBrowserTest : public WebLayerBrowserTest,
base::FilePath download_location() { return download_location_; }
int64_t total_bytes() { return total_bytes_; }
DownloadError download_state() { return download_state_; }
std::string mime_type() { return mime_type_; }
int completed_count() { return completed_count_; }
int failed_count() { return failed_count_; }
int download_dropped_count() { return download_dropped_count_; }
......@@ -118,6 +119,7 @@ class DownloadBrowserTest : public WebLayerBrowserTest,
download_location_ = download->GetLocation();
total_bytes_ = download->GetTotalBytes();
download_state_ = download->GetError();
mime_type_ = download->GetMimeType();
CHECK_EQ(download->GetReceivedBytes(), total_bytes_);
CHECK_EQ(download->GetState(), DownloadState::kComplete);
completed_run_loop_->Quit();
......@@ -142,6 +144,7 @@ class DownloadBrowserTest : public WebLayerBrowserTest,
base::FilePath download_location_;
int64_t total_bytes_ = 0;
DownloadError download_state_ = DownloadError::kNoError;
std::string mime_type_;
int completed_count_ = 0;
int failed_count_ = 0;
int download_dropped_count_ = 0;
......@@ -204,6 +207,7 @@ IN_PROC_BROWSER_TEST_F(DownloadBrowserTest, Basic) {
EXPECT_EQ(failed_count(), 0);
EXPECT_EQ(download_dropped_count(), 0);
EXPECT_EQ(download_state(), DownloadError::kNoError);
EXPECT_EQ(mime_type(), "text/html");
// Check that the size on disk matches what's expected.
{
......
......@@ -54,6 +54,13 @@ base::android::ScopedJavaLocalRef<jstring> DownloadImpl::GetLocation(
return base::android::ScopedJavaLocalRef<jstring>(
base::android::ConvertUTF8ToJavaString(env, GetLocation().value()));
}
base::android::ScopedJavaLocalRef<jstring> DownloadImpl::GetMimeTypeImpl(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
return base::android::ScopedJavaLocalRef<jstring>(
base::android::ConvertUTF8ToJavaString(env, GetMimeType()));
}
#endif
DownloadState DownloadImpl::GetState() {
......@@ -110,6 +117,10 @@ base::FilePath DownloadImpl::GetLocation() {
return item_->GetTargetFilePath();
}
std::string DownloadImpl::GetMimeType() {
return item_->GetMimeType();
}
DownloadError DownloadImpl::GetError() {
auto reason = item_->GetLastReason();
if (reason == download::DOWNLOAD_INTERRUPT_REASON_NONE)
......
......@@ -57,6 +57,10 @@ class DownloadImpl : public Download, public base::SupportsUserData::Data {
base::android::ScopedJavaLocalRef<jstring> GetLocation(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
// Add Impl suffix to avoid compiler clash with the C++ interface method.
base::android::ScopedJavaLocalRef<jstring> GetMimeTypeImpl(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
int GetError(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj) {
return static_cast<int>(GetError());
}
......@@ -74,6 +78,7 @@ class DownloadImpl : public Download, public base::SupportsUserData::Data {
void Resume() override;
void Cancel() override;
base::FilePath GetLocation() override;
std::string GetMimeType() override;
DownloadError GetError() override;
private:
......
......@@ -21,10 +21,12 @@ import org.chromium.weblayer_private.interfaces.ObjectWrapper;
@JNINamespace("weblayer")
public final class DownloadCallbackProxy {
private long mNativeDownloadCallbackProxy;
private BrowserImpl mBrowser;
private IDownloadCallbackClient mClient;
DownloadCallbackProxy(long tab, IDownloadCallbackClient client) {
DownloadCallbackProxy(BrowserImpl browser, long tab, IDownloadCallbackClient client) {
assert client != null;
mBrowser = browser;
mClient = client;
mNativeDownloadCallbackProxy =
DownloadCallbackProxyJni.get().createDownloadCallbackProxy(this, tab);
......@@ -70,27 +72,31 @@ public final class DownloadCallbackProxy {
@CalledByNative
private DownloadImpl createDownload(long nativeDownloadImpl) {
return new DownloadImpl(mClient, nativeDownloadImpl);
return new DownloadImpl(mBrowser, mClient, nativeDownloadImpl);
}
@CalledByNative
private void downloadStarted(DownloadImpl download) throws RemoteException {
mClient.downloadStarted(download.getClientDownload());
download.downloadStarted();
}
@CalledByNative
private void downloadProgressChanged(DownloadImpl download) throws RemoteException {
mClient.downloadProgressChanged(download.getClientDownload());
download.downloadProgressChanged();
}
@CalledByNative
private void downloadCompleted(DownloadImpl download) throws RemoteException {
mClient.downloadCompleted(download.getClientDownload());
download.downloadCompleted();
}
@CalledByNative
private void downloadFailed(DownloadImpl download) throws RemoteException {
mClient.downloadFailed(download.getClientDownload());
download.downloadFailed();
}
@NativeMethods
......
......@@ -220,7 +220,7 @@ public final class TabImpl extends ITab.Stub {
StrictModeWorkaround.apply();
if (client != null) {
if (mDownloadCallbackProxy == null) {
mDownloadCallbackProxy = new DownloadCallbackProxy(mNativeTab, client);
mDownloadCallbackProxy = new DownloadCallbackProxy(mBrowser, mNativeTab, client);
} else {
mDownloadCallbackProxy.setClient(client);
}
......
......@@ -5,6 +5,7 @@
package org.chromium.weblayer_private;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
......@@ -255,6 +256,13 @@ public final class WebLayerImpl extends IWebLayer.Stub {
return CrashReporterControllerImpl.getInstance();
}
@Override
public void onReceivedDownloadNotification(IObjectWrapper appContextWrapper, Intent intent) {
StrictModeWorkaround.apply();
Context context = ObjectWrapper.unwrap(appContextWrapper, Context.class);
DownloadImpl.forwardIntent(context, intent);
}
/**
* Creates a remote context. This should only be used for backwards compatibility when the
* client was not sending the remote context.
......
......@@ -16,4 +16,6 @@ interface IDownload {
void cancel() = 5;
String getLocation() = 6;
int getError() = 7;
String getMimeType() = 8;
void disableNotification() = 9;
}
......@@ -4,6 +4,7 @@
package org.chromium.weblayer_private.interfaces;
import android.content.Intent;
import org.chromium.weblayer_private.interfaces.IClientDownload;
import org.chromium.weblayer_private.interfaces.IDownload;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
......@@ -19,4 +20,5 @@ interface IDownloadCallbackClient {
void downloadProgressChanged(IClientDownload download) = 4;
void downloadCompleted(IClientDownload download) = 5;
void downloadFailed(IClientDownload download) = 6;
Intent createIntent() = 7;
}
......@@ -4,6 +4,7 @@
package org.chromium.weblayer_private.interfaces;
import android.content.Intent;
import android.os.Bundle;
import org.chromium.weblayer_private.interfaces.IBrowserFragment;
......@@ -67,4 +68,7 @@ interface IWebLayer {
ICrashReporterController getCrashReporterController(
in IObjectWrapper appContext,
in IObjectWrapper remoteContext) = 10;
// Forwards download intent notifications to the implementation.
void onReceivedDownloadNotification(in IObjectWrapper appContext, in Intent intent) = 11;
}
......@@ -5,6 +5,8 @@
#ifndef WEBLAYER_PUBLIC_DOWNLOAD_H_
#define WEBLAYER_PUBLIC_DOWNLOAD_H_
#include <string>
namespace base {
class FilePath;
}
......@@ -77,6 +79,9 @@ class Download {
// available until the download completes successfully.
virtual base::FilePath GetLocation() = 0;
// Returns the effective MIME type of downloaded content.
virtual std::string GetMimeType() = 0;
// Return information about the error, if any, that was encountered during the
// download.
virtual DownloadError GetError() = 0;
......
......@@ -52,5 +52,17 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/weblayer_file_paths" />
</provider>
<receiver android:name="org.chromium.weblayer.DownloadBroadcastReceiver"
android:exported="false">
<intent-filter>
<!-- these need to be in sync with DownloadImpl.java-->
<action android:name="org.chromium.weblayer.downloads.OPEN"/>
<action android:name="org.chromium.weblayer.downloads.DELETE"/>
<action android:name="org.chromium.weblayer.downloads.PAUSE"/>
<action android:name="org.chromium.weblayer.downloads.RESUME"/>
<action android:name="org.chromium.weblayer.downloads.CANCEL"/>
</intent-filter>
</receiver>
</application>
</manifest>
......@@ -33,6 +33,7 @@ android_library("java") {
"org/chromium/weblayer/CrashReporterCallback.java",
"org/chromium/weblayer/CrashReporterController.java",
"org/chromium/weblayer/Download.java",
"org/chromium/weblayer/DownloadBroadcastReceiver.java",
"org/chromium/weblayer/DownloadCallback.java",
"org/chromium/weblayer/DownloadError.java",
"org/chromium/weblayer/DownloadState.java",
......
......@@ -26,6 +26,18 @@ public final class Download extends IClientDownload.Stub {
mDownloadImpl = impl;
}
/**
* By default downloads will show a system notification. Call this to disable it.
*/
public void disableNotification() {
ThreadCheck.ensureOnUiThread();
try {
mDownloadImpl.disableNotification();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
@DownloadState
public int getState() {
ThreadCheck.ensureOnUiThread();
......@@ -111,6 +123,19 @@ public final class Download extends IClientDownload.Stub {
}
}
/**
* Returns the effective MIME type of downloaded content.
*/
@NonNull
public String getMimeType() {
ThreadCheck.ensureOnUiThread();
try {
return mDownloadImpl.getMimeType();
} catch (RemoteException e) {
throw new APICallException(e);
}
}
/**
* Return information about the error, if any, that was encountered during the download.
*/
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
/**
* Listens to events from the download system notifications.
*/
public class DownloadBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
WebLayer.loadAsync(context, webLayer -> {
try {
webLayer.getImpl().onReceivedDownloadNotification(
ObjectWrapper.wrap(context), intent);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
});
} catch (UnsupportedVersionException e) {
throw new RuntimeException(e);
}
}
}
......@@ -4,6 +4,7 @@
package org.chromium.weblayer;
import android.content.Intent;
import android.net.Uri;
import android.os.RemoteException;
import android.webkit.ValueCallback;
......@@ -328,6 +329,15 @@ public final class Tab {
StrictModeWorkaround.apply();
mCallback.onDownloadFailed((Download) download);
}
@Override
public Intent createIntent() {
StrictModeWorkaround.apply();
// Intent objects need to be created in the client library so they can refer to the
// broadcast receiver that will handle them. The broadcast receiver needs to be in the
// client library because it's referenced in the manifest.
return new Intent(WebLayer.getAppContext(), DownloadBroadcastReceiver.class);
}
}
private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub {
......
......@@ -47,6 +47,9 @@ public final class WebLayer {
@Nullable
private static Context sRemoteContext;
@Nullable
private static Context sAppContext;
@Nullable
private static WebLayerLoader sLoader;
......@@ -123,6 +126,10 @@ public final class WebLayer {
return sLoader;
}
IWebLayer getImpl() {
return mImpl;
}
/**
* Returns the supported version. Using any functions defined in a newer version than
* returned by {@link getSupportedMajorVersion} result in throwing an
......@@ -151,6 +158,12 @@ public final class WebLayer {
return sLoader.getMajorVersion();
}
// Internal getter for the app Context. This should only be used when you know WebLayer has
// been initialized.
static Context getAppContext() {
return sAppContext;
}
/**
* Returns the Chrome version of the WebLayer implementation. This will return a full version
* string such as "79.0.3945.0", while {@link getSupportedMajorVersion} will only return the
......@@ -405,6 +418,7 @@ public final class WebLayer {
}
Class<?> webViewFactoryClass = Class.forName("android.webkit.WebViewFactory");
String implPackageName = getImplPackageName(appContext);
sAppContext = appContext;
if (implPackageName != null) {
sRemoteContext = createRemoteContextFromPackageName(appContext, implPackageName);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
......
......@@ -8,4 +8,5 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="images" path="images/"/>
<external-path name="external_files" path="."/>
</paths>
......@@ -4,7 +4,6 @@
package org.chromium.weblayer.shell;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
......@@ -281,11 +280,7 @@ public class WebLayerShellActivity extends FragmentActivity {
@Override
public boolean onInterceptDownload(Uri uri, String userAgent, String contentDisposition,
String mimetype, long contentLength) {
DownloadManager.Request request = new DownloadManager.Request(uri);
request.setNotificationVisibility(
DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
getSystemService(DownloadManager.class).enqueue(request);
return true;
return false;
}
@Override
......
test
\ No newline at end of file
<html>
<body>
test
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment