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