// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.sync.settings;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.device_reauth.DeviceAuthSource;
import org.chromium.chrome.browser.device_reauth.ReauthenticatorBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.sync.DataType;
import org.chromium.components.sync.LocalDataDescription;
import org.chromium.components.sync.SyncService;
import org.chromium.ui.UiUtils;
import org.chromium.ui.modaldialog.ModalDialogManager;
import java.util.HashMap;
import java.util.Set;
public class BatchUploadCardPreference extends Preference
implements SyncService.SyncStateChangedListener, BatchUploadDialogCoordinator.Listener {
private Activity mActivity;
private Profile mProfile;
private SyncService mSyncService;
private HashMap<Integer, LocalDataDescription> mLocalDataDescriptionsMap;
private ModalDialogManager mDialogManager;
private OneshotSupplier<SnackbarManager> mSnackbarManagerSupplier;
private ReauthenticatorBridge mReauthenticatorBridge;
public BatchUploadCardPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.signin_settings_card_view);
}
/** Initialize the dependencies for the BatchUploadCardPreference and update the error card. */
public void initialize(Activity activity, Profile profile, ModalDialogManager dialogManager) {
mActivity = activity;
mProfile = profile;
mSyncService = SyncServiceFactory.getForProfile(mProfile);
mDialogManager = dialogManager;
if (mSyncService != null) {
mSyncService.addSyncStateChangedListener(this);
}
mReauthenticatorBridge =
ReauthenticatorBridge.create(
mActivity, mProfile, DeviceAuthSource.SETTINGS_BATCH_UPLOAD);
update();
}
public void hideBatchUploadCardAndUpdate() {
// Temporarily hide, it will become visible again once getLocalDataDescriptions() completes,
// which is triggered from update().
setVisible(false);
notifyChanged();
update();
}
public void setSnackbarManagerSupplier(
OneshotSupplier<SnackbarManager> snackbarManagerSupplier) {
mSnackbarManagerSupplier = snackbarManagerSupplier;
}
@Override
public void onDetached() {
super.onDetached();
if (mSyncService != null) {
mSyncService.removeSyncStateChangedListener(this);
}
if (mReauthenticatorBridge != null) {
mReauthenticatorBridge.destroy();
}
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.setDividerAllowedAbove(false);
setupBatchUploadCardView(holder.findViewById(R.id.signin_settings_card));
}
/** {@link SyncService.SyncStateChangedListener} implementation. */
@Override
public void syncStateChanged() {
update();
}
@Override
public void onSaveInAccountDialogButtonClicked(Set<Integer> types, int itemsCount) {
if (!types.contains(DataType.PASSWORDS)) {
uploadLocalDataAndShowSnackbar(types, itemsCount);
return;
}
// Uploading passwords requires a reauthentication.
mReauthenticatorBridge.reauthenticate(
success -> {
if (success) {
uploadLocalDataAndShowSnackbar(types, itemsCount);
}
});
}
private void uploadLocalDataAndShowSnackbar(Set<Integer> types, int itemsCount) {
SyncServiceFactory.getForProfile(mProfile).triggerLocalDataMigration(types);
String snackbarMessage =
getContext()
.getResources()
.getQuantityString(
R.plurals.account_settings_bulk_upload_saved_snackbar_message,
itemsCount,
IdentityServicesProvider.get()
.getIdentityManager(mProfile)
.getPrimaryAccountInfo(ConsentLevel.SIGNIN)
.getEmail());
mSnackbarManagerSupplier
.get()
.showSnackbar(
Snackbar.make(
snackbarMessage,
/* controller= */ null,
Snackbar.TYPE_ACTION,
Snackbar.UMA_SETTINGS_BATCH_UPLOAD)
.setSingleLine(false));
hideBatchUploadCardAndUpdate();
}
private void update() {
mSyncService.getLocalDataDescriptions(
mReauthenticatorBridge.canUseAuthenticationWithBiometricOrScreenLock()
? Set.of(DataType.BOOKMARKS, DataType.READING_LIST, DataType.PASSWORDS)
: Set.of(DataType.BOOKMARKS, DataType.READING_LIST),
localDataDescriptionsMap -> {
mLocalDataDescriptionsMap = localDataDescriptionsMap;
int sum =
mLocalDataDescriptionsMap.values().stream()
.map(LocalDataDescription::itemCount)
.reduce(0, Integer::sum);
if (sum == 0) {
setVisible(false);
} else {
setVisible(true);
}
notifyChanged();
});
}
private void setupBatchUploadCardView(View card) {
if (mLocalDataDescriptionsMap == null) {
return;
}
Context context = getContext();
Button button = (Button) card.findViewById(R.id.signin_settings_card_button);
button.setText(R.string.account_settings_bulk_upload_section_save_button);
button.setOnClickListener(
v -> {
BatchUploadDialogCoordinator.show(
context, mProfile, mLocalDataDescriptionsMap, mDialogManager, this);
});
ImageView image = (ImageView) card.findViewById(R.id.signin_settings_card_icon);
image.setImageDrawable(
UiUtils.getTintedDrawable(
getContext(),
R.drawable.ic_cloud_upload_24dp,
R.color.default_icon_color_accent1_tint_list));
int localPasswordsCount = 0;
LocalDataDescription passwordsLocalDataDescription =
mLocalDataDescriptionsMap.get(DataType.PASSWORDS);
if (passwordsLocalDataDescription != null) {
localPasswordsCount = passwordsLocalDataDescription.itemCount();
}
int localItemsCountExcludingPasswords = 0;
LocalDataDescription bookmarksLocalDataDescription =
mLocalDataDescriptionsMap.get(DataType.BOOKMARKS);
if (bookmarksLocalDataDescription != null) {
localItemsCountExcludingPasswords += bookmarksLocalDataDescription.itemCount();
}
LocalDataDescription readingListLocalDataDescription =
mLocalDataDescriptionsMap.get(DataType.READING_LIST);
if (readingListLocalDataDescription != null) {
localItemsCountExcludingPasswords += readingListLocalDataDescription.itemCount();
}
TextView text = (TextView) card.findViewById(R.id.signin_settings_card_description);
// TODO(b/354686035): Handle accounts with non-displayable email address.
CoreAccountInfo accountInfo =
IdentityServicesProvider.get()
.getIdentityManager(mProfile)
.getPrimaryAccountInfo(ConsentLevel.SIGNIN);
if (localItemsCountExcludingPasswords == 0) {
text.setText(
context.getResources()
.getQuantityString(
R.plurals
.account_settings_bulk_upload_section_description_password,
localPasswordsCount,
localPasswordsCount,
accountInfo.getEmail()));
} else if (localPasswordsCount == 0) {
text.setText(
context.getResources()
.getQuantityString(
R.plurals
.account_settings_bulk_upload_section_description_other,
localItemsCountExcludingPasswords,
localItemsCountExcludingPasswords,
accountInfo.getEmail()));
} else {
text.setText(
context.getResources()
.getQuantityString(
R.plurals
.account_settings_bulk_upload_section_description_password_and_other,
localPasswordsCount,
localPasswordsCount,
accountInfo.getEmail()));
}
}
}