1 /* 2 * Copyright (C) 2019 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.launcher3.touch; 18 19 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 20 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 21 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL; 22 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; 23 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; 24 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; 25 26 import android.content.res.Resources; 27 import android.graphics.PointF; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.graphics.drawable.ShapeDrawable; 31 import android.util.FloatProperty; 32 import android.view.MotionEvent; 33 import android.view.Surface; 34 import android.view.VelocityTracker; 35 import android.view.View; 36 import android.view.accessibility.AccessibilityEvent; 37 import android.widget.LinearLayout; 38 39 import com.android.launcher3.DeviceProfile; 40 import com.android.launcher3.R; 41 import com.android.launcher3.Utilities; 42 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 43 import com.android.launcher3.views.BaseDragLayer; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 48 public class PortraitPagedViewHandler implements PagedOrientationHandler { 49 50 @Override getPrimaryValue(T x, T y)51 public <T> T getPrimaryValue(T x, T y) { 52 return x; 53 } 54 55 @Override getSecondaryValue(T x, T y)56 public <T> T getSecondaryValue(T x, T y) { 57 return y; 58 } 59 60 @Override getPrimaryValue(int x, int y)61 public int getPrimaryValue(int x, int y) { 62 return x; 63 } 64 65 @Override getSecondaryValue(int x, int y)66 public int getSecondaryValue(int x, int y) { 67 return y; 68 } 69 70 @Override getPrimaryValue(float x, float y)71 public float getPrimaryValue(float x, float y) { 72 return x; 73 } 74 75 @Override getSecondaryValue(float x, float y)76 public float getSecondaryValue(float x, float y) { 77 return y; 78 } 79 80 @Override isLayoutNaturalToLauncher()81 public boolean isLayoutNaturalToLauncher() { 82 return true; 83 } 84 85 @Override adjustFloatingIconStartVelocity(PointF velocity)86 public void adjustFloatingIconStartVelocity(PointF velocity) { 87 //no-op 88 } 89 90 @Override set(T target, Int2DAction<T> action, int param)91 public <T> void set(T target, Int2DAction<T> action, int param) { 92 action.call(target, param, 0); 93 } 94 95 @Override set(T target, Float2DAction<T> action, float param)96 public <T> void set(T target, Float2DAction<T> action, float param) { 97 action.call(target, param, 0); 98 } 99 100 @Override setSecondary(T target, Float2DAction<T> action, float param)101 public <T> void setSecondary(T target, Float2DAction<T> action, float param) { 102 action.call(target, 0, param); 103 } 104 105 @Override getPrimaryDirection(MotionEvent event, int pointerIndex)106 public float getPrimaryDirection(MotionEvent event, int pointerIndex) { 107 return event.getX(pointerIndex); 108 } 109 110 @Override getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId)111 public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { 112 return velocityTracker.getXVelocity(pointerId); 113 } 114 115 @Override getMeasuredSize(View view)116 public int getMeasuredSize(View view) { 117 return view.getMeasuredWidth(); 118 } 119 120 @Override getPrimarySize(View view)121 public int getPrimarySize(View view) { 122 return view.getWidth(); 123 } 124 125 @Override getPrimarySize(RectF rect)126 public float getPrimarySize(RectF rect) { 127 return rect.width(); 128 } 129 130 @Override getStart(RectF rect)131 public float getStart(RectF rect) { 132 return rect.left; 133 } 134 135 @Override getEnd(RectF rect)136 public float getEnd(RectF rect) { 137 return rect.right; 138 } 139 140 @Override getClearAllSidePadding(View view, boolean isRtl)141 public int getClearAllSidePadding(View view, boolean isRtl) { 142 return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2; 143 } 144 145 @Override getSecondaryDimension(View view)146 public int getSecondaryDimension(View view) { 147 return view.getHeight(); 148 } 149 150 @Override getPrimaryViewTranslate()151 public FloatProperty<View> getPrimaryViewTranslate() { 152 return VIEW_TRANSLATE_X; 153 } 154 155 @Override getSecondaryViewTranslate()156 public FloatProperty<View> getSecondaryViewTranslate() { 157 return VIEW_TRANSLATE_Y; 158 } 159 160 @Override getSplitTaskViewDismissDirection(SplitPositionOption splitPosition, DeviceProfile dp)161 public int getSplitTaskViewDismissDirection(SplitPositionOption splitPosition, 162 DeviceProfile dp) { 163 if (splitPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT) { 164 if (dp.isLandscape) { 165 // Left side 166 return SPLIT_TRANSLATE_PRIMARY_NEGATIVE; 167 } else { 168 // Top side 169 return SPLIT_TRANSLATE_SECONDARY_NEGATIVE; 170 } 171 } else if (splitPosition.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { 172 // We don't have a bottom option, so should be right 173 return SPLIT_TRANSLATE_PRIMARY_POSITIVE; 174 } 175 throw new IllegalStateException("Invalid split stage position: " + 176 splitPosition.mStagePosition); 177 } 178 179 @Override getPrimaryScroll(View view)180 public int getPrimaryScroll(View view) { 181 return view.getScrollX(); 182 } 183 184 @Override getPrimaryScale(View view)185 public float getPrimaryScale(View view) { 186 return view.getScaleX(); 187 } 188 189 @Override setMaxScroll(AccessibilityEvent event, int maxScroll)190 public void setMaxScroll(AccessibilityEvent event, int maxScroll) { 191 event.setMaxScrollX(maxScroll); 192 } 193 194 @Override getRecentsRtlSetting(Resources resources)195 public boolean getRecentsRtlSetting(Resources resources) { 196 return !Utilities.isRtl(resources); 197 } 198 199 @Override getDegreesRotated()200 public float getDegreesRotated() { 201 return 0; 202 } 203 204 @Override getRotation()205 public int getRotation() { 206 return Surface.ROTATION_0; 207 } 208 209 @Override getChildStart(View view)210 public int getChildStart(View view) { 211 return view.getLeft(); 212 } 213 214 @Override getChildStartWithTranslation(View view)215 public float getChildStartWithTranslation(View view) { 216 return view.getLeft() + view.getTranslationX(); 217 } 218 219 @Override getCenterForPage(View view, Rect insets)220 public int getCenterForPage(View view, Rect insets) { 221 return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top 222 - insets.bottom - view.getPaddingBottom()) / 2; 223 } 224 225 @Override getScrollOffsetStart(View view, Rect insets)226 public int getScrollOffsetStart(View view, Rect insets) { 227 return insets.left + view.getPaddingLeft(); 228 } 229 230 @Override getScrollOffsetEnd(View view, Rect insets)231 public int getScrollOffsetEnd(View view, Rect insets) { 232 return view.getWidth() - view.getPaddingRight() - insets.right; 233 } 234 235 @Override getPrimaryTranslationDirectionFactor()236 public int getPrimaryTranslationDirectionFactor() { 237 return 1; 238 } 239 getSecondaryTranslationDirectionFactor()240 public int getSecondaryTranslationDirectionFactor() { 241 return -1; 242 } 243 244 @Override getSplitTranslationDirectionFactor(int stagePosition)245 public int getSplitTranslationDirectionFactor(int stagePosition) { 246 if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { 247 return -1; 248 } else { 249 return 1; 250 } 251 } 252 253 @Override getSplitAnimationTranslation(int translationOffset, DeviceProfile dp)254 public int getSplitAnimationTranslation(int translationOffset, DeviceProfile dp) { 255 if (dp.isLandscape) { 256 return translationOffset; 257 } 258 return 0; 259 } 260 261 @Override getTaskMenuX(float x, View thumbnailView, int overScroll)262 public float getTaskMenuX(float x, View thumbnailView, int overScroll) { 263 return x + overScroll; 264 } 265 266 @Override getTaskMenuY(float y, View thumbnailView, int overScroll)267 public float getTaskMenuY(float y, View thumbnailView, int overScroll) { 268 return y; 269 } 270 271 @Override getTaskMenuWidth(View view)272 public int getTaskMenuWidth(View view) { 273 return view.getMeasuredWidth(); 274 } 275 276 @Override setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable)277 public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, 278 LinearLayout taskMenuLayout, int dividerSpacing, 279 ShapeDrawable dividerDrawable) { 280 if (deviceProfile.isLandscape && !deviceProfile.isTablet) { 281 // Phone landscape 282 taskMenuLayout.setOrientation(LinearLayout.HORIZONTAL); 283 dividerDrawable.setIntrinsicWidth(dividerSpacing); 284 } else { 285 // Phone Portrait, LargeScreen Landscape/Portrait 286 taskMenuLayout.setOrientation(LinearLayout.VERTICAL); 287 dividerDrawable.setIntrinsicHeight(dividerSpacing); 288 } 289 taskMenuLayout.setDividerDrawable(dividerDrawable); 290 } 291 292 @Override setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile)293 public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, 294 LinearLayout viewGroup, DeviceProfile deviceProfile) { 295 if (deviceProfile.isLandscape && !deviceProfile.isTablet) { 296 // Phone landscape 297 viewGroup.setOrientation(LinearLayout.VERTICAL); 298 lp.width = 0; 299 lp.weight = 1; 300 Utilities.setStartMarginForView(viewGroup.findViewById(R.id.text), 0); 301 Utilities.setStartMarginForView(viewGroup.findViewById(R.id.icon), 0); 302 } else { 303 // Phone Portrait, LargeScreen Landscape/Portrait 304 viewGroup.setOrientation(LinearLayout.HORIZONTAL); 305 lp.width = LinearLayout.LayoutParams.MATCH_PARENT; 306 } 307 308 lp.height = LinearLayout.LayoutParams.WRAP_CONTENT; 309 } 310 311 @Override setTaskMenuAroundTaskView(LinearLayout taskView, float margin)312 public void setTaskMenuAroundTaskView(LinearLayout taskView, float margin) { 313 BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskView.getLayoutParams(); 314 lp.topMargin += margin; 315 lp.leftMargin += margin; 316 } 317 318 @Override getAdditionalInsetForTaskMenu(float margin)319 public PointF getAdditionalInsetForTaskMenu(float margin) { 320 return new PointF(0, 0); 321 } 322 323 /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ 324 325 @Override getUpDownSwipeDirection()326 public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() { 327 return VERTICAL; 328 } 329 330 @Override getUpDirection(boolean isRtl)331 public int getUpDirection(boolean isRtl) { 332 // Ignore rtl since it only affects X value displacement, Y displacement doesn't change 333 return SingleAxisSwipeDetector.DIRECTION_POSITIVE; 334 } 335 336 @Override isGoingUp(float displacement, boolean isRtl)337 public boolean isGoingUp(float displacement, boolean isRtl) { 338 // Ignore rtl since it only affects X value displacement, Y displacement doesn't change 339 return displacement < 0; 340 } 341 342 @Override getTaskDragDisplacementFactor(boolean isRtl)343 public int getTaskDragDisplacementFactor(boolean isRtl) { 344 // Ignore rtl since it only affects X value displacement, Y displacement doesn't change 345 return 1; 346 } 347 348 /* -------------------- */ 349 350 @Override getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild)351 public ChildBounds getChildBounds(View child, int childStart, int pageCenter, 352 boolean layoutChild) { 353 final int childWidth = child.getMeasuredWidth(); 354 final int childRight = childStart + childWidth; 355 final int childHeight = child.getMeasuredHeight(); 356 final int childTop = pageCenter - childHeight / 2; 357 if (layoutChild) { 358 child.layout(childStart, childTop, childRight, childTop + childHeight); 359 } 360 return new ChildBounds(childWidth, childHeight, childRight, childTop); 361 } 362 363 @Override getDistanceToBottomOfRect(DeviceProfile dp, Rect rect)364 public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { 365 return dp.heightPx - rect.bottom; 366 } 367 368 @Override getSplitPositionOptions(DeviceProfile dp)369 public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) { 370 List<SplitPositionOption> options = new ArrayList<>(1); 371 // Add both left and right options if we're in tablet mode 372 // TODO: Add in correct icons 373 if (dp.isTablet && dp.isLandscape) { 374 options.add(new SplitPositionOption( 375 R.drawable.ic_split_screen, R.string.split_screen_position_right, 376 STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN)); 377 options.add(new SplitPositionOption( 378 R.drawable.ic_split_screen, R.string.split_screen_position_left, 379 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); 380 } else { 381 if (dp.isSeascape()) { 382 // Add left/right options 383 options.add(new SplitPositionOption( 384 R.drawable.ic_split_screen, R.string.split_screen_position_right, 385 STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN)); 386 } else if (dp.isLandscape) { 387 options.add(new SplitPositionOption( 388 R.drawable.ic_split_screen, R.string.split_screen_position_left, 389 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); 390 } else { 391 // Only add top option 392 options.add(new SplitPositionOption( 393 R.drawable.ic_split_screen, R.string.split_screen_position_top, 394 STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); 395 } 396 } 397 return options; 398 } 399 400 @Override getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile dp)401 public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, 402 DeviceProfile dp) { 403 if (dp.isLandscape) { // or seascape 404 return primary; 405 } else { 406 return secondary; 407 } 408 } 409 } 410