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 android.support.v17.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 /**
24  * A ViewGroup for managing focus behavior between overlapping views.
25  */
26 public class BrowseFrameLayout extends FrameLayout {
27 
28     /**
29      * Interface for selecting a focused view in a BrowseFrameLayout when the system focus finder
30      * couldn't find a view to focus.
31      */
32     public interface OnFocusSearchListener {
33         /**
34          * Returns the view where focus should be requested given the current focused view and
35          * the direction of focus search.
36          */
onFocusSearch(View focused, int direction)37         View onFocusSearch(View focused, int direction);
38     }
39 
40     /**
41      * Interface for managing child focus in a BrowseFrameLayout.
42      */
43     public interface OnChildFocusListener {
44         /**
45          * See {@link android.view.ViewGroup#onRequestFocusInDescendants(
46          * int, android.graphics.Rect)}.
47          * @return True if handled by listener, otherwise returns {@link
48          * android.view.ViewGroup#onRequestFocusInDescendants(int, android.graphics.Rect)}.
49          */
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)50         boolean onRequestFocusInDescendants(int direction,
51                 Rect previouslyFocusedRect);
52         /**
53          * See {@link android.view.ViewGroup#requestChildFocus(
54          * android.view.View, android.view.View)}.
55          */
onRequestChildFocus(View child, View focused)56         void onRequestChildFocus(View child, View focused);
57     }
58 
BrowseFrameLayout(Context context)59     public BrowseFrameLayout(Context context) {
60         this(context, null, 0);
61     }
62 
BrowseFrameLayout(Context context, AttributeSet attrs)63     public BrowseFrameLayout(Context context, AttributeSet attrs) {
64         this(context, attrs, 0);
65     }
66 
BrowseFrameLayout(Context context, AttributeSet attrs, int defStyle)67     public BrowseFrameLayout(Context context, AttributeSet attrs, int defStyle) {
68         super(context, attrs, defStyle);
69     }
70 
71     private OnFocusSearchListener mListener;
72     private OnChildFocusListener mOnChildFocusListener;
73     private OnKeyListener mOnDispatchKeyListener;
74 
75     /**
76      * Sets a {@link OnFocusSearchListener}.
77      */
setOnFocusSearchListener(OnFocusSearchListener listener)78     public void setOnFocusSearchListener(OnFocusSearchListener listener) {
79         mListener = listener;
80     }
81 
82     /**
83      * Returns the {@link OnFocusSearchListener}.
84      */
getOnFocusSearchListener()85     public OnFocusSearchListener getOnFocusSearchListener() {
86         return mListener;
87     }
88 
89     /**
90      * Sets a {@link OnChildFocusListener}.
91      */
setOnChildFocusListener(OnChildFocusListener listener)92     public void setOnChildFocusListener(OnChildFocusListener listener) {
93         mOnChildFocusListener = listener;
94     }
95 
96     /**
97      * Returns the {@link OnChildFocusListener}.
98      */
getOnChildFocusListener()99     public OnChildFocusListener getOnChildFocusListener() {
100         return mOnChildFocusListener;
101     }
102 
103     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)104     protected boolean onRequestFocusInDescendants(int direction,
105             Rect previouslyFocusedRect) {
106         if (mOnChildFocusListener != null) {
107             if (mOnChildFocusListener.onRequestFocusInDescendants(direction,
108                     previouslyFocusedRect)) {
109                 return true;
110             }
111         }
112         return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
113     }
114 
115     @Override
focusSearch(View focused, int direction)116     public View focusSearch(View focused, int direction) {
117         if (mListener != null) {
118             View view = mListener.onFocusSearch(focused, direction);
119             if (view != null) {
120                 return view;
121             }
122         }
123         return super.focusSearch(focused, direction);
124     }
125 
126     @Override
requestChildFocus(View child, View focused)127     public void requestChildFocus(View child, View focused) {
128         if (mOnChildFocusListener != null) {
129             mOnChildFocusListener.onRequestChildFocus(child, focused);
130         }
131         super.requestChildFocus(child, focused);
132     }
133 
134     @Override
dispatchKeyEvent(KeyEvent event)135     public boolean dispatchKeyEvent(KeyEvent event) {
136         boolean consumed = super.dispatchKeyEvent(event);
137         if (mOnDispatchKeyListener != null) {
138             if (!consumed) {
139                 return mOnDispatchKeyListener.onKey(getRootView(), event.getKeyCode(), event);
140             }
141         }
142         return consumed;
143     }
144 
145     /**
146      * Sets the {@link android.view.View.OnKeyListener} on this view. This listener would fire
147      * only for unhandled {@link KeyEvent}s. We need to provide an external key listener to handle
148      * back button clicks when we are in full screen video mode because
149      * {@link View#setOnKeyListener(OnKeyListener)} doesn't fire as the focus is not on this view.
150      */
setOnDispatchKeyListener(OnKeyListener listener)151     public void setOnDispatchKeyListener(OnKeyListener listener) {
152         this.mOnDispatchKeyListener = listener;
153     }
154 }
155