android13/development/samples/SampleInputMethodAccessibil.../src/com/example/android/sampleinputmethodaccessibil.../SampleInputMethodAccessibil...

268 lines
12 KiB
Java

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.sampleinputmethodaccessibilityservice;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.InputMethod;
import android.os.SystemClock;
import android.util.Log;
import android.util.Pair;
import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.List;
import java.util.function.BiConsumer;
/**
* A sample {@link AccessibilityService} to demo how to use IME APIs.
*/
public final class SampleInputMethodAccessibilityService extends AccessibilityService {
private static final String TAG = "SampleImeA11yService";
private EventMonitor mEventMonitor;
private final class InputMethodImpl extends InputMethod {
InputMethodImpl(AccessibilityService service) {
super(service);
}
@Override
public void onStartInput(EditorInfo attribute, boolean restarting) {
Log.d(TAG, String.format("onStartInput(%s,%b)", attribute, restarting));
mEventMonitor.onStartInput(attribute, restarting);
}
@Override
public void onFinishInput() {
Log.d(TAG, "onFinishInput()");
mEventMonitor.onFinishInput();
}
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
int newSelEnd, int candidatesStart, int candidatesEnd) {
Log.d(TAG, String.format("onUpdateSelection(%d,%d,%d,%d,%d,%d)", oldSelStart, oldSelEnd,
newSelStart, newSelEnd, candidatesStart, candidatesEnd));
mEventMonitor.onUpdateSelection(oldSelStart, oldSelEnd,
newSelStart, newSelEnd, candidatesStart, candidatesEnd);
}
}
private static <T> Pair<CharSequence, T> item(@NonNull CharSequence label, @Nullable T value) {
return Pair.create(label, value);
}
private <T> void addButtons(@NonNull LinearLayout parentView, @NonNull String headerText,
@NonNull List<Pair<CharSequence, T>> items,
@NonNull BiConsumer<T, InputMethod.AccessibilityInputConnection> action) {
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
{
final TextView headerTextView = new TextView(this, null,
android.R.attr.listSeparatorTextViewStyle);
headerTextView.setAllCaps(false);
headerTextView.setText(headerText);
layout.addView(headerTextView);
}
{
final LinearLayout itemLayout = new LinearLayout(this);
itemLayout.setOrientation(LinearLayout.HORIZONTAL);
for (Pair<CharSequence, T> item : items) {
final Button button = new Button(this, null, android.R.attr.buttonStyleSmall);
button.setAllCaps(false);
button.setText(item.first);
button.setOnClickListener(view -> {
final InputMethod ime = getInputMethod();
if (ime == null) {
return;
}
final InputMethod.AccessibilityInputConnection ic =
ime.getCurrentInputConnection();
if (ic == null) {
return;
}
action.accept(item.second, ic);
});
itemLayout.addView(button);
}
final HorizontalScrollView scrollView = new HorizontalScrollView(this);
scrollView.addView(itemLayout);
layout.addView(scrollView);
}
parentView.addView(layout);
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
final WindowManager windowManager = getSystemService(WindowManager.class);
final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
// Create a monitor window.
{
final TextView textView = new TextView(this);
mEventMonitor = new EventMonitor(textView::setText);
final LinearLayout monitorWindowContent = new LinearLayout(this);
monitorWindowContent.setOrientation(LinearLayout.VERTICAL);
monitorWindowContent.setPadding(10, 10, 10, 10);
monitorWindowContent.addView(textView);
OverlayWindowBuilder.from(monitorWindowContent)
.setSize((metrics.getBounds().width() * 3) / 4,
WindowManager.LayoutParams.WRAP_CONTENT)
.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
.setBackgroundColor(0xeed2e3fc)
.show();
}
final LinearLayout contentView = new LinearLayout(this);
contentView.setOrientation(LinearLayout.VERTICAL);
{
final TextView textView = new TextView(this, null, android.R.attr.windowTitleStyle);
textView.setGravity(Gravity.CENTER);
textView.setText("A11Y IME");
contentView.addView(textView);
}
{
final LinearLayout buttonLayout = new LinearLayout(this);
buttonLayout.setBackgroundColor(0xfffeefc3);
buttonLayout.setPadding(10, 10, 10, 10);
buttonLayout.setOrientation(LinearLayout.VERTICAL);
addButtons(buttonLayout,
"commitText", List.of(
item("A", "A"),
item("Hello World", "Hello World"),
item("\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F",
"\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F")),
(value, ic) -> ic.commitText(value, 1, null));
addButtons(buttonLayout,
"sendKeyEvent", List.of(
item("A", KeyEvent.KEYCODE_A),
item("DEL", KeyEvent.KEYCODE_DEL),
item("DPAD_LEFT", KeyEvent.KEYCODE_DPAD_LEFT),
item("DPAD_RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT),
item("COPY", KeyEvent.KEYCODE_COPY),
item("CUT", KeyEvent.KEYCODE_CUT),
item("PASTE", KeyEvent.KEYCODE_PASTE)),
(keyCode, ic) -> {
final long eventTime = SystemClock.uptimeMillis();
ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE));
});
addButtons(buttonLayout,
"performEditorAction", List.of(
item("UNSPECIFIED", EditorInfo.IME_ACTION_UNSPECIFIED),
item("NONE", EditorInfo.IME_ACTION_NONE),
item("GO", EditorInfo.IME_ACTION_GO),
item("SEARCH", EditorInfo.IME_ACTION_SEARCH),
item("SEND", EditorInfo.IME_ACTION_SEND),
item("NEXT", EditorInfo.IME_ACTION_NEXT),
item("DONE", EditorInfo.IME_ACTION_DONE),
item("PREVIOUS", EditorInfo.IME_ACTION_PREVIOUS)),
(action, ic) -> ic.performEditorAction(action));
addButtons(buttonLayout,
"performContextMenuAction", List.of(
item("selectAll", android.R.id.selectAll),
item("startSelectingText", android.R.id.startSelectingText),
item("stopSelectingText", android.R.id.stopSelectingText),
item("cut", android.R.id.cut),
item("copy", android.R.id.copy),
item("paste", android.R.id.paste),
item("copyUrl", android.R.id.copyUrl),
item("switchInputMethod", android.R.id.switchInputMethod)),
(action, ic) -> ic.performContextMenuAction(action));
addButtons(buttonLayout,
"setSelection", List.of(
item("(0,0)", Pair.create(0, 0)),
item("(0,1)", Pair.create(0, 1)),
item("(1,1)", Pair.create(1, 1)),
item("(0,999)", Pair.create(0, 999))),
(pair, ic) -> ic.setSelection(pair.first, pair.second));
addButtons(buttonLayout,
"deleteSurroundingText", List.of(
item("(0,0)", Pair.create(0, 0)),
item("(0,1)", Pair.create(0, 1)),
item("(1,0)", Pair.create(1, 0)),
item("(1,1)", Pair.create(1, 1)),
item("(999,0)", Pair.create(999, 0)),
item("(0,999)", Pair.create(0, 999))),
(pair, ic) -> ic.deleteSurroundingText(pair.first, pair.second));
final ScrollView scrollView = new ScrollView(this);
scrollView.addView(buttonLayout);
contentView.addView(scrollView);
// Set margin
{
final LinearLayout.LayoutParams lp =
((LinearLayout.LayoutParams) scrollView.getLayoutParams());
lp.leftMargin = lp.rightMargin = lp.bottomMargin = 20;
scrollView.setLayoutParams(lp);
}
}
OverlayWindowBuilder.from(contentView)
.setSize((metrics.getBounds().width() * 3) / 4,
metrics.getBounds().height() / 5)
.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
.setRelativePosition(300, 300)
.setBackgroundColor(0xfffcc934)
.show();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
@Override
public InputMethod onCreateInputMethod() {
Log.d(TAG, "onCreateInputMethod");
return new InputMethodImpl(this);
}
}