Skip to content

Commit

Permalink
[M106][CCTBrand] Add rotating animation for security icon
Browse files Browse the repository at this point in the history
Add cross-fade + rotation animation for Branding icon. This transition is only used when CCT Branding is enabled.

Recording see: crbug.com/1338307#c14

(cherry picked from commit fc82f58)

Bug: 1338307
Change-Id: I9039ee895c8a119ff94a4ac018a61786afc24ef0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3837173
Reviewed-by: Theresa Sullivan <twellington@chromium.org>
Commit-Queue: Wenyu Fu <wenyufu@chromium.org>
Reviewed-by: Patrick Noland <pnoland@chromium.org>
Cr-Original-Commit-Position: refs/heads/main@{#1041287}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3877644
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Auto-Submit: Wenyu Fu <wenyufu@chromium.org>
Commit-Queue: Theresa Sullivan <twellington@chromium.org>
Cr-Commit-Position: refs/branch-heads/5249@{#326}
Cr-Branched-From: 4f7bea5-refs/heads/main@{#1036826}
  • Loading branch information
fwy423 authored and Chromium LUCI CQ committed Sep 6, 2022
1 parent 2192f88 commit 45cdcd4
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 3 deletions.
1 change: 1 addition & 0 deletions chrome/android/chrome_java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingDecision.java",
"java/src/org/chromium/chrome/browser/customtabs/features/branding/SharedPreferencesBrandingTimeStorage.java",
"java/src/org/chromium/chrome/browser/customtabs/features/branding/ToolbarBrandingDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrandingSecurityButtonAnimationDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabBrowserControlsVisibilityDelegate.java",
"java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java",
"java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarAnimationDelegate.java",
Expand Down
1 change: 1 addition & 0 deletions chrome/android/chrome_junit_test_java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/customtabs/features/ImmersiveModeControllerTest.java",
"junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingCheckerUnitTest.java",
"junit/src/org/chromium/chrome/browser/customtabs/features/branding/BrandingControllerUnitTest.java",
"junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrandingSecurityButtonAnimationDelegateUnitTest.java",
"junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java",
"junit/src/org/chromium/chrome/browser/customtabs/shadows/ShadowExternalNavigationDelegateImpl.java",
"junit/src/org/chromium/chrome/browser/directactions/FindInPageDirectActionHandlerTest.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.customtabs.features.toolbar;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RotateDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.view.View;
import android.widget.ImageView;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.components.browser_ui.widget.animation.Interpolators;

/**
* Animation Delegate for CCT Toolbar security icon. Show a cross-fade + rotation
* transitioning from an existing icon to a new icon resource. The transition animation is
* referencing {@link org.chromium.chrome.browser.omnibox.status.StatusView}.<br/><br/>
*
* <div>
* <p>
* How does the rotation security animation work?
* </p>
* 0. The rotation transition only works when image view is visible and displaying a |existing
* drawable|, and needs to be updated to a new |target drawable|;<br/>
* 1. The |existing drawable| will perform a 180-degree rotation from <b>regular position</b>, at
* the same time opacity transitioning from 100% -> 0%;<br/>
* 2. The |target drawable| will start perform a 180-degree rotation from <b>up-side-down</b>, at
* the same time opacity transitioning from 0% -> 100%.
* </div>
*/
// TODO(https://crbug.com/1354675): Share more code with StatusView.java.
class BrandingSecurityButtonAnimationDelegate {
public static final int ICON_ANIMATION_DURATION_MS = 250;
private static final int ICON_ROTATION_DEGREES = 180;

private boolean mIsAnimationInProgress;

/** The current drawable resource set through {@link #updateDrawableResource(int)} */
private @DrawableRes int mCurrentDrawableResource;
private final ImageView mImageView;

/**
* The animation delegate that will apply a rotation transition for image view.
* @param imageView The image view that the animation will performed on.
*/
BrandingSecurityButtonAnimationDelegate(@NonNull ImageView imageView) {
mImageView = imageView;
}

/**
* Update the image view into a new drawable resource with a rotation transition, if the
* image view is visible and has a drawable showing; otherwise, no transition will be applied.
* @param newResourceId The new drawable resource image view will get updated into.
*/
void updateDrawableResource(@DrawableRes int newResourceId) {
if (mCurrentDrawableResource == newResourceId) return;
mCurrentDrawableResource = newResourceId;

if (mImageView.getVisibility() == View.VISIBLE && mImageView.getDrawable() != null) {
updateWithTransitionalDrawable(newResourceId);
} else {
mImageView.setImageResource(newResourceId);
}
}

private void updateWithTransitionalDrawable(int resourceId) {
if (mIsAnimationInProgress) {
resetAnimationStatus();
}

Drawable targetDrawable = ApiCompatibilityUtils.getDrawable(
mImageView.getContext().getResources(), resourceId);
Drawable existingDrawable = mImageView.getDrawable();

// If the drawable is a transitional drawable, this means previous transition is in place.
// We should start the transition with the previous target drawable.
if (existingDrawable instanceof TransitionDrawable
&& ((TransitionDrawable) existingDrawable).getNumberOfLayers() == 2) {
existingDrawable = ((TransitionDrawable) existingDrawable).getDrawable(1);
}

// 1. Add padding to the smaller drawable so it has the same size as the bigger drawable.
// This is necessary to maintain the original icon size, otherwise TransitionDrawable will
// scale the smaller drawable to be at the same size as larger drawable;
// 2. Convert the drawable to Bitmap drawable. This is necessary for the cross fade to work
// for the TransitionDrawable.
Resources resources = mImageView.getResources();
int targetX =
Math.max(targetDrawable.getIntrinsicWidth(), existingDrawable.getIntrinsicWidth());
int targetY = Math.max(
targetDrawable.getIntrinsicHeight(), existingDrawable.getIntrinsicHeight());
targetDrawable = resizeToBitmapDrawable(resources, targetDrawable, targetX, targetY);
existingDrawable = resizeToBitmapDrawable(resources, existingDrawable, targetX, targetY);
TransitionDrawable transitionDrawable = new TransitionDrawable(
new Drawable[] {existingDrawable, getRotatedIcon(targetDrawable)});
transitionDrawable.setCrossFadeEnabled(true);
mImageView.setImageDrawable(transitionDrawable);

mIsAnimationInProgress = true;
mImageView.animate()
.setDuration(ICON_ANIMATION_DURATION_MS)
.rotationBy(ICON_ROTATION_DEGREES)
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR)
.withStartAction(
() -> transitionDrawable.startTransition(ICON_ANIMATION_DURATION_MS))
.withEndAction(() -> {
mIsAnimationInProgress = false;
mImageView.setRotation(0);
// Only update security icon if it is still the current icon.
if (mCurrentDrawableResource == resourceId) {
mImageView.setImageResource(resourceId);
}
})
.start();
}

private void resetAnimationStatus() {
mIsAnimationInProgress = false;
if (mImageView.getDrawable() instanceof TransitionDrawable) {
((TransitionDrawable) mImageView.getDrawable()).resetTransition();
}
}

/**
* Add padding around the |drawable| until |targetWidth| and |targetHeight|, and convert it
* to a {@link BitmapDrawable}.
*/
@VisibleForTesting
static BitmapDrawable resizeToBitmapDrawable(Resources resource, @NonNull Drawable drawable,
int targetWidth, int targetHeight) throws IllegalArgumentException {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (height > targetHeight || width > targetWidth) {
throw new IllegalArgumentException(
"The input drawable has a larger size than the target width / height.");
}

Bitmap bitmap = Bitmap.createBitmap(targetWidth, targetHeight, Config.ARGB_8888);
Canvas c = new Canvas(bitmap);

int paddingX = (targetWidth - width) / 2;
int paddingY = (targetHeight - height) / 2;
drawable.setBounds(paddingX, paddingY, targetWidth - paddingX, targetHeight - paddingY);
drawable.draw(c);

return new BitmapDrawable(resource, bitmap);
}

/** Returns a rotated version of the icon passed in. */
// TODO(https://crbug.com/1354675): Share more code with StatusView.java.
private static Drawable getRotatedIcon(Drawable icon) {
RotateDrawable rotated = new RotateDrawable();
rotated.setDrawable(icon);
rotated.setToDegrees(ICON_ROTATION_DEGREES);
rotated.setLevel(10000); // Max value for #setLevel.
return rotated;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,8 @@ public void showBrandingLocationBar() {
// align with the security icon.
setUrlBarHiddenIgnoreBranding(false);
setShowTitleIgnoreBranding(false);

mAnimDelegate.setUseRotationSecurityButtonTransition(true);
showBrandingIconAndText();
}

Expand All @@ -747,6 +749,7 @@ public void showRegularToolbar() {
mCurrentlyShowingBranding = false;
recoverFromRegularState();
runAfterBrandingRunnables();
mAnimDelegate.setUseRotationSecurityButtonTransition(false);

int token = mBrowserControlsVisibilityDelegate.showControlsPersistent();
PostTask.postDelayedTask(UiThreadTaskTraits.USER_VISIBLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import org.chromium.ui.interpolators.BakedBezierInterpolator;

/**
* A delegate class to handle the title animation and security icon fading animation in
* A delegate class to handle the title animation and security icon animation in
* {@link CustomTabToolbar}.
* <p>
* How does the title animation work?
Expand All @@ -29,14 +29,21 @@
* exactly the same as before the relayout. (Note the scale factor is calculated based on
* {@link TextView#getTextSize()}, not height or width.) 3. Finally the urlbar will be animated to
* its new position.
*
* <p>
* How does the security button animation work?
* </p>
* See {@link SecurityButtonAnimationDelegate} and {@link BrandingSecurityButtonAnimationDelegate}.
*/
class CustomTabToolbarAnimationDelegate {
private final SecurityButtonAnimationDelegate mSecurityButtonAnimationDelegate;
private final BrandingSecurityButtonAnimationDelegate mBrandingAnimationDelegate;

private TextView mUrlBar;
private TextView mTitleBar;
// A flag controlling whether the animation has run before.
private boolean mShouldRunTitleAnimation;
private boolean mUseRotationTransition;

/**
* Constructs an instance of {@link CustomTabToolbarAnimationDelegate}.
Expand All @@ -48,6 +55,7 @@ class CustomTabToolbarAnimationDelegate {
titleUrlContainer.setTranslationX(-securityButtonWidth);
mSecurityButtonAnimationDelegate = new SecurityButtonAnimationDelegate(
securityButton, titleUrlContainer, securityStatusIconSize);
mBrandingAnimationDelegate = new BrandingSecurityButtonAnimationDelegate(securityButton);
}

/**
Expand Down Expand Up @@ -132,7 +140,15 @@ public void onAnimationEnd(Animator animation) {
* When this is null, the icon is animated to the left and faded out.
*/
void updateSecurityButton(int securityIconResource) {
mSecurityButtonAnimationDelegate.updateSecurityButton(
securityIconResource, /*animate=*/true);
if (mUseRotationTransition) {
mBrandingAnimationDelegate.updateDrawableResource(securityIconResource);
} else {
mSecurityButtonAnimationDelegate.updateSecurityButton(
securityIconResource, /*animate=*/true);
}
}

void setUseRotationSecurityButtonTransition(boolean useRotation) {
mUseRotationTransition = useRotation;
}
}

0 comments on commit 45cdcd4

Please sign in to comment.