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