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