1 /* 2 * Copyright (C) 2012 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.Activity; 20 import android.app.Dialog; 21 import android.app.settings.SettingsEnums; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.hardware.input.InputDeviceIdentifier; 26 import android.hardware.input.InputManager; 27 import android.hardware.input.InputManager.InputDeviceListener; 28 import android.hardware.input.KeyboardLayout; 29 import android.os.Bundle; 30 import android.view.InputDevice; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.ArrayAdapter; 35 import android.widget.CheckedTextView; 36 import android.widget.RadioButton; 37 import android.widget.TextView; 38 39 import androidx.appcompat.app.AlertDialog; 40 import androidx.loader.app.LoaderManager.LoaderCallbacks; 41 import androidx.loader.content.AsyncTaskLoader; 42 import androidx.loader.content.Loader; 43 44 import com.android.settings.R; 45 import com.android.settings.core.instrumentation.InstrumentedDialogFragment; 46 47 import java.util.ArrayList; 48 import java.util.Collections; 49 50 public class KeyboardLayoutDialogFragment extends InstrumentedDialogFragment 51 implements InputDeviceListener, LoaderCallbacks<KeyboardLayoutDialogFragment.Keyboards> { 52 private static final String KEY_INPUT_DEVICE_IDENTIFIER = "inputDeviceIdentifier"; 53 54 private InputDeviceIdentifier mInputDeviceIdentifier; 55 private int mInputDeviceId = -1; 56 private InputManager mIm; 57 private KeyboardLayoutAdapter mAdapter; 58 KeyboardLayoutDialogFragment()59 public KeyboardLayoutDialogFragment() { 60 } 61 KeyboardLayoutDialogFragment(InputDeviceIdentifier inputDeviceIdentifier)62 public KeyboardLayoutDialogFragment(InputDeviceIdentifier inputDeviceIdentifier) { 63 mInputDeviceIdentifier = inputDeviceIdentifier; 64 } 65 66 67 @Override getMetricsCategory()68 public int getMetricsCategory() { 69 return SettingsEnums.DIALOG_KEYBOARD_LAYOUT; 70 } 71 72 @Override onAttach(Activity activity)73 public void onAttach(Activity activity) { 74 super.onAttach(activity); 75 76 Context context = activity.getBaseContext(); 77 mIm = (InputManager)context.getSystemService(Context.INPUT_SERVICE); 78 mAdapter = new KeyboardLayoutAdapter(context); 79 } 80 81 @Override onCreate(Bundle savedInstanceState)82 public void onCreate(Bundle savedInstanceState) { 83 super.onCreate(savedInstanceState); 84 85 if (savedInstanceState != null) { 86 mInputDeviceIdentifier = savedInstanceState.getParcelable(KEY_INPUT_DEVICE_IDENTIFIER); 87 } 88 89 getLoaderManager().initLoader(0, null, this); 90 } 91 92 @Override onSaveInstanceState(Bundle outState)93 public void onSaveInstanceState(Bundle outState) { 94 super.onSaveInstanceState(outState); 95 outState.putParcelable(KEY_INPUT_DEVICE_IDENTIFIER, mInputDeviceIdentifier); 96 } 97 98 @Override onCreateDialog(Bundle savedInstanceState)99 public Dialog onCreateDialog(Bundle savedInstanceState) { 100 Context context = getActivity(); 101 LayoutInflater inflater = LayoutInflater.from(context); 102 AlertDialog.Builder builder = new AlertDialog.Builder(context) 103 .setTitle(R.string.keyboard_layout_dialog_title) 104 .setPositiveButton(R.string.keyboard_layout_dialog_setup_button, 105 new DialogInterface.OnClickListener() { 106 @Override 107 public void onClick(DialogInterface dialog, int which) { 108 onSetupLayoutsButtonClicked(); 109 } 110 }) 111 .setSingleChoiceItems(mAdapter, -1, 112 new DialogInterface.OnClickListener() { 113 @Override 114 public void onClick(DialogInterface dialog, int which) { 115 onKeyboardLayoutClicked(which); 116 } 117 }) 118 .setView(inflater.inflate(R.layout.keyboard_layout_dialog_switch_hint, null)); 119 updateSwitchHintVisibility(); 120 return builder.create(); 121 } 122 123 @Override onResume()124 public void onResume() { 125 super.onResume(); 126 127 mIm.registerInputDeviceListener(this, null); 128 129 InputDevice inputDevice = 130 mIm.getInputDeviceByDescriptor(mInputDeviceIdentifier.getDescriptor()); 131 if (inputDevice == null) { 132 dismiss(); 133 return; 134 } 135 mInputDeviceId = inputDevice.getId(); 136 } 137 138 @Override onPause()139 public void onPause() { 140 mIm.unregisterInputDeviceListener(this); 141 mInputDeviceId = -1; 142 143 super.onPause(); 144 } 145 146 @Override onCancel(DialogInterface dialog)147 public void onCancel(DialogInterface dialog) { 148 super.onCancel(dialog); 149 dismiss(); 150 } 151 onSetupLayoutsButtonClicked()152 private void onSetupLayoutsButtonClicked() { 153 ((OnSetupKeyboardLayoutsListener)getTargetFragment()).onSetupKeyboardLayouts( 154 mInputDeviceIdentifier); 155 } 156 157 @Override onActivityResult(int requestCode, int resultCode, Intent data)158 public void onActivityResult(int requestCode, int resultCode, Intent data) { 159 super.onActivityResult(requestCode, resultCode, data); 160 show(getActivity().getSupportFragmentManager(), "layout"); 161 } 162 onKeyboardLayoutClicked(int which)163 private void onKeyboardLayoutClicked(int which) { 164 if (which >= 0 && which < mAdapter.getCount()) { 165 KeyboardLayout keyboardLayout = mAdapter.getItem(which); 166 if (keyboardLayout != null) { 167 mIm.setCurrentKeyboardLayoutForInputDevice(mInputDeviceIdentifier, 168 keyboardLayout.getDescriptor()); 169 } 170 dismiss(); 171 } 172 } 173 174 @Override onCreateLoader(int id, Bundle args)175 public Loader<Keyboards> onCreateLoader(int id, Bundle args) { 176 return new KeyboardLayoutLoader(getActivity().getBaseContext(), mInputDeviceIdentifier); 177 } 178 179 @Override onLoadFinished(Loader<Keyboards> loader, Keyboards data)180 public void onLoadFinished(Loader<Keyboards> loader, Keyboards data) { 181 mAdapter.clear(); 182 mAdapter.addAll(data.keyboardLayouts); 183 mAdapter.setCheckedItem(data.current); 184 AlertDialog dialog = (AlertDialog)getDialog(); 185 if (dialog != null) { 186 dialog.getListView().setItemChecked(data.current, true); 187 } 188 updateSwitchHintVisibility(); 189 } 190 191 @Override onLoaderReset(Loader<Keyboards> loader)192 public void onLoaderReset(Loader<Keyboards> loader) { 193 mAdapter.clear(); 194 updateSwitchHintVisibility(); 195 } 196 197 @Override onInputDeviceAdded(int deviceId)198 public void onInputDeviceAdded(int deviceId) { 199 } 200 201 @Override onInputDeviceChanged(int deviceId)202 public void onInputDeviceChanged(int deviceId) { 203 if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { 204 getLoaderManager().restartLoader(0, null, this); 205 } 206 } 207 208 @Override onInputDeviceRemoved(int deviceId)209 public void onInputDeviceRemoved(int deviceId) { 210 if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { 211 dismiss(); 212 } 213 } 214 updateSwitchHintVisibility()215 private void updateSwitchHintVisibility() { 216 AlertDialog dialog = (AlertDialog)getDialog(); 217 if (dialog != null) { 218 View customPanel = dialog.findViewById(R.id.customPanel); 219 customPanel.setVisibility(mAdapter.getCount() > 1 ? View.VISIBLE : View.GONE); 220 } 221 } 222 223 private static final class KeyboardLayoutAdapter extends ArrayAdapter<KeyboardLayout> { 224 private final LayoutInflater mInflater; 225 private int mCheckedItem = -1; 226 KeyboardLayoutAdapter(Context context)227 public KeyboardLayoutAdapter(Context context) { 228 super(context, com.android.internal.R.layout.simple_list_item_2_single_choice); 229 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 230 } 231 setCheckedItem(int position)232 public void setCheckedItem(int position) { 233 mCheckedItem = position; 234 } 235 236 @Override getView(int position, View convertView, ViewGroup parent)237 public View getView(int position, View convertView, ViewGroup parent) { 238 KeyboardLayout item = getItem(position); 239 String label, collection; 240 if (item != null) { 241 label = item.getLabel(); 242 collection = item.getCollection(); 243 } else { 244 label = getContext().getString(R.string.keyboard_layout_default_label); 245 collection = ""; 246 } 247 248 boolean checked = (position == mCheckedItem); 249 if (collection.isEmpty()) { 250 return inflateOneLine(convertView, parent, label, checked); 251 } else { 252 return inflateTwoLine(convertView, parent, label, collection, checked); 253 } 254 } 255 inflateOneLine(View convertView, ViewGroup parent, String label, boolean checked)256 private View inflateOneLine(View convertView, ViewGroup parent, 257 String label, boolean checked) { 258 View view = convertView; 259 if (view == null || isTwoLine(view)) { 260 view = mInflater.inflate( 261 com.android.internal.R.layout.simple_list_item_single_choice, 262 parent, false); 263 setTwoLine(view, false); 264 } 265 CheckedTextView headline = (CheckedTextView) view.findViewById(android.R.id.text1); 266 headline.setText(label); 267 headline.setChecked(checked); 268 return view; 269 } 270 inflateTwoLine(View convertView, ViewGroup parent, String label, String collection, boolean checked)271 private View inflateTwoLine(View convertView, ViewGroup parent, 272 String label, String collection, boolean checked) { 273 View view = convertView; 274 if (view == null || !isTwoLine(view)) { 275 view = mInflater.inflate( 276 com.android.internal.R.layout.simple_list_item_2_single_choice, 277 parent, false); 278 setTwoLine(view, true); 279 } 280 TextView headline = (TextView) view.findViewById(android.R.id.text1); 281 TextView subText = (TextView) view.findViewById(android.R.id.text2); 282 RadioButton radioButton = 283 (RadioButton)view.findViewById(com.android.internal.R.id.radio); 284 headline.setText(label); 285 subText.setText(collection); 286 radioButton.setChecked(checked); 287 return view; 288 } 289 isTwoLine(View view)290 private static boolean isTwoLine(View view) { 291 return view.getTag() == Boolean.TRUE; 292 } 293 setTwoLine(View view, boolean twoLine)294 private static void setTwoLine(View view, boolean twoLine) { 295 view.setTag(Boolean.valueOf(twoLine)); 296 } 297 } 298 299 private static final class KeyboardLayoutLoader extends AsyncTaskLoader<Keyboards> { 300 private final InputDeviceIdentifier mInputDeviceIdentifier; 301 KeyboardLayoutLoader(Context context, InputDeviceIdentifier inputDeviceIdentifier)302 public KeyboardLayoutLoader(Context context, InputDeviceIdentifier inputDeviceIdentifier) { 303 super(context); 304 mInputDeviceIdentifier = inputDeviceIdentifier; 305 } 306 307 @Override loadInBackground()308 public Keyboards loadInBackground() { 309 Keyboards keyboards = new Keyboards(); 310 InputManager im = (InputManager)getContext().getSystemService(Context.INPUT_SERVICE); 311 String[] keyboardLayoutDescriptors = im.getEnabledKeyboardLayoutsForInputDevice( 312 mInputDeviceIdentifier); 313 for (String keyboardLayoutDescriptor : keyboardLayoutDescriptors) { 314 KeyboardLayout keyboardLayout = im.getKeyboardLayout(keyboardLayoutDescriptor); 315 if (keyboardLayout != null) { 316 keyboards.keyboardLayouts.add(keyboardLayout); 317 } 318 } 319 Collections.sort(keyboards.keyboardLayouts); 320 321 String currentKeyboardLayoutDescriptor = 322 im.getCurrentKeyboardLayoutForInputDevice(mInputDeviceIdentifier); 323 if (currentKeyboardLayoutDescriptor != null) { 324 final int numKeyboardLayouts = keyboards.keyboardLayouts.size(); 325 for (int i = 0; i < numKeyboardLayouts; i++) { 326 if (keyboards.keyboardLayouts.get(i).getDescriptor().equals( 327 currentKeyboardLayoutDescriptor)) { 328 keyboards.current = i; 329 break; 330 } 331 } 332 } 333 334 if (keyboards.keyboardLayouts.isEmpty()) { 335 keyboards.keyboardLayouts.add(null); // default layout 336 keyboards.current = 0; 337 } 338 return keyboards; 339 } 340 341 @Override onStartLoading()342 protected void onStartLoading() { 343 super.onStartLoading(); 344 forceLoad(); 345 } 346 347 @Override onStopLoading()348 protected void onStopLoading() { 349 super.onStopLoading(); 350 cancelLoad(); 351 } 352 } 353 354 public static final class Keyboards { 355 public final ArrayList<KeyboardLayout> keyboardLayouts = new ArrayList<KeyboardLayout>(); 356 public int current = -1; 357 } 358 359 public interface OnSetupKeyboardLayoutsListener { onSetupKeyboardLayouts(InputDeviceIdentifier mInputDeviceIdentifier)360 public void onSetupKeyboardLayouts(InputDeviceIdentifier mInputDeviceIdentifier); 361 } 362 } 363