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.settings.inputmethod;
18 
19 import android.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.hardware.input.InputDeviceIdentifier;
22 import android.hardware.input.InputManager;
23 import android.hardware.input.KeyboardLayout;
24 import android.hardware.input.KeyboardLayoutSelectionResult;
25 import android.os.Bundle;
26 import android.view.inputmethod.InputMethodInfo;
27 import android.view.inputmethod.InputMethodSubtype;
28 
29 import androidx.fragment.app.Fragment;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceScreen;
32 
33 import com.android.settings.R;
34 import com.android.settings.core.BasePreferenceController;
35 import com.android.settings.overlay.FeatureFactory;
36 import com.android.settings.widget.TickButtonPreference;
37 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
38 import com.android.settingslib.core.lifecycle.LifecycleObserver;
39 import com.android.settingslib.core.lifecycle.events.OnStart;
40 import com.android.settingslib.core.lifecycle.events.OnStop;
41 
42 import java.util.HashMap;
43 import java.util.Map;
44 
45 public class NewKeyboardLayoutPickerController extends BasePreferenceController implements
46         InputManager.InputDeviceListener, LifecycleObserver, OnStart, OnStop {
47 
48     private final InputManager mIm;
49     private final Map<TickButtonPreference, KeyboardLayout> mPreferenceMap;
50     private Fragment mParent;
51     private CharSequence mTitle;
52     private int mInputDeviceId;
53     private int mUserId;
54     private InputDeviceIdentifier mInputDeviceIdentifier;
55     private InputMethodInfo mInputMethodInfo;
56     private InputMethodSubtype mInputMethodSubtype;
57     private KeyboardLayout[] mKeyboardLayouts;
58     private PreferenceScreen mScreen;
59     private String mPreviousSelection;
60     private String mFinalSelectedLayout;
61     private String mLayout;
62     private MetricsFeatureProvider mMetricsFeatureProvider;
63     private KeyboardLayoutSelectedCallback mKeyboardLayoutSelectedCallback;
64 
NewKeyboardLayoutPickerController(Context context, String key)65     public NewKeyboardLayoutPickerController(Context context, String key) {
66         super(context, key);
67         mIm = context.getSystemService(InputManager.class);
68         mInputDeviceId = -1;
69         mPreferenceMap = new HashMap<>();
70         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
71     }
72 
initialize(Fragment parent)73     public void initialize(Fragment parent) {
74         mParent = parent;
75         Bundle arguments = parent.getArguments();
76         mTitle = arguments.getCharSequence(NewKeyboardSettingsUtils.EXTRA_TITLE);
77         mUserId = arguments.getInt(NewKeyboardSettingsUtils.EXTRA_USER_ID);
78         mInputDeviceIdentifier =
79                 arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER);
80         mInputMethodInfo =
81                 arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_INFO);
82         mInputMethodSubtype =
83                 arguments.getParcelable(NewKeyboardSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE);
84         mLayout = getSelectedLayoutLabel();
85         mFinalSelectedLayout = mLayout;
86         mKeyboardLayouts = mIm.getKeyboardLayoutListForInputDevice(
87                 mInputDeviceIdentifier, mUserId, mInputMethodInfo, mInputMethodSubtype);
88         NewKeyboardSettingsUtils.sortKeyboardLayoutsByLabel(mKeyboardLayouts);
89         parent.getActivity().setTitle(mTitle);
90     }
91 
92     @Override
onStart()93     public void onStart() {
94         mIm.registerInputDeviceListener(this, null);
95         if (mInputDeviceIdentifier == null
96                 || NewKeyboardSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) {
97             return;
98         }
99         mInputDeviceId =
100                 NewKeyboardSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier).getId();
101     }
102 
103     @Override
onStop()104     public void onStop() {
105         if (mLayout != null && !mLayout.equals(mFinalSelectedLayout)) {
106             String change = "From:" + mLayout + ", to:" + mFinalSelectedLayout;
107             mMetricsFeatureProvider.action(
108                     mContext, SettingsEnums.ACTION_PK_LAYOUT_CHANGED, change);
109         }
110         mIm.unregisterInputDeviceListener(this);
111         mInputDeviceId = -1;
112     }
113 
114     @Override
displayPreference(PreferenceScreen screen)115     public void displayPreference(PreferenceScreen screen) {
116         super.displayPreference(screen);
117         mScreen = screen;
118         createPreferenceHierarchy();
119     }
120 
121     @Override
getAvailabilityStatus()122     public int getAvailabilityStatus() {
123         return AVAILABLE;
124     }
125 
126     /**
127      * Registers {@link KeyboardLayoutSelectedCallback} and get updated.
128      */
registerKeyboardSelectedCallback(KeyboardLayoutSelectedCallback keyboardLayoutSelectedCallback)129     public void registerKeyboardSelectedCallback(KeyboardLayoutSelectedCallback
130             keyboardLayoutSelectedCallback) {
131         this.mKeyboardLayoutSelectedCallback = keyboardLayoutSelectedCallback;
132     }
133 
134     @Override
handlePreferenceTreeClick(Preference preference)135     public boolean handlePreferenceTreeClick(Preference preference) {
136         if (!(preference instanceof TickButtonPreference)) {
137             return false;
138         }
139 
140         final TickButtonPreference pref = (TickButtonPreference) preference;
141         if (mKeyboardLayoutSelectedCallback != null && mPreferenceMap.containsKey(preference)) {
142             mKeyboardLayoutSelectedCallback.onSelected(mPreferenceMap.get(preference));
143         }
144         pref.setSelected(true);
145         if (mPreviousSelection != null && !mPreviousSelection.equals(preference.getKey())) {
146             TickButtonPreference preSelectedPref = mScreen.findPreference(mPreviousSelection);
147             preSelectedPref.setSelected(false);
148         }
149         setLayout(pref);
150         mPreviousSelection = preference.getKey();
151         mFinalSelectedLayout = pref.getTitle().toString();
152         return true;
153     }
154 
155     @Override
onInputDeviceAdded(int deviceId)156     public void onInputDeviceAdded(int deviceId) {
157         // Do nothing.
158     }
159 
160     @Override
onInputDeviceRemoved(int deviceId)161     public void onInputDeviceRemoved(int deviceId) {
162         if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
163             mParent.getActivity().finish();
164         }
165     }
166 
167     @Override
onInputDeviceChanged(int deviceId)168     public void onInputDeviceChanged(int deviceId) {
169         // Do nothing.
170     }
171 
createPreferenceHierarchy()172     private void createPreferenceHierarchy() {
173         if (mKeyboardLayouts == null) {
174             return;
175         }
176         for (KeyboardLayout layout : mKeyboardLayouts) {
177             final TickButtonPreference pref;
178             pref = new TickButtonPreference(mScreen.getContext());
179             pref.setTitle(layout.getLabel());
180 
181             if (mLayout.equals(layout.getLabel())) {
182                 if (mKeyboardLayoutSelectedCallback != null) {
183                     mKeyboardLayoutSelectedCallback.onSelected(layout);
184                 }
185                 pref.setSelected(true);
186                 mPreviousSelection = layout.getDescriptor();
187             }
188             pref.setKey(layout.getDescriptor());
189             mScreen.addPreference(pref);
190             mPreferenceMap.put(pref, layout);
191         }
192     }
193 
setLayout(TickButtonPreference preference)194     private void setLayout(TickButtonPreference preference) {
195         mIm.setKeyboardLayoutForInputDevice(
196                 mInputDeviceIdentifier,
197                 mUserId,
198                 mInputMethodInfo,
199                 mInputMethodSubtype,
200                 mPreferenceMap.get(preference).getDescriptor());
201     }
202 
getSelectedLayoutLabel()203     private String getSelectedLayoutLabel() {
204         String label = mContext.getString(R.string.keyboard_default_layout);
205         KeyboardLayoutSelectionResult result = NewKeyboardSettingsUtils.getKeyboardLayout(
206                 mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
207         KeyboardLayout[] keyboardLayouts = NewKeyboardSettingsUtils.getKeyboardLayouts(
208                 mIm, mUserId, mInputDeviceIdentifier, mInputMethodInfo, mInputMethodSubtype);
209         if (result.getLayoutDescriptor() != null) {
210             for (KeyboardLayout keyboardLayout : keyboardLayouts) {
211                 if (keyboardLayout.getDescriptor().equals(result.getLayoutDescriptor())) {
212                     label = keyboardLayout.getLabel();
213                     break;
214                 }
215             }
216         }
217         return label;
218     }
219 
220     public interface KeyboardLayoutSelectedCallback {
221         /**
222          * Called when KeyboardLayout been selected.
223          */
onSelected(KeyboardLayout keyboardLayout)224         void onSelected(KeyboardLayout keyboardLayout);
225     }
226 }
227