[Android] Added UMA for search by image context menu.
[chromium-blink-merge.git] / win8 / metro_driver / file_picker.cc
blob4557665b2acb56b69de2e150d2089271a356e29f
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "stdafx.h"
6 #include "win8/metro_driver/file_picker.h"
8 #include <windows.storage.pickers.h>
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_util.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/win/metro.h"
17 #include "base/win/scoped_comptr.h"
18 #include "win8/metro_driver/chrome_app_view.h"
19 #include "win8/metro_driver/winrt_utils.h"
21 namespace {
23 namespace winstorage = ABI::Windows::Storage;
24 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
26 // TODO(siggi): Complete this implementation and move it to a common place.
27 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
28 public:
29 ~StringVectorImpl() {
30 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
33 HRESULT RuntimeClassInitialize(const std::vector<string16>& list) {
34 for (size_t i = 0; i < list.size(); ++i)
35 strings_.push_back(MakeHString(list[i]));
37 return S_OK;
40 // IVector<HSTRING> implementation.
41 STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
42 if (index >= strings_.size())
43 return E_INVALIDARG;
45 return ::WindowsDuplicateString(strings_[index], item);
47 STDMETHOD(get_Size)(unsigned *size) {
48 if (strings_.size() > UINT_MAX)
49 return E_UNEXPECTED;
50 *size = static_cast<unsigned>(strings_.size());
51 return S_OK;
53 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
54 return E_NOTIMPL;
56 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
57 return E_NOTIMPL;
60 // write methods
61 STDMETHOD(SetAt)(unsigned index, HSTRING item) {
62 return E_NOTIMPL;
64 STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
65 return E_NOTIMPL;
67 STDMETHOD(RemoveAt)(unsigned index) {
68 return E_NOTIMPL;
70 STDMETHOD(Append)(HSTRING item) {
71 return E_NOTIMPL;
73 STDMETHOD(RemoveAtEnd)() {
74 return E_NOTIMPL;
76 STDMETHOD(Clear)() {
77 return E_NOTIMPL;
80 private:
81 std::vector<HSTRING> strings_;
84 class FilePickerSessionBase {
85 public:
86 // Creates a file picker for open_file_name.
87 explicit FilePickerSessionBase(OPENFILENAME* open_file_name);
89 // Runs the picker, returns true on success.
90 bool Run();
92 protected:
93 // Creates, configures and starts a file picker.
94 // If the HRESULT returned is a failure code the file picker has not started,
95 // so no callbacks should be expected.
96 virtual HRESULT StartFilePicker() = 0;
98 // The parameters to our picker.
99 OPENFILENAME* open_file_name_;
100 // The event Run waits on.
101 base::WaitableEvent event_;
102 // True iff a file picker has successfully finished.
103 bool success_;
105 private:
106 // Initiate a file picker, must be called on the metro dispatcher's thread.
107 void DoFilePicker();
109 DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase);
112 class OpenFilePickerSession : public FilePickerSessionBase {
113 public:
114 explicit OpenFilePickerSession(OPENFILENAME* open_file_name);
116 private:
117 virtual HRESULT StartFilePicker() OVERRIDE;
119 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
120 SingleFileAsyncOp;
121 typedef winfoundtn::Collections::IVectorView<
122 winstorage::StorageFile*> StorageFileVectorCollection;
123 typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*>
124 MultiFileAsyncOp;
126 // Called asynchronously when a single file picker is done.
127 HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status);
129 // Called asynchronously when a multi file picker is done.
130 HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status);
132 // Composes a multi-file result string suitable for returning to a
133 // from a storage file collection.
134 static HRESULT ComposeMultiFileResult(StorageFileVectorCollection* files,
135 string16* result);
136 private:
137 DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession);
140 class SaveFilePickerSession : public FilePickerSessionBase {
141 public:
142 explicit SaveFilePickerSession(OPENFILENAME* open_file_name);
144 private:
145 virtual HRESULT StartFilePicker() OVERRIDE;
147 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
148 SaveFileAsyncOp;
150 // Called asynchronously when the save file picker is done.
151 HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status);
154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name)
155 : open_file_name_(open_file_name),
156 event_(true, false),
157 success_(false) {
160 bool FilePickerSessionBase::Run() {
161 DCHECK(globals.appview_msg_loop != NULL);
163 // Post the picker request over to the metro thread.
164 bool posted = globals.appview_msg_loop->PostTask(FROM_HERE,
165 base::Bind(&FilePickerSessionBase::DoFilePicker, base::Unretained(this)));
166 if (!posted)
167 return false;
169 // Wait for the file picker to complete.
170 event_.Wait();
172 return success_;
175 void FilePickerSessionBase::DoFilePicker() {
176 // The file picker will fail if spawned from a snapped application,
177 // so let's attempt to unsnap first if we're in that state.
178 HRESULT hr = ChromeAppView::Unsnap();
179 if (FAILED(hr)) {
180 LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr;
183 if (SUCCEEDED(hr))
184 hr = StartFilePicker();
186 if (FAILED(hr)) {
187 LOG(ERROR) << "Failed to start file picker, error 0x"
188 << std::hex << hr;
190 event_.Signal();
194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name)
195 : FilePickerSessionBase(open_file_name) {
198 HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
199 AsyncStatus status) {
200 if (status == Completed) {
201 mswr::ComPtr<winstorage::IStorageFile> file;
202 HRESULT hr = async->GetResults(file.GetAddressOf());
204 if (file) {
205 mswr::ComPtr<winstorage::IStorageItem> storage_item;
206 if (SUCCEEDED(hr))
207 hr = file.As(&storage_item);
209 mswrw::HString file_path;
210 if (SUCCEEDED(hr))
211 hr = storage_item->get_Path(file_path.GetAddressOf());
213 if (SUCCEEDED(hr)) {
214 UINT32 path_len = 0;
215 const wchar_t* path_str =
216 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
218 // If the selected file name is longer than the supplied buffer,
219 // we return false as per GetOpenFileName documentation.
220 if (path_len < open_file_name_->nMaxFile) {
221 base::wcslcpy(open_file_name_->lpstrFile,
222 path_str,
223 open_file_name_->nMaxFile);
224 success_ = true;
227 } else {
228 LOG(ERROR) << "NULL IStorageItem";
230 } else {
231 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
234 event_.Signal();
236 return S_OK;
239 HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
240 AsyncStatus status) {
241 if (status == Completed) {
242 mswr::ComPtr<StorageFileVectorCollection> files;
243 HRESULT hr = async->GetResults(files.GetAddressOf());
245 if (files) {
246 string16 result;
247 if (SUCCEEDED(hr))
248 hr = ComposeMultiFileResult(files.Get(), &result);
250 if (SUCCEEDED(hr)) {
251 if (result.size() + 1 < open_file_name_->nMaxFile) {
252 // Because the result has embedded nulls, we must memcpy.
253 memcpy(open_file_name_->lpstrFile,
254 result.c_str(),
255 (result.size() + 1) * sizeof(result[0]));
256 success_ = true;
259 } else {
260 LOG(ERROR) << "NULL StorageFileVectorCollection";
262 } else {
263 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
266 event_.Signal();
268 return S_OK;
271 HRESULT OpenFilePickerSession::StartFilePicker() {
272 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
273 DCHECK(open_file_name_ != NULL);
275 mswrw::HStringReference class_name(
276 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
278 // Create the file picker.
279 mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker;
280 HRESULT hr = ::Windows::Foundation::ActivateInstance(
281 class_name.Get(), picker.GetAddressOf());
282 CheckHR(hr);
284 // Set the file type filter
285 mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
286 hr = picker->get_FileTypeFilter(filter.GetAddressOf());
287 if (FAILED(hr))
288 return hr;
290 if (open_file_name_->lpstrFilter == NULL) {
291 hr = filter->Append(mswrw::HStringReference(L"*").Get());
292 if (FAILED(hr))
293 return hr;
294 } else {
295 // The filter is a concatenation of zero terminated string pairs,
296 // where each pair is {description, extension}. The concatenation ends
297 // with a zero length string - e.g. a double zero terminator.
298 const wchar_t* walk = open_file_name_->lpstrFilter;
299 while (*walk != L'\0') {
300 // Walk past the description.
301 walk += wcslen(walk) + 1;
303 // We should have an extension, but bail on malformed filters.
304 if (*walk == L'\0')
305 break;
307 // There can be a single extension, or a list of semicolon-separated ones.
308 std::vector<string16> extensions_win32_style;
309 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
310 DCHECK_EQ(extension_count, extensions_win32_style.size());
312 // Metro wants suffixes only, not patterns.
313 mswrw::HString extension;
314 std::vector<string16> extensions;
315 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
316 if (extensions_win32_style[i] == L"*.*") {
317 // The wildcard filter is "*" for Metro. The string "*.*" produces
318 // an "invalid parameter" error.
319 hr = extension.Set(L"*");
320 } else {
321 // Metro wants suffixes only, not patterns.
322 string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
323 if ((ext.size() < 2) ||
324 (ext.find_first_of(L"*?") != string16::npos)) {
325 continue;
327 hr = extension.Set(ext.c_str());
329 if (SUCCEEDED(hr))
330 hr = filter->Append(extension.Get());
331 if (FAILED(hr))
332 return hr;
335 // Walk past the extension.
336 walk += wcslen(walk) + 1;
340 // Spin up a single or multi picker as appropriate.
341 if (open_file_name_->Flags & OFN_ALLOWMULTISELECT) {
342 mswr::ComPtr<MultiFileAsyncOp> completion;
343 hr = picker->PickMultipleFilesAsync(&completion);
344 if (FAILED(hr))
345 return hr;
347 // Create the callback method.
348 typedef winfoundtn::IAsyncOperationCompletedHandler<
349 StorageFileVectorCollection*> HandlerDoneType;
350 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
351 this, &OpenFilePickerSession::MultiPickerDone));
352 DCHECK(handler.Get() != NULL);
353 hr = completion->put_Completed(handler.Get());
355 return hr;
356 } else {
357 mswr::ComPtr<SingleFileAsyncOp> completion;
358 hr = picker->PickSingleFileAsync(&completion);
359 if (FAILED(hr))
360 return hr;
362 // Create the callback method.
363 typedef winfoundtn::IAsyncOperationCompletedHandler<
364 winstorage::StorageFile*> HandlerDoneType;
365 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
366 this, &OpenFilePickerSession::SinglePickerDone));
367 DCHECK(handler.Get() != NULL);
368 hr = completion->put_Completed(handler.Get());
370 return hr;
374 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
375 StorageFileVectorCollection* files, string16* result) {
376 DCHECK(files != NULL);
377 DCHECK(result != NULL);
379 // Empty the output string.
380 result->clear();
382 unsigned int num_files = 0;
383 HRESULT hr = files->get_Size(&num_files);
384 if (FAILED(hr))
385 return hr;
387 // Make sure we return an error on an empty collection.
388 if (num_files == 0) {
389 DLOG(ERROR) << "Empty collection on input.";
390 return E_UNEXPECTED;
393 // This stores the base path that should be the parent of all the files.
394 base::FilePath base_path;
396 // Iterate through the collection and append the file paths to the result.
397 for (unsigned int i = 0; i < num_files; ++i) {
398 mswr::ComPtr<winstorage::IStorageFile> file;
399 hr = files->GetAt(i, file.GetAddressOf());
400 if (FAILED(hr))
401 return hr;
403 mswr::ComPtr<winstorage::IStorageItem> storage_item;
404 hr = file.As(&storage_item);
405 if (FAILED(hr))
406 return hr;
408 mswrw::HString file_path_str;
409 hr = storage_item->get_Path(file_path_str.GetAddressOf());
410 if (FAILED(hr))
411 return hr;
413 base::FilePath file_path(MakeStdWString(file_path_str.Get()));
414 if (base_path.empty()) {
415 DCHECK(result->empty());
416 base_path = file_path.DirName();
418 // Append the path, including the terminating zero.
419 // We do this only for the first file.
420 result->append(base_path.value().c_str(), base_path.value().size() + 1);
422 DCHECK(!result->empty());
423 DCHECK(!base_path.empty());
424 DCHECK(base_path == file_path.DirName());
426 // Append the base name, including the terminating zero.
427 base::FilePath base_name = file_path.BaseName();
428 result->append(base_name.value().c_str(), base_name.value().size() + 1);
431 DCHECK(!result->empty());
433 return S_OK;
436 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
437 : FilePickerSessionBase(open_file_name) {
440 HRESULT SaveFilePickerSession::StartFilePicker() {
441 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
442 DCHECK(open_file_name_ != NULL);
444 mswrw::HStringReference class_name(
445 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
447 // Create the file picker.
448 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker;
449 HRESULT hr = ::Windows::Foundation::ActivateInstance(
450 class_name.Get(), picker.GetAddressOf());
451 CheckHR(hr);
453 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*>
454 StringVectorMap;
455 mswr::ComPtr<StringVectorMap> choices;
456 hr = picker->get_FileTypeChoices(choices.GetAddressOf());
457 if (FAILED(hr))
458 return hr;
460 if (open_file_name_->lpstrFilter) {
461 // The filter is a concatenation of zero terminated string pairs,
462 // where each pair is {description, extension list}. The concatenation ends
463 // with a zero length string - e.g. a double zero terminator.
464 const wchar_t* walk = open_file_name_->lpstrFilter;
465 while (*walk != L'\0') {
466 mswrw::HString description;
467 hr = description.Set(walk);
468 if (FAILED(hr))
469 return hr;
471 // Walk past the description.
472 walk += wcslen(walk) + 1;
474 // We should have an extension, but bail on malformed filters.
475 if (*walk == L'\0')
476 break;
478 // There can be a single extension, or a list of semicolon-separated ones.
479 std::vector<string16> extensions_win32_style;
480 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
481 DCHECK_EQ(extension_count, extensions_win32_style.size());
483 // Metro wants suffixes only, not patterns. Also, metro does not support
484 // the all files ("*") pattern in the save picker.
485 std::vector<string16> extensions;
486 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
487 string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
488 if ((ext.size() < 2) ||
489 (ext.find_first_of(L"*?") != string16::npos))
490 continue;
491 extensions.push_back(ext);
494 if (!extensions.empty()) {
495 // Convert to a Metro collection class.
496 mswr::ComPtr<StringVectorItf> list;
497 hr = mswr::MakeAndInitialize<StringVectorImpl>(
498 list.GetAddressOf(), extensions);
499 if (FAILED(hr))
500 return hr;
502 // Finally set the filter.
503 boolean replaced = FALSE;
504 hr = choices->Insert(description.Get(), list.Get(), &replaced);
505 if (FAILED(hr))
506 return hr;
507 DCHECK_EQ(FALSE, replaced);
510 // Walk past the extension(s).
511 walk += wcslen(walk) + 1;
515 // The save picker requires at least one choice. Callers are strongly advised
516 // to provide sensible choices. If none were given, fallback to .dat.
517 uint32 num_choices = 0;
518 hr = choices->get_Size(&num_choices);
519 if (FAILED(hr))
520 return hr;
522 if (num_choices == 0) {
523 mswrw::HString description;
524 // TODO(grt): Get a properly translated string. This can't be done from
525 // within metro_driver. Consider preprocessing the filter list in Chrome
526 // land to ensure it has this entry if all others are patterns. In that
527 // case, this whole block of code can be removed.
528 hr = description.Set(L"Data File");
529 if (FAILED(hr))
530 return hr;
532 mswr::ComPtr<StringVectorItf> list;
533 hr = mswr::MakeAndInitialize<StringVectorImpl>(
534 list.GetAddressOf(), std::vector<string16>(1, L".dat"));
535 if (FAILED(hr))
536 return hr;
538 boolean replaced = FALSE;
539 hr = choices->Insert(description.Get(), list.Get(), &replaced);
540 if (FAILED(hr))
541 return hr;
542 DCHECK_EQ(FALSE, replaced);
545 if (open_file_name_->lpstrFile != NULL) {
546 hr = picker->put_SuggestedFileName(
547 mswrw::HStringReference(
548 const_cast<const wchar_t*>(open_file_name_->lpstrFile)).Get());
549 if (FAILED(hr))
550 return hr;
553 mswr::ComPtr<SaveFileAsyncOp> completion;
554 hr = picker->PickSaveFileAsync(&completion);
555 if (FAILED(hr))
556 return hr;
558 // Create the callback method.
559 typedef winfoundtn::IAsyncOperationCompletedHandler<
560 winstorage::StorageFile*> HandlerDoneType;
561 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
562 this, &SaveFilePickerSession::FilePickerDone));
563 DCHECK(handler.Get() != NULL);
564 hr = completion->put_Completed(handler.Get());
566 return hr;
569 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
570 AsyncStatus status) {
571 if (status == Completed) {
572 mswr::ComPtr<winstorage::IStorageFile> file;
573 HRESULT hr = async->GetResults(file.GetAddressOf());
575 if (file) {
576 mswr::ComPtr<winstorage::IStorageItem> storage_item;
577 if (SUCCEEDED(hr))
578 hr = file.As(&storage_item);
580 mswrw::HString file_path;
581 if (SUCCEEDED(hr))
582 hr = storage_item->get_Path(file_path.GetAddressOf());
584 if (SUCCEEDED(hr)) {
585 string16 path_str = MakeStdWString(file_path.Get());
587 // If the selected file name is longer than the supplied buffer,
588 // we return false as per GetOpenFileName documentation.
589 if (path_str.size() < open_file_name_->nMaxFile) {
590 base::wcslcpy(open_file_name_->lpstrFile,
591 path_str.c_str(),
592 open_file_name_->nMaxFile);
593 success_ = true;
596 } else {
597 LOG(ERROR) << "NULL IStorageItem";
599 } else {
600 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
603 event_.Signal();
605 return S_OK;
608 } // namespace
610 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
611 OpenFilePickerSession session(open_file_name);
613 return session.Run();
616 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
617 SaveFilePickerSession session(open_file_name);
619 return session.Run();