Previously, we show settings's udfps enroll animation view (the fingerprint icon and progress view) once the FingerprintEnrollEnrolling is shown. However, touch events have to wait for systemui's udfps overlay to be valid. This CL lets settings's udfps enroll view wait for systemui's overlay. 1. Sets udfps enroll animation view's default visibility Gone. 2. Propagates FingerprintManager#onUdfpsOverlayShown to FingerprintEnrollEnrolling and when it's called, set the enroll view visible. Besides, this CL renames onPointerDown() and onPointerUp() with Udfps. Bug: 280718879 Test: atest FingerprintEnrollEnrollingTest Change-Id: Ieed3e74c182828918785edcacb021f19a3665f2a
237 lines
9.6 KiB
Java
237 lines
9.6 KiB
Java
/*
|
|
* Copyright (C) 2023 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.settings.biometrics.fingerprint;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Point;
|
|
import android.graphics.Rect;
|
|
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
|
import android.text.TextUtils;
|
|
import android.util.AttributeSet;
|
|
import android.view.DisplayInfo;
|
|
import android.view.Gravity;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.accessibility.AccessibilityManager;
|
|
import android.widget.Button;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
|
|
import androidx.annotation.ColorInt;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
import com.android.settings.R;
|
|
import com.android.settingslib.udfps.UdfpsOverlayParams;
|
|
import com.android.settingslib.udfps.UdfpsUtils;
|
|
|
|
import com.google.android.setupcompat.template.FooterBarMixin;
|
|
import com.google.android.setupdesign.GlifLayout;
|
|
import com.google.android.setupdesign.view.BottomScrollView;
|
|
|
|
import java.util.Locale;
|
|
|
|
/**
|
|
* View for udfps enrolling.
|
|
*/
|
|
public class UdfpsEnrollEnrollingView extends GlifLayout {
|
|
private final UdfpsUtils mUdfpsUtils;
|
|
private final Context mContext;
|
|
// We don't need to listen to onConfigurationChanged() for mRotation here because
|
|
// FingerprintEnrollEnrolling is always recreated once the configuration is changed.
|
|
private final int mRotation;
|
|
private final boolean mIsLandscape;
|
|
private final boolean mShouldUseReverseLandscape;
|
|
private UdfpsEnrollView mUdfpsEnrollView;
|
|
private View mHeaderView;
|
|
private AccessibilityManager mAccessibilityManager;
|
|
|
|
|
|
public UdfpsEnrollEnrollingView(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
mContext = context;
|
|
mRotation = mContext.getDisplay().getRotation();
|
|
mIsLandscape = mRotation == Surface.ROTATION_90 || mRotation == Surface.ROTATION_270;
|
|
final boolean isLayoutRtl = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
|
|
== View.LAYOUT_DIRECTION_RTL);
|
|
mShouldUseReverseLandscape = (mRotation == Surface.ROTATION_90 && isLayoutRtl)
|
|
|| (mRotation == Surface.ROTATION_270 && !isLayoutRtl);
|
|
|
|
mUdfpsUtils = new UdfpsUtils();
|
|
}
|
|
|
|
@Override
|
|
protected void onFinishInflate() {
|
|
super.onFinishInflate();
|
|
mHeaderView = findViewById(R.id.sud_landscape_header_area);
|
|
mUdfpsEnrollView = findViewById(R.id.udfps_animation_view);
|
|
}
|
|
|
|
void initView(FingerprintSensorPropertiesInternal udfpsProps,
|
|
UdfpsEnrollHelper udfpsEnrollHelper,
|
|
AccessibilityManager accessibilityManager) {
|
|
mAccessibilityManager = accessibilityManager;
|
|
initUdfpsEnrollView(udfpsProps, udfpsEnrollHelper);
|
|
|
|
if (!mIsLandscape) {
|
|
adjustPortraitPaddings();
|
|
} else if (mShouldUseReverseLandscape) {
|
|
swapHeaderAndContent();
|
|
}
|
|
setOnHoverListener();
|
|
}
|
|
|
|
void setSecondaryButtonBackground(@ColorInt int color) {
|
|
// Set the button background only when the button is not under udfps overlay to avoid UI
|
|
// overlap.
|
|
if (!mIsLandscape || mShouldUseReverseLandscape) {
|
|
return;
|
|
}
|
|
final Button secondaryButtonView =
|
|
getMixin(FooterBarMixin.class).getSecondaryButtonView();
|
|
secondaryButtonView.setBackgroundColor(color);
|
|
if (mRotation == Surface.ROTATION_90) {
|
|
secondaryButtonView.setGravity(Gravity.START);
|
|
} else {
|
|
secondaryButtonView.setGravity(Gravity.END);
|
|
}
|
|
mHeaderView.post(() -> {
|
|
secondaryButtonView.setLayoutParams(
|
|
new LinearLayout.LayoutParams(mHeaderView.getMeasuredWidth(),
|
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
|
});
|
|
}
|
|
|
|
private void initUdfpsEnrollView(FingerprintSensorPropertiesInternal udfpsProps,
|
|
UdfpsEnrollHelper udfpsEnrollHelper) {
|
|
DisplayInfo displayInfo = new DisplayInfo();
|
|
mContext.getDisplay().getDisplayInfo(displayInfo);
|
|
|
|
final float scaleFactor = mUdfpsUtils.getScaleFactor(displayInfo);
|
|
Rect udfpsBounds = udfpsProps.getLocation().getRect();
|
|
udfpsBounds.scale(scaleFactor);
|
|
|
|
final Rect overlayBounds = new Rect(
|
|
0, /* left */
|
|
displayInfo.getNaturalHeight() / 2, /* top */
|
|
displayInfo.getNaturalWidth(), /* right */
|
|
displayInfo.getNaturalHeight() /* botom */);
|
|
|
|
UdfpsOverlayParams params = new UdfpsOverlayParams(
|
|
udfpsBounds,
|
|
overlayBounds,
|
|
displayInfo.getNaturalWidth(),
|
|
displayInfo.getNaturalHeight(),
|
|
scaleFactor,
|
|
displayInfo.rotation);
|
|
|
|
mUdfpsEnrollView.setOverlayParams(params);
|
|
mUdfpsEnrollView.setEnrollHelper(udfpsEnrollHelper);
|
|
}
|
|
|
|
private void adjustPortraitPaddings() {
|
|
// In the portrait mode, layout_container's height is 0, so it's
|
|
// always shown at the bottom of the screen.
|
|
final FrameLayout portraitLayoutContainer = findViewById(R.id.layout_container);
|
|
|
|
// In the portrait mode, the title and lottie animation view may
|
|
// overlap when title needs three lines, so adding some paddings
|
|
// between them, and adjusting the fp progress view here accordingly.
|
|
final int layoutLottieAnimationPadding = (int) getResources()
|
|
.getDimension(R.dimen.udfps_lottie_padding_top);
|
|
portraitLayoutContainer.setPadding(0,
|
|
layoutLottieAnimationPadding, 0, 0);
|
|
final ImageView progressView = mUdfpsEnrollView.findViewById(
|
|
R.id.udfps_enroll_animation_fp_progress_view);
|
|
progressView.setPadding(0, -(layoutLottieAnimationPadding),
|
|
0, layoutLottieAnimationPadding);
|
|
final ImageView fingerprintView = mUdfpsEnrollView.findViewById(
|
|
R.id.udfps_enroll_animation_fp_view);
|
|
fingerprintView.setPadding(0, -layoutLottieAnimationPadding,
|
|
0, layoutLottieAnimationPadding);
|
|
|
|
// TODO(b/260970216) Instead of hiding the description text view, we should
|
|
// make the header view scrollable if the text is too long.
|
|
// If description text view has overlap with udfps progress view, hide it.
|
|
final View descView = getDescriptionTextView();
|
|
getViewTreeObserver().addOnDrawListener(() -> {
|
|
if (descView.getVisibility() == View.VISIBLE
|
|
&& hasOverlap(descView, mUdfpsEnrollView)) {
|
|
descView.setVisibility(View.GONE);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void setOnHoverListener() {
|
|
if (!mAccessibilityManager.isEnabled()) return;
|
|
|
|
final View.OnHoverListener onHoverListener = (v, event) -> {
|
|
// Map the touch to portrait mode if the device is in
|
|
// landscape mode.
|
|
final Point scaledTouch =
|
|
mUdfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0),
|
|
event, mUdfpsEnrollView.getOverlayParams());
|
|
|
|
if (mUdfpsUtils.isWithinSensorArea(event.getPointerId(0), event,
|
|
mUdfpsEnrollView.getOverlayParams())) {
|
|
return false;
|
|
}
|
|
|
|
final String theStr = mUdfpsUtils.onTouchOutsideOfSensorArea(
|
|
mAccessibilityManager.isTouchExplorationEnabled(), mContext,
|
|
scaledTouch.x, scaledTouch.y, mUdfpsEnrollView.getOverlayParams());
|
|
if (theStr != null) {
|
|
v.announceForAccessibility(theStr);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
findManagedViewById(mIsLandscape ? R.id.sud_landscape_content_area
|
|
: R.id.sud_layout_content).setOnHoverListener(onHoverListener);
|
|
}
|
|
|
|
private void swapHeaderAndContent() {
|
|
// Reverse header and body
|
|
ViewGroup parentView = (ViewGroup) mHeaderView.getParent();
|
|
parentView.removeView(mHeaderView);
|
|
parentView.addView(mHeaderView);
|
|
|
|
// Hide scroll indicators
|
|
BottomScrollView headerScrollView = mHeaderView.findViewById(R.id.sud_header_scroll_view);
|
|
headerScrollView.setScrollIndicators(0);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
boolean hasOverlap(View view1, View view2) {
|
|
int[] firstPosition = new int[2];
|
|
int[] secondPosition = new int[2];
|
|
|
|
view1.getLocationOnScreen(firstPosition);
|
|
view2.getLocationOnScreen(secondPosition);
|
|
|
|
// Rect constructor parameters: left, top, right, bottom
|
|
Rect rectView1 = new Rect(firstPosition[0], firstPosition[1],
|
|
firstPosition[0] + view1.getMeasuredWidth(),
|
|
firstPosition[1] + view1.getMeasuredHeight());
|
|
Rect rectView2 = new Rect(secondPosition[0], secondPosition[1],
|
|
secondPosition[0] + view2.getMeasuredWidth(),
|
|
secondPosition[1] + view2.getMeasuredHeight());
|
|
return rectView1.intersect(rectView2);
|
|
}
|
|
}
|