blob: 5d036fbe5e522edee00f1fad94b5e0adfe8c44c2 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.
*/
package com.android.systemui.shared.rotation;
import android.annotation.DimenRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
import android.annotation.StringRes;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import androidx.annotation.BoolRes;
import androidx.core.view.OneShotPreDrawListener;
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
/**
* Containing logic for the rotation button on the physical left bottom corner of the screen.
*/
public class FloatingRotationButton implements RotationButton {
private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
private final WindowManager mWindowManager;
private final ViewGroup mKeyButtonContainer;
private final FloatingRotationButtonView mKeyButtonView;
private int mContainerSize;
private final Context mContext;
@StringRes
private final int mContentDescriptionResource;
@DimenRes
private final int mMinMarginResource;
@DimenRes
private final int mRoundedContentPaddingResource;
@DimenRes
private final int mTaskbarLeftMarginResource;
@DimenRes
private final int mTaskbarBottomMarginResource;
@DimenRes
private final int mButtonDiameterResource;
@BoolRes
private final int mFloatingRotationBtnPositionLeftResource;
private AnimatedVectorDrawable mAnimatedDrawable;
private boolean mIsShowing;
private boolean mCanShow = true;
private int mDisplayRotation;
private boolean mIsTaskbarVisible = false;
private boolean mIsTaskbarStashed = false;
private FloatingRotationButtonPositionCalculator mPositionCalculator;
private RotationButtonController mRotationButtonController;
private RotationButtonUpdatesCallback mUpdatesCallback;
private Position mPosition;
public FloatingRotationButton(Context context, @StringRes int contentDescriptionResource,
@LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin,
@DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin,
@DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
@DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
mWindowManager = context.getSystemService(WindowManager.class);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null);
mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
mKeyButtonView.setVisibility(View.VISIBLE);
mKeyButtonView.setContentDescription(context.getString(contentDescriptionResource));
mKeyButtonView.setRipple(rippleMaxWidth);
mContext = context;
mContentDescriptionResource = contentDescriptionResource;
mMinMarginResource = minMargin;
mRoundedContentPaddingResource = roundedContentPadding;
mTaskbarLeftMarginResource = taskbarLeftMargin;
mTaskbarBottomMarginResource = taskbarBottomMargin;
mButtonDiameterResource = buttonDiameter;
mFloatingRotationBtnPositionLeftResource = floatingRotationBtnPositionLeftResource;
updateDimensionResources();
}
private void updateDimensionResources() {
Resources res = mContext.getResources();
int defaultMargin = Math.max(
res.getDimensionPixelSize(mMinMarginResource),
res.getDimensionPixelSize(mRoundedContentPaddingResource));
int taskbarMarginLeft =
res.getDimensionPixelSize(mTaskbarLeftMarginResource);
int taskbarMarginBottom =
res.getDimensionPixelSize(mTaskbarBottomMarginResource);
boolean floatingRotationButtonPositionLeft =
res.getBoolean(mFloatingRotationBtnPositionLeftResource);
mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin,
taskbarMarginLeft, taskbarMarginBottom, floatingRotationButtonPositionLeft);
final int diameter = res.getDimensionPixelSize(mButtonDiameterResource);
mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft,
taskbarMarginBottom));
}
@Override
public void setRotationButtonController(RotationButtonController rotationButtonController) {
mRotationButtonController = rotationButtonController;
updateIcon(mRotationButtonController.getLightIconColor(),
mRotationButtonController.getDarkIconColor());
}
@Override
public void setUpdatesCallback(RotationButtonUpdatesCallback updatesCallback) {
mUpdatesCallback = updatesCallback;
}
@Override
public View getCurrentView() {
return mKeyButtonView;
}
@Override
public boolean show() {
if (!mCanShow || mIsShowing) {
return false;
}
mIsShowing = true;
final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
mWindowManager.addView(mKeyButtonContainer, layoutParams);
if (mAnimatedDrawable != null) {
mAnimatedDrawable.reset();
mAnimatedDrawable.start();
}
// Notify about visibility only after first traversal so we can properly calculate
// the touch region for the button
OneShotPreDrawListener.add(mKeyButtonView, () -> {
if (mIsShowing && mUpdatesCallback != null) {
mUpdatesCallback.onVisibilityChanged(true);
}
});
return true;
}
@Override
public boolean hide() {
if (!mIsShowing) {
return false;
}
mWindowManager.removeViewImmediate(mKeyButtonContainer);
mIsShowing = false;
if (mUpdatesCallback != null) {
mUpdatesCallback.onVisibilityChanged(false);
}
return true;
}
@Override
public boolean isVisible() {
return mIsShowing;
}
@Override
public void updateIcon(int lightIconColor, int darkIconColor) {
mAnimatedDrawable = (AnimatedVectorDrawable) mKeyButtonView.getContext()
.getDrawable(mRotationButtonController.getIconResId());
mKeyButtonView.setImageDrawable(mAnimatedDrawable);
mKeyButtonView.setColors(lightIconColor, darkIconColor);
}
@Override
public void setOnClickListener(View.OnClickListener onClickListener) {
mKeyButtonView.setOnClickListener(onClickListener);
}
@Override
public void setOnHoverListener(View.OnHoverListener onHoverListener) {
mKeyButtonView.setOnHoverListener(onHoverListener);
}
@Override
public Drawable getImageDrawable() {
return mAnimatedDrawable;
}
@Override
public void setDarkIntensity(float darkIntensity) {
mKeyButtonView.setDarkIntensity(darkIntensity);
}
@Override
public void setCanShowRotationButton(boolean canShow) {
mCanShow = canShow;
if (!mCanShow) {
hide();
}
}
@Override
public void onTaskbarStateChanged(boolean taskbarVisible, boolean taskbarStashed) {
mIsTaskbarVisible = taskbarVisible;
mIsTaskbarStashed = taskbarStashed;
if (!mIsShowing) return;
final Position newPosition = mPositionCalculator
.calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
if (newPosition.getTranslationX() != mPosition.getTranslationX()
|| newPosition.getTranslationY() != mPosition.getTranslationY()) {
updateTranslation(newPosition, /* animate */ true);
mPosition = newPosition;
}
}
/**
* Updates resources that could be changed in runtime, should be called on configuration
* change with changes diff integer mask
* @param configurationChanges - configuration changes with flags from ActivityInfo e.g.
* {@link android.content.pm.ActivityInfo#CONFIG_DENSITY}
*/
public void onConfigurationChanged(@Config int configurationChanges) {
if ((configurationChanges & ActivityInfo.CONFIG_DENSITY) != 0
|| (configurationChanges & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) {
updateDimensionResources();
if (mIsShowing) {
final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams();
mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams);
}
}
if ((configurationChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
mKeyButtonView.setContentDescription(mContext.getString(mContentDescriptionResource));
}
}
private LayoutParams adjustViewPositionAndCreateLayoutParams() {
final LayoutParams lp = new LayoutParams(
mContainerSize,
mContainerSize,
/* xpos */ 0, /* ypos */ 0, LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("FloatingRotationButton");
lp.setFitInsetsTypes(/* types */ 0);
mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation();
mPosition = mPositionCalculator
.calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed);
lp.gravity = mPosition.getGravity();
((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity =
mPosition.getGravity();
updateTranslation(mPosition, /* animate */ false);
return lp;
}
private void updateTranslation(Position position, boolean animate) {
final int translationX = position.getTranslationX();
final int translationY = position.getTranslationY();
if (animate) {
mKeyButtonView
.animate()
.translationX(translationX)
.translationY(translationY)
.setDuration(MARGIN_ANIMATION_DURATION_MILLIS)
.setInterpolator(new AccelerateDecelerateInterpolator())
.withEndAction(() -> {
if (mUpdatesCallback != null && mIsShowing) {
mUpdatesCallback.onPositionChanged();
}
})
.start();
} else {
mKeyButtonView.setTranslationX(translationX);
mKeyButtonView.setTranslationY(translationY);
}
}
}