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 static android.os.Build.VERSION_CODES.R; 19 20 import android.annotation.TargetApi; 21 import android.content.Context; 22 import android.text.TextUtils; 23 import android.view.View; 24 import android.view.accessibility.AccessibilityEvent; 25 import android.view.accessibility.AccessibilityManager; 26 import android.view.accessibility.AccessibilityNodeInfo; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.VisibleForTesting; 30 31 /** Helper class to toggle direct manipulation mode. */ 32 public final class DirectManipulationHelper { 33 34 /** 35 * ContentDescription for a {@link View} to support direct manipulation mode. It's also used as 36 * class name of {@link AccessibilityEvent} to indicate that the AccessibilityEvent represents 37 * a request to toggle direct manipulation mode. 38 */ 39 @VisibleForTesting 40 public static final String DIRECT_MANIPULATION = 41 "com.android.car.ui.utils.DIRECT_MANIPULATION"; 42 43 /** This is a utility class. */ DirectManipulationHelper()44 private DirectManipulationHelper() { 45 } 46 47 /** 48 * Enables or disables direct manipulation mode. This method sends an {@link AccessibilityEvent} 49 * to tell {@link com.android.car.rotary.RotaryService} to enter or exit direct manipulation 50 * mode. Typically pressing the center button of the rotary controller with a direct 51 * manipulation view focused will enter direct manipulation mode, while pressing the Back button 52 * will exit direct manipulation mode. 53 * 54 * @param view the direct manipulation view 55 * @param enable true to enter direct manipulation mode, false to exit direct manipulation mode 56 * @return whether the AccessibilityEvent was sent 57 */ enableDirectManipulationMode(@onNull View view, boolean enable)58 public static boolean enableDirectManipulationMode(@NonNull View view, boolean enable) { 59 AccessibilityManager accessibilityManager = (AccessibilityManager) 60 view.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 61 if (accessibilityManager == null || !accessibilityManager.isEnabled()) { 62 return false; 63 } 64 AccessibilityEvent event = AccessibilityEvent.obtain(); 65 event.setClassName(DIRECT_MANIPULATION); 66 event.setSource(view); 67 event.setEventType(enable 68 ? AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED 69 : AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); 70 accessibilityManager.sendAccessibilityEvent(event); 71 return true; 72 } 73 74 /** Returns whether the given {@code event} is for direct manipulation. */ isDirectManipulation(@onNull AccessibilityEvent event)75 public static boolean isDirectManipulation(@NonNull AccessibilityEvent event) { 76 return TextUtils.equals(DIRECT_MANIPULATION, event.getClassName()); 77 } 78 79 /** Returns whether the given {@code node} supports rotate directly. */ 80 @TargetApi(R) supportRotateDirectly(@onNull AccessibilityNodeInfo node)81 public static boolean supportRotateDirectly(@NonNull AccessibilityNodeInfo node) { 82 return TextUtils.equals(DIRECT_MANIPULATION, node.getContentDescription()); 83 } 84 85 /** 86 * Sets whether the given {@code view} supports rotate directly. 87 * <p> 88 * If the view supports rotate directly, when it's focused but not in direct manipulation mode, 89 * clicking the center button of the rotary controller will make RotaryService enter direct 90 * manipulation mode. In this mode, the view's selected state is toggled, and only controller 91 * rotation and Back button press are supported. 92 * <ul> 93 * <li>When the controller is rotated, the view will be asked to perform ACTION_SCROLL_FORWARD 94 * or ACTION_SCROLL_BACKWARD. 95 * <li>When Back button is pressed, RotaryService will toggle off the view's selected state 96 * and exit this mode. 97 * </ul> 98 * To support controller nudges as well in direct manipulation mode, use {@link 99 * #enableDirectManipulationMode} instead. 100 */ 101 @TargetApi(R) setSupportsRotateDirectly(@onNull View view, boolean enable)102 public static void setSupportsRotateDirectly(@NonNull View view, boolean enable) { 103 view.setContentDescription(enable ? DIRECT_MANIPULATION : null); 104 } 105 106 /** 107 * Returns whether the given {@code node} supports rotate directly. 108 * 109 * @deprecated use {@link #supportRotateDirectly} instead 110 */ 111 @Deprecated supportDirectManipulation(@onNull AccessibilityNodeInfo node)112 public static boolean supportDirectManipulation(@NonNull AccessibilityNodeInfo node) { 113 return supportRotateDirectly(node); 114 } 115 116 /** 117 * Sets whether the given {@code view} supports rotate directly. 118 * 119 * @deprecated use {@link #setSupportsRotateDirectly} instead 120 */ 121 @Deprecated setSupportsDirectManipulation(@onNull View view, boolean enable)122 public static void setSupportsDirectManipulation(@NonNull View view, boolean enable) { 123 setSupportsRotateDirectly(view, enable); 124 } 125 } 126