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