• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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