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