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.util.Log; 24 import android.view.View; 25 import android.view.View.OnClickListener; 26 import android.widget.Button; 27 import android.widget.FrameLayout; 28 import android.widget.LinearLayout; 29 30 import androidx.annotation.IntDef; 31 import androidx.annotation.Nullable; 32 33 import com.android.launcher3.DeviceProfile; 34 import com.android.launcher3.Flags; 35 import com.android.launcher3.Insettable; 36 import com.android.launcher3.R; 37 import com.android.launcher3.anim.AnimatedFloat; 38 import com.android.launcher3.util.DisplayController; 39 import com.android.launcher3.util.MultiValueAlpha; 40 import com.android.launcher3.util.NavigationMode; 41 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks; 42 import com.android.quickstep.util.LayoutUtils; 43 44 import java.lang.annotation.Retention; 45 import java.lang.annotation.RetentionPolicy; 46 import java.util.Arrays; 47 48 /** 49 * View for showing action buttons in Overview 50 */ 51 public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayout 52 implements OnClickListener, Insettable { 53 public static final String TAG = "OverviewActionsView"; 54 private final Rect mInsets = new Rect(); 55 56 @IntDef(flag = true, value = { 57 HIDDEN_NON_ZERO_ROTATION, 58 HIDDEN_NO_TASKS, 59 HIDDEN_NO_RECENTS, 60 HIDDEN_SPLIT_SCREEN, 61 HIDDEN_SPLIT_SELECT_ACTIVE, 62 HIDDEN_ACTIONS_IN_MENU, 63 HIDDEN_DESKTOP 64 }) 65 @Retention(RetentionPolicy.SOURCE) 66 public @interface ActionsHiddenFlags { } 67 68 public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 0; 69 public static final int HIDDEN_NO_TASKS = 1 << 1; 70 public static final int HIDDEN_NO_RECENTS = 1 << 2; 71 public static final int HIDDEN_SPLIT_SCREEN = 1 << 3; 72 public static final int HIDDEN_SPLIT_SELECT_ACTIVE = 1 << 4; 73 public static final int HIDDEN_ACTIONS_IN_MENU = 1 << 5; 74 public static final int HIDDEN_DESKTOP = 1 << 6; 75 76 @IntDef(flag = true, value = { 77 DISABLED_SCROLLING, 78 DISABLED_ROTATED, 79 DISABLED_NO_THUMBNAIL}) 80 @Retention(RetentionPolicy.SOURCE) 81 public @interface ActionsDisabledFlags { } 82 83 public static final int DISABLED_SCROLLING = 1 << 0; 84 public static final int DISABLED_ROTATED = 1 << 1; 85 public static final int DISABLED_NO_THUMBNAIL = 1 << 2; 86 87 private static final int INDEX_CONTENT_ALPHA = 0; 88 private static final int INDEX_VISIBILITY_ALPHA = 1; 89 private static final int INDEX_FULLSCREEN_ALPHA = 2; 90 private static final int INDEX_HIDDEN_FLAGS_ALPHA = 3; 91 private static final int INDEX_SHARE_TARGET_ALPHA = 4; 92 private static final int INDEX_SCROLL_ALPHA = 5; 93 private static final int INDEX_GROUPED_ALPHA = 6; 94 private static final int INDEX_3P_LAUNCHER = 7; 95 private static final int NUM_ALPHAS = 8; 96 97 public @interface SplitButtonHiddenFlags { } 98 public static final int FLAG_SMALL_SCREEN_HIDE_SPLIT = 1 << 0; 99 100 /** 101 * Holds an AnimatedFloat for each alpha property, used to set or animate alpha values in 102 * {@link #mMultiValueAlphas}. 103 */ 104 private final AnimatedFloat[] mAlphaProperties = new AnimatedFloat[NUM_ALPHAS]; 105 106 /** Holds MultiValueAlpha values for all actions bars */ 107 private final MultiValueAlpha[] mMultiValueAlphas = new MultiValueAlpha[2]; 108 /** Index used for single-task actions in the mMultiValueAlphas array */ 109 private static final int ACTIONS_ALPHAS = 0; 110 /** Index used for grouped-task actions in the mMultiValueAlphas array */ 111 private static final int GROUP_ACTIONS_ALPHAS = 1; 112 113 /** Container for the action buttons below a focused, non-split Overview tile. */ 114 protected LinearLayout mActionButtons; 115 private Button mSplitButton; 116 /** 117 * The "save app pair" button. Currently this is the only button that is not contained in 118 * mActionButtons, since it is the sole button that appears for a grouped task. 119 */ 120 private Button mSaveAppPairButton; 121 122 @ActionsHiddenFlags 123 private int mHiddenFlags; 124 125 @ActionsDisabledFlags 126 protected int mDisabledFlags; 127 128 @SplitButtonHiddenFlags 129 private int mSplitButtonHiddenFlags; 130 131 @Nullable 132 protected T mCallbacks; 133 134 @Nullable 135 protected DeviceProfile mDp; 136 private final Rect mTaskSize = new Rect(); 137 private boolean mIsGroupedTask = false; 138 private boolean mCanSaveAppPair = false; 139 OverviewActionsView(Context context)140 public OverviewActionsView(Context context) { 141 this(context, null); 142 } 143 OverviewActionsView(Context context, @Nullable AttributeSet attrs)144 public OverviewActionsView(Context context, @Nullable AttributeSet attrs) { 145 this(context, attrs, 0); 146 } 147 OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)148 public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 149 super(context, attrs, defStyleAttr, 0); 150 } 151 152 @Override onFinishInflate()153 protected void onFinishInflate() { 154 super.onFinishInflate(); 155 // Initialize 2 view containers: one for single tasks, one for grouped tasks. 156 // These will take up the same space on the screen and alternate visibility as needed. 157 // Currently, the only grouped task action is "save app pairs". 158 mActionButtons = findViewById(R.id.action_buttons); 159 mSaveAppPairButton = findViewById(R.id.action_save_app_pair); 160 // Initialize a list to hold alphas for mActionButtons and any group action buttons. 161 mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS); 162 mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] = 163 new MultiValueAlpha(mSaveAppPairButton, NUM_ALPHAS); 164 Arrays.stream(mMultiValueAlphas).forEach(a -> a.setUpdateVisibility(true)); 165 // To control alpha simultaneously on mActionButtons and any group action buttons, we set up 166 // an AnimatedFloat for each alpha property. 167 for (int i = 0; i < NUM_ALPHAS; i++) { 168 final int index = i; 169 mAlphaProperties[index] = new AnimatedFloat(() -> { 170 for (MultiValueAlpha multiValueAlpha : mMultiValueAlphas) { 171 multiValueAlpha.get(index).setValue(mAlphaProperties[index].value); 172 } 173 }, 1f /* initialValue */); 174 } 175 176 // The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is 177 // an ImageButton in go launcher (does not share a common class with Button). Take care when 178 // casting this. 179 View screenshotButton = findViewById(R.id.action_screenshot); 180 screenshotButton.setOnClickListener(this); 181 mSplitButton = findViewById(R.id.action_split); 182 mSplitButton.setOnClickListener(this); 183 mSaveAppPairButton.setOnClickListener(this); 184 } 185 186 /** 187 * Set listener for callbacks on action button taps. 188 * 189 * @param callbacks for callbacks, or {@code null} to clear the listener. 190 */ setCallbacks(T callbacks)191 public void setCallbacks(T callbacks) { 192 mCallbacks = callbacks; 193 } 194 195 @Override onClick(View view)196 public void onClick(View view) { 197 if (mCallbacks == null) { 198 return; 199 } 200 int id = view.getId(); 201 if (id == R.id.action_screenshot) { 202 mCallbacks.onScreenshot(); 203 } else if (id == R.id.action_split) { 204 mCallbacks.onSplit(); 205 } else if (id == R.id.action_save_app_pair) { 206 mCallbacks.onSaveAppPair(); 207 } 208 } 209 210 @Override onConfigurationChanged(Configuration newConfig)211 protected void onConfigurationChanged(Configuration newConfig) { 212 super.onConfigurationChanged(newConfig); 213 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 214 } 215 216 @Override setInsets(Rect insets)217 public void setInsets(Rect insets) { 218 mInsets.set(insets); 219 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 220 updatePadding(); 221 } 222 updateHiddenFlags(@ctionsHiddenFlags int visibilityFlags, boolean enable)223 public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) { 224 if (enable) { 225 mHiddenFlags |= visibilityFlags; 226 } else { 227 mHiddenFlags &= ~visibilityFlags; 228 } 229 boolean isHidden = mHiddenFlags != 0; 230 mAlphaProperties[INDEX_HIDDEN_FLAGS_ALPHA].updateValue(isHidden ? 0 : 1); 231 } 232 233 /** 234 * Updates the proper disabled flag to indicate whether OverviewActionsView should be enabled. 235 * Ignores DISABLED_ROTATED flag for determining enabled. Flag is used to enable/disable 236 * buttons individually, currently done for select button in subclass. 237 * 238 * @param disabledFlags The flag to update. 239 * @param enable Whether to enable the disable flag: True will cause view to be disabled. 240 */ updateDisabledFlags(@ctionsDisabledFlags int disabledFlags, boolean enable)241 public void updateDisabledFlags(@ActionsDisabledFlags int disabledFlags, boolean enable) { 242 if (enable) { 243 mDisabledFlags |= disabledFlags; 244 } else { 245 mDisabledFlags &= ~disabledFlags; 246 } 247 boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0; 248 LayoutUtils.setViewEnabled(this, isEnabled); 249 } 250 251 /** 252 * Updates a batch of flags to hide and show actions buttons when a grouped task (split screen) 253 * is focused. 254 * @param isGroupedTask True if the focused task is a grouped task. 255 * @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app 256 * pair. 257 */ updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair)258 public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) { 259 Log.d(TAG, "updateForGroupedTask() called with: isGroupedTask = [" + isGroupedTask 260 + "], canSaveAppPair = [" + canSaveAppPair + "]"); 261 mIsGroupedTask = isGroupedTask; 262 mCanSaveAppPair = canSaveAppPair; 263 updateActionButtonsVisibility(); 264 } 265 266 /** 267 * Updates a batch of flags to hide and show actions buttons for tablet/non tablet case. 268 */ updateForIsTablet()269 private void updateForIsTablet() { 270 assert mDp != null; 271 // Update flags to see if split button should be hidden. 272 updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet); 273 updateActionButtonsVisibility(); 274 } 275 updateActionButtonsVisibility()276 private void updateActionButtonsVisibility() { 277 assert mDp != null; 278 boolean showSingleTaskActions = !mIsGroupedTask; 279 boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair; 280 Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = [" 281 + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]"); 282 getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0); 283 getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0); 284 } 285 286 /** 287 * Updates flags to hide and show actions buttons for 1p/3p launchers. 288 */ updateFor3pLauncher(boolean is3pLauncher)289 public void updateFor3pLauncher(boolean is3pLauncher) { 290 getGroupActionsAlphas().get(INDEX_3P_LAUNCHER).setValue(is3pLauncher ? 0 : 1); 291 } 292 getActionsAlphas()293 private MultiValueAlpha getActionsAlphas() { 294 return mMultiValueAlphas[ACTIONS_ALPHAS]; 295 } 296 getGroupActionsAlphas()297 private MultiValueAlpha getGroupActionsAlphas() { 298 return mMultiValueAlphas[GROUP_ACTIONS_ALPHAS]; 299 } 300 301 /** 302 * Updates the proper flags to indicate whether the "Split screen" button should be hidden. 303 * 304 * @param flag The flag to update. 305 * @param enable Whether to enable the hidden flag: True will cause view to be hidden. 306 */ updateSplitButtonHiddenFlags(@plitButtonHiddenFlags int flag, boolean enable)307 void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, 308 boolean enable) { 309 if (mSplitButton == null) return; 310 if (enable) { 311 mSplitButtonHiddenFlags |= flag; 312 } else { 313 mSplitButtonHiddenFlags &= ~flag; 314 } 315 int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE; 316 if (mSplitButton.getVisibility() != desiredVisibility) { 317 mSplitButton.setVisibility(desiredVisibility); 318 mActionButtons.requestLayout(); 319 } 320 } 321 getContentAlpha()322 public AnimatedFloat getContentAlpha() { 323 return mAlphaProperties[INDEX_CONTENT_ALPHA]; 324 } 325 getVisibilityAlpha()326 public AnimatedFloat getVisibilityAlpha() { 327 return mAlphaProperties[INDEX_VISIBILITY_ALPHA]; 328 } 329 getFullscreenAlpha()330 public AnimatedFloat getFullscreenAlpha() { 331 return mAlphaProperties[INDEX_FULLSCREEN_ALPHA]; 332 } 333 getShareTargetAlpha()334 public AnimatedFloat getShareTargetAlpha() { 335 return mAlphaProperties[INDEX_SHARE_TARGET_ALPHA]; 336 } 337 getIndexScrollAlpha()338 public AnimatedFloat getIndexScrollAlpha() { 339 return mAlphaProperties[INDEX_SCROLL_ALPHA]; 340 } 341 342 /** 343 * Returns the visibility of the overview actions buttons. 344 */ areActionsButtonsVisible()345 public boolean areActionsButtonsVisible() { 346 return mActionButtons.getVisibility() == View.VISIBLE 347 || mSaveAppPairButton.getVisibility() == View.VISIBLE; 348 } 349 350 /** 351 * Offsets OverviewActionsView horizontal position based on 3 button nav container in taskbar. 352 */ updatePadding()353 private void updatePadding() { 354 // If taskbar is in overview, overview action has dedicated space above nav buttons 355 setPadding(mInsets.left, 0, mInsets.right, 0); 356 } 357 358 /** Updates vertical margins for different navigation mode or configuration changes. */ updateVerticalMargin(NavigationMode mode)359 public void updateVerticalMargin(NavigationMode mode) { 360 updateActionBarPosition(mActionButtons); 361 updateActionBarPosition(mSaveAppPairButton); 362 } 363 364 /** Positions actions buttons according to device settings and insets. */ updateActionBarPosition(View actionBar)365 private void updateActionBarPosition(View actionBar) { 366 if (mDp == null) { 367 return; 368 } 369 370 LayoutParams actionParams = (LayoutParams) actionBar.getLayoutParams(); 371 actionParams.setMargins( 372 actionParams.leftMargin, mDp.overviewActionsTopMarginPx, 373 actionParams.rightMargin, getBottomMargin()); 374 } 375 getBottomMargin()376 private int getBottomMargin() { 377 if (mDp == null) { 378 return 0; 379 } 380 381 if (mDp.isTablet && Flags.enableGridOnlyOverview()) { 382 return mDp.stashedTaskbarHeight; 383 } 384 385 // Align to bottom of task Rect. 386 return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx 387 - mDp.overviewActionsHeight; 388 } 389 390 /** 391 * Updates device profile and task size for this view to draw with. 392 */ updateDimension(DeviceProfile dp, Rect taskSize)393 public void updateDimension(DeviceProfile dp, Rect taskSize) { 394 mDp = dp; 395 mTaskSize.set(taskSize); 396 updateVerticalMargin(DisplayController.getNavigationMode(getContext())); 397 updateForIsTablet(); 398 399 requestLayout(); 400 401 int splitIconRes = dp.isLeftRightSplit 402 ? R.drawable.ic_split_horizontal 403 : R.drawable.ic_split_vertical; 404 mSplitButton.setCompoundDrawablesRelativeWithIntrinsicBounds(splitIconRes, 0, 0, 0); 405 406 int appPairIconRes = dp.isLeftRightSplit 407 ? R.drawable.ic_save_app_pair_left_right 408 : R.drawable.ic_save_app_pair_up_down; 409 mSaveAppPairButton.setCompoundDrawablesRelativeWithIntrinsicBounds( 410 appPairIconRes, 0, 0, 0); 411 } 412 } 413