blob: 7dd92dccb9a2d858c439bea118d8ffd066f4614f [file] [log] [blame]
// 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.chrome.browser.ntp.search;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.Pair;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import org.chromium.chrome.browser.gsa.GSAState;
import org.chromium.chrome.browser.lens.LensController;
import org.chromium.chrome.browser.lens.LensEntryPoint;
import org.chromium.chrome.browser.lens.LensIntentParams;
import org.chromium.chrome.browser.lens.LensQueryParams;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.DestroyObserver;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.omnibox.voice.AssistantVoiceSearchService;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.externalauth.ExternalAuthUtils;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.ui.base.ViewUtils;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import java.util.ArrayList;
import java.util.List;
class SearchBoxMediator
implements DestroyObserver, NativeInitObserver, AssistantVoiceSearchService.Observer {
private final Context mContext;
private final PropertyModel mModel;
private final ViewGroup mView;
private final List<OnClickListener> mVoiceSearchClickListeners = new ArrayList<>();
private final List<OnClickListener> mLensClickListeners = new ArrayList<>();
private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
private AssistantVoiceSearchService mAssistantVoiceSearchService;
private SearchBoxChipDelegate mChipDelegate;
/** Constructor. */
SearchBoxMediator(Context context, PropertyModel model, ViewGroup view) {
mContext = context;
mModel = model;
mView = view;
PropertyModelChangeProcessor.create(mModel, mView, new SearchBoxViewBinder());
}
/**
* Initializes the SearchBoxContainerView with the given params. This must be called for
* classes that use the SearchBoxContainerView.
*
* @param activityLifecycleDispatcher Used to register for lifecycle events.
*/
void initialize(ActivityLifecycleDispatcher activityLifecycleDispatcher) {
assert mActivityLifecycleDispatcher == null;
mActivityLifecycleDispatcher = activityLifecycleDispatcher;
mActivityLifecycleDispatcher.register(this);
if (mActivityLifecycleDispatcher.isNativeInitializationFinished()) {
onFinishNativeInitialization();
}
}
@Override
public void onDestroy() {
if (mAssistantVoiceSearchService != null) {
mAssistantVoiceSearchService.destroy();
mAssistantVoiceSearchService = null;
}
if (mActivityLifecycleDispatcher != null) {
mActivityLifecycleDispatcher.unregister(this);
mActivityLifecycleDispatcher = null;
}
}
@Override
public void onFinishNativeInitialization() {
mAssistantVoiceSearchService = new AssistantVoiceSearchService(mContext,
ExternalAuthUtils.getInstance(), TemplateUrlServiceFactory.get(),
GSAState.getInstance(mContext), this, SharedPreferencesManager.getInstance(),
IdentityServicesProvider.get().getIdentityManager(
Profile.getLastUsedRegularProfile()),
AccountManagerFacadeProvider.getInstance());
onAssistantVoiceSearchServiceChanged();
}
@Override
public void onAssistantVoiceSearchServiceChanged() {
// Potential race condition between destroy and the observer, see crbug.com/1055274.
if (mAssistantVoiceSearchService == null) return;
Drawable drawable = mAssistantVoiceSearchService.getCurrentMicDrawable();
mModel.set(SearchBoxProperties.VOICE_SEARCH_DRAWABLE, drawable);
final @ColorInt int primaryColor = ChromeColors.getDefaultThemeColor(
mContext.getResources(), false /* forceDarkBgColor= */);
ColorStateList colorStateList =
mAssistantVoiceSearchService.getButtonColorStateList(primaryColor, mContext);
mModel.set(SearchBoxProperties.VOICE_SEARCH_COLOR_STATE_LIST, colorStateList);
}
/** Called to set a click listener for the search box. */
void setSearchBoxClickListener(OnClickListener listener) {
mModel.set(SearchBoxProperties.SEARCH_BOX_CLICK_CALLBACK, v -> {
boolean isChipVisible = mModel.get(SearchBoxProperties.CHIP_VISIBILITY);
if (isChipVisible) {
String chipText = mModel.get(SearchBoxProperties.CHIP_TEXT);
mModel.set(SearchBoxProperties.SEARCH_TEXT, Pair.create(chipText, true));
mChipDelegate.onCancelClicked();
}
listener.onClick(v);
});
}
/**
* Called to add a click listener for the voice search button.
*/
void addVoiceSearchButtonClickListener(OnClickListener listener) {
boolean hasExistingListeners = !mVoiceSearchClickListeners.isEmpty();
mVoiceSearchClickListeners.add(listener);
if (hasExistingListeners) return;
mModel.set(SearchBoxProperties.VOICE_SEARCH_CLICK_CALLBACK, v -> {
for (OnClickListener clickListener : mVoiceSearchClickListeners) {
clickListener.onClick(v);
}
});
}
/**
* Called to add a click listener for the voice search button.
*/
void addLensButtonClickListener(OnClickListener listener) {
boolean hasExistingListeners = !mLensClickListeners.isEmpty();
mLensClickListeners.add(listener);
if (hasExistingListeners) return;
mModel.set(SearchBoxProperties.LENS_CLICK_CALLBACK, v -> {
for (OnClickListener clickListener : mLensClickListeners) {
clickListener.onClick(v);
}
});
}
/**
* Called to set or clear a chip on the search box.
* @param chipText The text to be shown on the chip.
*/
void setChipText(String chipText) {
boolean chipVisible = !TextUtils.isEmpty(chipText);
mModel.set(SearchBoxProperties.CHIP_VISIBILITY, chipVisible);
mModel.set(SearchBoxProperties.SEARCH_HINT_VISIBILITY, !chipVisible);
mModel.set(SearchBoxProperties.CHIP_TEXT, chipText);
}
/**
* Called to set a delegate for handling the interactions between the chip and the embedder.
* @param chipDelegate A {@link SearchBoxChipDelegate}.
*/
void setChipDelegate(SearchBoxChipDelegate chipDelegate) {
mChipDelegate = chipDelegate;
mModel.set(SearchBoxProperties.CHIP_CLICK_CALLBACK, v -> chipDelegate.onChipClicked());
mModel.set(SearchBoxProperties.CHIP_CANCEL_CALLBACK, v -> chipDelegate.onCancelClicked());
mChipDelegate.getChipIcon(bitmap -> {
mModel.set(SearchBoxProperties.CHIP_DRAWABLE, getRoundedDrawable(bitmap));
});
}
/**
* Launch the Lens app.
* @param lensEntryPoint A {@link LensEntryPoint}.
* @param windowAndroid A {@link WindowAndroid} instance.
* @param isIncognito Whether the request is from a Incognito tab.
*/
void startLens(
@LensEntryPoint int lensEntryPoint, WindowAndroid windowAndroid, boolean isIncognito) {
LensController.getInstance().startLens(
windowAndroid, new LensIntentParams.Builder(lensEntryPoint, isIncognito).build());
}
/**
* Check whether the Lens is enabled for an entry point.
* @param lensEntryPoint A {@link LensEntryPoint}.
* @param isIncognito Whether the request is from a Incognito tab.
* @param isTablet Whether the request is from a tablet.
* @return Whether the Lens is currently enabled.
*/
boolean isLensEnabled(
@LensEntryPoint int lensEntryPoint, boolean isIncognito, boolean isTablet) {
return LensController.getInstance().isLensEnabled(
new LensQueryParams.Builder(lensEntryPoint, isIncognito, isTablet).build());
}
private Drawable getRoundedDrawable(Bitmap bitmap) {
if (bitmap == null) return null;
RoundedBitmapDrawable roundedBitmapDrawable =
ViewUtils.createRoundedBitmapDrawable(mContext.getResources(), bitmap, 0);
roundedBitmapDrawable.setCircular(true);
return roundedBitmapDrawable;
}
}