1 /* 2 * Copyright (C) 2020 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.quickstep.views; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.graphics.Rect; 22 import android.util.AttributeSet; 23 import android.view.View; 24 import android.view.View.OnClickListener; 25 import android.widget.Button; 26 import android.widget.FrameLayout; 27 28 import androidx.annotation.IntDef; 29 import androidx.annotation.Nullable; 30 31 import com.android.launcher3.DeviceProfile; 32 import com.android.launcher3.Insettable; 33 import com.android.launcher3.R; 34 import com.android.launcher3.config.FeatureFlags; 35 import com.android.launcher3.util.DisplayController; 36 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; 37 import com.android.launcher3.util.MultiValueAlpha; 38 import com.android.launcher3.util.NavigationMode; 39 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks; 40 import com.android.quickstep.util.LayoutUtils; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 45 /** 46 * View for showing action buttons in Overview 47 */ 48 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout 49 implements OnClickListener, Insettable { 50 51 private final Rect mInsets = new Rect(); 52 53 @IntDef(flag = true, value = { 54 HIDDEN_NON_ZERO_ROTATION, 55 HIDDEN_NO_TASKS, 56 HIDDEN_NO_RECENTS, 57 HIDDEN_SPLIT_SCREEN, 58 HIDDEN_SPLIT_SELECT_ACTIVE, 59 HIDDEN_ACTIONS_IN_MENU, 60 HIDDEN_DESKTOP 61 }) 62 @Retention(RetentionPolicy.SOURCE) 63 public @interface ActionsHiddenFlags { } 64 65 public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0; 66 public static final int HIDDEN_NO_TASKS = 1 << 1; 67 public static final int HIDDEN_NO_RECENTS = 1 << 2; 68 public static final int HIDDEN_SPLIT_SCREEN = 1 << 3; 69 public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4; 70 public static final int HIDDEN_ACTIONS_IN_MENU = 1 << 5; 71 public static final int HIDDEN_DESKTOP = 1 << 6; 72 73 @IntDef(flag = true, value = { 74 DISABLED_SCROLLING, 75 DISABLED_ROTATED, 76 DISABLED_NO_THUMBNAIL}) 77 @Retention(RetentionPolicy.SOURCE) 78 public @interface ActionsDisabledFlags { } 79 80 public static final int DISABLED_SCROLLING = 1 << 0; 81 public static final int DISABLED_ROTATED = 1 << 1; 82 public static final int DISABLED_NO_THUMBNAIL = 1 << 2; 83 84 private static final int INDEX_CONTENT_ALPHA = 0; 85 private static final int INDEX_VISIBILITY_ALPHA = 1; 86 private static final int INDEX_FULLSCREEN_ALPHA = 2; 87 private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3; 88 private static final int INDEX_SHARE_TARGET_ALPHA = 4; 89 private static final int INDEX_SCROLL_ALPHA = 5; 90 private static final int NUM_ALPHAS = 6; 91 92 public @interface SplitButtonHiddenFlags { } 93 public static final int FLAG_IS_NOT_TABLET = 1 << 0; 94 95 public @interface SplitButtonDisabledFlags { } 96 public static final int FLAG_SINGLE_TASK = 1 << 0; 97 98 private MultiValueAlpha mMultiValueAlpha; 99 private Button mSplitButton; 100 101 @ActionsHiddenFlags 102 private int mHiddenFlags; 103 104 @ActionsDisabledFlags 105 protected int mDisabledFlags; 106 107 @SplitButtonHiddenFlags 108 private int mSplitButtonHiddenFlags; 109 110 @SplitButtonDisabledFlags 111 private int mSplitButtonDisabledFlags; 112 113 @Nullable 114 protected T mCallbacks; 115 116 @Nullable 117 protected DeviceProfile mDp; 118 private final Rect mTaskSize = new Rect(); 119 OverviewActionsView(Context context)120 public OverviewActionsView(Context context) { 121 this(context, null); 122 } 123 OverviewActionsView(Context context, @Nullable AttributeSet attrs)124 public OverviewActionsView(Context context, @Nullable AttributeSet attrs) { 125 this(context, attrs, 0); 126 } 127 OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)128 public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 129 super(context, attrs, defStyleAttr, 0); 130 } 131 132 @Override onFinishInflate()133 protected void onFinishInflate() { 134 super.onFinishInflate(); 135 mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS); 136 mMultiValueAlpha.setUpdateVisibility(true); 137 138 findViewById(R.id.action_screenshot).setOnClickListener(this); 139 mSplitButton = findViewById(R.id.action_split); 140 mSplitButton.setOnClickListener(this); 141 } 142 143 /** 144 * Set listener for callbacks on action button taps. 145 * 146 * @param callbacks for callbacks, or {@code null} to clear the listener. 147 */ setCallbacks(T callbacks)148 public void setCallbacks(T callbacks) { 149 mCallbacks = callbacks; 150 } 151 152 @Override onClick(View view)153 public void onClick(View view) { 154 if (mCallbacks == null) { 155 return; 156 } 157 int id = view.getId(); 158 if (id == R.id.action_screenshot) { 159 mCallbacks.onScreenshot(); 160 } else if (id == R.id.action_split) { 161 mCallbacks.onSplit(); 162 } 163 } 164 165 @Override onConfigurationChanged(Configuration newConfig)166 protected void onConfigurationChanged(Configuration newConfig) { 167 super.onConfigurationChanged(newConfig); 168 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 169 } 170 171 @Override setInsets(Rect insets)172 public void setInsets(Rect insets) { 173 mInsets.set(insets); 174 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 175 updatePadding(); 176 } 177 updateHiddenFlags(@ctionsHiddenFlags int visibilityFlags, boolean enable)178 public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) { 179 if (enable) { 180 mHiddenFlags |= visibilityFlags; 181 } else { 182 mHiddenFlags &= ~visibilityFlags; 183 } 184 boolean isHidden = mHiddenFlags != 0; 185 mMultiValueAlpha.get(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1); 186 } 187 188 /** 189 * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled. 190 * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable 191 * buttons individually, currently done for select button in subclass. 192 * 193 * @param disabledFlags The flag to update. 194 * @param enable Whether to enable the disable flag: True will cause view to be disabled. 195 */ updateDisabledFlags(@ctionsDisabledFlags int disabledFlags, boolean enable)196 public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) { 197 if (enable) { 198 mDisabledFlags |= disabledFlags; 199 } else { 200 mDisabledFlags &= ~disabledFlags; 201 } 202 boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0; 203 LayoutUtils.setViewEnabled(this, isEnabled); 204 updateSplitButtonEnabledState(); 205 } 206 207 /** 208 * Updates the proper flags to indicate whether the "Split screen" button should be hidden. 209 * 210 * @param flag The flag to update. 211 * @param enable Whether to enable the hidden flag: True will cause view to be hidden. 212 */ updateSplitButtonHiddenFlags(@plitButtonHiddenFlags int flag, boolean enable)213 public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) { 214 if (enable) { 215 mSplitButtonHiddenFlags |= flag; 216 } else { 217 mSplitButtonHiddenFlags &= ~flag; 218 } 219 if (mSplitButton == null) return; 220 boolean shouldBeVisible = mSplitButtonHiddenFlags == 0; 221 mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE); 222 findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE); 223 } 224 225 /** 226 * Updates the proper flags to indicate whether the "Split screen" button should be disabled. 227 * 228 * @param flag The flag to update. 229 * @param enable Whether to enable the disable flag: True will cause view to be disabled. 230 */ updateSplitButtonDisabledFlags(@plitButtonDisabledFlags int flag, boolean enable)231 public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) { 232 if (enable) { 233 mSplitButtonDisabledFlags |= flag; 234 } else { 235 mSplitButtonDisabledFlags &= ~flag; 236 } 237 updateSplitButtonEnabledState(); 238 } 239 getContentAlpha()240 public MultiProperty getContentAlpha() { 241 return mMultiValueAlpha.get(INDEX_CONTENT_ALPHA); 242 } 243 getVisibilityAlpha()244 public MultiProperty getVisibilityAlpha() { 245 return mMultiValueAlpha.get(INDEX_VISIBILITY_ALPHA); 246 } 247 getFullscreenAlpha()248 public MultiProperty getFullscreenAlpha() { 249 return mMultiValueAlpha.get(INDEX_FULLSCREEN_ALPHA); 250 } 251 getShareTargetAlpha()252 public MultiProperty getShareTargetAlpha() { 253 return mMultiValueAlpha.get(INDEX_SHARE_TARGET_ALPHA); 254 } 255 getIndexScrollAlpha()256 public MultiProperty getIndexScrollAlpha() { 257 return mMultiValueAlpha.get(INDEX_SCROLL_ALPHA); 258 } 259 260 /** 261 * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar. 262 */ updatePadding()263 private void updatePadding() { 264 // If taskbar is in overview, overview action has dedicated space above nav buttons 265 setPadding(mInsets.left, 0, mInsets.right, 0); 266 } 267 268 /** Updates vertical margins for different navigation mode or configuration changes. */ updateVerticalMargin(NavigationMode mode)269 public void updateVerticalMargin(NavigationMode mode) { 270 if (mDp == null) { 271 return; 272 } 273 LayoutParams actionParams = (LayoutParams) findViewById( 274 R.id.action_buttons).getLayoutParams(); 275 actionParams.setMargins( 276 actionParams.leftMargin, mDp.overviewActionsTopMarginPx, 277 actionParams.rightMargin, getBottomMargin()); 278 } 279 getBottomMargin()280 private int getBottomMargin() { 281 if (mDp == null) { 282 return 0; 283 } 284 285 if (mDp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) { 286 return mDp.stashedTaskbarHeight; 287 } 288 289 // Align to bottom of task Rect. 290 return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx 291 - mDp.overviewActionsHeight; 292 } 293 294 /** 295 * Updates device profile and task size for this view to draw with. 296 */ updateDimension(DeviceProfile dp, Rect taskSize)297 public void updateDimension(DeviceProfile dp, Rect taskSize) { 298 mDp = dp; 299 mTaskSize.set(taskSize); 300 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 301 302 requestLayout(); 303 304 mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds( 305 (dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical), 306 0, 0, 0); 307 } 308 309 /** 310 * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and 311 * mDisabledFlags. 312 */ updateSplitButtonEnabledState()313 private void updateSplitButtonEnabledState() { 314 if (mSplitButton == null) { 315 return; 316 } 317 boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0; 318 boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled; 319 mSplitButton.setEnabled(shouldBeEnabled); 320 } 321 322 } 323