public final class

WearableButtons

extends java.lang.Object

 java.lang.Object

↳androidx.wear.input.WearableButtons

Gradle dependencies

compile group: 'androidx.wear', name: 'wear-input', version: '1.2.0-alpha02'

  • groupId: androidx.wear
  • artifactId: wear-input
  • version: 1.2.0-alpha02

Artifact androidx.wear:wear-input:1.2.0-alpha02 it located at Google repository (https://maven.google.com/)

Overview

Class containing helpers for managing wearable buttons.

Summary

Fields
public static final intLOC_BOTTOM_CENTER

Represents the center third of the bottom side on a square device.

public static final intLOC_BOTTOM_LEFT

Represents the left third of the bottom side on a square device.

public static final intLOC_BOTTOM_RIGHT

Represents the right third of the bottom side on a square device.

public static final intLOC_EAST

Represents the east position on a round device.

public static final intLOC_ENE

Represents the east-northeast position on a round device.

public static final intLOC_ESE

Represents the east-southeast position on a round device.

public static final intLOC_LEFT_BOTTOM

Represents the bottom third of the left side on a square device.

public static final intLOC_LEFT_CENTER

Represents the center third of the left side on a square device.

public static final intLOC_LEFT_TOP

Represents the top third of the left side on a square device.

public static final intLOC_NE

Represents the northeast position on a round device.

public static final intLOC_NNE

Represents the north-northeast position on a round device.

public static final intLOC_NNW

Represents the north-northwest position on a round device.

public static final intLOC_NORTH

Represents the north position on a round device.

public static final intLOC_NW

Represents the northwest position on a round device.

public static final intLOC_RIGHT_BOTTOM

Represents the bottom third of the right side on a square device.

public static final intLOC_RIGHT_CENTER

Represents the center third of the right side on a square device.

public static final intLOC_RIGHT_TOP

Represents the top third of the right side on a square device.

public static final intLOC_SE

Represents the southeast position on a round device.

public static final intLOC_SOUTH

Represents the south position on a round device.

public static final intLOC_SSE

Represents the south-southeast position on a round device.

public static final intLOC_SSW

Represents the south-southwest position on a round device.

public static final intLOC_SW

Represents the southwest position on a round device.

public static final intLOC_TOP_CENTER

Represents the center third of the top side on a square device.

public static final intLOC_TOP_LEFT

Represents the left third of the top side on a square device.

public static final intLOC_TOP_RIGHT

Represents the right third of the top side on a square device.

public static final intLOC_UNKNOWN

Represents that the location zone is unknown.

public static final intLOC_WEST

Represents the west position on a round device.

public static final intLOC_WNW

Represents the west-northwest position on a round device.

public static final intLOC_WSW

Represents the west-southwest position on a round device.

Methods
public static intgetButtonCount(Context context)

Get the number of hardware buttons available.

public static DrawablegetButtonIcon(Context context, int keycode)

Returns an icon that can be used to represent the location of a button.

public static WearableButtons.ButtonInfogetButtonInfo(Context context, int keycode)

Returns a WearableButtons.ButtonInfo containing the metadata for a specific button.

public static java.lang.CharSequencegetButtonLabel(Context context, int keycode)

Returns a CharSequence that describes the placement location of a button.

public static voidsetWearableButtonsProvider(WearableButtonsProvider provider)

Testing call to allow the underlying WearableButtonsProvider to be substituted in test code.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int LOC_UNKNOWN

Represents that the location zone is unknown.

public static final int LOC_EAST

Represents the east position on a round device.

public static final int LOC_ENE

Represents the east-northeast position on a round device.

public static final int LOC_NE

Represents the northeast position on a round device.

public static final int LOC_NNE

Represents the north-northeast position on a round device.

public static final int LOC_NORTH

Represents the north position on a round device.

public static final int LOC_NNW

Represents the north-northwest position on a round device.

public static final int LOC_NW

Represents the northwest position on a round device.

public static final int LOC_WNW

Represents the west-northwest position on a round device.

public static final int LOC_WEST

Represents the west position on a round device.

public static final int LOC_WSW

Represents the west-southwest position on a round device.

public static final int LOC_SW

Represents the southwest position on a round device.

public static final int LOC_SSW

Represents the south-southwest position on a round device.

public static final int LOC_SOUTH

Represents the south position on a round device.

public static final int LOC_SSE

Represents the south-southeast position on a round device.

public static final int LOC_SE

Represents the southeast position on a round device.

public static final int LOC_ESE

Represents the east-southeast position on a round device.

public static final int LOC_TOP_RIGHT

Represents the right third of the top side on a square device.

public static final int LOC_TOP_CENTER

Represents the center third of the top side on a square device.

public static final int LOC_TOP_LEFT

Represents the left third of the top side on a square device.

public static final int LOC_LEFT_TOP

Represents the top third of the left side on a square device.

public static final int LOC_LEFT_CENTER

Represents the center third of the left side on a square device.

public static final int LOC_LEFT_BOTTOM

Represents the bottom third of the left side on a square device.

public static final int LOC_BOTTOM_LEFT

Represents the left third of the bottom side on a square device.

public static final int LOC_BOTTOM_CENTER

Represents the center third of the bottom side on a square device.

public static final int LOC_BOTTOM_RIGHT

Represents the right third of the bottom side on a square device.

public static final int LOC_RIGHT_BOTTOM

Represents the bottom third of the right side on a square device.

public static final int LOC_RIGHT_CENTER

Represents the center third of the right side on a square device.

public static final int LOC_RIGHT_TOP

Represents the top third of the right side on a square device.

Methods

public static void setWearableButtonsProvider(WearableButtonsProvider provider)

Testing call to allow the underlying WearableButtonsProvider to be substituted in test code.

Parameters:

provider: The new WearableButtonsProvider to use.

public static WearableButtons.ButtonInfo getButtonInfo(Context context, int keycode)

Returns a WearableButtons.ButtonInfo containing the metadata for a specific button.

The location will be populated in the following manner:

  • The provided point will be on the screen, or more typically, on the edge of the screen.
  • The point won't be off the edge of the screen.
  • The location returned is a screen coordinate. The unit of measurement is in pixels. The coordinates do not take rotation into account and assume that the device is in the standard upright position.

Common keycodes to use are , , , and .

Parameters:

context: The context of the current activity
keycode: The keycode associated with the hardware button of interest

Returns:

A WearableButtons.ButtonInfo containing the metadata for the given keycode or null if the information is not available

public static int getButtonCount(Context context)

Get the number of hardware buttons available. This count includes the primary stem key as well as any secondary stem keys available.

Parameters:

context: The context of the current activity

Returns:

The number of buttons available, or the information is not available.

public static Drawable getButtonIcon(Context context, int keycode)

Returns an icon that can be used to represent the location of a button.

Parameters:

context: The context of the current activity
keycode: The keycode associated with the hardware button of interest

Returns:

A drawable representing the location of a button, or null if unavailable

public static java.lang.CharSequence getButtonLabel(Context context, int keycode)

Returns a CharSequence that describes the placement location of a button. An example might be "Top right" or "Bottom".

Parameters:

context: The context of the current activity
keycode: The keycode associated with the hardware button of interest

Returns:

A CharSequence describing the placement location of the button, or null if no location is available for that button.

Source

/*
 * Copyright 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 androidx.wear.input;

import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RotateDrawable;
import android.os.Bundle;
import android.provider.Settings;
import android.view.Surface;
import android.view.WindowManager;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;

import com.google.android.wearable.input.WearableInputDevice;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/** Class containing helpers for managing wearable buttons. */
public final class WearableButtons {

    private static WearableButtonsProvider sButtonsProvider = new DeviceWearableButtonsProvider();

    /** Represents that the count of available device buttons. */
    private static volatile int sButtonCount = -1;

    private WearableButtons() {
        throw new RuntimeException("WearableButtons should not be instantiated");
    }

    /**
     * Testing call to allow the underlying {@link WearableButtonsProvider} to be substituted in
     * test code.
     *
     * @param provider The new {@link WearableButtonsProvider} to use.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
    public static void setWearableButtonsProvider(@NonNull WearableButtonsProvider provider) {
        sButtonsProvider = provider;
    }

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @IntDef({
            LOC_UNKNOWN,
            LOC_EAST,
            LOC_ENE,
            LOC_NE,
            LOC_NNE,
            LOC_NORTH,
            LOC_NNW,
            LOC_NW,
            LOC_WNW,
            LOC_WEST,
            LOC_WSW,
            LOC_SW,
            LOC_SSW,
            LOC_SOUTH,
            LOC_SSE,
            LOC_SE,
            LOC_ESE,
            LOC_TOP_RIGHT,
            LOC_TOP_CENTER,
            LOC_TOP_LEFT,
            LOC_LEFT_TOP,
            LOC_LEFT_CENTER,
            LOC_LEFT_BOTTOM,
            LOC_BOTTOM_LEFT,
            LOC_BOTTOM_CENTER,
            LOC_BOTTOM_RIGHT,
            LOC_RIGHT_BOTTOM,
            LOC_RIGHT_CENTER,
            LOC_RIGHT_TOP
    })
    public @interface ButtonLocation {}

    /** Represents that the location zone is unknown. */
    public static final int LOC_UNKNOWN = -1;

    /** Represents the east position on a round device. */
    public static final int LOC_EAST = 0;

    /** Represents the east-northeast position on a round device. */
    public static final int LOC_ENE = 1;

    /** Represents the northeast position on a round device. */
    public static final int LOC_NE = 2;

    /** Represents the north-northeast position on a round device. */
    public static final int LOC_NNE = 3;

    /** Represents the north position on a round device. */
    public static final int LOC_NORTH = 4;

    /** Represents the north-northwest position on a round device. */
    public static final int LOC_NNW = 5;

    /** Represents the northwest position on a round device. */
    public static final int LOC_NW = 6;

    /** Represents the west-northwest position on a round device. */
    public static final int LOC_WNW = 7;

    /** Represents the west position on a round device. */
    public static final int LOC_WEST = 8;

    /** Represents the west-southwest position on a round device. */
    public static final int LOC_WSW = 9;

    /** Represents the southwest position on a round device. */
    public static final int LOC_SW = 10;

    /** Represents the south-southwest position on a round device. */
    public static final int LOC_SSW = 11;

    /** Represents the south position on a round device. */
    public static final int LOC_SOUTH = 12;

    /** Represents the south-southeast position on a round device. */
    public static final int LOC_SSE = 13;

    /** Represents the southeast position on a round device. */
    public static final int LOC_SE = 14;

    /** Represents the east-southeast position on a round device. */
    public static final int LOC_ESE = 15;

    private static final int LOC_ROUND_COUNT = 16;

    /** Represents the right third of the top side on a square device. */
    public static final int LOC_TOP_RIGHT = 100;

    /** Represents the center third of the top side on a square device. */
    public static final int LOC_TOP_CENTER = 101;

    /** Represents the left third of the top side on a square device. */
    public static final int LOC_TOP_LEFT = 102;

    /** Represents the top third of the left side on a square device. */
    public static final int LOC_LEFT_TOP = 103;

    /** Represents the center third of the left side on a square device. */
    public static final int LOC_LEFT_CENTER = 104;

    /** Represents the bottom third of the left side on a square device. */
    public static final int LOC_LEFT_BOTTOM = 105;

    /** Represents the left third of the bottom side on a square device. */
    public static final int LOC_BOTTOM_LEFT = 106;

    /** Represents the center third of the bottom side on a square device. */
    public static final int LOC_BOTTOM_CENTER = 107;

    /** Represents the right third of the bottom side on a square device. */
    public static final int LOC_BOTTOM_RIGHT = 108;

    /** Represents the bottom third of the right side on a square device. */
    public static final int LOC_RIGHT_BOTTOM = 109;

    /** Represents the center third of the right side on a square device. */
    public static final int LOC_RIGHT_CENTER = 110;

    /** Represents the top third of the right side on a square device. */
    public static final int LOC_RIGHT_TOP = 111;

    /**
     * Key used with the bundle returned by {@link #getButtonInfo}} to retrieve the x coordinate of
     * a button when the screen is rotated 180 degrees. (temporary copy from WearableInputDevice)
     */
    private static final String X_KEY_ROTATED = "x_key_rotated";

    /**
     * Key used with the bundle returned by {@link #getButtonInfo}} to retrieve the y coordinate of
     * a button when the screen is rotated 180 degrees. (temporary copy from WearableInputDevice)
     */
    private static final String Y_KEY_ROTATED = "y_key_rotated";

    /**
     * Returns a {@link ButtonInfo} containing the metadata for a specific button.
     *
     * <p>The location will be populated in the following manner:
     *
     * <ul>
     *   <li>The provided point will be on the screen, or more typically, on the edge of the screen.
     *   <li>The point won't be off the edge of the screen.
     *   <li>The location returned is a screen coordinate. The unit of measurement is in pixels. The
     *       coordinates do not take rotation into account and assume that the device is in the
     *       standard upright position.
     * </ul>
     *
     * <p>Common keycodes to use are {@link android.view.KeyEvent#KEYCODE_STEM_PRIMARY}, {@link
     * android.view.KeyEvent#KEYCODE_STEM_1}, {@link android.view.KeyEvent#KEYCODE_STEM_2}, and
     * {@link android.view.KeyEvent#KEYCODE_STEM_3}.
     *
     * @param context The context of the current activity
     * @param keycode The keycode associated with the hardware button of interest
     * @return A {@link ButtonInfo} containing the metadata for the given keycode or null if the
     *     information is not available
     */
    @SuppressWarnings("deprecation")
    @Nullable
    public static ButtonInfo getButtonInfo(@NonNull Context context, int keycode) {
        Bundle bundle = sButtonsProvider.getButtonInfo(context, keycode);

        if (bundle == null) {
            return null;
        }

        // If the information is not available, return null
        if (!bundle.containsKey(WearableInputDevice.X_KEY)
                || !bundle.containsKey(WearableInputDevice.Y_KEY)) {
            return null;
        }

        float screenLocationX = bundle.getFloat(WearableInputDevice.X_KEY);
        float screenLocationY = bundle.getFloat(WearableInputDevice.Y_KEY);

        // Get the screen size for the locationZone
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point screenSize = new Point();
        wm.getDefaultDisplay().getSize(screenSize);

        if (isLeftyModeEnabled(context)) {
            // By default, the rotated placement is exactly the opposite.
            // This may be overridden if there is a remapping of buttons applied as well.
            float screenRotatedX = screenSize.x - screenLocationX;
            float screenRotatedY = screenSize.y - screenLocationY;

            if (bundle.containsKey(X_KEY_ROTATED) && bundle.containsKey(Y_KEY_ROTATED)) {
                screenRotatedX = bundle.getFloat(X_KEY_ROTATED);
                screenRotatedY = bundle.getFloat(Y_KEY_ROTATED);
            }

            screenLocationX = screenRotatedX;
            screenLocationY = screenRotatedY;
        }

        boolean isRound = context.getResources().getConfiguration().isScreenRound();

        ButtonInfo info =
                new ButtonInfo(
                        keycode,
                        screenLocationX,
                        screenLocationY,
                        getLocationZone(isRound, screenSize, screenLocationX, screenLocationY));

        return info;
    }

    /**
     * Get the number of hardware buttons available. This count includes the primary stem key as
     * well as any secondary stem keys available.
     *
     * @param context The context of the current activity
     * @return The number of buttons available, or the information is not available.
     */
    public static int getButtonCount(@NonNull Context context) {
        if (sButtonCount == -1) {
            int[] buttonCodes = sButtonsProvider.getAvailableButtonKeyCodes(context);

            if (buttonCodes == null) {
                return -1;
            }

            sButtonCount = buttonCodes.length;
        }

        return sButtonCount;
    }

    /**
     * Returns an icon that can be used to represent the location of a button.
     *
     * @param context The context of the current activity
     * @param keycode The keycode associated with the hardware button of interest
     * @return A drawable representing the location of a button, or null if unavailable
     */
    @Nullable
    public static Drawable getButtonIcon(@NonNull Context context, int keycode) {
        ButtonInfo info = getButtonInfo(context, keycode);
        if (info == null) {
            return null;
        }
        return getButtonIconFromLocationZone(context, info.getLocationZone());
    }

    @VisibleForTesting
    static RotateDrawable getButtonIconFromLocationZone(
            Context context, @ButtonLocation int locationZone) {
        // To save memory for assets, we are using 4 icons to represent the 20+ possible
        // configurations.  These 4 base icons can be rotated to fit any configuration needed.
        // id is the drawable id for the base icon
        // degrees is the number of degrees the icon needs to be rotated to match the wanted
        // position
        int id;
        int degrees;

        switch (locationZone) {
                // Round constants
            case LOC_EAST:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = 0;
                break;
            case LOC_ENE:
            case LOC_NE:
            case LOC_NNE:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = -45;
                break;
            case LOC_NORTH:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = -90;
                break;
            case LOC_NNW:
            case LOC_NW:
            case LOC_WNW:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = -135;
                break;
            case LOC_WEST:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = 180;
                break;
            case LOC_WSW:
            case LOC_SW:
            case LOC_SSW:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = 135;
                break;
            case LOC_SOUTH:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = 90;
                break;
            case LOC_SSE:
            case LOC_SE:
            case LOC_ESE:
                id = R.drawable.ic_cc_settings_button_e;
                degrees = 45;
                break;

                // Rectangular constants
            case LOC_LEFT_TOP:
                id = R.drawable.ic_cc_settings_button_bottom;
                degrees = 180;
                break;
            case LOC_LEFT_CENTER:
                id = R.drawable.ic_cc_settings_button_center;
                degrees = 180;
                break;
            case LOC_LEFT_BOTTOM:
                id = R.drawable.ic_cc_settings_button_top;
                degrees = 180;
                break;
            case LOC_RIGHT_TOP:
                id = R.drawable.ic_cc_settings_button_top;
                degrees = 0;
                break;
            case LOC_RIGHT_CENTER:
                id = R.drawable.ic_cc_settings_button_center;
                degrees = 0;
                break;
            case LOC_RIGHT_BOTTOM:
                id = R.drawable.ic_cc_settings_button_bottom;
                degrees = 0;
                break;
            case LOC_TOP_LEFT:
                id = R.drawable.ic_cc_settings_button_top;
                degrees = -90;
                break;
            case LOC_TOP_CENTER:
                id = R.drawable.ic_cc_settings_button_center;
                degrees = -90;
                break;
            case LOC_TOP_RIGHT:
                id = R.drawable.ic_cc_settings_button_bottom;
                degrees = -90;
                break;
            case LOC_BOTTOM_LEFT:
                id = R.drawable.ic_cc_settings_button_bottom;
                degrees = 90;
                break;
            case LOC_BOTTOM_CENTER:
                id = R.drawable.ic_cc_settings_button_center;
                degrees = 90;
                break;
            case LOC_BOTTOM_RIGHT:
                id = R.drawable.ic_cc_settings_button_top;
                degrees = 90;
                break;
            default:
                throw new IllegalArgumentException("Unexpected location zone");
        }
        RotateDrawable rotateIcon = new RotateDrawable();
        rotateIcon.setDrawable(context.getDrawable(id));
        rotateIcon.setFromDegrees(degrees);
        rotateIcon.setToDegrees(degrees);
        rotateIcon.setLevel(1);
        return rotateIcon;
    }

    /**
     * Returns a CharSequence that describes the placement location of a button. An example might be
     * "Top right" or "Bottom".
     *
     * @param context The context of the current activity
     * @param keycode The keycode associated with the hardware button of interest
     * @return A CharSequence describing the placement location of the button, or null if no
     * location is available for that button.
     */
    @Nullable
    public static CharSequence getButtonLabel(@NonNull Context context, int keycode) {
        // 4 length array where the index uses the standard quadrant counting system (minus 1 for
        // 0 index)
        int[] buttonsInQuadrantCount = new int[4];

        // Retrieve ButtonInfo objects and count how many buttons are in a quadrant.  This is only
        // needed for round devices but will help us come up with friendly strings to show to the
        // user.
        // TODO(ahugh): We can cache quadrant counts to optimize. These values should be static for
        // each
        // device.
        int[] buttonCodes = sButtonsProvider.getAvailableButtonKeyCodes(context);

        if (buttonCodes == null) {
            return null;
        }

        for (int key : buttonCodes) {
            ButtonInfo info = getButtonInfo(context, key);

            if (info != null) {
                int quadrantIndex = getQuadrantIndex(info.getLocationZone());
                if (quadrantIndex != -1) {
                    ++buttonsInQuadrantCount[quadrantIndex];
                }
            }
        }

        ButtonInfo info = getButtonInfo(context, keycode);
        int quadrantIndex = (info != null ? getQuadrantIndex(info.getLocationZone()) : -1);
        return info == null
                ? null
                : context.getString(
                        getFriendlyLocationZoneStringId(
                                info.getLocationZone(),
                                (quadrantIndex == -1 ? 0 : buttonsInQuadrantCount[quadrantIndex])));
    }

    /**
     * Returns quadrant index if locationZone is for a round device. Follows the conventional
     * quadrant system with the top right quadrant being 0, incrementing the index by 1 going
     * counter-clockwise around.
     */
    private static int getQuadrantIndex(@ButtonLocation int locationZone) {
        switch (locationZone) {
            case LOC_ENE:
            case LOC_NE:
            case LOC_NNE:
                return 0;
            case LOC_NNW:
            case LOC_NW:
            case LOC_WNW:
                return 1;
            case LOC_WSW:
            case LOC_SW:
            case LOC_SSW:
                return 2;
            case LOC_SSE:
            case LOC_SE:
            case LOC_ESE:
                return 3;

            default:
                return -1;
        }
    }

    /**
     * If the screen is round, there is special logic we use to determine the string that should
     * show on the screen. Simple strings are broad descriptors like "top right". Detailed strings
     * are narrow descriptors like, "top right, upper" 1) If there are exactly 2 buttons in a
     * quadrant, use detailed strings to describe button locations. 2) Otherwise, use simple strings
     * to describe the button locations.
     *
     * @param locationZone The location zone to get a string id for
     * @param buttonsInQuadrantCount The number of buttons in the quadrant of the button
     * @return The string id to use to represent this button zone
     */
    @VisibleForTesting
    static int getFriendlyLocationZoneStringId(
            @ButtonLocation int locationZone, int buttonsInQuadrantCount) {
        if (buttonsInQuadrantCount == 2) {
            switch (locationZone) {
                case LOC_ENE:
                    return R.string.buttons_round_top_right_lower;
                case LOC_NE:
                case LOC_NNE:
                    return R.string.buttons_round_top_right_upper;
                case LOC_NNW:
                case LOC_NW:
                    return R.string.buttons_round_top_left_upper;
                case LOC_WNW:
                    return R.string.buttons_round_top_left_lower;
                case LOC_ESE:
                case LOC_SE:
                    return R.string.buttons_round_bottom_left_upper;
                case LOC_SSE:
                    return R.string.buttons_round_bottom_left_lower;
                case LOC_SSW:
                    return R.string.buttons_round_bottom_right_lower;
                case LOC_SW:
                case LOC_WSW:
                    return R.string.buttons_round_bottom_right_upper;
                default: // fall out
            }
        }

        // If we couldn't find a detailed string, or we need a simple string
        switch (locationZone) {
                // Round constants
            case LOC_EAST:
                return R.string.buttons_round_center_right;
            case LOC_ENE:
            case LOC_NE:
            case LOC_NNE:
                return R.string.buttons_round_top_right;
            case LOC_NORTH:
                return R.string.buttons_round_top_center;
            case LOC_NNW:
            case LOC_NW:
            case LOC_WNW:
                return R.string.buttons_round_top_left;
            case LOC_WEST:
                return R.string.buttons_round_center_left;
            case LOC_WSW:
            case LOC_SW:
            case LOC_SSW:
                return R.string.buttons_round_bottom_left;
            case LOC_SOUTH:
                return R.string.buttons_round_bottom_center;
            case LOC_SSE:
            case LOC_SE:
            case LOC_ESE:
                return R.string.buttons_round_bottom_right;

                // Rectangular constants
            case LOC_LEFT_TOP:
                return R.string.buttons_rect_left_top;
            case LOC_LEFT_CENTER:
                return R.string.buttons_rect_left_center;
            case LOC_LEFT_BOTTOM:
                return R.string.buttons_rect_left_bottom;
            case LOC_RIGHT_TOP:
                return R.string.buttons_rect_right_top;
            case LOC_RIGHT_CENTER:
                return R.string.buttons_rect_right_center;
            case LOC_RIGHT_BOTTOM:
                return R.string.buttons_rect_right_bottom;
            case LOC_TOP_LEFT:
                return R.string.buttons_rect_top_left;
            case LOC_TOP_CENTER:
                return R.string.buttons_rect_top_center;
            case LOC_TOP_RIGHT:
                return R.string.buttons_rect_top_right;
            case LOC_BOTTOM_LEFT:
                return R.string.buttons_rect_bottom_left;
            case LOC_BOTTOM_CENTER:
                return R.string.buttons_rect_bottom_center;
            case LOC_BOTTOM_RIGHT:
                return R.string.buttons_rect_bottom_right;
            default:
                throw new IllegalArgumentException("Unexpected location zone");
        }
    }

    /**
     * For round devices, the location zone is defined using 16 points in a compass arrangement. If
     * a button falls between anchor points, this method will return the closest anchor.
     *
     * <p>For rectangular devices, the location zone is defined by splitting each side into thirds.
     * If a button falls anywhere within a zone, the method will return that zone. The constants for
     * these zones are named LOC_[side in question]_[which third is affected]. E.g. LOC_TOP_RIGHT
     * would refer to the right third of the top side of the device.
     */
    @VisibleForTesting
    /* package */ static int getLocationZone(
            boolean isRound, Point screenSize, float screenLocationX, float screenLocationY) {
        if (screenLocationX == Float.MAX_VALUE || screenLocationY == Float.MAX_VALUE) {
            return LOC_UNKNOWN;
        }

        return isRound
                ? getLocationZoneRound(screenSize, screenLocationX, screenLocationY)
                : getLocationZoneRectangular(screenSize, screenLocationX, screenLocationY);
    }

    private static int getLocationZoneRound(
            Point screenSize, float screenLocationX, float screenLocationY) {
        // Convert screen coordinate to Cartesian coordinate
        float cartesianX = screenLocationX - screenSize.x / 2;
        float cartesianY = screenSize.y / 2 - screenLocationY;

        // Use polar coordinates to figure out which zone the point is in
        double angle = Math.atan2(cartesianY, cartesianX);

        // Convert angle to all positive values
        if (angle < 0) {
            angle += 2 * Math.PI;
        }

        // Return the associated section rounded to the nearest anchor.
        // Using some clever math tricks and enum declaration, we can reduce this calculation
        // down to a single formula that converts angle to enum value.
        return Math.round((float) (angle / (Math.PI / 8))) % LOC_ROUND_COUNT;
    }

    private static int getLocationZoneRectangular(
            Point screenSize, float screenLocationX, float screenLocationY) {
        // Calculate distance to each edge.
        float deltaFromLeft = screenLocationX;
        float deltaFromRight = screenSize.x - screenLocationX;
        float deltaFromTop = screenLocationY;
        float deltaFromBottom = screenSize.y - screenLocationY;
        float minDelta =
                Math.min(
                        deltaFromLeft,
                        Math.min(deltaFromRight, Math.min(deltaFromTop, deltaFromBottom)));

        // Prioritize ties to left and right sides of watch since they're more likely to be placed
        // on the side. Buttons directly on the corner are not accounted for with this API.
        if (minDelta == deltaFromLeft) {
            // Left is the primary side
            switch (whichThird(screenSize.y, screenLocationY)) {
                case 0:
                    return LOC_LEFT_TOP;
                case 1:
                    return LOC_LEFT_CENTER;
                default:
                    return LOC_LEFT_BOTTOM;
            }
        } else if (minDelta == deltaFromRight) {
            // Right is primary side
            switch (whichThird(screenSize.y, screenLocationY)) {
                case 0:
                    return LOC_RIGHT_TOP;
                case 1:
                    return LOC_RIGHT_CENTER;
                default:
                    return LOC_RIGHT_BOTTOM;
            }
        } else if (minDelta == deltaFromTop) {
            // Top is primary side
            switch (whichThird(screenSize.x, screenLocationX)) {
                case 0:
                    return LOC_TOP_LEFT;
                case 1:
                    return LOC_TOP_CENTER;
                default:
                    return LOC_TOP_RIGHT;
            }
        } else /* if (minDelta == deltaFromBottom) */ {
            // Bottom is primary side
            switch (whichThird(screenSize.x, screenLocationX)) {
                case 0:
                    return LOC_BOTTOM_LEFT;
                case 1:
                    return LOC_BOTTOM_CENTER;
                default:
                    return LOC_BOTTOM_RIGHT;
            }
        }
    }

    // Returns 0, 1, or 2 which correspond to the index of the third the screen point lies in
    // from 'left to right' or 'top to bottom'.
    private static int whichThird(float screenLength, float screenLocation) {
        if (screenLocation <= screenLength / 3) {
            return 0;
        } else if (screenLocation <= screenLength * 2 / 3) {
            return 1;
        } else {
            return 2;
        }
    }

    private static boolean isLeftyModeEnabled(Context context) {
        return Settings.System.getInt(
                        context.getContentResolver(),
                        Settings.System.USER_ROTATION,
                        Surface.ROTATION_0)
                == Surface.ROTATION_180;
    }

    /** Metadata for a specific button. */
    public static final class ButtonInfo {
        private final int mKeycode;
        private final float mX;
        private final float mY;

        /**
         * The location zone of the button as defined in the {@link #getButtonInfo(Context, int)}
         * method. The intended use is to help developers attach a friendly String to the button
         * location. This value is LOC_UNKNOWN if the information is not available.
         */
        @ButtonLocation private final int mLocationZone;

        /**
         * Gets the keycode this {@code ButtonInfo} provides information for.
         *
         * @return The keycode this {@code ButtonInfo} provides information for
         */
        public int getKeycode() {
            return mKeycode;
        }

        /** The x coordinate of the button in screen coordinates. */
        public float getX() {
            return mX;
        }

        /** The y coordinate of the button in screen coordinates. */
        public float getY() {
            return mY;
        }

        /** The location zone of the button (e.g. LOC_EAST) */
        @ButtonLocation public int getLocationZone() {
            return mLocationZone;
        }

        /** @hide */
        @RestrictTo(RestrictTo.Scope.LIBRARY)
        @VisibleForTesting
        public ButtonInfo(int keycode, float x, float y, @ButtonLocation int locationZone) {
            this.mKeycode = keycode;
            this.mX = x;
            this.mY = y;
            this.mLocationZone = locationZone;
        }
    }
}