abouttreesummaryrefslogcommitdiff
path: root/srcs/juloo.keyboard2
diff options
context:
space:
mode:
Diffstat (limited to 'srcs/juloo.keyboard2')
-rw-r--r--srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java22
-rw-r--r--srcs/juloo.keyboard2/ClipboardHistoryService.java180
-rw-r--r--srcs/juloo.keyboard2/ClipboardHistoryView.java125
-rw-r--r--srcs/juloo.keyboard2/ClipboardPinView.java139
-rw-r--r--srcs/juloo.keyboard2/Config.java2
-rw-r--r--srcs/juloo.keyboard2/KeyEventHandler.java10
-rw-r--r--srcs/juloo.keyboard2/KeyValue.java4
-rw-r--r--srcs/juloo.keyboard2/Keyboard2.java10
-rw-r--r--srcs/juloo.keyboard2/NonScrollListView.java38
-rw-r--r--srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java3
10 files changed, 532 insertions, 1 deletions
diff --git a/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java b/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java
new file mode 100644
index 0000000..9842058
--- /dev/null
+++ b/srcs/juloo.keyboard2/ClipboardHistoryCheckBox.java
@@ -0,0 +1,22 @@
+package juloo.keyboard2;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+final class ClipboardHistoryCheckBox extends CheckBox
+ implements CompoundButton.OnCheckedChangeListener
+{
+ public ClipboardHistoryCheckBox(Context ctx, AttributeSet attrs)
+ {
+ super(ctx, attrs);
+ setOnCheckedChangeListener(this);
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton _v, boolean isChecked)
+ {
+ ClipboardHistoryService.set_history_enabled(isChecked);
+ }
+}
diff --git a/srcs/juloo.keyboard2/ClipboardHistoryService.java b/srcs/juloo.keyboard2/ClipboardHistoryService.java
new file mode 100644
index 0000000..e3f01ba
--- /dev/null
+++ b/srcs/juloo.keyboard2/ClipboardHistoryService.java
@@ -0,0 +1,180 @@
+package juloo.keyboard2;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Build.VERSION;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public final class ClipboardHistoryService
+{
+ /** Start the service on startup and start listening to clipboard changes. */
+ public static void on_startup(Context ctx, ClipboardPasteCallback cb)
+ {
+ get_service(ctx);
+ _paste_callback = cb;
+ }
+
+ /** Start the service if it hasn't been started before. Returns [null] if the
+ feature is unsupported. */
+ public static ClipboardHistoryService get_service(Context ctx)
+ {
+ if (VERSION.SDK_INT <= 11)
+ return null;
+ if (_service == null)
+ _service = new ClipboardHistoryService(ctx);
+ return _service;
+ }
+
+ public static void set_history_enabled(boolean e)
+ {
+ if (_service == null)
+ return;
+ Config.globalPrefs().edit()
+ .putBoolean("clipboard_history_enabled", e)
+ .commit();
+ if (e)
+ _service.add_current_clip();
+ else
+ _service.clear_history();
+ }
+
+ /** Send the given string to the editor. */
+ public static void paste(String clip)
+ {
+ if (_paste_callback != null)
+ _paste_callback.paste_from_clipboard_pane(clip);
+ }
+
+ /** The maximum size limits the amount of user data stored in memory but also
+ gives a sense to the user that the history is not persisted and can be
+ forgotten as soon as the app stops. */
+ public static final int MAX_HISTORY_SIZE = 3;
+ /** Time in ms until history entries expire. */
+ public static final long HISTORY_TTL_MS = 5 * 60 * 1000;
+
+ static ClipboardHistoryService _service = null;
+ static ClipboardPasteCallback _paste_callback = null;
+
+ ClipboardManager _cm;
+ List<HistoryEntry> _history;
+ OnClipboardHistoryChange _listener = null;
+
+ ClipboardHistoryService(Context ctx)
+ {
+ _history = new ArrayList<HistoryEntry>();
+ _cm = (ClipboardManager)ctx.getSystemService(Context.CLIPBOARD_SERVICE);
+ _cm.addPrimaryClipChangedListener(this.new SystemListener());
+ }
+
+ public List<String> clear_expired_and_get_history()
+ {
+ long now_ms = System.currentTimeMillis();
+ List<String> dst = new ArrayList<String>();
+ Iterator<HistoryEntry> it = _history.iterator();
+ while (it.hasNext())
+ {
+ HistoryEntry ent = it.next();
+ if (ent.expiry_timestamp <= now_ms)
+ it.remove();
+ else
+ dst.add(ent.content);
+ }
+ return dst;
+ }
+
+ /** This will call [on_clipboard_history_change]. */
+ public void remove_history_entry(String clip)
+ {
+ int last_pos = _history.size() - 1;
+ for (int pos = last_pos; pos >= 0; pos--)
+ {
+ if (!_history.get(pos).content.equals(clip))
+ continue;
+ // Removing the current clipboard, clear the system clipboard.
+ if (pos == last_pos)
+ {
+ if (VERSION.SDK_INT >= 28)
+ _cm.clearPrimaryClip();
+ else
+ _cm.setText("");
+ }
+ _history.remove(pos);
+ if (_listener != null)
+ _listener.on_clipboard_history_change();
+ }
+ }
+
+ /** Add clipboard entries to the history, skipping consecutive duplicates and
+ empty strings. */
+ public void add_clip(String clip)
+ {
+ if (!Config.globalConfig().clipboard_history_enabled)
+ return;
+ int size = _history.size();
+ if (clip.equals("") || (size > 0 && _history.get(size - 1).content.equals(clip)))
+ return;
+ if (size >= MAX_HISTORY_SIZE)
+ _history.remove(0);
+ _history.add(new HistoryEntry(clip));
+ if (_listener != null)
+ _listener.on_clipboard_history_change();
+ }
+
+ public void clear_history()
+ {
+ _history.clear();
+ if (_listener != null)
+ _listener.on_clipboard_history_change();
+ }
+
+ public void set_on_clipboard_history_change(OnClipboardHistoryChange l) { _listener = l; }
+
+ public static interface OnClipboardHistoryChange
+ {
+ public void on_clipboard_history_change();
+ }
+
+ /** Add what is currently in the system clipboard into the history. */
+ void add_current_clip()
+ {
+ ClipData clip = _cm.getPrimaryClip();
+ if (clip == null)
+ return;
+ int count = clip.getItemCount();
+ for (int i = 0; i < count; i++)
+ add_clip(clip.getItemAt(i).getText().toString());
+ }
+
+ final class SystemListener implements ClipboardManager.OnPrimaryClipChangedListener
+ {
+ public SystemListener() {}
+
+ @Override
+ public void onPrimaryClipChanged()
+ {
+ add_current_clip();
+ }
+ }
+
+ static final class HistoryEntry
+ {
+ public final String content;
+
+ /** Time at which the entry expires. */
+ public final long expiry_timestamp;
+
+ public HistoryEntry(String c)
+ {
+ content = c;
+ expiry_timestamp = System.currentTimeMillis() + HISTORY_TTL_MS;
+ }
+ }
+
+ public interface ClipboardPasteCallback
+ {
+ public void paste_from_clipboard_pane(String content);
+ }
+}
diff --git a/srcs/juloo.keyboard2/ClipboardHistoryView.java b/srcs/juloo.keyboard2/ClipboardHistoryView.java
new file mode 100644
index 0000000..b4eb6fe
--- /dev/null
+++ b/srcs/juloo.keyboard2/ClipboardHistoryView.java
@@ -0,0 +1,125 @@
+package juloo.keyboard2;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class ClipboardHistoryView extends NonScrollListView
+ implements ClipboardHistoryService.OnClipboardHistoryChange
+{
+ List<String> _history;
+ ClipboardHistoryService _service;
+ ClipboardEntriesAdapter _adapter;
+
+ public ClipboardHistoryView(Context ctx, AttributeSet attrs)
+ {
+ super(ctx, attrs);
+ _history = Collections.EMPTY_LIST;
+ _adapter = this.new ClipboardEntriesAdapter();
+ _service = ClipboardHistoryService.get_service(ctx);
+ if (_service != null)
+ {
+ _service.set_on_clipboard_history_change(this);
+ _history = _service.clear_expired_and_get_history();
+ }
+ setAdapter(_adapter);
+ }
+
+ /** The history entry at index [pos] is removed from the history and added to
+ the list of pinned clipboards. */
+ public void pin_entry(int pos)
+ {
+ ClipboardPinView v = (ClipboardPinView)((ViewGroup)getParent().getParent()).findViewById(R.id.clipboard_pin_view);
+ String clip = _history.get(pos);
+ v.add_entry(clip);
+ _service.remove_history_entry(clip);
+ }
+
+ /** Send the specified entry to the editor. */
+ public void paste_entry(int pos)
+ {
+ ClipboardHistoryService.paste(_history.get(pos));
+ }
+
+ @Override
+ public void on_clipboard_history_change()
+ {
+ update_data();
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility)
+ {
+ if (visibility == View.VISIBLE)
+ update_data();
+ }
+
+ void update_data()
+ {
+ _history = _service.clear_expired_and_get_history();
+ _adapter.notifyDataSetChanged();
+ invalidate();
+ }
+
+ class ClipboardEntriesAdapter extends BaseAdapter
+ {
+ public ClipboardEntriesAdapter() {}
+
+ @Override
+ public int getCount() { return _history.size(); }
+ @Override
+ public Object getItem(int pos) { return _history.get(pos); }
+ @Override
+ public long getItemId(int pos) { return _history.get(pos).hashCode(); }
+
+ @Override
+ public View getView(final int pos, View v, ViewGroup _parent)
+ {
+ if (v == null)
+ v = View.inflate(getContext(), R.layout.clipboard_history_entry, null);
+ ((TextView)v.findViewById(R.id.clipboard_entry_text))
+ .setText(_history.get(pos));
+ v.findViewById(R.id.clipboard_entry_addpin).setOnClickListener(
+ new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v) { pin_entry(pos); }
+ });
+ v.findViewById(R.id.clipboard_entry_paste).setOnClickListener(
+ new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v) { paste_entry(pos); }
+ });
+ // v.findViewById(R.id.clipboard_entry_removehist).setOnClickListener(
+ // new View.OnClickListener()
+ // {
+ // @Override
+ // public void onClick(View v)
+ // {
+ // AlertDialog d = new AlertDialog.Builder(getContext())
+ // .setTitle(R.string.clipboard_remove_confirm)
+ // .setPositiveButton(R.string.clipboard_remove_confirmed,
+ // new DialogInterface.OnClickListener(){
+ // public void onClick(DialogInterface _dialog, int _which)
+ // {
+ // _service.remove_history_entry(_history.get(pos));
+ // }
+ // })
+ // .setNegativeButton(android.R.string.cancel, null)
+ // .create();
+ // Utils.show_dialog_on_ime(d, v.getWindowToken());
+ // }
+ // });
+ return v;
+ }
+ }
+}
diff --git a/srcs/juloo.keyboard2/ClipboardPinView.java b/srcs/juloo.keyboard2/ClipboardPinView.java
new file mode 100644
index 0000000..26833d6
--- /dev/null
+++ b/srcs/juloo.keyboard2/ClipboardPinView.java
@@ -0,0 +1,139 @@
+package juloo.keyboard2;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+public final class ClipboardPinView extends NonScrollListView
+{
+ /** Preference file name that store pinned clipboards. */
+ static final String PERSIST_FILE_NAME = "clipboards";
+ /** Preference name for pinned clipboards. */
+ static final String PERSIST_PREF = "pinned";
+
+ List<String> _entries;
+ ClipboardPinEntriesAdapter _adapter;
+ SharedPreferences _persist_store;
+
+ public ClipboardPinView(Context ctx, AttributeSet attrs)
+ {
+ super(ctx, attrs);
+ _entries = new ArrayList<String>();
+ _persist_store =
+ ctx.getSharedPreferences("pinned_clipboards", Context.MODE_PRIVATE);
+ load_from_prefs(_persist_store, _entries);
+ _adapter = this.new ClipboardPinEntriesAdapter();
+ setAdapter(_adapter);
+ }
+
+ /** Pin a clipboard and persist the change. */
+ public void add_entry(String text)
+ {
+ _entries.add(text);
+ _adapter.notifyDataSetChanged();
+ persist();
+ invalidate();
+ }
+
+ /** Remove the entry at index [pos] and persist the change. */
+ public void remove_entry(int pos)
+ {
+ if (pos < 0 || pos >= _entries.size())
+ return;
+ _entries.remove(pos);
+ _adapter.notifyDataSetChanged();
+ persist();
+ invalidate();
+ }
+
+ /** Send the specified entry to the editor. */
+ public void paste_entry(int pos)
+ {
+ ClipboardHistoryService.paste(_entries.get(pos));
+ }
+
+ void persist() { save_to_prefs(_persist_store, _entries); }
+
+ static void load_from_prefs(SharedPreferences store, List<String> dst)
+ {
+ String arr_s = store.getString(PERSIST_PREF, null);
+ if (arr_s == null)
+ return;
+ try
+ {
+ JSONArray arr = new JSONArray(arr_s);
+ for (int i = 0; i < arr.length(); i++)
+ dst.add(arr.getString(i));
+ }
+ catch (JSONException _e) {}
+ }
+
+ static void save_to_prefs(SharedPreferences store, List<String> entries)
+ {
+ JSONArray arr = new JSONArray();
+ for (int i = 0; i < entries.size(); i++)
+ arr.put(entries.get(i));
+ store.edit()
+ .putString(PERSIST_PREF, arr.toString())
+ .commit();
+ }
+
+ class ClipboardPinEntriesAdapter extends BaseAdapter
+ {
+ public ClipboardPinEntriesAdapter() {}
+
+ @Override
+ public int getCount() { return _entries.size(); }
+ @Override
+ public Object getItem(int pos) { return _entries.get(pos); }
+ @Override
+ public long getItemId(int pos) { return _entries.get(pos).hashCode(); }
+
+ @Override
+ public View getView(final int pos, View v, ViewGroup _parent)
+ {
+ if (v == null)
+ v = View.inflate(getContext(), R.layout.clipboard_pin_entry, null);
+ ((TextView)v.findViewById(R.id.clipboard_pin_text))
+ .setText(_entries.get(pos));
+ v.findViewById(R.id.clipboard_pin_paste).setOnClickListener(
+ new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v) { paste_entry(pos); }
+ });
+ v.findViewById(R.id.clipboard_pin_remove).setOnClickListener(
+ new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v)
+ {
+ AlertDialog d = new AlertDialog.Builder(getContext())
+ .setTitle(R.string.clipboard_remove_confirm)
+ .setPositiveButton(R.string.clipboard_remove_confirmed,
+ new DialogInterface.OnClickListener(){
+ public void onClick(DialogInterface _dialog, int _which)
+ {
+ remove_entry(pos);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .create();
+ Utils.show_dialog_on_ime(d, v.getWindowToken());
+ }
+ });
+ return v;
+ }
+ }
+}
diff --git a/srcs/juloo.keyboard2/Config.java b/srcs/juloo.keyboard2/Config.java
index 7570728..061183c 100644
--- a/srcs/juloo.keyboard2/Config.java
+++ b/srcs/juloo.keyboard2/Config.java
@@ -67,6 +67,7 @@ public final class Config
public boolean pin_entry_enabled;
public boolean borderConfig;
public int circle_sensitivity;
+ public boolean clipboard_history_enabled;
// Dynamically set
public boolean shouldOfferVoiceTyping;
@@ -185,6 +186,7 @@ public final class Config
current_layout_portrait = _prefs.getInt("current_layout_portrait", 0);
current_layout_landscape = _prefs.getInt("current_layout_landscape", 0);
circle_sensitivity = Integer.valueOf(_prefs.getString("circle_sensitivity", "2"));
+ clipboard_history_enabled = _prefs.getBoolean("clipboard_history_enabled", false);
}
public int get_current_layout()
diff --git a/srcs/juloo.keyboard2/KeyEventHandler.java b/srcs/juloo.keyboard2/KeyEventHandler.java
index b6225f1..087ac5b 100644
--- a/srcs/juloo.keyboard2/KeyEventHandler.java
+++ b/srcs/juloo.keyboard2/KeyEventHandler.java
@@ -10,7 +10,9 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import java.util.Iterator;
-public final class KeyEventHandler implements Config.IKeyEventHandler
+public final class KeyEventHandler
+ implements Config.IKeyEventHandler,
+ ClipboardHistoryService.ClipboardPasteCallback
{
IReceiver _recv;
Autocapitalisation _autocap;
@@ -105,6 +107,12 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
update_meta_state(mods);
}
+ @Override
+ public void paste_from_clipboard_pane(String content)
+ {
+ send_text(content);
+ }
+
/** Update [_mods] to be consistent with the [mods], sending key events if
needed. */
void update_meta_state(Pointers.Modifiers mods)
diff --git a/srcs/juloo.keyboard2/KeyValue.java b/srcs/juloo.keyboard2/KeyValue.java
index 1635bab..31a92f2 100644
--- a/srcs/juloo.keyboard2/KeyValue.java
+++ b/srcs/juloo.keyboard2/KeyValue.java
@@ -12,6 +12,8 @@ public final class KeyValue implements Comparable<KeyValue>
SWITCH_NUMERIC,
SWITCH_EMOJI,
SWITCH_BACK_EMOJI,
+ SWITCH_CLIPBOARD,
+ SWITCH_BACK_CLIPBOARD,
CHANGE_METHOD_PICKER,
CHANGE_METHOD_AUTO,
ACTION,
@@ -460,6 +462,8 @@ public final class KeyValue implements Comparable<KeyValue>
case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT);
case "switch_emoji": return eventKey(0xE001, Event.SWITCH_EMOJI, FLAG_SMALLER_FONT);
case "switch_back_emoji": return eventKey("ABC", Event.SWITCH_BACK_EMOJI, 0);
+ case "switch_clipboard": return eventKey(0xE017, Event.SWITCH_CLIPBOARD, 0);
+ case "switch_back_clipboard": return eventKey("ABC", Event.SWITCH_BACK_CLIPBOARD, 0);
case "switch_forward": return eventKey(0xE013, Event.SWITCH_FORWARD, FLAG_SMALLER_FONT);
case "switch_backward": return eventKey(0xE014, Event.SWITCH_BACKWARD, FLAG_SMALLER_FONT);
case "switch_greekmath": return eventKey("πλ∇¬", Event.SWITCH_GREEKMATH, FLAG_SMALLER_FONT);
diff --git a/srcs/juloo.keyboard2/Keyboard2.java b/srcs/juloo.keyboard2/Keyboard2.java
index c332375..0c82aaf 100644
--- a/srcs/juloo.keyboard2/Keyboard2.java
+++ b/srcs/juloo.keyboard2/Keyboard2.java
@@ -36,6 +36,7 @@ public class Keyboard2 extends InputMethodService
/** Layout associated with the currently selected locale. Not 'null'. */
private KeyboardData _localeTextLayout;
private ViewGroup _emojiPane = null;
+ private ViewGroup _clipboard_pane = null;
public int actionId; // Action performed by the Action key.
private Config _config;
@@ -113,6 +114,7 @@ public class Keyboard2 extends InputMethodService
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
_keyboardView.reset();
Logs.set_debug_logs(getResources().getBoolean(R.bool.debug_logs));
+ ClipboardHistoryService.on_startup(this, _keyeventhandler);
}
private List<InputMethodSubtype> getEnabledSubtypes(InputMethodManager imm)
@@ -223,6 +225,7 @@ public class Keyboard2 extends InputMethodService
{
_keyboardView = (Keyboard2View)inflate_view(R.layout.keyboard);
_emojiPane = null;
+ _clipboard_pane = null;
setInputView(_keyboardView);
}
_keyboardView.reset();
@@ -384,7 +387,14 @@ public class Keyboard2 extends InputMethodService
setInputView(_emojiPane);
break;
+ case SWITCH_CLIPBOARD:
+ if (_clipboard_pane == null)
+ _clipboard_pane = (ViewGroup)inflate_view(R.layout.clipboard_pane);
+ setInputView(_clipboard_pane);
+ break;
+
case SWITCH_BACK_EMOJI:
+ case SWITCH_BACK_CLIPBOARD:
setInputView(_keyboardView);
break;
diff --git a/srcs/juloo.keyboard2/NonScrollListView.java b/srcs/juloo.keyboard2/NonScrollListView.java
new file mode 100644
index 0000000..32ef744
--- /dev/null
+++ b/srcs/juloo.keyboard2/NonScrollListView.java
@@ -0,0 +1,38 @@
+package juloo.keyboard2;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ListView;
+
+/** A non-scrollable list view that can be embedded in a bigger ScrollView.
+ Credits to Dedaniya HirenKumar in
+ https://stackoverflow.com/questions/18813296/non-scrollable-listview-inside-scrollview */
+public class NonScrollListView extends ListView
+{
+ public NonScrollListView(Context context)
+ {
+ super(context);
+ }
+
+ public NonScrollListView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ public NonScrollListView(Context context, AttributeSet attrs, int defStyle)
+ {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+ {
+ int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
+ Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
+ ViewGroup.LayoutParams params = getLayoutParams();
+ params.height = getMeasuredHeight();
+ }
+}
diff --git a/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java
index adf66ec..22c6bd9 100644
--- a/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java
+++ b/srcs/juloo.keyboard2/prefs/ExtraKeysPreference.java
@@ -24,6 +24,7 @@ public class ExtraKeysPreference extends PreferenceCategory
"meta",
"compose",
"voice_typing",
+ "switch_clipboard",
"accent_aigu",
"accent_grave",
"accent_double_aigu",
@@ -79,6 +80,7 @@ public class ExtraKeysPreference extends PreferenceCategory
{
case "voice_typing":
case "change_method":
+ case "switch_clipboard":
case "compose":
case "tab":
case "esc":
@@ -117,6 +119,7 @@ public class ExtraKeysPreference extends PreferenceCategory
case "voice_typing": id = R.string.key_descr_voice_typing; break;
case "ª": id = R.string.key_descr_ª; break;
case "º": id = R.string.key_descr_º; break;
+ case "switch_clipboard": id = R.string.key_descr_clipboard; break;
}
if (id == 0)
return null;