• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.systemui.accessibility.accessibilitymenu.view;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.graphics.Insets;
22 import android.util.DisplayMetrics;
23 import android.view.LayoutInflater;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
27 import android.view.WindowInsets;
28 import android.view.WindowManager;
29 import android.view.WindowMetrics;
30 import android.widget.GridView;
31 
32 import androidx.viewpager.widget.ViewPager;
33 
34 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
35 import com.android.systemui.accessibility.accessibilitymenu.R;
36 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
37 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
38 import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuFooter.A11yMenuFooterCallBack;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 /**
44  * This class handles UI for viewPager and footer.
45  * It displays grid pages containing all shortcuts in viewPager,
46  * and handles the click events from footer to switch between pages.
47  */
48 public class A11yMenuViewPager {
49 
50     /** The default index of the ViewPager. */
51     public static final int DEFAULT_PAGE_INDEX = 0;
52 
53     /**
54      * The class holds the static parameters for grid view when large button settings is on/off.
55      */
56     public static final class GridViewParams {
57         /** Total shortcuts count in the grid view when large button settings is off. */
58         public static final int GRID_ITEM_COUNT = 9;
59 
60         /** The number of columns in the grid view when large button settings is off. */
61         public static final int GRID_COLUMN_COUNT = 3;
62 
63         /** Total shortcuts count in the grid view when large button settings is on. */
64         public static final int LARGE_GRID_ITEM_COUNT = 4;
65 
66         /** The number of columns in the grid view when large button settings is on. */
67         public static final int LARGE_GRID_COLUMN_COUNT = 2;
68 
69         /**
70          * Returns the number of items in the grid view.
71          *
72          * @param context The parent context
73          * @return Grid item count
74          */
getGridItemCount(Context context)75         public static int getGridItemCount(Context context) {
76             return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
77                    ? LARGE_GRID_ITEM_COUNT
78                    : GRID_ITEM_COUNT;
79         }
80 
81         /**
82          * Returns the number of columns in the grid view.
83          *
84          * @param context The parent context
85          * @return Grid column count
86          */
getGridColumnCount(Context context)87         public static int getGridColumnCount(Context context) {
88             return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
89                    ? LARGE_GRID_COLUMN_COUNT
90                    : GRID_COLUMN_COUNT;
91         }
92 
93         /**
94          * Returns the number of rows in the grid view.
95          *
96          * @param context The parent context
97          * @return Grid row count
98          */
getGridRowCount(Context context)99         public static int getGridRowCount(Context context) {
100             return A11yMenuPreferenceFragment.isLargeButtonsEnabled(context)
101                    ? (LARGE_GRID_ITEM_COUNT / LARGE_GRID_COLUMN_COUNT)
102                    : (GRID_ITEM_COUNT / GRID_COLUMN_COUNT);
103         }
104 
105         /**
106          * Separates a provided list of accessibility shortcuts into multiple sub-lists.
107          * Does not modify the original list.
108          *
109          * @param pageItemCount The maximum size of an individual sub-list.
110          * @param shortcutList The list of shortcuts to be separated into sub-lists.
111          * @return A list of shortcut sub-lists.
112          */
generateShortcutSubLists( int pageItemCount, List<A11yMenuShortcut> shortcutList)113         public static List<List<A11yMenuShortcut>> generateShortcutSubLists(
114                 int pageItemCount, List<A11yMenuShortcut> shortcutList) {
115             int start = 0;
116             int end;
117             int shortcutListSize = shortcutList.size();
118             List<List<A11yMenuShortcut>> subLists = new ArrayList<>();
119             while (start < shortcutListSize) {
120                 end = Math.min(start + pageItemCount, shortcutListSize);
121                 subLists.add(shortcutList.subList(start, end));
122                 start = end;
123             }
124             return subLists;
125         }
126 
GridViewParams()127         private GridViewParams() {}
128     }
129 
130     private final AccessibilityMenuService mService;
131 
132     /**
133      * The pager widget, which handles animation and allows swiping horizontally to access previous
134      * and next gridView pages.
135      */
136     protected ViewPager mViewPager;
137 
138     private ViewPagerAdapter<GridView> mViewPagerAdapter;
139     private final List<GridView> mGridPageList = new ArrayList<>();
140 
141     /** The footer, which provides buttons to switch between pages */
142     protected A11yMenuFooter mA11yMenuFooter;
143 
144     /** The shortcut list intended to show in grid pages of viewPager */
145     private List<A11yMenuShortcut> mA11yMenuShortcutList;
146 
147     /** The container layout for a11y menu. */
148     private ViewGroup mA11yMenuLayout;
149 
A11yMenuViewPager(AccessibilityMenuService service)150     public A11yMenuViewPager(AccessibilityMenuService service) {
151         this.mService = service;
152     }
153 
154     /**
155      * Configures UI for view pager and footer.
156      *
157      * @param a11yMenuLayout the container layout for a11y menu
158      * @param shortcutDataList the data list need to show in view pager
159      * @param pageIndex the index of ViewPager to show
160      */
configureViewPagerAndFooter( ViewGroup a11yMenuLayout, List<A11yMenuShortcut> shortcutDataList, int pageIndex)161     public void configureViewPagerAndFooter(
162             ViewGroup a11yMenuLayout, List<A11yMenuShortcut> shortcutDataList, int pageIndex) {
163         this.mA11yMenuLayout = a11yMenuLayout;
164         mA11yMenuShortcutList = shortcutDataList;
165         initViewPager();
166         initChildPage();
167         mA11yMenuFooter = new A11yMenuFooter(a11yMenuLayout, mFooterCallbacks);
168         updateFooterState();
169         registerOnGlobalLayoutListener();
170         goToPage(pageIndex);
171     }
172 
173     /** Initializes viewPager and its adapter. */
initViewPager()174     private void initViewPager() {
175         mViewPager = mA11yMenuLayout.findViewById(R.id.view_pager);
176         mViewPagerAdapter = new ViewPagerAdapter<>();
177         mViewPager.setAdapter(mViewPagerAdapter);
178         mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
179         mViewPager.addOnPageChangeListener(
180                 new ViewPager.OnPageChangeListener() {
181                     @Override
182                     public void onPageScrollStateChanged(int state) {}
183 
184                     @Override
185                     public void onPageScrolled(
186                             int position, float positionOffset, int positionOffsetPixels) {}
187 
188                     @Override
189                     public void onPageSelected(int position) {
190                         updateFooterState();
191                     }
192                 });
193     }
194 
195     /** Creates child pages of viewPager by the length of shortcuts and initializes them. */
initChildPage()196     private void initChildPage() {
197         if (mA11yMenuShortcutList == null || mA11yMenuShortcutList.isEmpty()) {
198             return;
199         }
200 
201         if (!mGridPageList.isEmpty()) {
202             mGridPageList.clear();
203         }
204 
205         // Generate pages by calculating # of items per grid.
206         for (List<A11yMenuShortcut> page : GridViewParams.generateShortcutSubLists(
207                 GridViewParams.getGridItemCount(mService), mA11yMenuShortcutList)
208         ) {
209             addGridPage(page);
210         }
211 
212         mViewPagerAdapter.set(mGridPageList);
213     }
214 
addGridPage(List<A11yMenuShortcut> shortcutDataListInPage)215     private void addGridPage(List<A11yMenuShortcut> shortcutDataListInPage) {
216         LayoutInflater inflater = LayoutInflater.from(mService);
217         View view = inflater.inflate(R.layout.grid_view, null);
218         GridView gridView = view.findViewById(R.id.gridview);
219         A11yMenuAdapter adapter = new A11yMenuAdapter(mService, shortcutDataListInPage);
220         gridView.setNumColumns(GridViewParams.getGridColumnCount(mService));
221         gridView.setAdapter(adapter);
222         mGridPageList.add(gridView);
223     }
224 
225     /** Updates footer's state by index of current page in view pager. */
updateFooterState()226     private void updateFooterState() {
227         int currentPage = mViewPager.getCurrentItem();
228         int lastPage = mViewPager.getAdapter().getCount() - 1;
229         mA11yMenuFooter.getPreviousPageBtn().setEnabled(currentPage > 0);
230         mA11yMenuFooter.getNextPageBtn().setEnabled(currentPage < lastPage);
231     }
232 
233     private void goToPage(int pageIndex) {
234         if (mViewPager == null) {
235             return;
236         }
237         if ((pageIndex >= 0) && (pageIndex < mViewPager.getAdapter().getCount())) {
238             mViewPager.setCurrentItem(pageIndex);
239         }
240     }
241 
242     /** Registers OnGlobalLayoutListener to adjust menu UI by running callback at first time. */
243     private void registerOnGlobalLayoutListener() {
244         mA11yMenuLayout
245                 .getViewTreeObserver()
246                 .addOnGlobalLayoutListener(
247                         new OnGlobalLayoutListener() {
248 
249                             boolean mIsFirstTime = true;
250 
251                             @Override
252                             public void onGlobalLayout() {
253                                 if (!mIsFirstTime) {
254                                     return;
255                                 }
256 
257                                 if (mGridPageList.isEmpty()) {
258                                     return;
259                                 }
260 
261                                 GridView firstGridView = mGridPageList.get(0);
262                                 if (firstGridView == null
263                                         || firstGridView.getChildAt(0) == null) {
264                                     return;
265                                 }
266 
267                                 mIsFirstTime = false;
268 
269                                 int gridItemHeight = firstGridView.getChildAt(0)
270                                                 .getMeasuredHeight();
271                                 adjustMenuUISize(gridItemHeight);
272                             }
273                         });
274     }
275 
276     /**
277      * Adjusts menu UI to fit both landscape and portrait mode.
278      *
279      * <ol>
280      *   <li>Adjust view pager's height.
281      *   <li>Adjust vertical interval between grid items.
282      *   <li>Adjust padding in view pager.
283      * </ol>
284      */
285     private void adjustMenuUISize(int gridItemHeight) {
286         final int rowsInGridView = GridViewParams.getGridRowCount(mService);
287         final int defaultMargin =
288                 (int) mService.getResources().getDimension(R.dimen.a11ymenu_layout_margin);
289         final int topMargin = (int) mService.getResources().getDimension(R.dimen.table_margin_top);
290         final int displayMode = mService.getResources().getConfiguration().orientation;
291         int viewPagerHeight = mViewPager.getMeasuredHeight();
292 
293         if (displayMode == Configuration.ORIENTATION_PORTRAIT) {
294             // In portrait mode, we only need to adjust view pager's height to match its
295             // child's height.
296             viewPagerHeight = gridItemHeight * rowsInGridView + defaultMargin + topMargin;
297         } else if (displayMode == Configuration.ORIENTATION_LANDSCAPE) {
298             // In landscape mode, we need to adjust view pager's height to match screen height
299             // and adjust its child too,
300             // because a11y menu layout height is limited by the screen height.
301             DisplayMetrics displayMetrics = mService.getResources().getDisplayMetrics();
302             float densityScale = (float) displayMetrics.densityDpi
303                     / DisplayMetrics.DENSITY_DEVICE_STABLE;
304             View footerLayout = mA11yMenuLayout.findViewById(R.id.footerlayout);
305             // Keeps footer window height unchanged no matter the density is changed.
306             footerLayout.getLayoutParams().height =
307                     (int) (footerLayout.getLayoutParams().height / densityScale);
308             // Adjust the view pager height for system bar and display cutout insets.
309             WindowManager windowManager = mService.getSystemService(WindowManager.class);
310             WindowMetrics windowMetric = windowManager.getCurrentWindowMetrics();
311             Insets windowInsets = windowMetric.getWindowInsets().getInsetsIgnoringVisibility(
312                     WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
313             viewPagerHeight =
314                     windowMetric.getBounds().height()
315                             - footerLayout.getLayoutParams().height
316                             - windowInsets.bottom;
317             // Sets vertical interval between grid items.
318             int interval =
319                     (viewPagerHeight - topMargin - defaultMargin
320                             - (rowsInGridView * gridItemHeight))
321                             / (rowsInGridView + 1);
322             for (GridView gridView : mGridPageList) {
323                 gridView.setVerticalSpacing(interval);
324             }
325 
326             // Sets padding to view pager.
327             final int finalMarginTop = interval + topMargin;
328             mViewPager.setPadding(defaultMargin, finalMarginTop, defaultMargin, defaultMargin);
329         }
330         final ViewGroup.LayoutParams layoutParams = mViewPager.getLayoutParams();
331         layoutParams.height = viewPagerHeight;
332         mViewPager.setLayoutParams(layoutParams);
333     }
334 
335     /** Callback object to handle click events from A11yMenuFooter */
336     protected A11yMenuFooterCallBack mFooterCallbacks =
337             new A11yMenuFooterCallBack() {
338                 @Override
339                 public void onLeftButtonClicked() {
340                     // Moves to previous page.
341                     int targetPage = mViewPager.getCurrentItem() - 1;
342                     goToPage(targetPage);
343                     updateFooterState();
344                 }
345 
346                 @Override
347                 public void onRightButtonClicked() {
348                     // Moves to next page.
349                     int targetPage = mViewPager.getCurrentItem() + 1;
350                     goToPage(targetPage);
351                     updateFooterState();
352                 }
353             };
354 }
355