1 /* 2 * Copyright (C) 2020 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 package com.android.car.ui.utils; 17 18 import android.annotation.TargetApi; 19 import android.content.Context; 20 import android.text.TextUtils; 21 import android.view.View; 22 import android.view.accessibility.AccessibilityEvent; 23 import android.view.accessibility.AccessibilityManager; 24 import android.view.accessibility.AccessibilityNodeInfo; 25 26 import androidx.annotation.NonNull; 27 28 import java.lang.reflect.Method; 29 30 /** Helper class to toggle direct manipulation mode. */ 31 public final class DirectManipulationHelper { 32 33 /** 34 * StateDescription for a {@link View} to support direct manipulation mode. It's also used as 35 * class name of {@link AccessibilityEvent} to indicate that the AccessibilityEvent represents 36 * a request to toggle direct manipulation mode. 37 */ 38 private static final String DIRECT_MANIPULATION = 39 "com.android.car.ui.utils.DIRECT_MANIPULATION"; 40 41 /** This is a utility class. */ DirectManipulationHelper()42 private DirectManipulationHelper() { 43 } 44 45 /** 46 * Enables or disables direct manipulation mode. This method sends an {@link AccessibilityEvent} 47 * to tell {@link com.android.car.rotary.RotaryService} to enter or exit direct manipulation 48 * mode. Typically pressing the center button of the rotary controller with a direct 49 * manipulation view focused will enter direct manipulation mode, while pressing the Back button 50 * will exit direct manipulation mode. 51 * 52 * @param view the direct manipulation view 53 * @param enable true to enter direct manipulation mode, false to exit direct manipulation mode 54 * @return whether the AccessibilityEvent was sent 55 */ enableDirectManipulationMode(@onNull View view, boolean enable)56 public static boolean enableDirectManipulationMode(@NonNull View view, boolean enable) { 57 AccessibilityManager accessibilityManager = (AccessibilityManager) 58 view.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 59 if (accessibilityManager == null || !accessibilityManager.isEnabled()) { 60 return false; 61 } 62 AccessibilityEvent event = AccessibilityEvent.obtain(); 63 event.setClassName(DIRECT_MANIPULATION); 64 event.setSource(view); 65 event.setEventType(enable 66 ? AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED 67 : AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 68 accessibilityManager.sendAccessibilityEvent(event); 69 return true; 70 } 71 72 /** Returns whether the given {@code event} is for direct manipulation. */ isDirectManipulation(@onNull AccessibilityEvent event)73 public static boolean isDirectManipulation(@NonNull AccessibilityEvent event) { 74 return TextUtils.equals(DIRECT_MANIPULATION, event.getClassName()); 75 } 76 77 /** Returns whether the given {@code node} supports direct manipulation. */ 78 @TargetApi(30) supportDirectManipulation(@onNull AccessibilityNodeInfo node)79 public static boolean supportDirectManipulation(@NonNull AccessibilityNodeInfo node) { 80 try { 81 // TODO(b/156115044): remove the reflection once Android R sdk is publicly released. 82 Method getStateDescription = 83 AccessibilityNodeInfo.class.getMethod("getStateDescription"); 84 CharSequence stateDescription = (CharSequence) getStateDescription.invoke(node); 85 return TextUtils.equals(DIRECT_MANIPULATION, stateDescription); 86 } catch (ReflectiveOperationException e) { 87 throw new RuntimeException(e); 88 } 89 } 90 91 /** Sets whether the given {@code view} supports direct manipulation. */ 92 @TargetApi(30) setSupportsDirectManipulation(@onNull View view, boolean enable)93 public static void setSupportsDirectManipulation(@NonNull View view, boolean enable) { 94 try { 95 // TODO(b/156115044): remove the reflection once Android R sdk is publicly released. 96 Method setStateDescription = 97 View.class.getMethod("setStateDescription", CharSequence.class); 98 CharSequence stateDescription = enable ? DIRECT_MANIPULATION : null; 99 setStateDescription.invoke(view, stateDescription); 100 } catch (ReflectiveOperationException e) { 101 throw new RuntimeException(e); 102 } 103 } 104 } 105