script-astra/Android/Sdk/sources/android-35/android/inputmethodservice/navigationbar/NavigationBarView.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

387 lines
14 KiB
Java

/*
* Copyright (C) 2022 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 android.inputmethodservice.navigationbar;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET;
import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import java.util.function.Consumer;
/**
* @hide
*/
public final class NavigationBarView extends FrameLayout {
private static final boolean DEBUG = false;
private static final String TAG = "NavBarView";
// Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN
private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
// The current view is always mHorizontal.
View mCurrentView = null;
private View mHorizontal;
private int mCurrentRotation = -1;
int mDisabledFlags = 0;
int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT;
private final int mNavBarMode = NAV_BAR_MODE_GESTURAL;
private KeyButtonDrawable mBackIcon;
private KeyButtonDrawable mImeSwitcherIcon;
private Context mLightContext;
private final int mLightIconColor;
private final int mDarkIconColor;
private final android.inputmethodservice.navigationbar.DeadZone mDeadZone;
private boolean mDeadZoneConsuming = false;
private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
private Configuration mConfiguration;
private Configuration mTmpLastConfiguration;
private NavigationBarInflaterView mNavigationInflaterView;
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mLightContext = context;
mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE;
mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE;
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back,
new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back));
mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher,
new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher));
mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle,
new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle));
mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
getBackButton().setLongClickable(false);
final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
imeSwitchButton.setLongClickable(false);
imeSwitchButton.setOnClickListener(view -> view.getContext()
.getSystemService(InputMethodManager.class).showInputMethodPicker());
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
shouldDeadZoneConsumeTouchEvents(event);
return super.onTouchEvent(event);
}
private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
mDeadZoneConsuming = false;
}
if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
switch (action) {
case MotionEvent.ACTION_DOWN:
mDeadZoneConsuming = true;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mDeadZoneConsuming = false;
break;
}
return true;
}
return false;
}
public View getCurrentView() {
return mCurrentView;
}
/**
* Applies {@code consumer} to each of the nav bar views.
*/
public void forEachView(Consumer<View> consumer) {
if (mHorizontal != null) {
consumer.accept(mHorizontal);
}
}
public ButtonDispatcher getBackButton() {
return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back);
}
public ButtonDispatcher getImeSwitchButton() {
return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher);
}
public ButtonDispatcher getHomeHandle() {
return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle);
}
public SparseArray<ButtonDispatcher> getButtonDispatchers() {
return mButtonDispatchers;
}
private void reloadNavIcons() {
updateIcons(Configuration.EMPTY);
}
private void updateIcons(Configuration oldConfig) {
final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
final boolean dirChange =
oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
if (densityChange || dirChange) {
mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher);
}
if (orientationChange || densityChange || dirChange) {
mBackIcon = getBackDrawable();
}
}
private KeyButtonDrawable getBackDrawable() {
KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back);
orientBackButton(drawable);
return drawable;
}
/**
* @return whether this nav bar mode is edge to edge
*/
public static boolean isGesturalMode(int mode) {
return mode == NAV_BAR_MODE_GESTURAL;
}
private void orientBackButton(KeyButtonDrawable drawable) {
final boolean useAltBack =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
if (drawable.getRotation() == degrees) {
return;
}
if (isGesturalMode(mNavBarMode)) {
drawable.setRotation(degrees);
return;
}
// Animate the back button's rotation to the new degrees and only in portrait move up the
// back button to line up with the other buttons
float targetY = useAltBack
? -dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources())
: 0;
ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN);
navBarAnimator.setDuration(200);
navBarAnimator.start();
}
private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon,
true /* hasShadow */, null /* ovalBackgroundColor */);
}
@Override
public void setLayoutDirection(int layoutDirection) {
reloadNavIcons();
super.setLayoutDirection(layoutDirection);
}
/**
* Updates the navigation icons based on {@code hints}.
*
* @param hints bit flags defined in {@link StatusBarManager}.
*/
public void setNavigationIconHints(int hints) {
if (hints == mNavigationIconHints) return;
final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
final boolean oldBackAlt =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
if (newBackAlt != oldBackAlt) {
//onImeVisibilityChanged(newBackAlt);
}
if (DEBUG) {
android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500)
.show();
}
mNavigationIconHints = hints;
updateNavButtonIcons();
}
private void updateNavButtonIcons() {
// We have to replace or restore the back and home button icons when exiting or entering
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
KeyButtonDrawable backIcon = mBackIcon;
orientBackButton(backIcon);
getBackButton().setImageDrawable(backIcon);
getImeSwitchButton().setImageDrawable(mImeSwitcherIcon);
// Update IME button visibility, a11y and rotate button always overrides the appearance
final boolean imeSwitcherVisible =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0;
getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE);
getBackButton().setVisibility(View.VISIBLE);
getHomeHandle().setVisibility(View.INVISIBLE);
// We used to be reporting the touch regions via notifyActiveTouchRegions() here.
// TODO(b/215593010): Consider taking care of this in the Launcher side.
}
private Display getContextDisplay() {
return getContext().getDisplay();
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
updateOrientationViews();
reloadNavIcons();
}
@Override
protected void onDraw(Canvas canvas) {
mDeadZone.onDraw(canvas);
super.onDraw(canvas);
}
private void updateOrientationViews() {
mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal);
updateCurrentView();
}
private void updateCurrentView() {
resetViews();
mCurrentView = mHorizontal;
mCurrentView.setVisibility(View.VISIBLE);
mCurrentRotation = getContextDisplay().getRotation();
mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
mNavigationInflaterView.updateButtonDispatchersCurrentView();
}
private void resetViews() {
mHorizontal.setVisibility(View.GONE);
}
private void reorient() {
updateCurrentView();
final android.inputmethodservice.navigationbar.NavigationBarFrame frame =
getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame);
frame.setDeadZone(mDeadZone);
mDeadZone.onConfigurationChanged(mCurrentRotation);
if (DEBUG) {
Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
}
// Resolve layout direction if not resolved since components changing layout direction such
// as changing languages will recreate this view and the direction will be resolved later
if (!isLayoutDirectionResolved()) {
resolveLayoutDirection();
}
updateNavButtonIcons();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mTmpLastConfiguration.updateFrom(mConfiguration);
final int changes = mConfiguration.updateFrom(newConfig);
updateIcons(mTmpLastConfiguration);
if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
|| mTmpLastConfiguration.getLayoutDirection()
!= mConfiguration.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
updateNavButtonIcons();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// This needs to happen first as it can changed the enabled state which can affect whether
// the back button is visible
requestApplyInsets();
reorient();
updateNavButtonIcons();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
for (int i = 0; i < mButtonDispatchers.size(); ++i) {
mButtonDispatchers.valueAt(i).onDestroy();
}
}
/**
* Updates the dark intensity.
*
* @param intensity The intensity of darkness from {@code 0.0f} to {@code 1.0f}.
*/
public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) {
for (int i = 0; i < mButtonDispatchers.size(); ++i) {
mButtonDispatchers.valueAt(i).setDarkIntensity(intensity);
}
}
}