Skip to content

Commit

Permalink
[Files app] Open a DSS file in the app when it's available
Browse files Browse the repository at this point in the history
When DSS files are opened from Files app they currently default to
opening via a browser. This means when the DSS app exists it bypasses
this. If the app exists then try to launch the app with the URL instead
of via the NewWindowDelegate.

Demo: http://shortn/_jGdig5nO0w

Bug: b/307149411, b/307168369
Test: browser_tests --gtest_filter=*OpenWithHostedApp*
Change-Id: I67dc9dbdc54c9a236c5c9c2ed40bc638d92c8d62
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4975936
Reviewed-by: Alan Cutter <alancutter@chromium.org>
Commit-Queue: Ben Reich <benreich@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1216781}
  • Loading branch information
ben-reich authored and Chromium LUCI CQ committed Oct 30, 2023
1 parent 11a53dd commit a8b2c48
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 27 deletions.
2 changes: 1 addition & 1 deletion chrome/browser/ash/file_manager/file_tasks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ bool OpenFilesWithBrowser(Profile* profile,
for (const FileSystemURL& file_url : file_urls) {
if (ash::FileSystemBackend::CanHandleURL(file_url)) {
num_opened +=
util::OpenFileWithBrowser(profile, file_url, action_id) ? 1 : 0;
util::OpenFileWithAppOrBrowser(profile, file_url, action_id) ? 1 : 0;
}
}
return num_opened > 0;
Expand Down
98 changes: 84 additions & 14 deletions chrome/browser/ash/file_manager/open_with_browser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_result_type.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/file_manager/file_tasks.h"
#include "chrome/browser/ash/file_manager/filesystem_api_util.h"
#include "chrome/browser/ash/fileapi/external_file_url_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_util.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/ash/components/drivefs/drivefs_util.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/file_system_core_util.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/filename_util.h"
#include "pdf/buildflags.h"
Expand All @@ -34,8 +41,7 @@

using content::BrowserThread;

namespace file_manager {
namespace util {
namespace file_manager::util {
namespace {

// List of file extensions viewable in the browser.
Expand Down Expand Up @@ -75,6 +81,63 @@ bool OpenNewTab(const GURL& url) {
return true;
}

absl::optional<std::string> GetAppIdFromFilePath(
const GURL& url,
const base::FilePath& file_path) {
if (url.SchemeIsFile()) {
return absl::nullopt;
}
const std::string& file_extension = file_path.FinalExtension();
if (file_extension == ".gdoc") {
return web_app::kGoogleDocsAppId;
} else if (file_extension == ".gsheet") {
return web_app::kGoogleSheetsAppId;
} else if (file_extension == ".gslides") {
return web_app::kGoogleSlidesAppId;
}
return absl::nullopt;
}

bool OpenHostedFileInNewTabOrApp(Profile* profile,
const base::FilePath& file_path,
LaunchAppCallback callback,
const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);

absl::optional<std::string> app_id = GetAppIdFromFilePath(url, file_path);
if (!app_id.has_value()) {
std::move(callback).Run(absl::nullopt);
return OpenNewTab(url);
}
apps::AppServiceProxy* app_service =
apps::AppServiceProxyFactory::GetForProfile(profile);
DCHECK(app_service);
const apps::AppRegistryCache& cache = app_service->AppRegistryCache();
bool is_app_available = false;
cache.ForOneApp(
app_id.value(), [&is_app_available](const apps::AppUpdate& update) {
is_app_available = update.Readiness() == apps::Readiness::kReady;
});

if (!is_app_available) {
std::move(callback).Run(absl::nullopt);
return OpenNewTab(url);
}

auto chained_callback =
base::BindOnce([](apps::LaunchResult&& result) {
LOG_IF(ERROR, result.state != apps::LaunchResult::SUCCESS)
<< "Failed to launch hosted file via app despite "
"it being ready";
return result.state;
}).Then(std::move(callback));

app_service->LaunchAppWithUrl(app_id.value(), ui::EF_NONE, url,
apps::LaunchSource::kFromFileManager, nullptr,
std::move(chained_callback));
return true;
}

// Reads the alternate URL from a GDoc file. When it fails, returns a file URL
// for |file_path| as fallback.
// Note that an alternate url is a URL to open a hosted document.
Expand All @@ -87,30 +150,36 @@ GURL ReadUrlFromGDocAsync(const base::FilePath& file_path) {
}

// Parse a local file to extract the Docs url and open this url.
void OpenGDocUrlFromFile(const base::FilePath& file_path) {
void OpenGDocUrlFromFile(Profile* profile,
const base::FilePath& file_path,
LaunchAppCallback callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&ReadUrlFromGDocAsync, file_path),
base::BindOnce(base::IgnoreResult(&OpenNewTab)));
base::BindOnce(base::IgnoreResult(&OpenHostedFileInNewTabOrApp), profile,
file_path, std::move(callback)));
}

// Open a hosted GDoc, from a path hosted in DriveFS.
void OpenHostedDriveFsFile(const base::FilePath& file_path,
void OpenHostedDriveFsFile(Profile* profile,
const base::FilePath& file_path,
LaunchAppCallback callback,
drive::FileError error,
drivefs::mojom::FileMetadataPtr metadata) {
if (error != drive::FILE_ERROR_OK) {
return;
}
if (drivefs::IsLocal(metadata->type)) {
OpenGDocUrlFromFile(file_path);
OpenGDocUrlFromFile(profile, file_path, std::move(callback));
return;
}
GURL hosted_url(metadata->alternate_url);
if (!hosted_url.is_valid()) {
return;
}

OpenNewTab(hosted_url);
OpenHostedFileInNewTabOrApp(profile, file_path, std::move(callback),
hosted_url);
}

// Open an encrypted file by redirecting the user to Google Drive.
Expand All @@ -130,9 +199,10 @@ void OpenEncryptedDriveFsFile(const base::FilePath& file_path,

} // namespace

bool OpenFileWithBrowser(Profile* profile,
const storage::FileSystemURL& file_system_url,
const std::string& action_id) {
bool OpenFileWithAppOrBrowser(Profile* profile,
const storage::FileSystemURL& file_system_url,
const std::string& action_id,
LaunchAppCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(profile);

Expand Down Expand Up @@ -184,10 +254,11 @@ bool OpenFileWithBrowser(Profile* profile,
integration_service->GetDriveFsInterface() &&
integration_service->GetRelativeDrivePath(file_path, &path)) {
integration_service->GetDriveFsInterface()->GetMetadata(
path, base::BindOnce(&OpenHostedDriveFsFile, file_path));
path, base::BindOnce(&OpenHostedDriveFsFile, profile, file_path,
std::move(callback)));
return true;
}
OpenGDocUrlFromFile(file_path);
OpenGDocUrlFromFile(profile, file_path, std::move(callback));
}
return true;
}
Expand All @@ -197,5 +268,4 @@ bool OpenFileWithBrowser(Profile* profile,
return false;
}

} // namespace util
} // namespace file_manager
} // namespace file_manager::util
26 changes: 19 additions & 7 deletions chrome/browser/ash/file_manager/open_with_browser.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,43 @@
#define CHROME_BROWSER_ASH_FILE_MANAGER_OPEN_WITH_BROWSER_H_

#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "chrome/browser/apps/app_service/launch_result_type.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

class Profile;

namespace storage {
class FileSystemURL;
}

namespace file_manager {
namespace util {
namespace file_manager::util {

using LaunchAppCallback =
base::OnceCallback<void(absl::optional<apps::LaunchResult::State>)>;

// Opens the file specified by `file_path` with the browser for `profile`. In
// the event the `file_path` refers to a hosted document AND the document has an
// app installed, it will try to launch the app instead of a browser.

// Opens the file specified by |file_path| with the browser for
// |profile|. This function takes care of the following intricacies:
//
// - If there is no active browser window, open it.
// - If the file is a Drive hosted document, the hosted document will be
// opened in the browser by extracting the right URL for the file.
// - If the file is a Drive hosted document, check if there is an installed app
// that can handle the document type, if it can launch the app instead.
// - If the file is on Drive, the file will be downloaded from Drive as
// needed.
//
// Returns false if failed to open. This happens if the file type is unknown.
bool OpenFileWithBrowser(Profile* profile,
const storage::FileSystemURL& file_system_url,
const std::string& action_id);
bool OpenFileWithAppOrBrowser(Profile* profile,
const storage::FileSystemURL& file_system_url,
const std::string& action_id,
LaunchAppCallback callback = base::DoNothing());

} // namespace util
} // namespace file_manager
} // namespace file_manager::util

#endif // CHROME_BROWSER_ASH_FILE_MANAGER_OPEN_WITH_BROWSER_H_

0 comments on commit a8b2c48

Please sign in to comment.