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