1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.settings.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.hardware.input.InputDeviceIdentifier;
23 import android.hardware.input.InputManager;
24 import android.hardware.input.KeyboardLayout;
25 import android.os.Bundle;
26 
27 import androidx.preference.Preference;
28 import androidx.preference.PreferenceScreen;
29 
30 import com.android.tv.settings.RadioPreference;
31 import com.android.tv.settings.SettingsPreferenceFragment;
32 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
33 import com.android.tv.twopanelsettings.slices.SliceFragment;
34 
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.Objects;
39 
40 /**
41  * Presents the user a list of all available keyboard layouts / languages and switches to
42  * the selected layout.
43  */
44 public class KeyboardLayoutSelectionFragment extends SettingsPreferenceFragment implements
45         InputManager.InputDeviceListener {
46     private static final String LAYOUT_RADIO_GROUP = "layout";
47     private static final String ARG_DEVICE_NAME = "deviceName";
48     private static final String ARG_LAYOUT_IDENTIFIER = "layoutIdentifier";
49     private static final String ARG_DEVICE_ID = "deviceId";
50     private static final String ARG_INPUT_DEVICE_IDENTIFIER = "inputDeviceIdentifier";
51 
52     private InputDeviceIdentifier mInputDeviceIdentifier;
53     private int mDeviceId;
54     private InputManager mIm;
55     private final Map<String, KeyboardLayout> mKeyboardLayoutMap = new HashMap<>();
56 
57     /**
58      * Prepares the args with the provided parameters.
59      */
prepareArgs(@onNull Bundle args, @NonNull InputDeviceIdentifier inputDeviceIdentifier, @NonNull String deviceName, int deviceId, @Nullable String currentLayoutIdentifier)60     public static void prepareArgs(@NonNull Bundle args,
61             @NonNull InputDeviceIdentifier inputDeviceIdentifier,
62             @NonNull String deviceName, int deviceId, @Nullable String currentLayoutIdentifier) {
63         args.putObject(ARG_INPUT_DEVICE_IDENTIFIER, inputDeviceIdentifier);
64         args.putString(ARG_DEVICE_NAME, deviceName);
65         args.putInt(ARG_DEVICE_ID, deviceId);
66         if (currentLayoutIdentifier != null) {
67             args.putString(ARG_LAYOUT_IDENTIFIER, currentLayoutIdentifier);
68         }
69     }
70 
71     @Override
onCreatePreferences(Bundle bundle, String s)72     public void onCreatePreferences(Bundle bundle, String s) {
73         final Context themedContext = getPreferenceManager().getContext();
74         mInputDeviceIdentifier = getArguments().getParcelable(ARG_INPUT_DEVICE_IDENTIFIER);
75         String mDeviceName = getArguments().getString(ARG_DEVICE_NAME);
76         mDeviceId = getArguments().getInt(ARG_DEVICE_ID);
77         String currentLayoutIdentifier = getArguments().getString(ARG_LAYOUT_IDENTIFIER);
78         final PreferenceScreen screen =
79                 getPreferenceManager().createPreferenceScreen(themedContext);
80         screen.setTitle(themedContext.getString(
81                 com.android.settingslib.R.string.keyboard_layout_dialog_title));
82         screen.setSummary(mDeviceName);
83         mIm = Objects.requireNonNull(themedContext.getSystemService(InputManager.class));
84         mIm.registerInputDeviceListener(this, null);
85 
86         KeyboardLayout[] keyboardLayouts = mIm.getKeyboardLayoutsForInputDevice(
87                 mInputDeviceIdentifier);
88         Arrays.sort(keyboardLayouts);
89         RadioPreference activePreference = null;
90         for (KeyboardLayout kl : keyboardLayouts) {
91             final RadioPreference radioPreference = new RadioPreference(themedContext);
92             radioPreference.setKey(kl.getDescriptor());
93             radioPreference.setPersistent(false);
94             radioPreference.setTitle(kl.getLabel());
95             radioPreference.setRadioGroup(LAYOUT_RADIO_GROUP);
96             radioPreference.setLayoutResource(
97                     com.android.tv.settings.R.layout.preference_reversed_widget);
98             if (kl.getDescriptor().equals(currentLayoutIdentifier)) {
99                 radioPreference.setChecked(true);
100                 activePreference = radioPreference;
101             }
102             screen.addPreference(radioPreference);
103             mKeyboardLayoutMap.put(kl.getDescriptor(), kl);
104         }
105 
106         if (activePreference != null) {
107             scrollToPreference(activePreference);
108         }
109 
110         setPreferenceScreen(screen);
111     }
112 
113     @Override
onPreferenceTreeClick(Preference preference)114     public boolean onPreferenceTreeClick(Preference preference) {
115         if (preference instanceof RadioPreference) {
116             final RadioPreference radioPreference = (RadioPreference) preference;
117             radioPreference.clearOtherRadioPreferences(getPreferenceScreen());
118             if (radioPreference.isChecked()) {
119                 mIm.setCurrentKeyboardLayoutForInputDevice(mInputDeviceIdentifier,
120                         mKeyboardLayoutMap.get(radioPreference.getKey()).getDescriptor());
121             } else {
122                 radioPreference.setChecked(true);
123             }
124         }
125         return super.onPreferenceTreeClick(preference);
126     }
127 
128     @Override
getPageId()129     protected int getPageId() {
130         return super.getPageId();
131     }
132 
133     @Override
onInputDeviceAdded(int deviceId)134     public void onInputDeviceAdded(int deviceId) {
135         // ignore
136     }
137 
138     @Override
onInputDeviceRemoved(int deviceId)139     public void onInputDeviceRemoved(int deviceId) {
140         if (deviceId == mDeviceId) {
141             back();
142         }
143     }
144 
145     @Override
onInputDeviceChanged(int deviceId)146     public void onInputDeviceChanged(int deviceId) {
147         // ignore
148     }
149 
back()150     private void back() {
151         if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
152             TwoPanelSettingsFragment parentFragment =
153                     (TwoPanelSettingsFragment) getCallbackFragment();
154             if (parentFragment.isFragmentInTheMainPanel(this)) {
155                 parentFragment.navigateBack();
156             }
157         } else if (getCallbackFragment() instanceof SliceFragment.OnePanelSliceFragmentContainer) {
158             ((SliceFragment.OnePanelSliceFragmentContainer) getCallbackFragment()).navigateBack();
159         }
160     }
161 }
162