diff --git a/android/brave_java_resources.gni b/android/brave_java_resources.gni
index c05055eb63d..df6d86033e8 100644
--- a/android/brave_java_resources.gni
+++ b/android/brave_java_resources.gni
@@ -61,6 +61,7 @@ brave_java_resources = [
"java/res/drawable-hdpi/ic_onboarding_bg.png",
"java/res/drawable-hdpi/ic_onboarding_bottom_leaf.png",
"java/res/drawable-hdpi/ic_onboarding_top_leaf.png",
+ "java/res/drawable-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-hdpi/icn_frowning_face.png",
"java/res/drawable-hdpi/icon_validated_notification.png",
"java/res/drawable-hdpi/incognito_simple.png",
@@ -139,6 +140,7 @@ brave_java_resources = [
"java/res/drawable-mdpi/ic_onboarding_bg.png",
"java/res/drawable-mdpi/ic_onboarding_bottom_leaf.png",
"java/res/drawable-mdpi/ic_onboarding_top_leaf.png",
+ "java/res/drawable-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-mdpi/icn_frowning_face.png",
"java/res/drawable-mdpi/icon_validated_notification.png",
"java/res/drawable-mdpi/incognito_simple.png",
@@ -172,6 +174,11 @@ brave_java_resources = [
"java/res/drawable-night-xhdpi/ic_cookie_background.png",
"java/res/drawable-night-xxhdpi/ic_cookie_background.png",
"java/res/drawable-night-xxxhdpi/ic_cookie_background.png",
+ "java/res/drawable-night-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
+ "java/res/drawable-night-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
+ "java/res/drawable-night-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
+ "java/res/drawable-night-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
+ "java/res/drawable-night-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-nodpi/dylan_malval_sea_min.webp",
"java/res/drawable-nodpi/eth.png",
"java/res/drawable-nodpi/fee_icon.png",
@@ -227,6 +234,7 @@ brave_java_resources = [
"java/res/drawable-xhdpi/ic_onboarding_bg.png",
"java/res/drawable-xhdpi/ic_onboarding_bottom_leaf.png",
"java/res/drawable-xhdpi/ic_onboarding_top_leaf.png",
+ "java/res/drawable-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-xhdpi/icn_frowning_face.png",
"java/res/drawable-xhdpi/icon_validated_notification.png",
"java/res/drawable-xhdpi/incognito_simple.png",
@@ -304,6 +312,7 @@ brave_java_resources = [
"java/res/drawable-xxhdpi/ic_onboarding_bg.png",
"java/res/drawable-xxhdpi/ic_onboarding_bottom_leaf.png",
"java/res/drawable-xxhdpi/ic_onboarding_top_leaf.png",
+ "java/res/drawable-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-xxhdpi/icn_frowning_face.png",
"java/res/drawable-xxhdpi/icon_validated_notification.png",
"java/res/drawable-xxhdpi/incognito_simple.png",
@@ -380,6 +389,7 @@ brave_java_resources = [
"java/res/drawable-xxxhdpi/ic_onboarding_bg.png",
"java/res/drawable-xxxhdpi/ic_onboarding_bottom_leaf.png",
"java/res/drawable-xxxhdpi/ic_onboarding_top_leaf.png",
+ "java/res/drawable-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png",
"java/res/drawable-xxxhdpi/icn_frowning_face.png",
"java/res/drawable-xxxhdpi/icon_validated_notification.png",
"java/res/drawable-xxxhdpi/incognito_simple.png",
@@ -545,6 +555,7 @@ brave_java_resources = [
"java/res/drawable/ic_hearts_rewards.xml",
"java/res/drawable/ic_help.xml",
"java/res/drawable/ic_help_filled.xml",
+ "java/res/drawable/ic_home_screen_widget.xml",
"java/res/drawable/ic_homepage.xml",
"java/res/drawable/ic_icon_bat_eta.xml",
"java/res/drawable/ic_info.xml",
@@ -589,6 +600,7 @@ brave_java_resources = [
"java/res/drawable/ic_phone.xml",
"java/res/drawable/ic_plans.xml",
"java/res/drawable/ic_plus.xml",
+ "java/res/drawable/ic_plus_add_widget.xml",
"java/res/drawable/ic_privacy_reports.xml",
"java/res/drawable/ic_promoted.xml",
"java/res/drawable/ic_qr_code.xml",
@@ -605,6 +617,7 @@ brave_java_resources = [
"java/res/drawable/ic_search_21dp.xml",
"java/res/drawable/ic_search_vector.xml",
"java/res/drawable/ic_send_tip.xml",
+ "java/res/drawable/ic_search_widget_promotion_icon.xml",
"java/res/drawable/ic_server_selection_check.xml",
"java/res/drawable/ic_settings.xml",
"java/res/drawable/ic_settings_dapps.xml",
@@ -649,6 +662,9 @@ brave_java_resources = [
"java/res/drawable/progress_indeterminate.xml",
"java/res/drawable/progress_indeterminate_orange.xml",
"java/res/drawable/publisher_favicon_background.xml",
+ "java/res/drawable/quick_action_search_and_bookmark_widget_background.xml",
+ "java/res/drawable/quick_action_search_and_bookmark_widget_bookmark_bg.xml",
+ "java/res/drawable/quick_action_search_and_bookmark_widget_search_bg.xml",
"java/res/drawable/radio_button_holo_bg.xml",
"java/res/drawable/radio_button_selected_bg.xml",
"java/res/drawable/radiobutton_background.xml",
@@ -847,6 +863,7 @@ brave_java_resources = [
"java/res/layout/item_fragment_two_line_divider_item.xml",
"java/res/layout/item_fragment_two_line_item.xml",
"java/res/layout/item_network_selector.xml",
+ "java/res/layout/layout_search_widget_promo.xml",
"java/res/layout/location_status_incognito_badge.xml",
"java/res/layout/menu_separator.xml",
"java/res/layout/network_spinner_items.xml",
@@ -860,6 +877,7 @@ brave_java_resources = [
"java/res/layout/pref_edittext_dialog.xml",
"java/res/layout/qa_code_check.xml",
"java/res/layout/qa_command_line.xml",
+ "java/res/layout/quick_action_search_and_bookmark_widget_layout.xml",
"java/res/layout/radio_button_group_ens_resolve_method_preference.xml",
"java/res/layout/radio_button_group_unstoppable_domains_resolve_method_preference.xml",
"java/res/layout/recovery_phrase_item_layout.xml",
@@ -925,6 +943,7 @@ brave_java_resources = [
"java/res/xml/legal_information_preferences.xml",
"java/res/xml/play_yt_video_in_browser_preferences.xml",
"java/res/xml/qa_preferences.xml",
+ "java/res/xml/quick_action_search_and_bookmark_widget_info.xml",
"java/res/xml/unstoppable_domains_preferences.xml",
"java/res/xml/use_custom_tabs_brave_preference.xml",
]
diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni
index e43f98bc321..4221dec4264 100644
--- a/android/brave_java_sources.gni
+++ b/android/brave_java_sources.gni
@@ -401,6 +401,9 @@ brave_java_sources = [
"../../brave/android/java/org/chromium/chrome/browser/vpn/wireguard/WireguardConfigUtils.java",
"../../brave/android/java/org/chromium/chrome/browser/vpn/wireguard/WireguardServiceImpl.java",
"../../brave/android/java/org/chromium/chrome/browser/vpn/wireguard/WireguardUtils.java",
+ "../../brave/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/QuickActionSearchAndBookmarkWidgetProvider.java",
+ "../../brave/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/promo/SearchWidgetPromoPanel.java",
+ "../../brave/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/utils/BraveSearchWidgetUtils.java",
]
brave_java_sources += brave_ads_java_sources
diff --git a/android/java/AndroidManifest.xml b/android/java/AndroidManifest.xml
index cd3bb3ef808..e41e5e50179 100644
--- a/android/java/AndroidManifest.xml
+++ b/android/java/AndroidManifest.xml
@@ -160,6 +160,18 @@
+
+
+
+
+
+
+
+
diff --git a/android/java/apk_for_test.flags b/android/java/apk_for_test.flags
index d54e06f3435..851493005f3 100644
--- a/android/java/apk_for_test.flags
+++ b/android/java/apk_for_test.flags
@@ -41,6 +41,7 @@
}
-keep class org.chromium.chrome.browser.suggestions.tile.MostVisitedTilesMediator {
+ *** mTileGroup;
public (...);
*** updateTilePlaceholderVisibility(...);
}
diff --git a/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java b/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java
index ede47c5dfa4..a8e166a676d 100644
--- a/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java
+++ b/android/java/org/chromium/chrome/browser/settings/BraveMainPreferencesBase.java
@@ -6,7 +6,6 @@
package org.chromium.chrome.browser.settings;
import android.app.AlertDialog;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
@@ -38,6 +37,7 @@ import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarConfiguration;
import org.chromium.chrome.browser.vpn.utils.BraveVpnPrefUtils;
import org.chromium.chrome.browser.vpn.utils.BraveVpnUtils;
import org.chromium.chrome.browser.vpn.utils.InAppPurchaseWrapper;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.utils.BraveSearchWidgetUtils;
import org.chromium.components.browser_ui.settings.ChromeBasePreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
@@ -90,6 +90,7 @@ public class BraveMainPreferencesBase
private static final String PREF_RATE_BRAVE = "rate_brave";
private static final String PREF_BRAVE_STATS = "brave_stats";
private static final String PREF_DOWNLOADS = "brave_downloads";
+ private static final String PREF_HOME_SCREEN_WIDGET = "home_screen_widget";
private final HashMap mRemovedPreferences = new HashMap<>();
@@ -226,6 +227,12 @@ public class BraveMainPreferencesBase
if (preference != null) {
preference.setOrder(++generalOrder);
}
+
+ if (BraveSearchWidgetUtils.isRequestPinAppWidgetSupported())
+ findPreference(PREF_HOME_SCREEN_WIDGET).setOrder(++generalOrder);
+ else
+ removePreferenceIfPresent(PREF_HOME_SCREEN_WIDGET);
+
findPreference(PREF_PASSWORDS).setOrder(++generalOrder);
findPreference(PREF_SYNC).setOrder(++generalOrder);
findPreference(PREF_BRAVE_STATS).setOrder(++generalOrder);
@@ -367,6 +374,18 @@ public class BraveMainPreferencesBase
return true;
}
});
+
+ Preference homeScreenWidgetPreference = findPreference(PREF_HOME_SCREEN_WIDGET);
+ if (homeScreenWidgetPreference != null) {
+ homeScreenWidgetPreference.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ BraveSearchWidgetUtils.requestPinAppWidget();
+ return true;
+ }
+ });
+ }
}
// TODO(simonhong): Make this static public with proper class.
diff --git a/android/java/org/chromium/chrome/browser/settings/BraveSearchEngineUtils.java b/android/java/org/chromium/chrome/browser/settings/BraveSearchEngineUtils.java
index cd003dc7e23..170d1ddcc96 100644
--- a/android/java/org/chromium/chrome/browser/settings/BraveSearchEngineUtils.java
+++ b/android/java/org/chromium/chrome/browser/settings/BraveSearchEngineUtils.java
@@ -11,6 +11,7 @@ import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.search_engines.settings.BraveSearchEngineAdapter;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.QuickActionSearchAndBookmarkWidgetProvider;
import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService;
@@ -64,6 +65,9 @@ public class BraveSearchEngineUtils {
static public void setDSEPrefs(TemplateUrl templateUrl, boolean isPrivate) {
BraveSearchEngineAdapter.setDSEPrefs(templateUrl, isPrivate);
+ if (!isPrivate && templateUrl != null)
+ QuickActionSearchAndBookmarkWidgetProvider.updateSearchEngine(
+ templateUrl.getShortName());
}
static public void updateActiveDSE(boolean isPrivate) {
diff --git a/android/java/org/chromium/chrome/browser/suggestions/tile/BraveMostVisitedTilesMediator.java b/android/java/org/chromium/chrome/browser/suggestions/tile/BraveMostVisitedTilesMediator.java
index 5bf3a36908d..af7e07c5480 100644
--- a/android/java/org/chromium/chrome/browser/suggestions/tile/BraveMostVisitedTilesMediator.java
+++ b/android/java/org/chromium/chrome/browser/suggestions/tile/BraveMostVisitedTilesMediator.java
@@ -11,10 +11,13 @@ import android.view.ViewStub;
import androidx.annotation.Nullable;
+import org.chromium.chrome.browser.suggestions.tile.Tile;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.QuickActionSearchAndBookmarkWidgetProvider;
import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
import org.chromium.ui.modelutil.PropertyModel;
public class BraveMostVisitedTilesMediator extends MostVisitedTilesMediator {
+ private TileGroup mTileGroup;
public BraveMostVisitedTilesMediator(Resources resources, UiConfig uiConfig,
ViewGroup mvTilesLayout, ViewStub noMvPlaceholderStub, TileRenderer renderer,
PropertyModel propertyModel, boolean shouldShowSkeletonUIPreNative,
@@ -29,4 +32,17 @@ public class BraveMostVisitedTilesMediator extends MostVisitedTilesMediator {
protected void updateTilePlaceholderVisibility() {
// This function is kept empty to avoid placeholder implementation
}
+
+ @Override
+ public void onTileDataChanged() {
+ super.onTileDataChanged();
+ QuickActionSearchAndBookmarkWidgetProvider.DataManager.parseTilesAndWriteWidgetTiles(
+ mTileGroup.getTileSections().get(TileSectionType.PERSONALIZED));
+ }
+
+ @Override
+ public void onTileIconChanged(Tile tile) {
+ super.onTileIconChanged(tile);
+ QuickActionSearchAndBookmarkWidgetProvider.updateTileIcon(tile);
+ }
}
diff --git a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java
index 23bd6ed2c4e..bb48d8cda24 100644
--- a/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java
+++ b/android/java/org/chromium/chrome/browser/toolbar/top/BraveToolbarLayoutImpl.java
@@ -119,6 +119,7 @@ import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
import org.chromium.chrome.browser.toolbar.top.ToolbarTablet.OfflineDownloader;
import org.chromium.chrome.browser.util.BraveConstants;
import org.chromium.chrome.browser.util.PackageUtils;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.promo.SearchWidgetPromoPanel;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.embedder_support.util.UrlUtilities;
@@ -193,6 +194,8 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout
private ColorStateList mDarkModeTint;
private ColorStateList mLightModeTint;
+ private SearchWidgetPromoPanel mSearchWidgetPromoPanel;
+
private final Set mTabsWithWalletIcon =
Collections.synchronizedSet(new HashSet());
@@ -249,7 +252,7 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout
mDarkModeTint = ThemeUtils.getThemedToolbarIconTint(getContext(), false);
mLightModeTint =
ColorStateList.valueOf(ContextCompat.getColor(getContext(), R.color.brave_white));
-
+ mSearchWidgetPromoPanel = new SearchWidgetPromoPanel(getContext());
if (mHomeButton != null) {
mHomeButton.setOnLongClickListener(this);
}
@@ -1043,6 +1046,9 @@ public abstract class BraveToolbarLayoutImpl extends ToolbarLayout
Intent searchActivityIntent = new Intent(context, SearchActivity.class);
context.startActivity(searchActivityIntent);
}
+ if (hasFocus)
+ mSearchWidgetPromoPanel.showIfNeeded(this);
+
if (OnboardingPrefManager.getInstance().getUrlFocusCount() == 0) {
OnboardingPrefManager.getInstance().updateUrlFocusCount();
}
diff --git a/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/QuickActionSearchAndBookmarkWidgetProvider.java b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/QuickActionSearchAndBookmarkWidgetProvider.java
new file mode 100644
index 00000000000..037c8735262
--- /dev/null
+++ b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/QuickActionSearchAndBookmarkWidgetProvider.java
@@ -0,0 +1,436 @@
+/* Copyright (c) 2020 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.chromium.chrome.browser.widget.quickactionsearchandbookmark;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.IntentUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.browserservices.intents.WebappConstants;
+import org.chromium.chrome.browser.document.ChromeLauncherActivity;
+import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.searchwidget.SearchActivity;
+import org.chromium.chrome.browser.settings.BraveSearchEngineUtils;
+import org.chromium.chrome.browser.suggestions.tile.Tile;
+import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
+import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityConstants;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.utils.BraveSearchWidgetUtils;
+import org.chromium.components.browser_ui.widget.RoundedIconGenerator;
+import org.chromium.components.favicon.IconType;
+import org.chromium.components.favicon.LargeIconBridge;
+import org.chromium.components.favicon.LargeIconBridge.LargeIconCallback;
+import org.chromium.components.search_engines.TemplateUrl;
+import org.chromium.components.webapps.ShortcutSource;
+import org.chromium.ui.base.ViewUtils;
+import org.chromium.url.GURL;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+public class QuickActionSearchAndBookmarkWidgetProvider extends AppWidgetProvider {
+ public static String FROM_SETTINGS = "FROM_SETTINGS";
+
+ private static final int TOTAL_TILES = 16;
+ private static final int TILES_PER_ROW = 4;
+ private static final int MIN_VISIBLE_HEIGHT_ROW_1 = 116;
+ private static final int MIN_VISIBLE_HEIGHT_ROW_2 = 184;
+ private static final int MIN_VISIBLE_HEIGHT_ROW_3 = 252;
+ private static final int MIN_VISIBLE_HEIGHT_ROW_4 = 320;
+ private static final int DESIRED_ICON_SIZE = 44;
+ private static final int DESIRED_ICON_RADIUS = 16;
+
+ private final static int[][][] tileViewsIdArray = new int[][][] {
+ {
+ {R.id.ivRow1Bookmark1Icon, R.id.tvRow1Bookmark1Name, R.id.layoutRow1Bookmark1},
+ {R.id.ivRow1Bookmark2Icon, R.id.tvRow1Bookmark2Name, R.id.layoutRow1Bookmark2},
+ {R.id.ivRow1Bookmark3Icon, R.id.tvRow1Bookmark3Name, R.id.layoutRow1Bookmark3},
+ {R.id.ivRow1Bookmark4Icon, R.id.tvRow1Bookmark4Name, R.id.layoutRow1Bookmark4},
+ },
+ {
+ {R.id.ivRow2Bookmark1Icon, R.id.tvRow2Bookmark1Name, R.id.layoutRow2Bookmark1},
+ {R.id.ivRow2Bookmark2Icon, R.id.tvRow2Bookmark2Name, R.id.layoutRow2Bookmark2},
+ {R.id.ivRow2Bookmark3Icon, R.id.tvRow2Bookmark3Name, R.id.layoutRow2Bookmark3},
+ {R.id.ivRow2Bookmark4Icon, R.id.tvRow2Bookmark4Name, R.id.layoutRow2Bookmark4},
+ },
+ {
+ {R.id.ivRow3Bookmark1Icon, R.id.tvRow3Bookmark1Name, R.id.layoutRow3Bookmark1},
+ {R.id.ivRow3Bookmark2Icon, R.id.tvRow3Bookmark2Name, R.id.layoutRow3Bookmark2},
+ {R.id.ivRow3Bookmark3Icon, R.id.tvRow3Bookmark3Name, R.id.layoutRow3Bookmark3},
+ {R.id.ivRow3Bookmark4Icon, R.id.tvRow3Bookmark4Name, R.id.layoutRow3Bookmark4},
+ },
+ {
+ {R.id.ivRow4Bookmark1Icon, R.id.tvRow4Bookmark1Name, R.id.layoutRow4Bookmark1},
+ {R.id.ivRow4Bookmark2Icon, R.id.tvRow4Bookmark2Name, R.id.layoutRow4Bookmark2},
+ {R.id.ivRow4Bookmark3Icon, R.id.tvRow4Bookmark3Name, R.id.layoutRow4Bookmark3},
+ {R.id.ivRow4Bookmark4Icon, R.id.tvRow4Bookmark4Name, R.id.layoutRow4Bookmark4},
+ },
+ };
+
+ public QuickActionSearchAndBookmarkWidgetProvider() {
+ ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ String widgetAddedToHomeScreen = context.getString(R.string.widget_added_to_home_screen);
+ boolean isComingFromSettings = intent.getBooleanExtra(FROM_SETTINGS, false);
+ if (isComingFromSettings) {
+ Toast.makeText(context, widgetAddedToHomeScreen, Toast.LENGTH_SHORT).show();
+ }
+ BraveSearchWidgetUtils.setShouldShowWidgetPromo(false);
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+ updateAppWidgets(appWidgetIds);
+ }
+
+ @Override
+ public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
+ int appWidgetId, Bundle newOptions) {
+ super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
+ updateAppWidgets(new int[] {appWidgetId});
+ }
+
+ public static void updateTileIcon(Tile tile) {
+ int index = indexOf(tile);
+ if (index != -1)
+ updateTileIcon(tileViewsIdArray[index / TILES_PER_ROW][index % TILES_PER_ROW][0],
+ getBitmap(tile.getIcon()));
+ }
+
+ public static void updateSearchEngine(String searchEngine) {
+ Context context = ContextUtils.getApplicationContext();
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = getAppWidgetIds(context, appWidgetManager);
+ for (int appWidgetId : appWidgetIds) {
+ RemoteViews views = getBaseRemoteViews();
+ String searchWithDefaultSearchEngine =
+ context.getString(R.string.search_with_search_engine, searchEngine);
+ views.setTextViewText(R.id.tvSearchWithBrave, searchWithDefaultSearchEngine);
+ appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views);
+ }
+ }
+
+ private static void updateAppWidgets() {
+ Context context = ContextUtils.getApplicationContext();
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ updateAppWidgets(getAppWidgetIds(context, appWidgetManager));
+ }
+
+ private static int indexOf(Tile tile) {
+ List widgetTileList = DataManager.readWidgetTiles();
+ for (int i = 0; i < widgetTileList.size(); i++) {
+ if (widgetTileList.get(i).equals(tile)) return i;
+ }
+ return -1;
+ }
+
+ private static void updateTileIcon(int imageView, Bitmap icon) {
+ Context context = ContextUtils.getApplicationContext();
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = getAppWidgetIds(context, appWidgetManager);
+ for (int appWidgetId : appWidgetIds) {
+ RemoteViews views = getBaseRemoteViews();
+ views.setImageViewBitmap(imageView, icon);
+ appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views);
+ }
+ }
+
+ private static RemoteViews getBaseRemoteViews() {
+ return new RemoteViews(ContextUtils.getApplicationContext().getPackageName(),
+ R.layout.quick_action_search_and_bookmark_widget_layout);
+ }
+
+ private static int[] getAppWidgetIds(Context context, AppWidgetManager appWidgetManager) {
+ return appWidgetManager.getAppWidgetIds(
+ new ComponentName(context, QuickActionSearchAndBookmarkWidgetProvider.class));
+ }
+
+ private static void updateAppWidgets(int[] appWidgetIds) {
+ Context context = ContextUtils.getApplicationContext();
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ List widgetTileList = DataManager.readWidgetTiles();
+ for (int appWidgetId : appWidgetIds) {
+ RemoteViews views = getBaseRemoteViews();
+ Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId);
+ int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+ setDefaultSearchEngineString(views);
+ setSearchBarPendingIntent(context, views);
+ setTopTiles(context, views, widgetTileList);
+ setRowsVisibility(views, widgetTileList.size(), minHeight);
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+
+ private static void setDefaultSearchEngineString(RemoteViews views) {
+ TemplateUrl templateUrl = BraveSearchEngineUtils.getTemplateUrlByShortName(
+ BraveSearchEngineUtils.getDSEShortName(false));
+ if (templateUrl != null) {
+ String searchWithDefaultSearchEngine = ContextUtils.getApplicationContext().getString(
+ R.string.search_with_search_engine, templateUrl.getShortName());
+ views.setTextViewText(R.id.tvSearchWithBrave, searchWithDefaultSearchEngine);
+ }
+ }
+
+ private static void setTopTiles(
+ Context context, RemoteViews views, List widgetTileList) {
+ int tilesSize = widgetTileList.size();
+ int i = 0;
+ int j = 0;
+
+ while (i < tilesSize && i < TOTAL_TILES) {
+ j = j % TILES_PER_ROW;
+ int row = i / TILES_PER_ROW;
+
+ WidgetTile tile = widgetTileList.get(i);
+ int tileLayoutId = tileViewsIdArray[row][j][2];
+ int tileImageViewId = tileViewsIdArray[row][j][0];
+ int tileTextViewId = tileViewsIdArray[row][j][1];
+
+ views.setViewVisibility(tileLayoutId, View.VISIBLE);
+ views.setOnClickPendingIntent(tileLayoutId, createIntent(context, tile.getUrl()));
+ views.setTextViewText(tileTextViewId, tile.getTitle());
+ views.setInt(tileImageViewId, "setColorFilter", 0);
+ fetchGurlIcon(tileImageViewId, tile.getGURL());
+
+ i++;
+ j++;
+ }
+
+ // hide and uninitialize the remaining placeholder tiles
+ while (i < TOTAL_TILES) {
+ j = j % TILES_PER_ROW;
+ int row = i / TILES_PER_ROW;
+ int tileLayoutId = tileViewsIdArray[row][j][2];
+ int tileImageViewId = tileViewsIdArray[row][j][0];
+ int tileTextViewId = tileViewsIdArray[row][j][1];
+ views.setViewVisibility(tileLayoutId, View.INVISIBLE);
+ views.setOnClickPendingIntent(tileLayoutId, null);
+ views.setTextViewText(tileTextViewId, "");
+ views.setImageViewResource(tileImageViewId, 0);
+ i++;
+ j++;
+ }
+ }
+
+ private static void fetchGurlIcon(final int imageViewId, GURL gurl) {
+ LargeIconBridge largeIconBridge = new LargeIconBridge(Profile.getLastUsedRegularProfile());
+ LargeIconCallback callback = new LargeIconCallback() {
+ @Override
+ public void onLargeIconAvailable(Bitmap icon, int fallbackColor,
+ boolean isFallbackColorDefault, @IconType int iconType) {
+ if (icon == null)
+ updateTileIcon(imageViewId,
+ getTileIconFromColor(gurl, fallbackColor, isFallbackColorDefault));
+ else
+ updateTileIcon(imageViewId, getRoundedTileIconFromBitmap(icon));
+ }
+ };
+ largeIconBridge.getLargeIconForUrl(gurl, DESIRED_ICON_SIZE, callback);
+ }
+
+ private static Bitmap getRoundedTileIconFromBitmap(Bitmap icon) {
+ RoundedBitmapDrawable roundedIcon = ViewUtils.createRoundedBitmapDrawable(
+ ContextUtils.getApplicationContext().getResources(), icon, DESIRED_ICON_RADIUS);
+ roundedIcon.setAntiAlias(true);
+ roundedIcon.setFilterBitmap(true);
+ return getBitmap(roundedIcon);
+ }
+
+ private static Bitmap getTileIconFromColor(
+ GURL gurl, int fallbackColor, boolean isFallbackColorDefault) {
+ RoundedIconGenerator mIconGenerator = FaviconUtils.createRoundedRectangleIconGenerator(
+ ContextUtils.getApplicationContext());
+ mIconGenerator.setBackgroundColor(fallbackColor);
+ return mIconGenerator.generateIconForUrl(gurl);
+ }
+
+ private static void setSearchBarPendingIntent(Context context, RemoteViews views) {
+ views.setOnClickPendingIntent(R.id.ivIncognito, createIncognitoIntent(context));
+ views.setOnClickPendingIntent(R.id.layoutSearchWithBrave, createIntent(context, false));
+ views.setOnClickPendingIntent(R.id.ivVoiceSearch, createIntent(context, true));
+ }
+
+ private static Bitmap getBitmap(@Nullable Drawable drawable) {
+ if (drawable != null) {
+ Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ } else
+ return null;
+ }
+
+ private static void setRowsVisibility(RemoteViews views, int tilesSize, int minHeight) {
+ views.setViewVisibility(R.id.BookmarkLayoutRow1,
+ tilesSize > 0 * TILES_PER_ROW && minHeight >= MIN_VISIBLE_HEIGHT_ROW_1
+ ? View.VISIBLE
+ : View.GONE);
+ views.setViewVisibility(R.id.BookmarkLayoutRow2,
+ tilesSize > 1 * TILES_PER_ROW && minHeight >= MIN_VISIBLE_HEIGHT_ROW_2
+ ? View.VISIBLE
+ : View.GONE);
+ views.setViewVisibility(R.id.BookmarkLayoutRow3,
+ tilesSize > 2 * TILES_PER_ROW && minHeight >= MIN_VISIBLE_HEIGHT_ROW_3
+ ? View.VISIBLE
+ : View.GONE);
+ views.setViewVisibility(R.id.BookmarkLayoutRow4,
+ tilesSize > 3 * TILES_PER_ROW && minHeight >= MIN_VISIBLE_HEIGHT_ROW_4
+ ? View.VISIBLE
+ : View.GONE);
+ }
+
+ private static PendingIntent createIntent(Context context, String url) {
+ Intent intent = new Intent(
+ Intent.ACTION_VIEW, Uri.parse(url), context, ChromeLauncherActivity.class);
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(WebappConstants.EXTRA_SOURCE, ShortcutSource.BOOKMARK_NAVIGATOR_WIDGET);
+ intent.putExtra(WebappConstants.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, true);
+ return createPendingIntent(context, intent);
+ }
+
+ private static PendingIntent createIntent(Context context, boolean startVoiceSearch) {
+ Intent searchIntent =
+ new Intent(startVoiceSearch ? SearchActivityConstants.ACTION_START_VOICE_SEARCH
+ : SearchActivityConstants.ACTION_START_TEXT_SEARCH);
+ searchIntent.setComponent(new ComponentName(context, SearchActivity.class));
+ searchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ searchIntent.putExtra(
+ SearchActivityConstants.EXTRA_BOOLEAN_FROM_QUICK_ACTION_SEARCH_WIDGET, true);
+ return createPendingIntent(context, searchIntent);
+ }
+
+ private static PendingIntent createIncognitoIntent(Context context) {
+ Intent trustedIncognitoIntent =
+ IntentHandler.createTrustedOpenNewTabIntent(context, /*incognito=*/true);
+ trustedIncognitoIntent.putExtra(IntentHandler.EXTRA_INVOKED_FROM_APP_WIDGET, true);
+ trustedIncognitoIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ trustedIncognitoIntent.putExtra(
+ SearchActivityConstants.EXTRA_BOOLEAN_FROM_QUICK_ACTION_SEARCH_WIDGET, true);
+ return createPendingIntent(context, trustedIncognitoIntent);
+ }
+
+ private static PendingIntent createPendingIntent(Context context, Intent intent) {
+ return PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ | IntentUtils.getPendingIntentMutabilityFlag(false));
+ }
+
+ /**
+ * This class acts as a single source of truth for this widet. This widget would use this class
+ *to fetch the data. Also, any modification to the data should be done through this class.
+ **/
+
+ public static class DataManager {
+ private static final String TILES =
+ "org.chromium.chrome.browser.widget.quickactionsearchandbookmark.QuickActionSearchAndBookmarkWidgetProvider.TILES";
+ public static void parseTilesAndWriteWidgetTiles(List tiles) {
+ List widgetTileList = new ArrayList<>();
+ for (Tile tile : tiles) {
+ WidgetTile widgetTile = new WidgetTile(tile.getTitle(), tile.getUrl());
+ widgetTileList.add(widgetTile);
+ }
+ writeWidgetTiles(widgetTileList);
+ }
+
+ public static void writeWidgetTiles(List widgetTileList) {
+ SharedPreferencesManager.getInstance().writeString(
+ TILES, new Gson().toJson(widgetTileList));
+ updateAppWidgets();
+ }
+
+ public static List readWidgetTiles() {
+ String widgetTilesJson = SharedPreferencesManager.getInstance().readString(TILES, null);
+ if (widgetTilesJson != null) {
+ Type type = TypeToken.getParameterized(List.class, WidgetTile.class).getType();
+ return new Gson().fromJson(widgetTilesJson, type);
+ }
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * A short class for tile. It keeps only information needed to this widget.
+ **/
+
+ public static class WidgetTile {
+ private String title;
+ private GURL gurl;
+
+ public WidgetTile(String title, GURL gurl) {
+ this.title = title;
+ this.gurl = gurl;
+ }
+
+ public String getUrl() {
+ return getGURL() != null ? getGURL().getSpec() : null;
+ }
+
+ public GURL getGURL() {
+ return this.gurl;
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public void parseTile(Tile tile) {
+ this.gurl = tile.getUrl();
+ this.title = tile.getTitle();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof WidgetTile) {
+ return Objects.equals(getUrl(), ((WidgetTile) obj).getUrl());
+ } else if (obj instanceof Tile) {
+ Tile tile = (Tile) obj;
+ if (tile.getUrl() != null) return Objects.equals(getUrl(), tile.getUrl().getSpec());
+ } else if (obj instanceof String) {
+ return Objects.equals(getUrl(), (String) obj);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(this.getUrl());
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/promo/SearchWidgetPromoPanel.java b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/promo/SearchWidgetPromoPanel.java
new file mode 100644
index 00000000000..3f9a88bdd78
--- /dev/null
+++ b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/promo/SearchWidgetPromoPanel.java
@@ -0,0 +1,50 @@
+/*
+ Copyright (c) 2021 The Brave Authors. All rights reserved.
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package org.chromium.chrome.browser.widget.quickactionsearchandbookmark.promo;
+
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.PopupWindow;
+import org.chromium.chrome.R;
+import android.content.Context;
+import androidx.cardview.widget.CardView;
+import androidx.appcompat.widget.AppCompatTextView;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.utils.BraveSearchWidgetUtils;
+
+public class SearchWidgetPromoPanel implements View.OnClickListener {
+
+ private PopupWindow mPopupWindow;
+
+ public SearchWidgetPromoPanel(@NonNull Context context) {
+ View view = View.inflate(context, R.layout.layout_search_widget_promo, null);
+ CardView cvAddWidget = view.findViewById(R.id.cvAddWidget);
+ AppCompatTextView tvNotNow = view.findViewById(R.id.tvNotNow);
+ cvAddWidget.setOnClickListener(this);
+ tvNotNow.setOnClickListener(this);
+ int width = LinearLayout.LayoutParams.MATCH_PARENT;
+ int height = LinearLayout.LayoutParams.WRAP_CONTENT;
+ mPopupWindow = new PopupWindow(view, width, height, true);
+ }
+
+ @Override
+ public void onClick(@NonNull View view) {
+ if (view.getId() == R.id.cvAddWidget)
+ BraveSearchWidgetUtils.requestPinAppWidget();
+ else if (view.getId() == R.id.tvNotNow)
+ BraveSearchWidgetUtils.setShouldShowWidgetPromo(false);
+ mPopupWindow.dismiss();
+ }
+
+ public void showIfNeeded(@NonNull View anchorView) {
+ if (BraveSearchWidgetUtils.getShouldShowWidgetPromo())
+ mPopupWindow.showAsDropDown(anchorView);
+ }
+}
diff --git a/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/utils/BraveSearchWidgetUtils.java b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/utils/BraveSearchWidgetUtils.java
new file mode 100644
index 00000000000..f4b55623e5c
--- /dev/null
+++ b/android/java/org/chromium/chrome/browser/widget/quickactionsearchandbookmark/utils/BraveSearchWidgetUtils.java
@@ -0,0 +1,67 @@
+/*
+ Copyright (c) 2021 The Brave Authors. All rights reserved.
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+package org.chromium.chrome.browser.widget.quickactionsearchandbookmark.utils;
+
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import android.appwidget.AppWidgetManager;
+import org.chromium.chrome.browser.widget.quickactionsearchandbookmark.QuickActionSearchAndBookmarkWidgetProvider;
+import org.chromium.base.ContextUtils;
+import android.content.ComponentName;
+import android.os.Build;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.app.PendingIntent;
+import android.os.Bundle;
+
+public class BraveSearchWidgetUtils {
+
+ private static final String SHOW_WIDGET = "org.chromium.chrome.browser.widget.quickactionsearchandbookmark.utils.SHOW_WIDGET";
+
+ public static boolean getShouldShowWidgetPromo() {
+ return SharedPreferencesManager.getInstance().readBoolean(SHOW_WIDGET, isRequestPinAppWidgetSupported());
+ }
+
+ public static void setShouldShowWidgetPromo(boolean shouldShow) {
+ SharedPreferencesManager.getInstance().writeBoolean(SHOW_WIDGET, shouldShow);
+ }
+
+ public static boolean isRequestPinAppWidgetSupported() {
+ AppWidgetManager appWidgetManager =
+ ContextUtils.getApplicationContext().getSystemService(AppWidgetManager.class);
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appWidgetManager != null
+ && appWidgetManager.isRequestPinAppWidgetSupported();
+ }
+
+ public static void requestPinAppWidget() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Context context = ContextUtils.getApplicationContext();
+ AppWidgetManager appWidgetManager =
+ context.getSystemService(AppWidgetManager.class);
+
+ ComponentName appWidgetProvider = new ComponentName(
+ context, QuickActionSearchAndBookmarkWidgetProvider.class);
+
+ if (appWidgetManager != null
+ && appWidgetManager.isRequestPinAppWidgetSupported()) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(
+ QuickActionSearchAndBookmarkWidgetProvider.FROM_SETTINGS, true);
+ Intent pinnedWidgetCallbackIntent = new Intent(
+ context, QuickActionSearchAndBookmarkWidgetProvider.class);
+ pinnedWidgetCallbackIntent.putExtras(bundle);
+ PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
+ pinnedWidgetCallbackIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT);
+
+ appWidgetManager.requestPinAppWidget(appWidgetProvider, null, successCallback);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/java/res/drawable-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..9f11c2df15a
Binary files /dev/null and b/android/java/res/drawable-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..af274cca341
Binary files /dev/null and b/android/java/res/drawable-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-night-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-night-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..c469e6c23f6
Binary files /dev/null and b/android/java/res/drawable-night-hdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-night-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-night-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..ca112fc64f7
Binary files /dev/null and b/android/java/res/drawable-night-mdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-night-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-night-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..b1f1cb9be35
Binary files /dev/null and b/android/java/res/drawable-night-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-night-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-night-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..94baf48d738
Binary files /dev/null and b/android/java/res/drawable-night-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-night-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-night-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..1b039a63b13
Binary files /dev/null and b/android/java/res/drawable-night-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..7e37c97342d
Binary files /dev/null and b/android/java/res/drawable-xhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..d4cafc59d9f
Binary files /dev/null and b/android/java/res/drawable-xxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png b/android/java/res/drawable-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png
new file mode 100644
index 00000000000..095c0e5e9ff
Binary files /dev/null and b/android/java/res/drawable-xxxhdpi/ic_quick_action_search_and_bookmark_widget_preview.png differ
diff --git a/android/java/res/drawable/ic_home_screen_widget.xml b/android/java/res/drawable/ic_home_screen_widget.xml
new file mode 100644
index 00000000000..2b3ddab35a7
--- /dev/null
+++ b/android/java/res/drawable/ic_home_screen_widget.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/drawable/ic_plus_add_widget.xml b/android/java/res/drawable/ic_plus_add_widget.xml
new file mode 100644
index 00000000000..6d65a97ea42
--- /dev/null
+++ b/android/java/res/drawable/ic_plus_add_widget.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/android/java/res/drawable/ic_search_widget_promotion_icon.xml b/android/java/res/drawable/ic_search_widget_promotion_icon.xml
new file mode 100644
index 00000000000..6eca33af905
--- /dev/null
+++ b/android/java/res/drawable/ic_search_widget_promotion_icon.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/drawable/quick_action_search_and_bookmark_widget_background.xml b/android/java/res/drawable/quick_action_search_and_bookmark_widget_background.xml
new file mode 100644
index 00000000000..0cbd3862b38
--- /dev/null
+++ b/android/java/res/drawable/quick_action_search_and_bookmark_widget_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/drawable/quick_action_search_and_bookmark_widget_bookmark_bg.xml b/android/java/res/drawable/quick_action_search_and_bookmark_widget_bookmark_bg.xml
new file mode 100644
index 00000000000..c2681bdd529
--- /dev/null
+++ b/android/java/res/drawable/quick_action_search_and_bookmark_widget_bookmark_bg.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/drawable/quick_action_search_and_bookmark_widget_search_bg.xml b/android/java/res/drawable/quick_action_search_and_bookmark_widget_search_bg.xml
new file mode 100644
index 00000000000..238d961cb1d
--- /dev/null
+++ b/android/java/res/drawable/quick_action_search_and_bookmark_widget_search_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/layout/layout_search_widget_promo.xml b/android/java/res/layout/layout_search_widget_promo.xml
new file mode 100644
index 00000000000..c9350b3c5e8
--- /dev/null
+++ b/android/java/res/layout/layout_search_widget_promo.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/layout/quick_action_search_and_bookmark_widget_layout.xml b/android/java/res/layout/quick_action_search_and_bookmark_widget_layout.xml
new file mode 100644
index 00000000000..a24f42ea0ae
--- /dev/null
+++ b/android/java/res/layout/quick_action_search_and_bookmark_widget_layout.xml
@@ -0,0 +1,587 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/java/res/values-night/brave_colors.xml b/android/java/res/values-night/brave_colors.xml
index afcdaef0bdd..7d337125f80 100644
--- a/android/java/res/values-night/brave_colors.xml
+++ b/android/java/res/values-night/brave_colors.xml
@@ -125,4 +125,16 @@
@android:color/white
#F0F2FF
#1E2029
+
+
+ #F21E2029
+ #405E6175
+ #FFFFFF
+ #F0F2FF
+ #CED0DB
+
+
+ #FFFFFF
+ #F0F2FF
+ #495057
diff --git a/android/java/res/values/brave_colors.xml b/android/java/res/values/brave_colors.xml
index b113d9bf507..b7dd4dd3c39 100644
--- a/android/java/res/values/brave_colors.xml
+++ b/android/java/res/values/brave_colors.xml
@@ -232,4 +232,16 @@
@color/brave_action_color
#212529
@android:color/white
+
+
+ #F2FFFFFF
+ #40AEB1C2
+ #EEEFF1
+ #495057
+ #495057
+
+
+ #FFFFFF
+ #4C54D2
+ #FFFFFF
diff --git a/android/java/res/xml/brave_main_preferences.xml b/android/java/res/xml/brave_main_preferences.xml
index 0bfe7249703..8a0e0510f13 100644
--- a/android/java/res/xml/brave_main_preferences.xml
+++ b/android/java/res/xml/brave_main_preferences.xml
@@ -51,6 +51,13 @@
android:icon="@drawable/ic_search_vector"
android:title="@string/brave_search_engines"/>
+
+
+
\ No newline at end of file
diff --git a/android/javatests/org/chromium/chrome/browser/BytecodeTest.java b/android/javatests/org/chromium/chrome/browser/BytecodeTest.java
index 57dae6c766b..16c9e96256a 100644
--- a/android/javatests/org/chromium/chrome/browser/BytecodeTest.java
+++ b/android/javatests/org/chromium/chrome/browser/BytecodeTest.java
@@ -719,6 +719,9 @@ public class BytecodeTest {
Assert.assertTrue(fieldExists(
"org/chromium/chrome/browser/omnibox/suggestions/editurl/EditUrlSuggestionProcessor",
"mHasClearedOmniboxForFocus"));
+ Assert.assertTrue(
+ fieldExists("org/chromium/chrome/browser/suggestions/tile/MostVisitedTilesMediator",
+ "mTileGroup"));
Assert.assertTrue(
fieldExists("org/chromium/chrome/browser/sync/settings/ManageSyncSettings",
"mGoogleActivityControls"));
diff --git a/browser/ui/android/strings/android_brave_strings.grd b/browser/ui/android/strings/android_brave_strings.grd
index 10932905e41..9fdf5968b43 100644
--- a/browser/ui/android/strings/android_brave_strings.grd
+++ b/browser/ui/android/strings/android_brave_strings.grd
@@ -3210,6 +3210,33 @@ If you don't accept this request, VPN will not reconnect and your internet conne
Skip Preflight:
+
+ Top Visited Site
+
+
+ Search with Brave
+
+
+ Search with %1$s
+
+
+ Home screen widget
+
+
+ Widget added to the home screen
+
+
+ Try the Brave search widget
+
+
+ Access search and more with a quick tap directly from your home screen.
+
+
+ Add widget
+
+
+ Brave quick action search
+
diff --git a/build/android/bytecode/java/org/brave/bytecode/BraveMostVisitedTilesMediatorClassAdapter.java b/build/android/bytecode/java/org/brave/bytecode/BraveMostVisitedTilesMediatorClassAdapter.java
index 13b816adb22..0c19b671228 100644
--- a/build/android/bytecode/java/org/brave/bytecode/BraveMostVisitedTilesMediatorClassAdapter.java
+++ b/build/android/bytecode/java/org/brave/bytecode/BraveMostVisitedTilesMediatorClassAdapter.java
@@ -22,5 +22,8 @@ public class BraveMostVisitedTilesMediatorClassAdapter extends BraveClassVisitor
makePublicMethod(sMostVisitedTilesMediatorClassName, "updateTilePlaceholderVisibility");
addMethodAnnotation(sBraveMostVisitedTilesMediatorClassName,
"updateTilePlaceholderVisibility", "Ljava/lang/Override;");
+
+ deleteField(sBraveMostVisitedTilesMediatorClassName, "mTileGroup");
+ makeProtectedField(sMostVisitedTilesMediatorClassName, "mTileGroup");
}
}
diff --git a/build/android/config.gni b/build/android/config.gni
index 1e9aa7ab29e..41d893749c6 100644
--- a/build/android/config.gni
+++ b/build/android/config.gni
@@ -33,6 +33,7 @@ brave_chrome_java_deps = [
"//brave/third_party/android_deps:com_wireguard_android_J__unpack_aar",
"//brave/third_party/android_deps:com_wireguard_android_java",
"//brave/third_party/android_deps:google_play_services_safetynet_java",
+ "//brave/third_party/gson:gson_java",
"//brave/third_party/zxing:zxing_java",
"//third_party/android_deps:google_play_services_vision_common_java",
"//third_party/android_deps:google_play_services_vision_java",
diff --git a/third_party/gson/BUILD.gn b/third_party/gson/BUILD.gn
new file mode 100644
index 00000000000..4bf510baf3f
--- /dev/null
+++ b/third_party/gson/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2016 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("//build/config/android/rules.gni")
+
+java_prebuilt("gson_java") {
+ supports_android = true
+ jar_path = "lib/gson-2.9.1.jar"
+}
diff --git a/third_party/gson/LICENSE b/third_party/gson/LICENSE
new file mode 100644
index 00000000000..510991edab0
--- /dev/null
+++ b/third_party/gson/LICENSE
@@ -0,0 +1,245 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+========================================================================
+jai-imageio
+========================================================================
+
+Copyright (c) 2005 Sun Microsystems, Inc.
+Copyright © 2010-2014 University of Manchester
+Copyright © 2010-2015 Stian Soiland-Reyes
+Copyright © 2015 Peter Hull
+All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistribution of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+- Redistribution in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+Neither the name of Sun Microsystems, Inc. or the names of
+contributors may be used to endorse or promote products derived
+from this software without specific prior written permission.
+
+This software is provided "AS IS," without a warranty of any
+kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
+WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
+EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
+NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
+USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
+ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
+CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
+REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
+INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+You acknowledge that this software is not designed or intended for
+use in the design, construction, operation or maintenance of any
+nuclear facility.
\ No newline at end of file
diff --git a/third_party/gson/README.chromium b/third_party/gson/README.chromium
new file mode 100644
index 00000000000..96ddffbf3fd
--- /dev/null
+++ b/third_party/gson/README.chromium
@@ -0,0 +1,3 @@
+Name: Gson
+URL: https://github.com/google/gson
+License: Apache-2.0
diff --git a/third_party/gson/lib/gson-2.9.1.jar b/third_party/gson/lib/gson-2.9.1.jar
new file mode 100644
index 00000000000..8a663ecceb9
Binary files /dev/null and b/third_party/gson/lib/gson-2.9.1.jar differ
diff --git a/third_party/gson/lib/gson-2.9.1.jar:Zone.Identifier b/third_party/gson/lib/gson-2.9.1.jar:Zone.Identifier
new file mode 100644
index 00000000000..084bdc2a163
--- /dev/null
+++ b/third_party/gson/lib/gson-2.9.1.jar:Zone.Identifier
@@ -0,0 +1,4 @@
+[ZoneTransfer]
+ZoneId=3
+ReferrerUrl=https://mvnrepository.com/
+HostUrl=https://repo1.maven.org/maven2/com/google/code/gson/gson/2.9.1/gson-2.9.1.jar