diff --git a/android/brave_java_resources.gni b/android/brave_java_resources.gni index 0cb568f15c44..4152efb289de 100644 --- a/android/brave_java_resources.gni +++ b/android/brave_java_resources.gni @@ -391,6 +391,7 @@ brave_java_resources = [ "java/res/drawable/ellipse_217.xml", "java/res/drawable/exchange_shape.xml", "java/res/drawable/fingerprint_unlock_layer_list.xml", + "java/res/drawable/fullscreen_on.xml", "java/res/drawable/ic_ac_onboarding.xml", "java/res/drawable/ic_accessibility.xml", "java/res/drawable/ic_add.xml", @@ -643,6 +644,7 @@ brave_java_resources = [ "java/res/drawable/orange_rounded_button.xml", "java/res/drawable/p2p_rewards_inside_background.xml", "java/res/drawable/password_match.xml", + "java/res/drawable/picture_in_picture.xml", "java/res/drawable/private_tab_gradient_background.xml", "java/res/drawable/product_private_layer_list.xml", "java/res/drawable/product_vpn_layer_list.xml", @@ -756,8 +758,6 @@ brave_java_resources = [ "java/res/drawable/youtube_features_background.xml", "java/res/drawable/youtube_features_ripple_left_mask.xml", "java/res/drawable/youtube_features_ripple_right_mask.xml", - "java/res/drawable/picture_in_picture.xml", - "java/res/drawable/fullscreen_on.xml", "java/res/layout-land/brave_shields_stats_row.xml", "java/res/layout-sw600dp-land/brave_shields_stats_row.xml", "java/res/layout-sw600dp/brave_rewards_tippingpanel_fragment.xml", @@ -1005,6 +1005,7 @@ brave_java_resources = [ "java/res/values-h748dp/dimens.xml", "java/res/values-h765dp/dimens.xml", "java/res/values-h820dp/dimens.xml", + "java/res/values-land/brave_dimens.xml", "java/res/values-night/brave_colors.xml", "java/res/values-sw600dp/brave_dimens.xml", "java/res/values-v21/brave_styles.xml", @@ -1014,7 +1015,6 @@ brave_java_resources = [ "java/res/values/brave_attrs.xml", "java/res/values/brave_colors.xml", "java/res/values/brave_dimens.xml", - "java/res/values-land/brave_dimens.xml", "java/res/values/brave_ids.xml", "java/res/values/brave_styles.xml", "java/res/values/shimmer_attrs.xml", diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index dfb0752c64a2..cf17ed159b9c 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -452,7 +452,6 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/upgrade/BraveUpgradeJobIntentServiceImpl.java", "../../brave/android/java/org/chromium/chrome/browser/upgrade/NotificationIntent.java", "../../brave/android/java/org/chromium/chrome/browser/util/BraveConstants.java", - "../../brave/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java", "../../brave/android/java/org/chromium/chrome/browser/util/BraveDbUtil.java", "../../brave/android/java/org/chromium/chrome/browser/util/BraveDynamicColors.java", "../../brave/android/java/org/chromium/chrome/browser/util/BraveReferrer.java", @@ -462,6 +461,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/util/KeyboardVisibilityHelper.java", "../../brave/android/java/org/chromium/chrome/browser/util/LiveDataUtil.java", "../../brave/android/java/org/chromium/chrome/browser/util/PackageUtils.java", + "../../brave/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java", "../../brave/android/java/org/chromium/chrome/browser/util/SharedPreferencesHelper.java", "../../brave/android/java/org/chromium/chrome/browser/util/TabUtils.java", "../../brave/android/java/org/chromium/chrome/browser/util/TouchDelegateComposite.java", diff --git a/android/java/org/chromium/chrome/browser/BackgroundVideoPlaybackTabHelper.java b/android/java/org/chromium/chrome/browser/BackgroundVideoPlaybackTabHelper.java index 66c525dff039..300a846bd65d 100644 --- a/android/java/org/chromium/chrome/browser/BackgroundVideoPlaybackTabHelper.java +++ b/android/java/org/chromium/chrome/browser/BackgroundVideoPlaybackTabHelper.java @@ -5,12 +5,12 @@ package org.chromium.chrome.browser; -import org.chromium.base.Log; -import org.chromium.chrome.browser.app.BraveActivity; import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; import org.jni_zero.NativeMethods; +import org.chromium.base.Log; +import org.chromium.chrome.browser.app.BraveActivity; import org.chromium.content_public.browser.WebContents; /** diff --git a/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java b/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java index 04fc64e776a6..b322c96a51dc 100644 --- a/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java +++ b/android/java/org/chromium/chrome/browser/util/PictureInPictureUtils.java @@ -10,37 +10,46 @@ import androidx.annotation.NonNull; /** - * Auxiliary class containing static methods that helps - * to determine if picture in picture mode is supported by device - * and enabled via permission settings. + * Auxiliary class containing static methods that helps to determine if picture in picture mode is + * supported by device and enabled via permission settings. */ public class PictureInPictureUtils { - /** - * Returns {@code true} if picture in picture permission is enabled - * in app settings. - */ - public static boolean hasPictureInPicturePermissionEnabled(@NonNull final Context context) { - final AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return appOps != null && appOps.unsafeCheckOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED; - } else { - //noinspection deprecation - return appOps != null && appOps.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, android.os.Process.myUid(), context.getPackageName()) == AppOpsManager.MODE_ALLOWED; - } - } + /** Returns {@code true} if picture in picture permission is enabled in app settings. */ + public static boolean hasPictureInPicturePermissionEnabled(@NonNull final Context context) { + final AppOpsManager appOps = + (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return appOps != null + && appOps.unsafeCheckOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + android.os.Process.myUid(), + context.getPackageName()) + == AppOpsManager.MODE_ALLOWED; + } else { + //noinspection deprecation + return appOps != null + && appOps.checkOpNoThrow( + AppOpsManager.OPSTR_PICTURE_IN_PICTURE, + android.os.Process.myUid(), + context.getPackageName()) + == AppOpsManager.MODE_ALLOWED; + } + } - /** - * Launches picture in picture permission settings screen. - * Note: this method should be called only when a device correctly supports picture in picture mode. - */ - public static void launchPictureInPictureSettings(@NonNull final Context context) { - context.startActivity(new Intent("android.settings.PICTURE_IN_PICTURE_SETTINGS", Uri.parse(String.format("package:%s",context.getPackageName())))); - } + /** + * Launches picture in picture permission settings screen. Note: this method should be called + * only when a device correctly supports picture in picture mode. + */ + public static void launchPictureInPictureSettings(@NonNull final Context context) { + context.startActivity( + new Intent( + "android.settings.PICTURE_IN_PICTURE_SETTINGS", + Uri.parse(String.format("package:%s", context.getPackageName())))); + } - /** - * Returns {code true} if a device supports picture in picture mode. - */ - public static boolean deviceSupportedPictureInPictureMode(@NonNull final Context context) { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); - } + /** Returns {code true} if a device supports picture in picture mode. */ + public static boolean deviceSupportedPictureInPictureMode(@NonNull final Context context) { + return context.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } } diff --git a/android/java/org/chromium/chrome/browser/util/TabUtils.java b/android/java/org/chromium/chrome/browser/util/TabUtils.java index 0147b89d1270..f09fe15b9119 100644 --- a/android/java/org/chromium/chrome/browser/util/TabUtils.java +++ b/android/java/org/chromium/chrome/browser/util/TabUtils.java @@ -351,12 +351,12 @@ public static int getTransition(Tab tab) { } /** - * Returns {@code true} when current tab URL matches the URL of a - * YouTube video. Current tab or current tab URL might be {@code null} - * if the model is not initialized yet. + * Returns {@code true} when current tab URL matches the URL of a YouTube video. Current tab or + * current tab URL might be {@code null} if the model is not initialized yet. + * * @param currentTab Current tab holding the URL to check. - * @return {@code true} if the current tab URL matches the URL of a - * YouTube video, {@code false} otherwise. + * @return {@code true} if the current tab URL matches the URL of a YouTube video, {@code false} + * otherwise. */ public static boolean isYouTubeVideo(@Nullable final Tab currentTab) { if (currentTab == null || currentTab.getUrl() == null) { diff --git a/android/java/org/chromium/chrome/browser/youtube/YouTubeFeaturesLayout.java b/android/java/org/chromium/chrome/browser/youtube/YouTubeFeaturesLayout.java index 7e72b3edff17..c0fb16265d5e 100644 --- a/android/java/org/chromium/chrome/browser/youtube/YouTubeFeaturesLayout.java +++ b/android/java/org/chromium/chrome/browser/youtube/YouTubeFeaturesLayout.java @@ -19,119 +19,128 @@ import org.chromium.ui.base.ViewUtils; /** - * YouTube layout extending a linear layout used to draw - * extra buttons to switch into fullscreen or picture in picture - * mode. - * The layout needs to be instantiated programmatically and will - * take care of drawing two buttons with rounded corners to each side. - * The buttons will provide a consistent ripple effect that will not go over their boundaries - * and a custom outline for the elevation effect. + * YouTube layout extending a linear layout used to draw extra buttons to switch into fullscreen or + * picture in picture mode. The layout needs to be instantiated programmatically and will take care + * of drawing two buttons with rounded corners to each side. The buttons will provide a consistent + * ripple effect that will not go over their boundaries and a custom outline for the elevation + * effect. */ @SuppressLint("ViewConstructor") public class YouTubeFeaturesLayout extends LinearLayout { - public interface Callback { - void onFullscreenClick(); - void onPictureInPictureClick(); - } - - private static final float PADDING_DP = 8f; - private static final float ELEVATION_DP = 2f; - - private static final int AUTO_HIDE_MS = (int) (2.5 * 1000); - private static final int SHOW_DELAY_MS = 350; - - @NonNull - private final Callback mCallback; - @NonNull - private final Runnable mShowRunnable; - @NonNull - private final Runnable mHideRunnable; - - public YouTubeFeaturesLayout(@NonNull final Context context, @NonNull final Callback callback) { - super(context); - mCallback = callback; - setId(R.id.youtube_features_layout); - setOrientation(HORIZONTAL); - // Initial visibility set as GONE by default. - super.setVisibility(View.GONE); - setBackgroundResource(R.drawable.youtube_features_background); - final int elevationPx = ViewUtils.dpToPx(context, ELEVATION_DP); - setElevation(elevationPx); - // Enable outline clipping to render a nice elevation effect. - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - // Implement a custom shadow based on the background provided with rounded - // corners having a radius equals to half of the view's height. - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight()/2f); - } - }); - - final int paddingPx = ViewUtils.dpToPx(context, PADDING_DP); - - final ImageButton fullscreenButton = new ImageButton(context); - initLayoutButton(fullscreenButton, paddingPx, R.drawable.fullscreen_on, R.drawable.youtube_button_ripple_left); - - final ImageButton pictureInPictureButton = new ImageButton(context); - initLayoutButton(pictureInPictureButton, paddingPx, R.drawable.picture_in_picture, R.drawable.youtube_button_ripple_right); - - fullscreenButton.setOnClickListener(v -> mCallback.onFullscreenClick()); - pictureInPictureButton.setOnClickListener(v -> mCallback.onPictureInPictureClick()); - - addView(fullscreenButton); - addView(pictureInPictureButton); - - mShowRunnable = () -> super.setVisibility(View.VISIBLE); - mHideRunnable = () -> super.setVisibility(View.GONE); - } - - private void initLayoutButton(@NonNull final ImageButton imageButton, - final int paddingPx, - @DrawableRes int drawableRes, - @DrawableRes final int rippleEffectDrawableRes) { - imageButton.setImageResource(drawableRes); - imageButton.setBackgroundResource(0); - imageButton.setPadding(paddingPx, paddingPx, paddingPx, paddingPx); - // Set a custom foreground to provide a ripple effect that is consistent with the - // button shape and does not go over its boundaries. - imageButton.setForeground(ContextCompat.getDrawable(imageButton.getContext(), rippleEffectDrawableRes)); - - // Set layout weight to 0.5 to give each button half of the layout width. - final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 0.5f); - params.setMargins(0, 0, 0, 0); - imageButton.setLayoutParams(params); - } - - @Override - public void setVisibility(final int visibility) { - if (visibility == View.VISIBLE) { - // Remove any pending callbacks to hide the layout. - removeCallbacks(mHideRunnable); - - // Execute the runnable after 2.5 seconds. - postDelayed(mHideRunnable, AUTO_HIDE_MS); - // Add a small delay before showing the layout. - postDelayed(mShowRunnable, SHOW_DELAY_MS); - } else { - // Remove any pending callback to hide and show the layout. - removeCallbacks(mHideRunnable); - removeCallbacks(mShowRunnable); - super.setVisibility(visibility); - } - } - - public void setVisibility(@NonNull final Tab tab) { - if (!tab.isHidden()) { - setVisibility(TabUtils.isYouTubeVideo(tab) ? View.VISIBLE : View.GONE); - } - } - - public void setVisibility(@Nullable final Tab tab, final boolean show) { - if (!TabUtils.isYouTubeVideo(tab)) { - setVisibility(View.GONE); - return; - } - setVisibility(show ? View.VISIBLE : View.GONE); - } + public interface Callback { + void onFullscreenClick(); + + void onPictureInPictureClick(); + } + + private static final float PADDING_DP = 8f; + private static final float ELEVATION_DP = 2f; + + private static final int AUTO_HIDE_MS = (int) (2.5 * 1000); + private static final int SHOW_DELAY_MS = 350; + + @NonNull private final Callback mCallback; + @NonNull private final Runnable mShowRunnable; + @NonNull private final Runnable mHideRunnable; + + public YouTubeFeaturesLayout(@NonNull final Context context, @NonNull final Callback callback) { + super(context); + mCallback = callback; + setId(R.id.youtube_features_layout); + setOrientation(HORIZONTAL); + // Initial visibility set as GONE by default. + super.setVisibility(View.GONE); + setBackgroundResource(R.drawable.youtube_features_background); + final int elevationPx = ViewUtils.dpToPx(context, ELEVATION_DP); + setElevation(elevationPx); + // Enable outline clipping to render a nice elevation effect. + setClipToOutline(true); + setOutlineProvider( + new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + // Implement a custom shadow based on the background provided with rounded + // corners having a radius equals to half of the view's height. + outline.setRoundRect( + 0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2f); + } + }); + + final int paddingPx = ViewUtils.dpToPx(context, PADDING_DP); + + final ImageButton fullscreenButton = new ImageButton(context); + initLayoutButton( + fullscreenButton, + paddingPx, + R.drawable.fullscreen_on, + R.drawable.youtube_button_ripple_left); + + final ImageButton pictureInPictureButton = new ImageButton(context); + initLayoutButton( + pictureInPictureButton, + paddingPx, + R.drawable.picture_in_picture, + R.drawable.youtube_button_ripple_right); + + fullscreenButton.setOnClickListener(v -> mCallback.onFullscreenClick()); + pictureInPictureButton.setOnClickListener(v -> mCallback.onPictureInPictureClick()); + + addView(fullscreenButton); + addView(pictureInPictureButton); + + mShowRunnable = () -> super.setVisibility(View.VISIBLE); + mHideRunnable = () -> super.setVisibility(View.GONE); + } + + private void initLayoutButton( + @NonNull final ImageButton imageButton, + final int paddingPx, + @DrawableRes int drawableRes, + @DrawableRes final int rippleEffectDrawableRes) { + imageButton.setImageResource(drawableRes); + imageButton.setBackgroundResource(0); + imageButton.setPadding(paddingPx, paddingPx, paddingPx, paddingPx); + // Set a custom foreground to provide a ripple effect that is consistent with the + // button shape and does not go over its boundaries. + imageButton.setForeground( + ContextCompat.getDrawable(imageButton.getContext(), rippleEffectDrawableRes)); + + // Set layout weight to 0.5 to give each button half of the layout width. + final LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 0.5f); + params.setMargins(0, 0, 0, 0); + imageButton.setLayoutParams(params); + } + + @Override + public void setVisibility(final int visibility) { + if (visibility == View.VISIBLE) { + // Remove any pending callbacks to hide the layout. + removeCallbacks(mHideRunnable); + + // Execute the runnable after 2.5 seconds. + postDelayed(mHideRunnable, AUTO_HIDE_MS); + // Add a small delay before showing the layout. + postDelayed(mShowRunnable, SHOW_DELAY_MS); + } else { + // Remove any pending callback to hide and show the layout. + removeCallbacks(mHideRunnable); + removeCallbacks(mShowRunnable); + super.setVisibility(visibility); + } + } + + public void setVisibility(@NonNull final Tab tab) { + if (!tab.isHidden()) { + setVisibility(TabUtils.isYouTubeVideo(tab) ? View.VISIBLE : View.GONE); + } + } + + public void setVisibility(@Nullable final Tab tab, final boolean show) { + if (!TabUtils.isYouTubeVideo(tab)) { + setVisibility(View.GONE); + return; + } + setVisibility(show ? View.VISIBLE : View.GONE); + } } diff --git a/browser/sources.gni b/browser/sources.gni index 16aa5074e121..d01b500738da 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -394,11 +394,11 @@ if (is_android) { "//brave/browser/ntp_background/android", "//brave/build/android:jni_headers", "//brave/components/brave_sync:sync_service_impl_helper", - "//chrome/android:jni_headers", - "//components/sync_device_info", "//brave/components/youtube_script_injector/browser/content", "//brave/components/youtube_script_injector/browser/core", "//brave/components/youtube_script_injector/common", + "//chrome/android:jni_headers", + "//components/sync_device_info", ] } else { brave_chrome_browser_sources += [ diff --git a/components/youtube_script_injector/browser/content/youtube_tab_helper.cc b/components/youtube_script_injector/browser/content/youtube_tab_helper.cc index e2af3a022030..5daebbe5c6ce 100644 --- a/components/youtube_script_injector/browser/content/youtube_tab_helper.cc +++ b/components/youtube_script_injector/browser/content/youtube_tab_helper.cc @@ -113,10 +113,12 @@ void YouTubeTabHelper::DidFinishNavigation( blink::mojom::UserActivationOption::kDoNotActivate)); } -void YouTubeTabHelper::DidToggleFullscreenModeForTab(bool entered_fullscreen, - bool /*is_user_initiated*/) { +void YouTubeTabHelper::DidToggleFullscreenModeForTab( + bool entered_fullscreen, + bool /*is_user_initiated*/) { JNIEnv* env = base::android::AttachCurrentThread(); - Java_BackgroundVideoPlaybackTabHelper_showYouTubeFeaturesLayout(env, !entered_fullscreen); + Java_BackgroundVideoPlaybackTabHelper_showYouTubeFeaturesLayout( + env, !entered_fullscreen); } void YouTubeTabHelper::MediaStartedPlaying( diff --git a/components/youtube_script_injector/browser/content/youtube_tab_helper.h b/components/youtube_script_injector/browser/content/youtube_tab_helper.h index dcce3e3c8f69..447e2434a51d 100644 --- a/components/youtube_script_injector/browser/content/youtube_tab_helper.h +++ b/components/youtube_script_injector/browser/content/youtube_tab_helper.h @@ -55,8 +55,8 @@ class COMPONENT_EXPORT(YOUTUBE_SCRIPT_INJECTOR_BROWSER_CONTENT) YouTubeTabHelper friend class content::WebContentsUserData; // Override fullscreen toggle notification. - void DidToggleFullscreenModeForTab(bool entered_fullscreen, - bool /*is_user_initiated*/) override; + void DidToggleFullscreenModeForTab(bool entered_fullscreen, + bool /*is_user_initiated*/) override; // content::WebContentsObserver overrides void DidFinishNavigation( diff --git a/components/youtube_script_injector/browser/core/youtube_component_installer.cc b/components/youtube_script_injector/browser/core/youtube_component_installer.cc index e7a0d171c331..aed19cafe9ec 100644 --- a/components/youtube_script_injector/browser/core/youtube_component_installer.cc +++ b/components/youtube_script_injector/browser/core/youtube_component_installer.cc @@ -39,7 +39,13 @@ constexpr size_t kHashSize = 32; constexpr char kYouTubeComponentName[] = "Brave YouTube Injector"; constexpr char kYouTubeComponentId[] = "ninjlighifanhiflenpeafpnanjemako"; constexpr char kYouTubeComponentBase64PublicKey[] = - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnwhDV1BTzTeDNsi+DxnyxrKqugj/a/8dKQE8usNweMkpORdxFxflbJkA5IErDyW3xca63pdMYhArPjHr0OUBACYKBfzEYF/Yo0kNxxJLWylr7FKwZxOKosmlZXUW8J2wtFu8ua6oQB2lEOk7y2jxwUAka/a0JPxeDsU9ByadmAdVZ7aaiCEGsJgEtHrpPha/2AJi8xerYLeB4bFfFc2bdXKEsN4UzjVLYpCfsKAuxEtO2QC2+Rylz60Vq3ANjjnlXINDJrl3jsSwF6GXHEE4Qzl7Qvu142N9G2rsSxMyPlunD8EGrEjK9fGRc6RPGQy1LbyY3imXpiDp0vXicCaYuwIDAQAB"; + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnwhDV1BTzTeDNsi+DxnyxrKqugj/a/" + "8dKQE8usNweMkpORdxFxflbJkA5IErDyW3xca63pdMYhArPjHr0OUBACYKBfzEYF/" + "Yo0kNxxJLWylr7FKwZxOKosmlZXUW8J2wtFu8ua6oQB2lEOk7y2jxwUAka/" + "a0JPxeDsU9ByadmAdVZ7aaiCEGsJgEtHrpPha/" + "2AJi8xerYLeB4bFfFc2bdXKEsN4UzjVLYpCfsKAuxEtO2QC2+" + "Rylz60Vq3ANjjnlXINDJrl3jsSwF6GXHEE4Qzl7Qvu142N9G2rsSxMyPlunD8EGrEjK9fGRc6R" + "PGQy1LbyY3imXpiDp0vXicCaYuwIDAQAB"; } // namespace