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