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