Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0de19dfa authored by Michael Wright's avatar Michael Wright Committed by Android (Google) Code Review
Browse files

Merge changes from topic 'kb-layout'

* changes:
  Always show enabled keyboards.
  Bandaid over broken keyboard layout selection process.
parents d68cc200 b0e804a3
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -49,11 +49,12 @@ interface IInputManager {

    // Keyboard layouts configuration.
    KeyboardLayout[] getKeyboardLayouts();
    KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
    KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
    String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
    void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
            String keyboardLayoutDescriptor);
    String[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
    String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
    void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
            String keyboardLayoutDescriptor);
    void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
+25 −2
Original line number Diff line number Diff line
@@ -473,6 +473,29 @@ public final class InputManager {
        }
    }

    /**
     * Gets information about all supported keyboard layouts appropriate
     * for a specific input device.
     * <p>
     * The input manager consults the built-in keyboard layouts as well
     * as all keyboard layouts advertised by applications using a
     * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
     * </p>
     *
     * @return A list of all supported keyboard layouts for a specific
     * input device.
     *
     * @hide
     */
    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
        try {
            return mIm.getKeyboardLayoutsForInputDevice(identifier);
        } catch (RemoteException ex) {
            Log.w(TAG, "Could not get list of keyboard layouts for input device.", ex);
            return new KeyboardLayout[0];
        }
    }

    /**
     * Gets the keyboard layout with the specified descriptor.
     *
@@ -551,13 +574,13 @@ public final class InputManager {
     * @return The keyboard layout descriptors.
     * @hide
     */
    public String[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
        if (identifier == null) {
            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
        }

        try {
            return mIm.getKeyboardLayoutsForInputDevice(identifier);
            return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
        } catch (RemoteException ex) {
            Log.w(TAG, "Could not get keyboard layouts for input device.", ex);
            return ArrayUtils.emptyArray(String.class);
+58 −1
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.hardware.input;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.Locale;

/**
 * Describes a keyboard layout.
 *
@@ -30,6 +32,9 @@ public final class KeyboardLayout implements Parcelable,
    private final String mLabel;
    private final String mCollection;
    private final int mPriority;
    private final Locale[] mLocales;
    private final int mVendorId;
    private final int mProductId;

    public static final Parcelable.Creator<KeyboardLayout> CREATOR =
            new Parcelable.Creator<KeyboardLayout>() {
@@ -41,11 +46,19 @@ public final class KeyboardLayout implements Parcelable,
        }
    };

    public KeyboardLayout(String descriptor, String label, String collection, int priority) {
    public KeyboardLayout(String descriptor, String label, String collection, int priority,
            Locale[] locales, int vid, int pid) {
        mDescriptor = descriptor;
        mLabel = label;
        mCollection = collection;
        mPriority = priority;
        if (locales != null) {
            mLocales = locales;
        } else {
            mLocales = new Locale[0];
        }
        mVendorId = vid;
        mProductId = pid;
    }

    private KeyboardLayout(Parcel source) {
@@ -53,6 +66,13 @@ public final class KeyboardLayout implements Parcelable,
        mLabel = source.readString();
        mCollection = source.readString();
        mPriority = source.readInt();
        int N = source.readInt();
        mLocales = new Locale[N];
        for (int i = 0; i < N; i++) {
            mLocales[i] = Locale.forLanguageTag(source.readString());
        }
        mVendorId = source.readInt();
        mProductId = source.readInt();
    }

    /**
@@ -83,6 +103,33 @@ public final class KeyboardLayout implements Parcelable,
        return mCollection;
    }

    /**
     * Gets the locales that this keyboard layout is intended for.
     * This may be empty if a locale has not been assigned to this keyboard layout.
     * @return The keyboard layout's intended locale.
     */
    public Locale[] getLocales() {
        return mLocales;
    }

    /**
     * Gets the vendor ID of the hardware device this keyboard layout is intended for.
     * Returns -1 if this is not specific to any piece of hardware.
     * @return The hardware vendor ID of the keyboard layout's intended device.
     */
    public int getVendorId() {
        return mVendorId;
    }

    /**
     * Gets the product ID of the hardware device this keyboard layout is intended for.
     * Returns -1 if this is not specific to any piece of hardware.
     * @return The hardware product ID of the keyboard layout's intended device.
     */
    public int getProductId() {
        return mProductId;
    }

    @Override
    public int describeContents() {
        return 0;
@@ -94,6 +141,16 @@ public final class KeyboardLayout implements Parcelable,
        dest.writeString(mLabel);
        dest.writeString(mCollection);
        dest.writeInt(mPriority);
        if (mLocales != null) {
            dest.writeInt(mLocales.length);
            for (Locale l : mLocales) {
                dest.writeString(l.toLanguageTag());
            }
        } else {
            dest.writeInt(0);
        }
        dest.writeInt(mVendorId);
        dest.writeInt(mProductId);
    }

    @Override
+6 −0
Original line number Diff line number Diff line
@@ -7860,6 +7860,12 @@ i
        <attr name="label" />
        <!-- The key character map file resource. -->
        <attr name="keyboardLayout" format="reference" />
        <!-- The locales the given keyboard layout corresponds to. -->
        <attr name="locale" format="string" />
        <!-- The vendor ID of the hardware the given layout corresponds to. @hide -->
        <attr name="vendorId" format="integer" />
        <!-- The product ID of the hardware the given layout corresponds to. @hide -->
        <attr name="productId" format="integer" />
    </declare-styleable>

    <declare-styleable name="MediaRouteButton">
+196 −55
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -97,9 +97,11 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;

import libcore.io.Streams;
import libcore.util.Objects;
@@ -720,40 +722,35 @@ public class InputManagerService extends IInputManager.Stub
        mTempInputDevicesChangedListenersToNotify.clear();

        // Check for missing keyboard layouts.
        if (mNotificationManager != null) {
        List<InputDevice> keyboardsMissingLayout = new ArrayList<>();
        final int numFullKeyboards = mTempFullKeyboards.size();
            boolean missingLayoutForExternalKeyboard = false;
            boolean missingLayoutForExternalKeyboardAdded = false;
            boolean multipleMissingLayoutsForExternalKeyboardsAdded = false;
            InputDevice keyboardMissingLayout = null;
        synchronized (mDataStore) {
            for (int i = 0; i < numFullKeyboards; i++) {
                final InputDevice inputDevice = mTempFullKeyboards.get(i);
                    final String layout =
                String layout =
                    getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
                if (layout == null) {
                        missingLayoutForExternalKeyboard = true;
                        if (i < numFullKeyboardsAdded) {
                            missingLayoutForExternalKeyboardAdded = true;
                            if (keyboardMissingLayout == null) {
                                keyboardMissingLayout = inputDevice;
                            } else {
                                multipleMissingLayoutsForExternalKeyboardsAdded = true;
                    layout = getDefaultKeyboardLayout(inputDevice);
                    if (layout != null) {
                        setCurrentKeyboardLayoutForInputDevice(
                                inputDevice.getIdentifier(), layout);
                    }
                }
                if (layout == null) {
                    keyboardsMissingLayout.add(inputDevice);
                }
            }
        }
            if (missingLayoutForExternalKeyboard) {
                if (missingLayoutForExternalKeyboardAdded) {
                    if (multipleMissingLayoutsForExternalKeyboardsAdded) {

        if (mNotificationManager != null) {
            if (!keyboardsMissingLayout.isEmpty()) {
                if (keyboardsMissingLayout.size() > 1) {
                    // We have more than one keyboard missing a layout, so drop the
                    // user at the generic input methods page so they can pick which
                    // one to set.
                    showMissingKeyboardLayoutNotification(null);
                } else {
                        showMissingKeyboardLayoutNotification(keyboardMissingLayout);
                    }
                    showMissingKeyboardLayoutNotification(keyboardsMissingLayout.get(0));
                }
            } else if (mKeyboardLayoutNotificationShown) {
                hideMissingKeyboardLayoutNotification();
@@ -762,6 +759,78 @@ public class InputManagerService extends IInputManager.Stub
        mTempFullKeyboards.clear();
    }

    private String getDefaultKeyboardLayout(final InputDevice d) {
        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
        // If our locale doesn't have a language for some reason, then we don't really have a
        // reasonable default.
        if (TextUtils.isEmpty(systemLocale.getLanguage())) {
            return null;
        }
        final List<KeyboardLayout> layouts = new ArrayList<>();
        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
            @Override
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                // Only select a default when we know the layout is appropriate. For now, this
                // means its a custom layout for a specific keyboard.
                if (layout.getVendorId() != d.getVendorId()
                        || layout.getProductId() != d.getProductId()) {
                    return;
                }
                for (Locale l : layout.getLocales()) {
                    if (isCompatibleLocale(systemLocale, l)) {
                        layouts.add(layout);
                        break;
                    }
                }
            }
        });

        if (layouts.isEmpty()) {
            return null;
        }

        // First sort so that ones with higher priority are listed at the top
        Collections.sort(layouts);
        // Next we want to try to find an exact match of language, country and variant.
        final int N = layouts.size();
        for (int i = 0; i < N; i++) {
            KeyboardLayout layout = layouts.get(i);
            for (Locale l : layout.getLocales()) {
                if (l.getCountry().equals(systemLocale.getCountry())
                        && l.getVariant().equals(systemLocale.getVariant())) {
                    return layout.getDescriptor();
                }
            }
        }
        // Then try an exact match of language and country
        for (int i = 0; i < N; i++) {
            KeyboardLayout layout = layouts.get(i);
            for (Locale l : layout.getLocales()) {
                if (l.getCountry().equals(systemLocale.getCountry())) {
                    return layout.getDescriptor();
                }
            }
        }

        // Give up and just use the highest priority layout with matching language
        return layouts.get(0).getDescriptor();
    }

    private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
        // Different languages are never compatible
        if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
            return false;
        }
        // If both the system and the keyboard layout have a country specifier, they must be equal.
        if (!TextUtils.isEmpty(systemLocale.getCountry())
                && !TextUtils.isEmpty(keyboardLocale.getCountry())
                && !systemLocale.getCountry().equals(keyboardLocale.getCountry())) {
            return false;
        }
        return true;
    }

    @Override // Binder call & native callback
    public TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor,
            int surfaceRotation) {
@@ -911,9 +980,9 @@ public class InputManagerService extends IInputManager.Stub
        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
            @Override
            public void visitKeyboardLayout(Resources resources, String descriptor, String label,
                    String collection, int keyboardLayoutResId, int priority) {
                availableKeyboardLayouts.add(descriptor);
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                availableKeyboardLayouts.add(layout.getDescriptor());
            }
        });
        synchronized (mDataStore) {
@@ -945,14 +1014,63 @@ public class InputManagerService extends IInputManager.Stub
        final ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>();
        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
            @Override
            public void visitKeyboardLayout(Resources resources, String descriptor, String label,
                    String collection, int keyboardLayoutResId, int priority) {
                list.add(new KeyboardLayout(descriptor, label, collection, priority));
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                list.add(layout);
            }
        });
        return list.toArray(new KeyboardLayout[list.size()]);
    }

    @Override // Binder call
    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
            final InputDeviceIdentifier identifier) {
        final String[] enabledLayoutDescriptors =
            getEnabledKeyboardLayoutsForInputDevice(identifier);
        final ArrayList<KeyboardLayout> enabledLayouts =
            new ArrayList<KeyboardLayout>(enabledLayoutDescriptors.length);
        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<KeyboardLayout>();
        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
            boolean mHasSeenDeviceSpecificLayout;

            @Override
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                // First check if it's enabled. If the keyboard layout is enabled then we always
                // want to return it as a possible layout for the device.
                for (String s : enabledLayoutDescriptors) {
                    if (s != null && s.equals(layout.getDescriptor())) {
                        enabledLayouts.add(layout);
                        return;
                    }
                }
                // Next find any potential layouts that aren't yet enabled for the device. For
                // devices that have special layouts we assume there's a reason that the generic
                // layouts don't work for them so we don't want to return them since it's likely
                // to result in a poor user experience.
                if (layout.getVendorId() == identifier.getVendorId()
                        && layout.getProductId() == identifier.getProductId()) {
                    if (!mHasSeenDeviceSpecificLayout) {
                        mHasSeenDeviceSpecificLayout = true;
                        potentialLayouts.clear();
                    }
                    potentialLayouts.add(layout);
                } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
                        && !mHasSeenDeviceSpecificLayout) {
                    potentialLayouts.add(layout);
                }
            }
        });
        final int enabledLayoutSize = enabledLayouts.size();
        final int potentialLayoutSize = potentialLayouts.size();
        KeyboardLayout[] layouts = new KeyboardLayout[enabledLayoutSize + potentialLayoutSize];
        enabledLayouts.toArray(layouts);
        for (int i = 0; i < potentialLayoutSize; i++) {
            layouts[enabledLayoutSize + i] = potentialLayouts.get(i);
        }
        return layouts;
    }

    @Override // Binder call
    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
        if (keyboardLayoutDescriptor == null) {
@@ -962,13 +1080,13 @@ public class InputManagerService extends IInputManager.Stub
        final KeyboardLayout[] result = new KeyboardLayout[1];
        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
            @Override
            public void visitKeyboardLayout(Resources resources, String descriptor,
                    String label, String collection, int keyboardLayoutResId, int priority) {
                result[0] = new KeyboardLayout(descriptor, label, collection, priority);
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                result[0] = layout;
            }
        });
        if (result[0] == null) {
            Log.w(TAG, "Could not get keyboard layout with descriptor '"
            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
                    + keyboardLayoutDescriptor + "'.");
        }
        return result[0];
@@ -1010,7 +1128,7 @@ public class InputManagerService extends IInputManager.Stub

        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
        if (configResId == 0) {
            Log.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
            Slog.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
            return;
        }
@@ -1047,8 +1165,16 @@ public class InputManagerService extends IInputManager.Stub
                            int keyboardLayoutResId = a.getResourceId(
                                    com.android.internal.R.styleable.KeyboardLayout_keyboardLayout,
                                    0);
                            String languageTags = a.getString(
                                    com.android.internal.R.styleable.KeyboardLayout_locale);
                            Locale[] locales = getLocalesFromLanguageTags(languageTags);
                            int vid = a.getInt(
                                    com.android.internal.R.styleable.KeyboardLayout_vendorId, -1);
                            int pid = a.getInt(
                                    com.android.internal.R.styleable.KeyboardLayout_productId, -1);

                            if (name == null || label == null || keyboardLayoutResId == 0) {
                                Log.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
                                Slog.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
                                        + "attributes in keyboard layout "
                                        + "resource from receiver "
                                        + receiver.packageName + "/" + receiver.name);
@@ -1056,15 +1182,18 @@ public class InputManagerService extends IInputManager.Stub
                                String descriptor = KeyboardLayoutDescriptor.format(
                                        receiver.packageName, receiver.name, name);
                                if (keyboardName == null || name.equals(keyboardName)) {
                                    visitor.visitKeyboardLayout(resources, descriptor,
                                            label, collection, keyboardLayoutResId, priority);
                                    KeyboardLayout layout = new KeyboardLayout(
                                            descriptor, label, collection, priority,
                                            locales, vid, pid);
                                    visitor.visitKeyboardLayout(
                                            resources, keyboardLayoutResId, layout);
                                }
                            }
                        } finally {
                            a.recycle();
                        }
                    } else {
                        Log.w(TAG, "Skipping unrecognized element '" + element
                        Slog.w(TAG, "Skipping unrecognized element '" + element
                                + "' in keyboard layout resource from receiver "
                                + receiver.packageName + "/" + receiver.name);
                    }
@@ -1073,11 +1202,23 @@ public class InputManagerService extends IInputManager.Stub
                parser.close();
            }
        } catch (Exception ex) {
            Log.w(TAG, "Could not parse keyboard layout resource from receiver "
            Slog.w(TAG, "Could not parse keyboard layout resource from receiver "
                    + receiver.packageName + "/" + receiver.name, ex);
        }
    }

    private static Locale[] getLocalesFromLanguageTags(String languageTags) {
        if (TextUtils.isEmpty(languageTags)) {
            return new Locale[0];
        }
        String[] tags = languageTags.split("\\|");
        Locale[] locales = new Locale[tags.length];
        for (int i = 0; i < tags.length; i++) {
            locales[i] = Locale.forLanguageTag(tags[i]);
        }
        return locales;
    }

    /**
     * Builds a layout descriptor for the vendor/product. This returns the
     * descriptor for ids that aren't useful (such as the default 0, 0).
@@ -1143,7 +1284,7 @@ public class InputManagerService extends IInputManager.Stub
    }

    @Override // Binder call
    public String[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
        String key = getLayoutDescriptor(identifier);
        synchronized (mDataStore) {
            String[] layouts = mDataStore.getKeyboardLayouts(key);
@@ -1718,10 +1859,10 @@ public class InputManagerService extends IInputManager.Stub
        final String[] result = new String[2];
        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
            @Override
            public void visitKeyboardLayout(Resources resources, String descriptor, String label,
                    String collection, int keyboardLayoutResId, int priority) {
            public void visitKeyboardLayout(Resources resources,
                    int keyboardLayoutResId, KeyboardLayout layout) {
                try {
                    result[0] = descriptor;
                    result[0] = layout.getDescriptor();
                    result[1] = Streams.readFully(new InputStreamReader(
                            resources.openRawResource(keyboardLayoutResId)));
                } catch (IOException ex) {
@@ -1730,7 +1871,7 @@ public class InputManagerService extends IInputManager.Stub
            }
        });
        if (result[0] == null) {
            Log.w(TAG, "Could not get keyboard layout with descriptor '"
            Slog.w(TAG, "Could not get keyboard layout with descriptor '"
                    + keyboardLayoutDescriptor + "'.");
            return null;
        }
@@ -1883,8 +2024,8 @@ public class InputManagerService extends IInputManager.Stub
    }

    private interface KeyboardLayoutVisitor {
        void visitKeyboardLayout(Resources resources, String descriptor, String label,
                String collection, int keyboardLayoutResId, int priority);
        void visitKeyboardLayout(Resources resources,
                int keyboardLayoutResId, KeyboardLayout layout);
    }

    private final class InputDevicesChangedListenerRecord implements DeathRecipient {