blob: 31f014c27a139f970d806b34281f15322984af7c [file] [log] [blame]
/*
* 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.settingslib.udfps;
import android.content.Context;
import android.graphics.Point;
import android.util.DisplayUtils;
import android.util.Log;
import android.util.RotationUtils;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.Surface;
import com.android.settingslib.R;
/** Utility class for working with udfps. */
public class UdfpsUtils {
private static final String TAG = "UdfpsUtils";
/**
* Gets the scale factor representing the user's current resolution / the stable (default)
* resolution.
*
* @param displayInfo The display information.
*/
public float getScaleFactor(DisplayInfo displayInfo) {
Display.Mode maxDisplayMode =
DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes);
float scaleFactor =
DisplayUtils.getPhysicalPixelDisplaySizeRatio(
maxDisplayMode.getPhysicalWidth(),
maxDisplayMode.getPhysicalHeight(),
displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight()
);
return (scaleFactor == Float.POSITIVE_INFINITY) ? 1f : scaleFactor;
}
/**
* Gets the touch in native coordinates. Map the touch to portrait mode if the device is in
* landscape mode.
*
* @param idx The pointer identifier.
* @param event The MotionEvent object containing full information about the event.
* @param udfpsOverlayParams The [UdfpsOverlayParams] used.
* @return The mapped touch event.
*/
public Point getTouchInNativeCoordinates(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
Point portraitTouch = getPortraitTouch(idx, event, udfpsOverlayParams);
// Scale the coordinates to native resolution.
float scale = udfpsOverlayParams.getScaleFactor();
portraitTouch.x = (int) (portraitTouch.x / scale);
portraitTouch.y = (int) (portraitTouch.y / scale);
return portraitTouch;
}
/**
* @param idx The pointer identifier.
* @param event The MotionEvent object containing full information about the event.
* @param udfpsOverlayParams The [UdfpsOverlayParams] used.
* @return Whether the touch event is within sensor area.
*/
public boolean isWithinSensorArea(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
Point portraitTouch = getPortraitTouch(idx, event, udfpsOverlayParams);
return udfpsOverlayParams.getSensorBounds().contains(portraitTouch.x, portraitTouch.y);
}
/**
* This function computes the angle of touch relative to the sensor and maps the angle to a list
* of help messages which are announced if accessibility is enabled.
*
* @return Whether the announcing string is null
*/
public String onTouchOutsideOfSensorArea(boolean touchExplorationEnabled, Context context,
int scaledTouchX, int scaledTouchY, UdfpsOverlayParams udfpsOverlayParams) {
if (!touchExplorationEnabled) {
return null;
}
String[] touchHints = context.getResources().getStringArray(
R.array.udfps_accessibility_touch_hints);
if (touchHints.length != 4) {
Log.e(TAG, "expected exactly 4 touch hints, got " + touchHints.length + "?");
return null;
}
// Scale the coordinates to native resolution.
float scale = udfpsOverlayParams.getScaleFactor();
float scaledSensorX = udfpsOverlayParams.getSensorBounds().centerX() / scale;
float scaledSensorY = udfpsOverlayParams.getSensorBounds().centerY() / scale;
String theStr =
onTouchOutsideOfSensorAreaImpl(
touchHints,
scaledTouchX,
scaledTouchY,
scaledSensorX,
scaledSensorY,
udfpsOverlayParams.getRotation()
);
Log.v(TAG, "Announcing touch outside : $theStr");
return theStr;
}
/**
* This function computes the angle of touch relative to the sensor and maps the angle to a list
* of help messages which are announced if accessibility is enabled.
*
* There are 4 quadrants of the circle (90 degree arcs)
*
* [315, 360] && [0, 45) -> touchHints[0] = "Move Fingerprint to the left" [45, 135) ->
* touchHints[1] = "Move Fingerprint down" And so on.
*/
private String onTouchOutsideOfSensorAreaImpl(String[] touchHints, float touchX,
float touchY, float sensorX, float sensorY, int rotation) {
float xRelativeToSensor = touchX - sensorX;
// Touch coordinates are with respect to the upper left corner, so reverse
// this calculation
float yRelativeToSensor = sensorY - touchY;
double angleInRad = Math.atan2(yRelativeToSensor, xRelativeToSensor);
// If the radians are negative, that means we are counting clockwise.
// So we need to add 360 degrees
if (angleInRad < 0.0) {
angleInRad += 2.0 * Math.PI;
}
// rad to deg conversion
double degrees = Math.toDegrees(angleInRad);
double degreesPerBucket = 360.0 / touchHints.length;
double halfBucketDegrees = degreesPerBucket / 2.0;
// The mapping should be as follows
// [315, 360] && [0, 45] -> 0
// [45, 135] -> 1
int index = (int) ((degrees + halfBucketDegrees) % 360 / degreesPerBucket);
index %= touchHints.length;
// A rotation of 90 degrees corresponds to increasing the index by 1.
if (rotation == Surface.ROTATION_90) {
index = (index + 1) % touchHints.length;
}
if (rotation == Surface.ROTATION_270) {
index = (index + 3) % touchHints.length;
}
return touchHints[index];
}
/**
* Map the touch to portrait mode if the device is in landscape mode.
*/
private Point getPortraitTouch(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
Point portraitTouch = new Point((int) event.getRawX(idx), (int) event.getRawY(idx));
int rot = udfpsOverlayParams.getRotation();
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
RotationUtils.rotatePoint(
portraitTouch,
RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
udfpsOverlayParams.getLogicalDisplayWidth(),
udfpsOverlayParams.getLogicalDisplayHeight()
);
}
return portraitTouch;
}
}