Commit a27c5abe authored by Ioana Pandele's avatar Ioana Pandele Committed by Commit Bot

Completion of the passwords export flow

This CL adds:
- Request for password serialization
- Writing passwords to a temporary file
- Handling of errors that can occur during writing
- Presenting a UIActivityViewController with options to save the file

Bug:789122

Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I2963b9b425b5e6de62f0461c2d66cc19b2d61f22
Reviewed-on: https://chromium-review.googlesource.com/842623Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Ioana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/master@{#533275}
parent 657dbad5
...@@ -1072,6 +1072,15 @@ Handoff must also be enabled in the General section of Settings, and your device ...@@ -1072,6 +1072,15 @@ Handoff must also be enabled in the General section of Settings, and your device
<message name="IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE" desc="The message of the alert displayed as a warning to the user who tapped on the button to export passwords. [iOS only]"> <message name="IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE" desc="The message of the alert displayed as a warning to the user who tapped on the button to export passwords. [iOS only]">
Your passwords will be visible to anyone who can see the exported file. Your passwords will be visible to anyone who can see the exported file.
</message> </message>
<message name="IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE" desc="Title of the alert informing the user that exporting passwords has failed. [iOS only]">
Can't Export Passwords
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_UNKNOWN_ERROR_ALERT_MESSAGE" desc="The message of the alert informing the user that an error has occured while exporting passwords. [iOS only]">
An error occured. Try again later.
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_OUT_OF_SPACE_ALERT_MESSAGE" desc="The message of an error alert displayed while trying to export passwords. If informs the user that their device storage is almost full and instructing them to free space. [iOS only]">
Your device is almost full. Free up space and try again.
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON" desc="Label of a confirmation dialog button which allows the user to cancel passwords export. [Length: 22em] [iOS only]"> <message name="IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON" desc="Label of a confirmation dialog button which allows the user to cancel passwords export. [Length: 22em] [iOS only]">
Cancel Cancel
</message> </message>
......
...@@ -182,6 +182,7 @@ source_set("test_support") { ...@@ -182,6 +182,7 @@ source_set("test_support") {
"passphrase_collection_view_controller_test.h", "passphrase_collection_view_controller_test.h",
"passphrase_collection_view_controller_test.mm", "passphrase_collection_view_controller_test.mm",
"password_details_collection_view_controller_for_testing.h", "password_details_collection_view_controller_for_testing.h",
"password_exporter_for_testing.h",
"personal_data_manager_data_changed_observer.cc", "personal_data_manager_data_changed_observer.cc",
"personal_data_manager_data_changed_observer.h", "personal_data_manager_data_changed_observer.h",
"reauthentication_module_for_testing.h", "reauthentication_module_for_testing.h",
...@@ -229,6 +230,7 @@ source_set("unit_tests") { ...@@ -229,6 +230,7 @@ source_set("unit_tests") {
"do_not_track_collection_view_controller_unittest.mm", "do_not_track_collection_view_controller_unittest.mm",
"import_data_collection_view_controller_unittest.mm", "import_data_collection_view_controller_unittest.mm",
"password_details_collection_view_controller_unittest.mm", "password_details_collection_view_controller_unittest.mm",
"password_exporter_unittest.mm",
"privacy_collection_view_controller_unittest.mm", "privacy_collection_view_controller_unittest.mm",
"reauthentication_module_unittest.mm", "reauthentication_module_unittest.mm",
"save_passwords_collection_view_controller_unittest.mm", "save_passwords_collection_view_controller_unittest.mm",
......
...@@ -7,14 +7,59 @@ ...@@ -7,14 +7,59 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <memory>
#include <vector>
namespace autofill {
struct PasswordForm;
} // namespace autofill
enum class WriteToURLStatus {
SUCCESS,
OUT_OF_DISK_SPACE_ERROR,
UNKNOWN_ERROR,
};
@protocol ReauthenticationProtocol; @protocol ReauthenticationProtocol;
@protocol FileWriterProtocol<NSObject>
// Posts a task to write the data in |data| to the file at |fileURL| and
// executes |handler| when the writing is finished.
- (void)writeData:(NSString*)data
toURL:(NSURL*)fileURL
handler:(void (^)(WriteToURLStatus))handler;
@end
@protocol PasswordSerializerBridge<NSObject>
// Posts task to serialize passwords and calls |serializedPasswordsHandler|
// when serialization is finished.
- (void)serializePasswords:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords
handler:(void (^)(std::string))serializedPasswordsHandler;
@end
@protocol PasswordExporterDelegate<NSObject> @protocol PasswordExporterDelegate<NSObject>
// Displays a dialog informing the user that they must set up a passcode // Displays a dialog informing the user that they must set up a passcode
// in order to export passwords. // in order to export passwords.
- (void)showSetPasscodeDialog; - (void)showSetPasscodeDialog;
// Displays an alert detailing an error that has occured during export.
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason;
// Displays an activity view that allows the user to pick an app to process
// the exported passwords file.
- (void)showActivityViewWithActivityItems:(NSArray*)activityItems
completionHandler:
(void (^)(NSString* activityType,
BOOL completed,
NSArray* returnedItems,
NSError* activityError))completionHandler;
@end @end
/** Class handling all the operations necessary to export passwords.*/ /** Class handling all the operations necessary to export passwords.*/
...@@ -31,8 +76,12 @@ ...@@ -31,8 +76,12 @@
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
// Method to be called in order to start the export flow. This initiates // Method to be called in order to start the export flow. This initiates
// the re-authentication procedure and asks for password serialization. // the reauthentication procedure and asks for password serialization.
- (void)startExportFlow; - (void)startExportFlow:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords;
// Whether an export operation is already in progress.
@property(nonatomic, assign, readonly) BOOL isExporting;
@end @end
......
// Copyright 2018 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.
#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_EXPORTER_FOR_TESTING_H_
#define IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_EXPORTER_FOR_TESTING_H_
#import "ios/chrome/browser/ui/settings/password_exporter.h"
@interface PasswordExporter (ForTesting)
- (void)setPasswordSerializerBridge:
(id<PasswordSerializerBridge>)passwordSerialzerBridge;
- (void)setPasswordFileWriter:(id<FileWriterProtocol>)passwordFileWriter;
@end
#endif // IOS_CHROME_BROWSER_UI_SETTINGS_PASSWORD_EXPORTER_FOR_TESTING_H_
// Copyright 2018 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.
#import "ios/chrome/browser/ui/settings/password_exporter_for_testing.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "components/autofill/core/common/password_form.h"
#include "components/strings/grit/components_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/password_test_util.h"
#include "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface FakePasswordSerialzerBridge : NSObject<PasswordSerializerBridge>
// Allows for on demand execution of the block that handles the serialized
// passwords.
- (void)executeHandler;
@end
@implementation FakePasswordSerialzerBridge {
// Handler processing the serialized passwords.
void (^_serializedPasswordsHandler)(std::string);
}
- (void)serializePasswords:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords
handler:(void (^)(std::string))serializedPasswordsHandler {
_serializedPasswordsHandler = serializedPasswordsHandler;
}
- (void)executeHandler {
_serializedPasswordsHandler("test serialized passwords");
}
@end
@interface FakePasswordFileWriter : NSObject<FileWriterProtocol>
// Indicates if the writing of the file was finished successfully or with an
// error.
@property(nonatomic, assign) WriteToURLStatus writingStatus;
@end
@implementation FakePasswordFileWriter
@synthesize writingStatus = _writingStatus;
- (void)writeData:(NSString*)data
toURL:(NSURL*)fileURL
handler:(void (^)(WriteToURLStatus))handler {
handler(self.writingStatus);
}
@end
namespace {
class PasswordExporterTest : public PlatformTest {
public:
PasswordExporterTest() = default;
protected:
void SetUp() override {
PlatformTest::SetUp();
mock_reauthentication_module_ = [[MockReauthenticationModule alloc] init];
password_exporter_delegate_ =
OCMProtocolMock(@protocol(PasswordExporterDelegate));
password_exporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:mock_reauthentication_module_
delegate:password_exporter_delegate_];
}
std::vector<std::unique_ptr<autofill::PasswordForm>> CreatePasswordList() {
auto password_form = std::make_unique<autofill::PasswordForm>();
password_form->origin = GURL("http://accounts.google.com/a/LoginAuth");
password_form->username_value = base::ASCIIToUTF16("test@testmail.com");
password_form->password_value = base::ASCIIToUTF16("test1");
std::vector<std::unique_ptr<autofill::PasswordForm>> password_forms;
password_forms.push_back(std::move(password_form));
return password_forms;
}
void TearDown() override {
NSURL* passwords_file_url = GetPasswordsFileURL();
if ([[NSFileManager defaultManager]
fileExistsAtPath:[passwords_file_url path]]) {
[[NSFileManager defaultManager] removeItemAtURL:passwords_file_url
error:nil];
}
PlatformTest::TearDown();
}
NSURL* GetPasswordsFileURL() {
NSString* passwords_file_name =
[l10n_util::GetNSString(IDS_PASSWORD_MANAGER_DEFAULT_EXPORT_FILENAME)
stringByAppendingString:@".csv"];
return [[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:passwords_file_name
isDirectory:NO];
}
BOOL PasswordFileExists() {
return [[NSFileManager defaultManager]
fileExistsAtPath:[GetPasswordsFileURL() path]];
}
id password_exporter_delegate_;
PasswordExporter* password_exporter_;
MockReauthenticationModule* mock_reauthentication_module_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
TEST_F(PasswordExporterTest, PasswordFileWriteAuthSuccessful) {
mock_reauthentication_module_.shouldSucceed = YES;
NSURL* passwords_file_url = GetPasswordsFileURL();
OCMExpect([password_exporter_delegate_
showActivityViewWithActivityItems:[OCMArg isEqual:@[ passwords_file_url ]]
completionHandler:[OCMArg any]]);
[password_exporter_ startExportFlow:CreatePasswordList()];
// Wait for all asynchronous tasks to complete.
scoped_task_environment_.RunUntilIdle();
EXPECT_OCMOCK_VERIFY(password_exporter_delegate_);
EXPECT_TRUE(PasswordFileExists());
}
TEST_F(PasswordExporterTest, PasswordFileWriteAuthFailed) {
mock_reauthentication_module_.shouldSucceed = NO;
FakePasswordSerialzerBridge* fake_password_serializer_bridge =
[[FakePasswordSerialzerBridge alloc] init];
[password_exporter_
setPasswordSerializerBridge:fake_password_serializer_bridge];
[[password_exporter_delegate_ reject]
showActivityViewWithActivityItems:[OCMArg any]
completionHandler:[OCMArg any]];
// Use @try/@catch as -reject raises an exception.
@try {
[password_exporter_ startExportFlow:CreatePasswordList()];
// Wait for all asynchronous tasks to complete.
scoped_task_environment_.RunUntilIdle();
EXPECT_OCMOCK_VERIFY(password_exporter_delegate_);
} @catch (NSException* exception) {
// The exception is raised when
// - showActivityViewWithActivityItems:completionHandler:
// is invoked. As this should not happen, mark the test as failed.
GTEST_FAIL();
}
// Serializing passwords hasn't finished.
EXPECT_FALSE(PasswordFileExists());
EXPECT_TRUE(password_exporter_.isExporting);
[fake_password_serializer_bridge executeHandler];
// Serializing password has finished, but reauthentication was not successful.
EXPECT_FALSE(PasswordFileExists());
EXPECT_FALSE(password_exporter_.isExporting);
}
TEST_F(PasswordExporterTest,
PasswordFileNotWrittenBeforeSerializationFinished) {
mock_reauthentication_module_.shouldSucceed = YES;
FakePasswordSerialzerBridge* fake_password_serializer_bridge =
[[FakePasswordSerialzerBridge alloc] init];
[password_exporter_
setPasswordSerializerBridge:fake_password_serializer_bridge];
[[password_exporter_delegate_ reject]
showActivityViewWithActivityItems:[OCMArg any]
completionHandler:[OCMArg any]];
// Use @try/@catch as -reject raises an exception.
@try {
[password_exporter_ startExportFlow:CreatePasswordList()];
// Wait for all asynchronous tasks to complete.
scoped_task_environment_.RunUntilIdle();
EXPECT_OCMOCK_VERIFY(password_exporter_delegate_);
} @catch (NSException* exception) {
// The exception is raised when
// - showActivityViewWithActivityItems:completionHandler:
// is invoked. As this should not happen, mark the test as failed.
GTEST_FAIL();
}
EXPECT_FALSE(PasswordFileExists());
}
TEST_F(PasswordExporterTest, WritingFailedOutOfDiskSpace) {
mock_reauthentication_module_.shouldSucceed = YES;
FakePasswordFileWriter* fake_password_file_writer =
[[FakePasswordFileWriter alloc] init];
fake_password_file_writer.writingStatus =
WriteToURLStatus::OUT_OF_DISK_SPACE_ERROR;
[password_exporter_ setPasswordFileWriter:fake_password_file_writer];
OCMExpect([password_exporter_delegate_
showExportErrorAlertWithLocalizedReason:
l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_OUT_OF_SPACE_ALERT_MESSAGE)]);
[password_exporter_ startExportFlow:CreatePasswordList()];
// Wait for all asynchronous tasks to complete.
scoped_task_environment_.RunUntilIdle();
EXPECT_OCMOCK_VERIFY(password_exporter_delegate_);
EXPECT_FALSE(password_exporter_.isExporting);
}
TEST_F(PasswordExporterTest, WritingFailedUnknownError) {
mock_reauthentication_module_.shouldSucceed = YES;
FakePasswordFileWriter* fake_password_file_writer =
[[FakePasswordFileWriter alloc] init];
fake_password_file_writer.writingStatus = WriteToURLStatus::UNKNOWN_ERROR;
[password_exporter_ setPasswordFileWriter:fake_password_file_writer];
OCMExpect([password_exporter_delegate_
showExportErrorAlertWithLocalizedReason:
l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_UNKNOWN_ERROR_ALERT_MESSAGE)]);
[password_exporter_ startExportFlow:CreatePasswordList()];
// Wait for all asynchronous tasks to complete.
scoped_task_environment_.RunUntilIdle();
EXPECT_OCMOCK_VERIFY(password_exporter_delegate_);
EXPECT_FALSE(password_exporter_.isExporting);
}
} // namespace
...@@ -19,6 +19,9 @@ namespace ios { ...@@ -19,6 +19,9 @@ namespace ios {
class ChromeBrowserState; class ChromeBrowserState;
} // namespace ios } // namespace ios
@protocol ReauthenticationProtocol;
@class PasswordExporter;
@interface SavePasswordsCollectionViewController @interface SavePasswordsCollectionViewController
: SettingsRootCollectionViewController : SettingsRootCollectionViewController
...@@ -34,11 +37,19 @@ class ChromeBrowserState; ...@@ -34,11 +37,19 @@ class ChromeBrowserState;
@interface SavePasswordsCollectionViewController ( @interface SavePasswordsCollectionViewController (
Testing)<PasswordDetailsCollectionViewControllerDelegate> Testing)<PasswordDetailsCollectionViewControllerDelegate>
// Callback called when the async request launched from // Callback called when the async request launched from
// |getLoginsFromPasswordStore| finishes. // |getLoginsFromPasswordStore| finishes.
- (void)onGetPasswordStoreResults: - (void)onGetPasswordStoreResults:
(const std::vector<std::unique_ptr<autofill::PasswordForm>>&)result; (const std::vector<std::unique_ptr<autofill::PasswordForm>>&)result;
// Initializes the password exporter with a (fake) |reauthenticationModule|.
- (void)setReauthenticationModuleForExporter:
(id<ReauthenticationProtocol>)reauthenticationModule;
// Returns the password exporter to allow setting fake testing objects on it.
- (PasswordExporter*)getPasswordExporter;
@end @end
#endif // IOS_CHROME_BROWSER_UI_SETTINGS_SAVE_PASSWORDS_COLLECTION_VIEW_CONTROLLER_H_ #endif // IOS_CHROME_BROWSER_UI_SETTINGS_SAVE_PASSWORDS_COLLECTION_VIEW_CONTROLLER_H_
...@@ -43,6 +43,8 @@ ...@@ -43,6 +43,8 @@
#import "ios/chrome/browser/ui/settings/reauthentication_module.h" #import "ios/chrome/browser/ui/settings/reauthentication_module.h"
#import "ios/chrome/browser/ui/settings/settings_utils.h" #import "ios/chrome/browser/ui/settings/settings_utils.h"
#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h" #import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
#include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h"
#include "ios/chrome/grit/ios_strings.h" #include "ios/chrome/grit/ios_strings.h"
#import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h" #import "ios/third_party/material_components_ios/src/components/Palettes/src/MaterialPalettes.h"
#include "ui/base/l10n/l10n_util_mac.h" #include "ui/base/l10n/l10n_util_mac.h"
...@@ -71,6 +73,16 @@ typedef NS_ENUM(NSInteger, ItemType) { ...@@ -71,6 +73,16 @@ typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeExportPasswordsButton, ItemTypeExportPasswordsButton,
}; };
std::vector<std::unique_ptr<autofill::PasswordForm>> CopyOf(
const std::vector<std::unique_ptr<autofill::PasswordForm>>& password_list) {
std::vector<std::unique_ptr<autofill::PasswordForm>> password_list_copy;
for (const auto& form : password_list) {
password_list_copy.push_back(
std::make_unique<autofill::PasswordForm>(*form));
}
return password_list_copy;
}
} // namespace } // namespace
namespace password_manager { namespace password_manager {
...@@ -168,6 +180,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -168,6 +180,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
@implementation SavePasswordsCollectionViewController @implementation SavePasswordsCollectionViewController
// Private synthesized properties
@synthesize passwordExporter = passwordExporter_; @synthesize passwordExporter = passwordExporter_;
#pragma mark - Initialization #pragma mark - Initialization
...@@ -184,7 +197,6 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -184,7 +197,6 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
passwordExporter_ = [[PasswordExporter alloc] passwordExporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:reauthenticationModule_ initWithReauthenticationModule:reauthenticationModule_
delegate:self]; delegate:self];
self.title = l10n_util::GetNSString(IDS_IOS_SAVE_PASSWORDS); self.title = l10n_util::GetNSString(IDS_IOS_SAVE_PASSWORDS);
self.collectionViewAccessibilityIdentifier = self.collectionViewAccessibilityIdentifier =
@"SavePasswordsCollectionViewController"; @"SavePasswordsCollectionViewController";
...@@ -484,6 +496,10 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -484,6 +496,10 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
} }
- (void)startPasswordsExportFlow { - (void)startPasswordsExportFlow {
// TODO(crbug.com/789122): Consider disabling the button while another export
// operation is in progress.
if (self.passwordExporter.isExporting)
return;
UIAlertController* exportConfirmation = [UIAlertController UIAlertController* exportConfirmation = [UIAlertController
alertControllerWithTitle:nil alertControllerWithTitle:nil
message:l10n_util::GetNSString( message:l10n_util::GetNSString(
...@@ -505,7 +521,8 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -505,7 +521,8 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
if (!strongSelf) { if (!strongSelf) {
return; return;
} }
[strongSelf.passwordExporter startExportFlow]; [strongSelf.passwordExporter
startExportFlow:CopyOf(strongSelf->savedForms_)];
}]; }];
[exportConfirmation addAction:exportAction]; [exportConfirmation addAction:exportAction];
...@@ -758,6 +775,62 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -758,6 +775,62 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self presentViewController:alertController animated:YES completion:nil]; [self presentViewController:alertController animated:YES completion:nil];
} }
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason {
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE)
message:localizedReason
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showActivityViewWithActivityItems:(NSArray*)activityItems
completionHandler:(void (^)(NSString* activityType,
BOOL completed,
NSArray* returnedItems,
NSError* activityError))
completionHandler {
UIActivityViewController* activityViewController =
[[UIActivityViewController alloc] initWithActivityItems:activityItems
applicationActivities:nil];
NSArray* excludedActivityTypes = @[
UIActivityTypeAddToReadingList, UIActivityTypeAirDrop,
UIActivityTypeCopyToPasteboard, UIActivityTypeOpenInIBooks,
UIActivityTypePostToFacebook, UIActivityTypePostToFlickr,
UIActivityTypePostToTencentWeibo, UIActivityTypePostToTwitter,
UIActivityTypePostToVimeo, UIActivityTypePostToWeibo, UIActivityTypePrint
];
[activityViewController setExcludedActivityTypes:excludedActivityTypes];
[activityViewController setCompletionWithItemsHandler:completionHandler];
UIView* sourceView = nil;
CGRect sourceRect = CGRectZero;
if (IsIPadIdiom() && !IsCompact()) {
NSIndexPath* indexPath = [self.collectionViewModel
indexPathForItemType:ItemTypeExportPasswordsButton
sectionIdentifier:SectionIdentifierExportPasswordsButton];
UICollectionViewCell* cell =
[self.collectionView cellForItemAtIndexPath:indexPath];
sourceView = self.collectionView;
sourceRect = cell.frame;
}
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
activityViewController.popoverPresentationController.sourceView = sourceView;
activityViewController.popoverPresentationController.sourceRect = sourceRect;
activityViewController.popoverPresentationController
.permittedArrowDirections =
UIPopoverArrowDirectionDown | UIPopoverArrowDirectionDown;
[self presentViewController:activityViewController
animated:YES
completion:nil];
}
#pragma mark Helper methods #pragma mark Helper methods
// Sets the save passwords switch item's enabled status to |enabled| and // Sets the save passwords switch item's enabled status to |enabled| and
...@@ -779,4 +852,17 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults( ...@@ -779,4 +852,17 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self reconfigureCellsForItems:@[ switchItem ]]; [self reconfigureCellsForItems:@[ switchItem ]];
} }
#pragma mark - Testing
- (void)setReauthenticationModuleForExporter:
(id<ReauthenticationProtocol>)reauthenticationModule {
passwordExporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:reauthenticationModule
delegate:self];
}
- (PasswordExporter*)getPasswordExporter {
return passwordExporter_;
}
@end @end
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