blob: 24a32d1d4d0e7c16808233de22170cb3fbd543d3 [file] [log] [blame]
/*
* Copyright (C) 2020 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.android.car.ui.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
import java.lang.reflect.Method;
/** Helper class to toggle direct manipulation mode. */
public final class DirectManipulationHelper {
/**
* StateDescription for a {@link View} to support direct manipulation mode. It's also used as
* class name of {@link AccessibilityEvent} to indicate that the AccessibilityEvent represents
* a request to toggle direct manipulation mode.
*/
private static final String DIRECT_MANIPULATION =
"com.android.car.ui.utils.DIRECT_MANIPULATION";
/** This is a utility class. */
private DirectManipulationHelper() {
}
/**
* Enables or disables direct manipulation mode. This method sends an {@link AccessibilityEvent}
* to tell {@link com.android.car.rotary.RotaryService} to enter or exit direct manipulation
* mode. Typically pressing the center button of the rotary controller with a direct
* manipulation view focused will enter direct manipulation mode, while pressing the Back button
* will exit direct manipulation mode.
*
* @param view the direct manipulation view
* @param enable true to enter direct manipulation mode, false to exit direct manipulation mode
* @return whether the AccessibilityEvent was sent
*/
public static boolean enableDirectManipulationMode(@NonNull View view, boolean enable) {
AccessibilityManager accessibilityManager = (AccessibilityManager)
view.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager == null || !accessibilityManager.isEnabled()) {
return false;
}
AccessibilityEvent event = AccessibilityEvent.obtain();
event.setClassName(DIRECT_MANIPULATION);
event.setSource(view);
event.setEventType(enable
? AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
: AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
accessibilityManager.sendAccessibilityEvent(event);
return true;
}
/** Returns whether the given {@code event} is for direct manipulation. */
public static boolean isDirectManipulation(@NonNull AccessibilityEvent event) {
return TextUtils.equals(DIRECT_MANIPULATION, event.getClassName());
}
/** Returns whether the given {@code node} supports direct manipulation. */
@TargetApi(30)
public static boolean supportDirectManipulation(@NonNull AccessibilityNodeInfo node) {
try {
// TODO(b/156115044): remove the reflection once Android R sdk is publicly released.
Method getStateDescription =
AccessibilityNodeInfo.class.getMethod("getStateDescription");
CharSequence stateDescription = (CharSequence) getStateDescription.invoke(node);
return TextUtils.equals(DIRECT_MANIPULATION, stateDescription);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
/** Sets whether the given {@code view} supports direct manipulation. */
@TargetApi(30)
public static void setSupportsDirectManipulation(@NonNull View view, boolean enable) {
try {
// TODO(b/156115044): remove the reflection once Android R sdk is publicly released.
Method setStateDescription =
View.class.getMethod("setStateDescription", CharSequence.class);
CharSequence stateDescription = enable ? DIRECT_MANIPULATION : null;
setStateDescription.invoke(view, stateDescription);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}