| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.globalactions; |
| |
| import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; |
| import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; |
| import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; |
| |
| import android.content.Context; |
| import android.util.AttributeSet; |
| import android.view.Gravity; |
| import android.view.View; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.systemui.R; |
| |
| /** |
| * Grid-based implementation of the button layout created by the global actions dialog. |
| */ |
| public class GlobalActionsColumnLayout extends GlobalActionsLayout { |
| private boolean mLastSnap; |
| |
| public GlobalActionsColumnLayout(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| super.onLayout(changed, l, t, r, b); |
| |
| post(() -> updateSnap()); |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| } |
| |
| @VisibleForTesting |
| protected boolean shouldReverseListItems() { |
| int rotation = getCurrentRotation(); |
| if (rotation == ROTATION_NONE) { |
| return false; |
| } |
| if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { |
| return rotation == ROTATION_LANDSCAPE; |
| } |
| return rotation == ROTATION_SEASCAPE; |
| } |
| |
| @Override |
| public void onUpdateList() { |
| super.onUpdateList(); |
| updateChildOrdering(); |
| } |
| |
| private void updateChildOrdering() { |
| if (shouldReverseListItems()) { |
| getListView().bringToFront(); |
| } else { |
| getSeparatedView().bringToFront(); |
| } |
| } |
| |
| /** |
| * Snap this layout to align with the power button. |
| */ |
| @VisibleForTesting |
| protected void snapToPowerButton() { |
| int offset = getPowerButtonOffsetDistance(); |
| switch (getCurrentRotation()) { |
| case (ROTATION_LANDSCAPE): |
| setPadding(offset, 0, 0, 0); |
| setGravity(Gravity.LEFT | Gravity.TOP); |
| break; |
| case (ROTATION_SEASCAPE): |
| setPadding(0, 0, offset, 0); |
| setGravity(Gravity.RIGHT | Gravity.BOTTOM); |
| break; |
| default: |
| setPadding(0, offset, 0, 0); |
| setGravity(Gravity.TOP | Gravity.RIGHT); |
| break; |
| } |
| } |
| |
| /** |
| * Detach this layout from snapping to the power button and instead center along that edge. |
| */ |
| @VisibleForTesting |
| protected void centerAlongEdge() { |
| switch (getCurrentRotation()) { |
| case (ROTATION_LANDSCAPE): |
| setPadding(0, 0, 0, 0); |
| setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); |
| break; |
| case (ROTATION_SEASCAPE): |
| setPadding(0, 0, 0, 0); |
| setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); |
| break; |
| default: |
| setPadding(0, 0, 0, 0); |
| setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); |
| break; |
| } |
| } |
| |
| /** |
| * Determines the distance from the top of the screen to the power button. |
| */ |
| @VisibleForTesting |
| protected int getPowerButtonOffsetDistance() { |
| return Math.round(getContext().getResources().getDimension( |
| R.dimen.global_actions_top_padding)); |
| } |
| |
| /** |
| * Check whether there is enough extra space below the dialog such that we can offset the top |
| * of the dialog from the top of the phone to line it up with the power button, then either |
| * snap the dialog to the power button or center it along the edge with snapToPowerButton. |
| */ |
| @VisibleForTesting |
| protected boolean shouldSnapToPowerButton() { |
| int offsetSize = getPowerButtonOffsetDistance(); |
| int dialogSize; |
| int screenSize; |
| View wrapper = getWrapper(); |
| int rotation = getCurrentRotation(); |
| if (rotation == ROTATION_NONE) { |
| dialogSize = wrapper.getMeasuredHeight(); |
| screenSize = getMeasuredHeight(); |
| } else { |
| dialogSize = wrapper.getMeasuredWidth(); |
| screenSize = getMeasuredWidth(); |
| } |
| return dialogSize + offsetSize < screenSize; |
| } |
| |
| @VisibleForTesting |
| protected void updateSnap() { |
| boolean snap = shouldSnapToPowerButton(); |
| if (snap != mLastSnap) { |
| if (snap) { |
| snapToPowerButton(); |
| } else { |
| centerAlongEdge(); |
| } |
| } |
| mLastSnap = snap; |
| } |
| |
| @VisibleForTesting |
| protected float getGridItemSize() { |
| return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); |
| } |
| |
| @VisibleForTesting |
| protected float getAnimationDistance() { |
| return getGridItemSize() / 2; |
| } |
| |
| @Override |
| public float getAnimationOffsetX() { |
| if (getCurrentRotation() == ROTATION_NONE) { |
| return getAnimationDistance(); |
| } |
| return 0; |
| } |
| |
| @Override |
| public float getAnimationOffsetY() { |
| switch (getCurrentRotation()) { |
| case ROTATION_LANDSCAPE: |
| return -getAnimationDistance(); |
| case ROTATION_SEASCAPE: |
| return getAnimationDistance(); |
| default: // Portrait |
| return 0; |
| } |
| } |
| } |