1/* 2 * Copyright (C) 2015 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 17package android.support.v17.leanback.widget; 18 19import android.content.Context; 20import android.graphics.Rect; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.View; 26import android.view.ViewGroup; 27import android.widget.FrameLayout; 28 29import java.util.ArrayList; 30 31/** 32 * Saves the focused grandchild position. 33 * Helps add persistent focus feature to various ViewGroups. 34 * @hide 35 */ 36class PersistentFocusWrapper extends FrameLayout { 37 38 private static final String TAG = "PersistentFocusWrapper"; 39 private static final boolean DEBUG = false; 40 41 private int mSelectedPosition = -1; 42 43 /** 44 * By default, focus is persisted when searching vertically 45 * but not horizontally. 46 */ 47 private boolean mPersistFocusVertical = true; 48 49 public PersistentFocusWrapper(Context context, AttributeSet attrs) { 50 super(context, attrs); 51 } 52 53 public PersistentFocusWrapper(Context context, AttributeSet attrs, int defStyle) { 54 super(context, attrs, defStyle); 55 } 56 57 int getGrandChildCount() { 58 ViewGroup wrapper = (ViewGroup) getChildAt(0); 59 return wrapper == null ? 0 : wrapper.getChildCount(); 60 } 61 62 /** 63 * Clears the selected position and clears focus. 64 */ 65 public void clearSelection() { 66 mSelectedPosition = -1; 67 if (hasFocus()) { 68 clearFocus(); 69 } 70 } 71 72 /** 73 * Persist focus when focus search direction is up or down. 74 */ 75 public void persistFocusVertical() { 76 mPersistFocusVertical = true; 77 } 78 79 /** 80 * Persist focus when focus search direction is left or right. 81 */ 82 public void persistFocusHorizontal() { 83 mPersistFocusVertical = false; 84 } 85 86 private boolean shouldPersistFocusFromDirection(int direction) { 87 return ((mPersistFocusVertical && (direction == FOCUS_UP || direction == FOCUS_DOWN)) || 88 (!mPersistFocusVertical && (direction == FOCUS_LEFT || direction == FOCUS_RIGHT))); 89 } 90 91 @Override 92 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 93 if (DEBUG) Log.v(TAG, "addFocusables"); 94 if (hasFocus() || getGrandChildCount() == 0 || 95 !shouldPersistFocusFromDirection(direction)) { 96 super.addFocusables(views, direction, focusableMode); 97 } else { 98 // Select a child in requestFocus 99 views.add(this); 100 } 101 } 102 103 @Override 104 public void requestChildFocus(View child, View focused) { 105 super.requestChildFocus(child, focused); 106 View view = focused; 107 while (view != null && view.getParent() != child) { 108 view = (View) view.getParent(); 109 } 110 mSelectedPosition = view == null ? -1 : ((ViewGroup) child).indexOfChild(view); 111 if (DEBUG) Log.v(TAG, "requestChildFocus focused " + focused + " mSelectedPosition " + mSelectedPosition); 112 } 113 114 @Override 115 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 116 if (DEBUG) Log.v(TAG, "requestFocus mSelectedPosition " + mSelectedPosition); 117 ViewGroup wrapper = (ViewGroup) getChildAt(0); 118 if (wrapper != null && mSelectedPosition >= 0 && mSelectedPosition < getGrandChildCount()) { 119 if (wrapper.getChildAt(mSelectedPosition).requestFocus( 120 direction, previouslyFocusedRect)) { 121 return true; 122 } 123 } 124 return super.requestFocus(direction, previouslyFocusedRect); 125 } 126 127 static class SavedState extends View.BaseSavedState { 128 129 int mSelectedPosition; 130 131 SavedState(Parcel in) { 132 super(in); 133 mSelectedPosition = in.readInt(); 134 } 135 136 SavedState(Parcelable superState) { 137 super(superState); 138 } 139 140 @Override 141 public void writeToParcel(Parcel dest, int flags) { 142 super.writeToParcel(dest, flags); 143 dest.writeInt(mSelectedPosition); 144 } 145 146 public static final Parcelable.Creator<SavedState> CREATOR 147 = new Parcelable.Creator<SavedState>() { 148 @Override 149 public SavedState createFromParcel(Parcel in) { 150 return new SavedState(in); 151 } 152 153 @Override 154 public SavedState[] newArray(int size) { 155 return new SavedState[size]; 156 } 157 }; 158 } 159 160 @Override 161 protected Parcelable onSaveInstanceState() { 162 if (DEBUG) Log.v(TAG, "onSaveInstanceState"); 163 SavedState savedState = new SavedState(super.onSaveInstanceState()); 164 savedState.mSelectedPosition = mSelectedPosition; 165 return savedState; 166 } 167 168 @Override 169 protected void onRestoreInstanceState(Parcelable state) { 170 if (!(state instanceof SavedState)) { 171 super.onRestoreInstanceState(state); 172 return; 173 } 174 SavedState savedState = (SavedState) state; 175 mSelectedPosition = ((SavedState) state).mSelectedPosition; 176 if (DEBUG) Log.v(TAG, "onRestoreInstanceState mSelectedPosition " + mSelectedPosition); 177 super.onRestoreInstanceState(savedState.getSuperState()); 178 } 179} 180