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