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.settings.inputmethod;
18 
19 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
20 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
21 
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.content.Context;
25 import android.graphics.Color;
26 import android.graphics.Point;
27 import android.os.Bundle;
28 import android.view.Display;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.ViewTreeObserver;
33 import android.view.Window;
34 import android.view.WindowInsets;
35 import android.view.WindowManager;
36 import android.widget.Button;
37 import android.widget.FrameLayout;
38 import android.widget.ImageView;
39 
40 import androidx.viewpager.widget.PagerAdapter;
41 import androidx.viewpager.widget.ViewPager;
42 
43 import com.android.settings.R;
44 
45 import com.google.android.material.bottomsheet.BottomSheetBehavior;
46 import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 
51 public class TrackpadGestureDialogFragment extends BottomSheetDialogFragment {
52 
53     private Context mContext;
54     private LayoutInflater mInflater;
55     private View mViewArrowPrevious;
56     private View mViewArrowNext;
57     private ViewPager mViewPager;
58     private ArrayList<View> mPageList;
59     private ImageView[] mDotIndicators;
60     private View[] mViewPagerItems;
61     private Button mButtonStartSkip;
62     private Button mButtonStartRestart;
63     private Button mButtonEndNext;
64     private Button mButtonEndDone;
65 
66     private static final int DOT_INDICATOR_SIZE = 12;
67     private static final int DOT_INDICATOR_LEFT_PADDING = 6;
68     private static final int DOT_INDICATOR_RIGHT_PADDING = 6;
69 
TrackpadGestureDialogFragment()70     public TrackpadGestureDialogFragment() {}
71 
72     @Override
onAttach(Context context)73     public void onAttach(Context context) {
74         super.onAttach(context);
75         mContext = context;
76     }
77 
78     @Override
onCreate(Bundle savedInstanceState)79     public void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81     }
82 
83     @Override
onStart()84     public void onStart() {
85         super.onStart();
86         final Dialog dialog = getDialog();
87         if (dialog == null) {
88             return;
89         }
90         Window window = dialog.getWindow();
91         if (window == null) {
92             return;
93         }
94         setLayoutEdgeToEdge(window);
95         final Point size = getScreenSize();
96         final WindowManager.LayoutParams attributes = window.getAttributes();
97         attributes.width = (int) (size.x * 0.75);
98         window.setAttributes(attributes);
99     }
100 
101     @Override
onPause()102     public void onPause() {
103         super.onPause();
104         dismiss();
105     }
106 
setLayoutEdgeToEdge(Window window)107     private static void setLayoutEdgeToEdge(Window window) {
108         View windowDecorView = window.getDecorView();
109         windowDecorView.setSystemUiVisibility(
110                 windowDecorView.getSystemUiVisibility()
111                         | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
112                         | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
113         window.setNavigationBarColor(Color.TRANSPARENT);
114     }
115 
116     @Override
onCreateDialog(final Bundle savedInstanceState)117     public Dialog onCreateDialog(final Bundle savedInstanceState) {
118         Dialog dialog = super.onCreateDialog(savedInstanceState);
119 
120         mInflater = mContext.getSystemService(LayoutInflater.class);
121         View contentView = mInflater.inflate(R.layout.trackpad_gesture_preview, null);
122         addViewPager(contentView);
123         dialog.setContentView(contentView);
124         Window gestureDialogWindow = dialog.getWindow();
125         gestureDialogWindow.setType(TYPE_SYSTEM_DIALOG);
126 
127         // Workaround for solve issue about dialog not full expanded when landscape.
128         FrameLayout bottomSheet = (FrameLayout)
129                 dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
130         bottomSheet.setBackgroundResource(android.R.color.transparent);
131         BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
132         if (!isGestureNavigationEnabled()) {
133             behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
134         }
135         ViewTreeObserver observer = contentView.getViewTreeObserver();
136         observer.addOnGlobalLayoutListener(
137                 new ViewTreeObserver.OnGlobalLayoutListener() {
138                     @Override
139                     public void onGlobalLayout() {
140                         contentView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
141                         int contentViewHeight = contentView.getMeasuredHeight();
142                         int navigationBarHeight = getNavigationBarHeight();
143                         behavior.setPeekHeight(contentViewHeight - navigationBarHeight);
144                     }
145                 });
146 
147         // The gesture education view shouldn't be draggable."
148         behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
149             @Override
150             public void onStateChanged(View bottomSheet, int newState) {
151                 if (newState == BottomSheetBehavior.STATE_DRAGGING) {
152                     if (isGestureNavigationEnabled()) {
153                         behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
154                     } else {
155                         behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
156                     }
157                 }
158             }
159 
160             @Override
161             public void onSlide(View bottomSheet, float slideOffset) {
162                 // Do nothing.
163             }
164         });
165 
166         return dialog;
167     }
168 
getViewPagerResource()169     private ArrayList<Integer> getViewPagerResource() {
170         return new ArrayList<Integer>(
171                 Arrays.asList(
172                         R.layout.gesture_tip1_go_home,
173                         R.layout.gesture_tip2_go_back,
174                         R.layout.gesture_tip3_recent_apps,
175                         R.layout.gesture_tip4_notifications,
176                         R.layout.gesture_tip5_switch_apps));
177     }
178 
addViewPager(View preview)179     private void addViewPager(View preview) {
180         mViewPager = preview.findViewById(R.id.viewpager);
181         int viewPagerResourceSize = getViewPagerResource().size();
182         mViewPagerItems = new View[viewPagerResourceSize];
183         for (int i = 0; i < viewPagerResourceSize; i++) {
184             mViewPagerItems[i] =
185                     mInflater.inflate(getViewPagerResource().get(i), null /* root */);
186         }
187 
188         mPageList = new ArrayList<View>();
189         for (int i = 0; i < mViewPagerItems.length; i++) {
190             mPageList.add(mViewPagerItems[i]);
191         }
192 
193         mViewPager.setAdapter(new GesturePagerAdapter(mPageList));
194 
195         mButtonStartRestart = (Button) preview.findViewById(R.id.button_restart);
196         mButtonStartRestart.setOnClickListener(v -> {
197             final int firstPos = mViewPager.getCurrentItem() - mViewPagerItems.length;
198             mViewPager.setCurrentItem(firstPos, true);
199         });
200 
201         mButtonEndDone = (Button) preview.findViewById(R.id.button_done);
202         mButtonEndDone.setOnClickListener(v -> {
203             dismiss();
204         });
205 
206         mButtonStartSkip = (Button) preview.findViewById(R.id.button_skip);
207         mButtonStartSkip.setOnClickListener(v -> {
208             dismiss();
209         });
210 
211         mButtonEndNext = (Button) preview.findViewById(R.id.button_next);
212         mButtonEndNext.setOnClickListener(v -> {
213             final int nextPos = mViewPager.getCurrentItem() + 1;
214             mViewPager.setCurrentItem(nextPos, true);
215         });
216 
217         mViewPager.addOnPageChangeListener(createPageListener());
218         final ViewGroup viewGroup = (ViewGroup) preview.findViewById(R.id.viewGroup);
219         mDotIndicators = new ImageView[mPageList.size()];
220         for (int i = 0; i < mPageList.size(); i++) {
221             final ImageView imageView = new ImageView(mContext);
222             final ViewGroup.MarginLayoutParams lp =
223                     new ViewGroup.MarginLayoutParams(DOT_INDICATOR_SIZE, DOT_INDICATOR_SIZE);
224             lp.setMargins(DOT_INDICATOR_LEFT_PADDING, 0, DOT_INDICATOR_RIGHT_PADDING, 0);
225             imageView.setLayoutParams(lp);
226             mDotIndicators[i] = imageView;
227             viewGroup.addView(mDotIndicators[i]);
228         }
229     }
230 
231     private static class GesturePagerAdapter extends PagerAdapter {
232         private final ArrayList<View> mPageViewList;
233 
GesturePagerAdapter(ArrayList<View> pageViewList)234         GesturePagerAdapter(ArrayList<View> pageViewList) {
235             mPageViewList = pageViewList;
236         }
237 
238         @Override
destroyItem(ViewGroup container, int position, Object object)239         public void destroyItem(ViewGroup container, int position, Object object) {
240             if (mPageViewList.get(position) != null) {
241                 container.removeView(mPageViewList.get(position));
242             }
243         }
244 
245         @Override
instantiateItem(ViewGroup container, int position)246         public Object instantiateItem(ViewGroup container, int position) {
247             container.addView(mPageViewList.get(position));
248             return mPageViewList.get(position);
249         }
250 
251         @Override
getCount()252         public int getCount() {
253             return mPageViewList.size();
254         }
255 
256         @Override
isViewFromObject(View view, Object object)257         public boolean isViewFromObject(View view, Object object) {
258             return object == view;
259         }
260     }
261 
createPageListener()262     private ViewPager.OnPageChangeListener createPageListener() {
263         return new ViewPager.OnPageChangeListener() {
264             @Override
265             public void onPageScrolled(
266                     int position, float positionOffset, int positionOffsetPixels) {
267                 if (positionOffset != 0) {
268                     for (int i = 0; i < mPageList.size(); i++) {
269                         mViewPagerItems[i].setVisibility(View.VISIBLE);
270                     }
271                 } else {
272                     updateIndicator(position);
273                 }
274             }
275 
276             @Override
277             public void onPageSelected(int position) {}
278 
279             @Override
280             public void onPageScrollStateChanged(int state) {}
281         };
282     }
283 
284     private void updateIndicator(int position) {
285         for (int i = 0; i < mPageList.size(); i++) {
286             if (position == i) {
287                 mDotIndicators[i].setBackgroundResource(
288                         R.drawable.ic_color_page_indicator_focused);
289                 mViewPagerItems[i].setVisibility(View.VISIBLE);
290             } else {
291                 mDotIndicators[i].setBackgroundResource(
292                         R.drawable.ic_color_page_indicator_unfocused);
293                 mViewPagerItems[i].setVisibility(View.INVISIBLE);
294             }
295         }
296 
297         if (position >= 0 && position < mViewPagerItems.length - 1) {
298             mButtonStartSkip.setVisibility(View.VISIBLE);
299             mButtonEndNext.setVisibility(View.VISIBLE);
300             mButtonStartRestart.setVisibility(View.GONE);
301             mButtonEndDone.setVisibility(View.GONE);
302         } else {
303             mButtonStartSkip.setVisibility(View.GONE);
304             mButtonEndNext.setVisibility(View.GONE);
305             mButtonStartRestart.setVisibility(View.VISIBLE);
306             mButtonEndDone.setVisibility(View.VISIBLE);
307         }
308     }
309 
310     private Point getScreenSize() {
311         final Point size = new Point();
312         final Activity activity = (Activity) mContext;
313         final Display display = activity.getWindowManager().getDefaultDisplay();
314         display.getSize(size);
315         return size;
316     }
317 
318     private int getNavigationBarHeight() {
319         final Activity activity = (Activity) mContext;
320         WindowInsets insets =
321                 activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
322         return insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
323     }
324 
325     private boolean isGestureNavigationEnabled() {
326         return NAV_BAR_MODE_GESTURAL == mContext.getResources().getInteger(
327                 com.android.internal.R.integer.config_navBarInteractionMode);
328     }
329 }
330