1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package androidx.leanback.widget; 15 16 import android.content.Context; 17 import android.graphics.Rect; 18 import android.util.AttributeSet; 19 import android.view.KeyEvent; 20 import android.view.View; 21 import android.widget.FrameLayout; 22 23 import org.jspecify.annotations.NonNull; 24 import org.jspecify.annotations.Nullable; 25 26 /** 27 * A ViewGroup for managing focus behavior between overlapping views. 28 */ 29 public class BrowseFrameLayout extends FrameLayout { 30 31 /** 32 * Interface for selecting a focused view in a BrowseFrameLayout when the system focus finder 33 * couldn't find a view to focus. 34 */ 35 public interface OnFocusSearchListener { 36 /** 37 * Returns the view where focus should be requested given the current focused view and 38 * the direction of focus search. 39 */ onFocusSearch(@ullable View focused, int direction)40 @Nullable View onFocusSearch(@Nullable View focused, int direction); 41 } 42 43 /** 44 * Interface for managing child focus in a BrowseFrameLayout. 45 */ 46 public interface OnChildFocusListener { 47 /** 48 * See {@link android.view.ViewGroup#onRequestFocusInDescendants( 49 * int, android.graphics.Rect)}. 50 * @return True if handled by listener, otherwise returns {@link 51 * android.view.ViewGroup#onRequestFocusInDescendants(int, android.graphics.Rect)}. 52 */ onRequestFocusInDescendants(int direction, @Nullable Rect previouslyFocusedRect)53 boolean onRequestFocusInDescendants(int direction, 54 @Nullable Rect previouslyFocusedRect); 55 /** 56 * See {@link android.view.ViewGroup#requestChildFocus( 57 * android.view.View, android.view.View)}. 58 */ onRequestChildFocus(@ullable View child, @Nullable View focused)59 void onRequestChildFocus(@Nullable View child, @Nullable View focused); 60 } 61 BrowseFrameLayout(@onNull Context context)62 public BrowseFrameLayout(@NonNull Context context) { 63 this(context, null, 0); 64 } 65 BrowseFrameLayout(@onNull Context context, @Nullable AttributeSet attrs)66 public BrowseFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 BrowseFrameLayout(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)70 public BrowseFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 71 super(context, attrs, defStyle); 72 } 73 74 private OnFocusSearchListener mListener; 75 private OnChildFocusListener mOnChildFocusListener; 76 private OnKeyListener mOnDispatchKeyListener; 77 78 /** 79 * Sets a {@link OnFocusSearchListener}. 80 */ setOnFocusSearchListener(@ullable OnFocusSearchListener listener)81 public void setOnFocusSearchListener(@Nullable OnFocusSearchListener listener) { 82 mListener = listener; 83 } 84 85 /** 86 * Returns the {@link OnFocusSearchListener}. 87 */ getOnFocusSearchListener()88 public @Nullable OnFocusSearchListener getOnFocusSearchListener() { 89 return mListener; 90 } 91 92 /** 93 * Sets a {@link OnChildFocusListener}. 94 */ setOnChildFocusListener(@ullable OnChildFocusListener listener)95 public void setOnChildFocusListener(@Nullable OnChildFocusListener listener) { 96 mOnChildFocusListener = listener; 97 } 98 99 /** 100 * Returns the {@link OnChildFocusListener}. 101 */ getOnChildFocusListener()102 public @Nullable OnChildFocusListener getOnChildFocusListener() { 103 return mOnChildFocusListener; 104 } 105 106 @Override onRequestFocusInDescendants(int direction, @Nullable Rect previouslyFocusedRect)107 protected boolean onRequestFocusInDescendants(int direction, 108 @Nullable Rect previouslyFocusedRect) { 109 if (mOnChildFocusListener != null) { 110 if (mOnChildFocusListener.onRequestFocusInDescendants(direction, 111 previouslyFocusedRect)) { 112 return true; 113 } 114 } 115 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 116 } 117 118 @Override focusSearch(@ullable View focused, int direction)119 public View focusSearch(@Nullable View focused, int direction) { 120 if (mListener != null) { 121 View view = mListener.onFocusSearch(focused, direction); 122 if (view != null) { 123 return view; 124 } 125 } 126 return super.focusSearch(focused, direction); 127 } 128 129 @Override requestChildFocus(View child, View focused)130 public void requestChildFocus(View child, View focused) { 131 if (mOnChildFocusListener != null) { 132 mOnChildFocusListener.onRequestChildFocus(child, focused); 133 } 134 super.requestChildFocus(child, focused); 135 } 136 137 @Override dispatchKeyEvent(KeyEvent event)138 public boolean dispatchKeyEvent(KeyEvent event) { 139 boolean consumed = super.dispatchKeyEvent(event); 140 if (mOnDispatchKeyListener != null) { 141 if (!consumed) { 142 return mOnDispatchKeyListener.onKey(getRootView(), event.getKeyCode(), event); 143 } 144 } 145 return consumed; 146 } 147 148 /** 149 * Sets the {@link android.view.View.OnKeyListener} on this view. This listener would fire 150 * only for unhandled {@link KeyEvent}s. We need to provide an external key listener to handle 151 * back button clicks when we are in full screen video mode because 152 * {@link View#setOnKeyListener(OnKeyListener)} doesn't fire as the focus is not on this view. 153 */ setOnDispatchKeyListener(@ullable OnKeyListener listener)154 public void setOnDispatchKeyListener(@Nullable OnKeyListener listener) { 155 this.mOnDispatchKeyListener = listener; 156 } 157 } 158