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