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 17 package com.android.tv.common.ui.setup; 18 19 import static android.content.Context.ACCESSIBILITY_SERVICE; 20 21 import android.os.Bundle; 22 import androidx.leanback.app.GuidedStepFragment; 23 import androidx.leanback.widget.GuidanceStylist; 24 import androidx.leanback.widget.GuidedAction; 25 import androidx.leanback.widget.GuidedActionsStylist; 26 import androidx.leanback.widget.VerticalGridView; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.View.AccessibilityDelegate; 30 import android.view.ViewGroup; 31 import android.view.ViewGroup.MarginLayoutParams; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.view.accessibility.AccessibilityManager; 34 import android.view.accessibility.AccessibilityNodeInfo; 35 import android.widget.LinearLayout; 36 import com.android.tv.common.R; 37 38 /** A fragment for channel source info/setup. */ 39 public abstract class SetupGuidedStepFragment extends GuidedStepFragment { 40 /** 41 * Key of the argument which indicate whether the parent of this fragment has three panes. 42 * 43 * <p>Value type: boolean 44 */ 45 public static final String KEY_THREE_PANE = "key_three_pane"; 46 47 private View mContentFragment; 48 private boolean mFromContentFragment; 49 private boolean mAccessibilityMode; 50 51 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)52 public View onCreateView( 53 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 54 View view = super.onCreateView(inflater, container, savedInstanceState); 55 Bundle arguments = getArguments(); 56 view.findViewById(androidx.leanback.R.id.action_fragment_root) 57 .setPadding(0, 0, 0, 0); 58 mContentFragment = view.findViewById(androidx.leanback.R.id.content_fragment); 59 LinearLayout.LayoutParams guidanceLayoutParams = 60 (LinearLayout.LayoutParams) mContentFragment.getLayoutParams(); 61 guidanceLayoutParams.weight = 0; 62 if (arguments != null && arguments.getBoolean(KEY_THREE_PANE, false)) { 63 // Content fragment. 64 guidanceLayoutParams.width = 65 getResources() 66 .getDimensionPixelOffset( 67 R.dimen.setup_guidedstep_guidance_section_width_3pane); 68 int doneButtonWidth = 69 getResources() 70 .getDimensionPixelOffset(R.dimen.setup_done_button_container_width); 71 // Guided actions list 72 View list = view.findViewById(androidx.leanback.R.id.guidedactions_list); 73 MarginLayoutParams marginLayoutParams = (MarginLayoutParams) list.getLayoutParams(); 74 // Use content view to check layout direction while view is being created. 75 if (getResources().getConfiguration().getLayoutDirection() 76 == View.LAYOUT_DIRECTION_LTR) { 77 marginLayoutParams.rightMargin = doneButtonWidth; 78 } else { 79 marginLayoutParams.leftMargin = doneButtonWidth; 80 } 81 } else { 82 // Content fragment. 83 guidanceLayoutParams.width = 84 getResources() 85 .getDimensionPixelOffset( 86 R.dimen.setup_guidedstep_guidance_section_width_2pane); 87 } 88 // gridView Alignment 89 VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView(); 90 int offset = 91 getResources() 92 .getDimensionPixelOffset(R.dimen.setup_guidedactions_selector_margin_top); 93 gridView.setWindowAlignmentOffset(offset); 94 gridView.setWindowAlignmentOffsetPercent(0); 95 gridView.setItemAlignmentOffsetPercent(0); 96 ((ViewGroup) view.findViewById(androidx.leanback.R.id.guidedactions_list)) 97 .setTransitionGroup(false); 98 // Needed for the shared element transition. 99 // content_frame is defined in leanback. 100 ViewGroup group = 101 (ViewGroup) view.findViewById(androidx.leanback.R.id.content_frame); 102 group.setClipChildren(false); 103 group.setClipToPadding(false); 104 return view; 105 } 106 107 @Override onCreateActionsStylist()108 public GuidedActionsStylist onCreateActionsStylist() { 109 return new SetupGuidedStepFragmentGuidedActionsStylist(); 110 } 111 112 @Override onResume()113 public void onResume() { 114 super.onResume(); 115 AccessibilityManager am = 116 (AccessibilityManager) getActivity().getSystemService(ACCESSIBILITY_SERVICE); 117 mAccessibilityMode = am != null && am.isEnabled() && am.isTouchExplorationEnabled(); 118 mContentFragment.setFocusable(mAccessibilityMode); 119 if (mAccessibilityMode) { 120 mContentFragment.setAccessibilityDelegate( 121 new AccessibilityDelegate() { 122 @Override 123 public boolean performAccessibilityAction(View host, int action, Bundle args) { 124 if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS 125 && !getActions().isEmpty()) { 126 // scroll to the top. This makes the first action view on the screen. 127 // Otherwise, the view can be recycled, so accessibility events cannot 128 // be sent later. 129 getGuidedActionsStylist().getActionsGridView().scrollToPosition(0); 130 mFromContentFragment = true; 131 } 132 return super.performAccessibilityAction(host, action, args); 133 } 134 }); 135 mContentFragment.requestFocus(); 136 } 137 } 138 139 @Override onCreateGuidanceStylist()140 public GuidanceStylist onCreateGuidanceStylist() { 141 return new GuidanceStylist() { 142 @Override 143 public View onCreateView( 144 LayoutInflater inflater, ViewGroup container, Guidance guidance) { 145 View view = super.onCreateView(inflater, container, guidance); 146 if (guidance.getIconDrawable() == null) { 147 // Icon view should not take up space when we don't use image. 148 getIconView().setVisibility(View.GONE); 149 } 150 return view; 151 } 152 }; 153 } 154 155 protected abstract String getActionCategory(); 156 157 protected View getDoneButton() { 158 return getActivity().findViewById(R.id.button_done); 159 } 160 161 @Override 162 public void onGuidedActionClicked(GuidedAction action) { 163 if (!action.isFocusable()) { 164 // an unfocusable action may be clicked in accessibility mode when it's accessibility 165 // focused 166 return; 167 } 168 SetupActionHelper.onActionClick(this, getActionCategory(), (int) action.getId()); 169 } 170 171 @Override 172 protected void onProvideFragmentTransitions() { 173 // Don't use the fragment transition defined in GuidedStepFragment. 174 } 175 176 @Override 177 public boolean isFocusOutEndAllowed() { 178 return true; 179 } 180 181 protected void setAccessibilityDelegate(GuidedActionsStylist.ViewHolder vh, 182 GuidedAction action) { 183 if (!mAccessibilityMode || findActionPositionById(action.getId()) == 0) { 184 return; 185 } 186 vh.itemView.setAccessibilityDelegate( 187 new AccessibilityDelegate() { 188 @Override 189 public boolean performAccessibilityAction(View host, int action, Bundle args) { 190 if ((action == AccessibilityNodeInfo.ACTION_FOCUS 191 || action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) 192 && mFromContentFragment) { 193 // block the action and make the first action view accessibility focused 194 View view = getActionItemView(0); 195 if (view != null) { 196 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); 197 mFromContentFragment = false; 198 return true; 199 } 200 } 201 return super.performAccessibilityAction(host, action, args); 202 } 203 }); 204 } 205 206 private class SetupGuidedStepFragmentGuidedActionsStylist extends GuidedActionsStylist { 207 208 @Override 209 public void onBindViewHolder(GuidedActionsStylist.ViewHolder vh, GuidedAction action) { 210 super.onBindViewHolder(vh, action); 211 setAccessibilityDelegate(vh, action); 212 } 213 } 214 } 215