| /* |
| * 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; |
| } |
| } |