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 17 package com.android.car.ui; 18 19 import static com.android.car.ui.utils.RotaryConstants.I_FOCUS_AREA_CLASS_NAME; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Rect; 24 import android.os.Bundle; 25 import android.util.AttributeSet; 26 import android.view.FocusFinder; 27 import android.view.View; 28 import android.view.accessibility.AccessibilityNodeInfo; 29 import android.widget.LinearLayout; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 import androidx.annotation.VisibleForTesting; 34 35 /** 36 * A {@link LinearLayout} used as a navigation block for the rotary controller. 37 * <p> 38 * When creating a navigation block in the layout file, if you intend to use a LinearLayout as a 39 * container for that block, just use a FocusArea instead; otherwise use other {@link IFocusArea} 40 * implementations, such as {@link FrameFocusArea} which extends {@link android.widget.FrameLayout}. 41 * <p> 42 * DO NOT nest an IFocusArea inside another IFocusArea because it will result in undefined 43 * navigation behavior. 44 */ 45 public class FocusArea extends LinearLayout implements IFocusArea { 46 47 @NonNull 48 private final FocusAreaHelper mFocusAreaHelper; 49 FocusArea(Context context)50 public FocusArea(Context context) { 51 this(context, null); 52 } 53 FocusArea(Context context, @Nullable AttributeSet attrs)54 public FocusArea(Context context, @Nullable AttributeSet attrs) { 55 this(context, attrs, 0); 56 } 57 FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr)58 public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 59 this(context, attrs, defStyleAttr, 0); 60 } 61 FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)62 public FocusArea(Context context, @Nullable AttributeSet attrs, int defStyleAttr, 63 int defStyleRes) { 64 super(context, attrs, defStyleAttr, defStyleRes); 65 mFocusAreaHelper = new FocusAreaHelper(this, attrs); 66 } 67 68 @Override onFinishInflate()69 protected void onFinishInflate() { 70 super.onFinishInflate(); 71 mFocusAreaHelper.onFinishInflate(); 72 } 73 74 @Override onLayout(boolean changed, int left, int top, int right, int bottom)75 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 76 super.onLayout(changed, left, top, right, bottom); 77 mFocusAreaHelper.onLayout(); 78 } 79 80 @Override onAttachedToWindow()81 protected void onAttachedToWindow() { 82 super.onAttachedToWindow(); 83 mFocusAreaHelper.onAttachedToWindow(); 84 } 85 86 @Override onDetachedFromWindow()87 protected void onDetachedFromWindow() { 88 mFocusAreaHelper.onDetachedFromWindow(); 89 super.onDetachedFromWindow(); 90 } 91 92 @Override onWindowFocusChanged(boolean hasWindowFocus)93 public void onWindowFocusChanged(boolean hasWindowFocus) { 94 if (!mFocusAreaHelper.onWindowFocusChanged(hasWindowFocus)) { 95 super.onWindowFocusChanged(hasWindowFocus); 96 } 97 } 98 99 @Override performAccessibilityAction(int action, Bundle arguments)100 public boolean performAccessibilityAction(int action, Bundle arguments) { 101 if (mFocusAreaHelper.isFocusAreaAction(action)) { 102 return mFocusAreaHelper.performAccessibilityAction(action, arguments); 103 } 104 return super.performAccessibilityAction(action, arguments); 105 } 106 107 @Override onDraw(Canvas canvas)108 public void onDraw(Canvas canvas) { 109 super.onDraw(canvas); 110 mFocusAreaHelper.onDraw(canvas); 111 } 112 113 @Override draw(Canvas canvas)114 public void draw(Canvas canvas) { 115 super.draw(canvas); 116 mFocusAreaHelper.draw(canvas); 117 } 118 119 @Override getAccessibilityClassName()120 public CharSequence getAccessibilityClassName() { 121 return I_FOCUS_AREA_CLASS_NAME; 122 } 123 124 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)125 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 126 super.onInitializeAccessibilityNodeInfo(info); 127 mFocusAreaHelper.onInitializeAccessibilityNodeInfo(info); 128 } 129 130 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)131 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 132 if (isInTouchMode()) { 133 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 134 } 135 return mFocusAreaHelper.onRequestFocusInDescendants(); 136 } 137 138 @Override restoreDefaultFocus()139 public boolean restoreDefaultFocus() { 140 return mFocusAreaHelper.restoreDefaultFocus(); 141 } 142 143 /** 144 * @inheritDoc 145 * <p> 146 * When wrap-around is allowed, the search is restricted to descendants of this 147 * {@link FocusArea}. 148 */ 149 @Override focusSearch(View focused, int direction)150 public View focusSearch(View focused, int direction) { 151 if (mFocusAreaHelper.isWrapAround()) { 152 return FocusFinder.getInstance().findNextFocus(/* root= */ this, focused, direction); 153 } 154 return super.focusSearch(focused, direction); 155 } 156 157 @VisibleForTesting 158 @Override 159 @NonNull getHelper()160 public FocusAreaHelper getHelper() { 161 return mFocusAreaHelper; 162 } 163 164 @Override getDefaultFocusView()165 public View getDefaultFocusView() { 166 return mFocusAreaHelper.getDefaultFocusView(); 167 } 168 169 @Override setDefaultFocus(@onNull View defaultFocus)170 public void setDefaultFocus(@NonNull View defaultFocus) { 171 mFocusAreaHelper.setDefaultFocus(defaultFocus); 172 } 173 174 @Override setHighlightPadding(int left, int top, int right, int bottom)175 public void setHighlightPadding(int left, int top, int right, int bottom) { 176 mFocusAreaHelper.setHighlightPadding(left, top, right, bottom); 177 } 178 179 @Override setBoundsOffset(int left, int top, int right, int bottom)180 public void setBoundsOffset(int left, int top, int right, int bottom) { 181 mFocusAreaHelper.setBoundsOffset(left, top, right, bottom); 182 } 183 184 @Override setWrapAround(boolean wrapAround)185 public void setWrapAround(boolean wrapAround) { 186 mFocusAreaHelper.setWrapAround(wrapAround); 187 } 188 189 @Override setNudgeShortcut(int direction, @Nullable View view)190 public void setNudgeShortcut(int direction, @Nullable View view) { 191 mFocusAreaHelper.setNudgeShortcut(direction, view); 192 } 193 194 @Override setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target)195 public void setNudgeTargetFocusArea(int direction, @Nullable IFocusArea target) { 196 mFocusAreaHelper.setNudgeTargetFocusArea(direction, target); 197 } 198 199 @Override setDefaultFocusOverridesHistory(boolean override)200 public void setDefaultFocusOverridesHistory(boolean override) { 201 mFocusAreaHelper.setDefaultFocusOverridesHistory(override); 202 } 203 } 204