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.systemui.globalactions; 18 19 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; 20 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; 21 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; 22 23 import android.content.Context; 24 import android.util.AttributeSet; 25 import android.view.Gravity; 26 import android.view.View; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.systemui.R; 30 31 /** 32 * Grid-based implementation of the button layout created by the global actions dialog. 33 */ 34 public class GlobalActionsColumnLayout extends GlobalActionsLayout { 35 private boolean mLastSnap; 36 GlobalActionsColumnLayout(Context context, AttributeSet attrs)37 public GlobalActionsColumnLayout(Context context, AttributeSet attrs) { 38 super(context, attrs); 39 } 40 41 @Override onLayout(boolean changed, int l, int t, int r, int b)42 protected void onLayout(boolean changed, int l, int t, int r, int b) { 43 super.onLayout(changed, l, t, r, b); 44 45 post(() -> updateSnap()); 46 } 47 48 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)49 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 50 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 51 } 52 53 @VisibleForTesting shouldReverseListItems()54 protected boolean shouldReverseListItems() { 55 int rotation = getCurrentRotation(); 56 if (rotation == ROTATION_NONE) { 57 return false; 58 } 59 if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 60 return rotation == ROTATION_LANDSCAPE; 61 } 62 return rotation == ROTATION_SEASCAPE; 63 } 64 65 @Override onUpdateList()66 public void onUpdateList() { 67 super.onUpdateList(); 68 updateChildOrdering(); 69 } 70 updateChildOrdering()71 private void updateChildOrdering() { 72 if (shouldReverseListItems()) { 73 getListView().bringToFront(); 74 } else { 75 getSeparatedView().bringToFront(); 76 } 77 } 78 79 /** 80 * Snap this layout to align with the power button. 81 */ 82 @VisibleForTesting snapToPowerButton()83 protected void snapToPowerButton() { 84 int offset = getPowerButtonOffsetDistance(); 85 switch (getCurrentRotation()) { 86 case (ROTATION_LANDSCAPE): 87 setPadding(offset, 0, 0, 0); 88 setGravity(Gravity.LEFT | Gravity.TOP); 89 break; 90 case (ROTATION_SEASCAPE): 91 setPadding(0, 0, offset, 0); 92 setGravity(Gravity.RIGHT | Gravity.BOTTOM); 93 break; 94 default: 95 setPadding(0, offset, 0, 0); 96 setGravity(Gravity.TOP | Gravity.RIGHT); 97 break; 98 } 99 } 100 101 /** 102 * Detach this layout from snapping to the power button and instead center along that edge. 103 */ 104 @VisibleForTesting centerAlongEdge()105 protected void centerAlongEdge() { 106 switch (getCurrentRotation()) { 107 case (ROTATION_LANDSCAPE): 108 setPadding(0, 0, 0, 0); 109 setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP); 110 break; 111 case (ROTATION_SEASCAPE): 112 setPadding(0, 0, 0, 0); 113 setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); 114 break; 115 default: 116 setPadding(0, 0, 0, 0); 117 setGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT); 118 break; 119 } 120 } 121 122 /** 123 * Determines the distance from the top of the screen to the power button. 124 */ 125 @VisibleForTesting getPowerButtonOffsetDistance()126 protected int getPowerButtonOffsetDistance() { 127 return Math.round(getContext().getResources().getDimension( 128 R.dimen.global_actions_top_padding)); 129 } 130 131 /** 132 * Check whether there is enough extra space below the dialog such that we can offset the top 133 * of the dialog from the top of the phone to line it up with the power button, then either 134 * snap the dialog to the power button or center it along the edge with snapToPowerButton. 135 */ 136 @VisibleForTesting shouldSnapToPowerButton()137 protected boolean shouldSnapToPowerButton() { 138 int offsetSize = getPowerButtonOffsetDistance(); 139 int dialogSize; 140 int screenSize; 141 View wrapper = getWrapper(); 142 int rotation = getCurrentRotation(); 143 if (rotation == ROTATION_NONE) { 144 dialogSize = wrapper.getMeasuredHeight(); 145 screenSize = getMeasuredHeight(); 146 } else { 147 dialogSize = wrapper.getMeasuredWidth(); 148 screenSize = getMeasuredWidth(); 149 } 150 return dialogSize + offsetSize < screenSize; 151 } 152 153 @VisibleForTesting updateSnap()154 protected void updateSnap() { 155 boolean snap = shouldSnapToPowerButton(); 156 if (snap != mLastSnap) { 157 if (snap) { 158 snapToPowerButton(); 159 } else { 160 centerAlongEdge(); 161 } 162 } 163 mLastSnap = snap; 164 } 165 166 @VisibleForTesting getGridItemSize()167 protected float getGridItemSize() { 168 return getContext().getResources().getDimension(R.dimen.global_actions_grid_item_height); 169 } 170 171 @VisibleForTesting getAnimationDistance()172 protected float getAnimationDistance() { 173 return getGridItemSize() / 2; 174 } 175 176 @Override getAnimationOffsetX()177 public float getAnimationOffsetX() { 178 if (getCurrentRotation() == ROTATION_NONE) { 179 return getAnimationDistance(); 180 } 181 return 0; 182 } 183 184 @Override getAnimationOffsetY()185 public float getAnimationOffsetY() { 186 switch (getCurrentRotation()) { 187 case ROTATION_LANDSCAPE: 188 return -getAnimationDistance(); 189 case ROTATION_SEASCAPE: 190 return getAnimationDistance(); 191 default: // Portrait 192 return 0; 193 } 194 } 195 } 196