Merge "Removing some unused overrides in Launcher" into udc-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b5b453b..6cc54ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -676,7 +676,6 @@
hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
mAnimator = null;
- mIsStashed = isStashed;
}));
return;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f42a882..f2d4660 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -196,6 +196,10 @@
public static final boolean ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
SystemProperties.getBoolean("persist.wm.debug.enable_pip_keep_clear_algorithm", true);
+ private static final boolean TRACE_LAYOUTS =
+ SystemProperties.getBoolean("persist.debug.trace_layouts", false);
+ private static final String TRACE_RELAYOUT_CLASS =
+ SystemProperties.get("persist.debug.trace_request_layout_class", null);
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
@@ -607,6 +611,8 @@
mViewCapture = SettingsAwareViewCapture.getInstance(this).startCapture(getWindow());
}
getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
+ View.setTraceLayoutSteps(TRACE_LAYOUTS);
+ View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 2d5b8db..af49774 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -413,20 +413,14 @@
}
/**
- * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
- * if needed
+ * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
+ * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
+ * Technically this case should be taken care of by
+ * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
+ * it's a single task or multiple tasks results in different entry-points.
*
- * We could manually try to animate the just the bounds for the leashes we get back, but we try
- * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
- * us.
- *
- * First you have to call TVS#setPreview() to indicate which leash it will operate one
- * Then operations happen in TVS#apply() on each frame callback.
- *
- * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
- * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
- * device is considered in multiWindowMode and things like insets and stuff change
- * and calculations have to be adjusted in the animations for that
+ * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
+ * case where launcher handles animating starting split tasks from app icon)
*/
public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
@NonNull StateManager stateManager, @Nullable DepthController depthController,
@@ -461,9 +455,9 @@
return;
}
- // TODO: consider initialTaskPendingIntent
TransitionInfo.Change splitRoot1 = null;
TransitionInfo.Change splitRoot2 = null;
+ final ArrayList<SurfaceControl> openingTargets = new ArrayList<>();
for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
if (change.getTaskInfo() == null) {
@@ -484,32 +478,48 @@
if (taskId == initialTaskId) {
splitRoot1 = change.getParent() == null ? change :
transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot1.getLeash());
}
if (taskId == secondTaskId) {
splitRoot2 = change.getParent() == null ? change :
transitionInfo.getChange(change.getParent());
+ openingTargets.add(splitRoot2.getLeash());
}
}
- // This is where we should animate the split roots. For now, though, just make them visible.
- animateSplitRoot(t, splitRoot1);
- animateSplitRoot(t, splitRoot2);
+ SurfaceControl.Transaction animTransaction = new SurfaceControl.Transaction();
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+ animator.setDuration(SPLIT_LAUNCH_DURATION);
+ animator.addUpdateListener(valueAnimator -> {
+ float progress = valueAnimator.getAnimatedFraction();
+ for (SurfaceControl leash: openingTargets) {
+ animTransaction.setAlpha(leash, progress);
+ }
+ animTransaction.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ for (SurfaceControl leash: openingTargets) {
+ animTransaction.show(leash)
+ .setAlpha(leash, 0.0f);
+ }
+ animTransaction.apply();
+ }
- // This contains the initial state (before animation), so apply this at the beginning of
- // the animation.
- t.apply();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishCallback.run();
+ }
+ });
- // Once there is an animation, this should be called AFTER the animation completes.
- finishCallback.run();
- }
-
- private static void animateSplitRoot(SurfaceControl.Transaction t,
- TransitionInfo.Change splitRoot) {
- testLogD(LAUNCH_SPLIT_PAIR, "animateSplitRoot: " + splitRoot);
- if (splitRoot != null) {
- t.show(splitRoot.getLeash());
- t.setAlpha(splitRoot.getLeash(), 1.f);
+ if (splitRoot1 != null && splitRoot1.getParent() != null) {
+ // Set the highest level split root alpha; we could technically use the parent of either
+ // splitRoot1 or splitRoot2
+ t.setAlpha(transitionInfo.getChange(splitRoot1.getParent()).getLeash(), 1f);
}
+ t.apply();
+ animator.start();
}
/**
@@ -522,7 +532,9 @@
* it's a single task or multiple tasks results in different entry-points.
*
* If it is null, then it will simply fade in the starting apps and fade out launcher (for the
- * case where launcher handles animating starting split tasks from app icon) */
+ * case where launcher handles animating starting split tasks from app icon)
+ * @deprecated with shell transitions
+ */
public static void composeRecentsSplitLaunchAnimatorLegacy(
@Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
@NonNull RemoteAnimationTarget[] appTargets,
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5565139..0c89766 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -592,9 +592,8 @@
if (mSuccessCallback != null) {
mSuccessCallback.accept(true);
}
+ resetState();
});
- // After successful launch, call resetState
- resetState();
});
}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 5127190..20aa410 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -16,14 +16,17 @@
package com.android.quickstep;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,9 +47,9 @@
startTestActivity(2);
}
- @Ignore
@Test
@NavigationModeSwitch
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
public void testStressPressHome() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
@@ -57,9 +60,9 @@
}
}
- @Ignore
@Test
@NavigationModeSwitch
+ @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/187761685
public void testStressSwipeToOverview() {
for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
// Destroy Launcher activity.
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4d75fb8..05eaf88 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -202,6 +202,7 @@
<attr name="demoModeLayoutId" format="reference" />
<attr name="isScalable" format="boolean" />
<attr name="devicePaddingId" format="reference" />
+
<!-- File that contains the specs for the workspace.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="workspaceSpecsId" format="reference" />
@@ -210,11 +211,14 @@
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="allAppsSpecsId" format="reference" />
<attr name="allAppsSpecsTwoPanelId" format="reference" />
-
<!-- File that contains the specs for the workspace.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="folderSpecsId" format="reference" />
<attr name="folderSpecsTwoPanelId" format="reference" />
+ <!-- File that contains the specs for hotseat bar.
+ Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+ <attr name="hotseatSpecsId" format="reference" />
+ <attr name="hotseatSpecsTwoPanelId" format="reference" />
<!-- By default all categories are enabled -->
<attr name="deviceCategory" format="integer">
@@ -278,6 +282,11 @@
<attr name="maxAvailableSize" />
</declare-styleable>
+ <declare-styleable name="HotseatSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
+ </declare-styleable>
+
<declare-styleable name="SizeSpec">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 353d006..7ece9a4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,8 +56,10 @@
import com.android.launcher3.responsive.AllAppsSpecs;
import com.android.launcher3.responsive.CalculatedAllAppsSpec;
import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.CalculatedHotseatSpec;
import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
import com.android.launcher3.responsive.FolderSpecs;
+import com.android.launcher3.responsive.HotseatSpecs;
import com.android.launcher3.responsive.WorkspaceSpecs;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
@@ -121,6 +123,7 @@
private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
private CalculatedFolderSpec mResponsiveFolderWidthSpec;
private CalculatedFolderSpec mResponsiveFolderHeightSpec;
+ private CalculatedHotseatSpec mResponsiveHotseatSpec;
/**
* The maximum amount of left/right workspace padding as a percentage of the screen width.
@@ -316,7 +319,8 @@
// TODO(b/241386436): shouldn't change any launcher behaviour
mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
&& inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
- && inv.folderSpecsId != INVALID_RESOURCE_HANDLE;
+ && inv.folderSpecsId != INVALID_RESOURCE_HANDLE
+ && inv.hotseatSpecsId != INVALID_RESOURCE_HANDLE;
mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
// Determine device posture.
@@ -495,7 +499,17 @@
int hotseatBarBottomSpace = pxFromDp(inv.hotseatBarBottomSpace[mTypeIndex], mMetrics);
int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
- hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+
+ if (mIsResponsiveGrid) {
+ HotseatSpecs hotseatSpecs =
+ HotseatSpecs.create(new ResourceHelper(context,
+ isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
+ mResponsiveHotseatSpec = hotseatSpecs.getCalculatedHeightSpec(heightPx);
+ hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
+ } else {
+ hotseatQsbSpace = pxFromDp(inv.hotseatQsbSpace[mTypeIndex], mMetrics);
+ }
+
// Have a little space between the inset and the QSB
if (mInsets.bottom + minQsbMargin > hotseatBarBottomSpace) {
int availableSpace = hotseatQsbSpace - (mInsets.bottom - hotseatBarBottomSpace);
@@ -1985,6 +1999,7 @@
+ mAllAppsResponsiveWidthSpec.toString());
writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
+ writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
}
}
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index f94a3c5..3c90408 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
@@ -37,6 +38,8 @@
* Note: AppCompatEditText doesn't fully support #displayCompletions and #onCommitCompletion
*/
public class ExtendedEditText extends EditText {
+ private static final String TAG = "ExtendedEditText";
+
private final Set<OnFocusChangeListener> mOnFocusChangeListeners = new HashSet<>();
private boolean mForceDisableSuggestions = false;
@@ -89,9 +92,17 @@
return false;
}
- public void showKeyboard() {
+ /**
+ * Synchronously shows the soft input method.
+ *
+ * @param shouldFocus whether this EditText should also request focus.
+ * @return true if the keyboard is shown correctly and focus is given to this view (if
+ * applicable).
+ */
+ public boolean showKeyboard(boolean shouldFocus) {
onKeyboardShown();
- showSoftInput();
+ boolean focusResult = !shouldFocus || requestFocus();
+ return focusResult && showSoftInputInternal();
}
public void hideKeyboard() {
@@ -104,10 +115,15 @@
.keyboardStateManager().setKeyboardState(SHOW);
}
- private boolean showSoftInput() {
- return requestFocus() &&
- getContext().getSystemService(InputMethodManager.class)
- .showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+ private boolean showSoftInputInternal() {
+ boolean result = false;
+ InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
+ if (imm != null) {
+ result = imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT);
+ } else {
+ Log.w(TAG, "Failed to retrieve InputMethodManager from the system.");
+ }
+ return result;
}
public void dispatchBackKey() {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index a4d77bd..be14844 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -190,6 +190,8 @@
public int folderSpecsId = INVALID_RESOURCE_HANDLE;
@XmlRes
public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
public String dbFile;
public int defaultLayoutId;
@@ -369,6 +371,8 @@
allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
folderSpecsId = closestProfile.mFolderSpecsId;
folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
+ hotseatSpecsId = closestProfile.mHotseatSpecsId;
+ hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
this.deviceType = deviceType;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -820,6 +824,8 @@
private final int mAllAppsSpecsTwoPanelId;
private final int mFolderSpecsId;
private final int mFolderSpecsTwoPanelId;
+ private final int mHotseatSpecsId;
+ private final int mHotseatSpecsTwoPanelId;
public GridOption(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(
@@ -897,6 +903,11 @@
mFolderSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
+ mHotseatSpecsTwoPanelId = a.getResourceId(
+ R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
+ INVALID_RESOURCE_HANDLE);
} else {
mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -904,6 +915,8 @@
mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mFolderSpecsId = INVALID_RESOURCE_HANDLE;
mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
+ mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
}
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4427a49..ecbc7a9 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -160,7 +160,7 @@
* Focuses the search field to handle key events.
*/
public void focusSearchField() {
- mInput.showKeyboard();
+ mInput.showKeyboard(true /* shouldFocus */);
}
/**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e530070..7ca6c70 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -83,11 +83,11 @@
*/
// TODO(Block 1): Clean up flags
public static final BooleanFlag ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES = getReleaseFlag(
- 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", TEAMFOOD,
+ 270394041, "ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", ENABLED,
"Enable option to replace decorator-based search result backgrounds with drawables");
public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = getReleaseFlag(
- 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", TEAMFOOD,
+ 270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", ENABLED,
"Enable option to launch search results using the new view container transitions");
// TODO(Block 2): Clean up flags
@@ -256,7 +256,7 @@
"Inject fallback app corpus result when AiAi fails to return it.");
public static final BooleanFlag ENABLE_LONG_PRESS_NAV_HANDLE =
- getDebugFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", DISABLED,
+ getReleaseFlag(282993230, "ENABLE_LONG_PRESS_NAV_HANDLE", TEAMFOOD,
"Enables long pressing on the bottom bar nav handle to trigger events.");
// TODO(Block 17): Clean up flags
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index f38cce1..55a539a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -535,7 +535,7 @@
mFolderName.selectAll();
}
}
- mFolderName.showKeyboard();
+ mFolderName.showKeyboard(true /* shouldFocus */);
mFolderName.displayCompletions(
Stream.of(mInfo.suggestedFolderNames.getLabels())
.filter(Objects::nonNull)
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecs.kt
new file mode 100644
index 0000000..482508d
--- /dev/null
+++ b/src/com/android/launcher3/responsive/HotseatSpecs.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 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.launcher3.responsive
+
+import android.content.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+
+class HotseatSpecs(val specs: List<HotseatSpec>) {
+
+ fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
+ val spec = specs.firstOrNull { availableHeight <= it.maxAvailableSize }
+ check(spec != null) { "No available height spec found within $availableHeight." }
+ return CalculatedHotseatSpec(availableHeight, spec)
+ }
+
+ companion object {
+ private const val XML_HOTSEAT_SPEC = "hotseatSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): HotseatSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
+ return HotseatSpecs(specs.filter { it.specType == ResponsiveSpec.SpecType.HEIGHT })
+ }
+ }
+}
+
+data class HotseatSpec(
+ val maxAvailableSize: Int,
+ val specType: ResponsiveSpec.SpecType,
+ val hotseatQsbSpace: SizeSpec
+) {
+
+ init {
+ check(isValid()) { "Invalid HotseatSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ ResponsiveSpec.SpecType.values()[
+ attrs.getInt(
+ R.styleable.ResponsiveSpec_specType,
+ ResponsiveSpec.SpecType.HEIGHT.ordinal
+ )],
+ hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE)
+ )
+
+ fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean {
+ return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize()
+ }
+
+ companion object {
+ private const val LOG_TAG = "HotseatSpec"
+ }
+}
+
+class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) {
+
+ var hotseatQsbSpace: Int = 0
+ private set
+
+ init {
+ hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace)
+ }
+
+ override fun hashCode(): Int {
+ var result = availableSpace.hashCode()
+ result = 31 * result + hotseatQsbSpace.hashCode()
+ result = 31 * result + spec.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is CalculatedHotseatSpec &&
+ availableSpace == other.availableSpace &&
+ hotseatQsbSpace == other.hotseatQsbSpace &&
+ spec == other.spec
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " +
+ "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ ")"
+ }
+}
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index d3868f0..c868c9f 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -107,11 +107,20 @@
return true
}
+ fun onlyFixedSize(): Boolean {
+ if (ofAvailableSpace > 0 || ofRemainderSpace > 0 || matchWorkspace) {
+ Log.e(TAG, "SizeSpec#onlyFixedSize - only fixed size allowed for this tag")
+ return false
+ }
+ return true
+ }
+
object XmlTags {
const val START_PADDING = "startPadding"
const val END_PADDING = "endPadding"
const val GUTTER = "gutter"
const val CELL_SIZE = "cellSize"
+ const val HOTSEAT_QSB_SPACE = "hotseatQsbSpace"
}
companion object {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index abca1f8..438d4a0 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -888,6 +888,18 @@
return mContent;
}
+ /** Gets the search bar, which is used for testing */ // b/294050472
+ @VisibleForTesting
+ public View getSearchBar() {
+ return (View) mSearchBar;
+ }
+
+ /** Gets the search bar container, which is used for testing */ // b/294050472
+ @VisibleForTesting
+ public View getSearchBarContainer() {
+ return (View) mSearchBarContainer;
+ }
+
/** Opens the first header in widget picker and scrolls to the top of the RecyclerView. */
@VisibleForTesting
public void openFirstHeader() {
diff --git a/tests/res/xml/invalid_hotseat_file_case_1.xml b/tests/res/xml/invalid_hotseat_file_case_1.xml
new file mode 100644
index 0000000..fcbc5ea
--- /dev/null
+++ b/tests/res/xml/invalid_hotseat_file_case_1.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 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.
+ -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="847dp">
+ <hotseatQsbSpace launcher:ofAvailableSpace="1" />
+ </hotseatSpec>
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <hotseatQsbSpace launcher:fixedSize="36dp" />
+ </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/valid_hotseat_file.xml b/tests/res/xml/valid_hotseat_file.xml
new file mode 100644
index 0000000..c7f52e8
--- /dev/null
+++ b/tests/res/xml/valid_hotseat_file.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2023 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.
+ -->
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="847dp">
+ <hotseatQsbSpace launcher:fixedSize="24dp" />
+ </hotseatSpec>
+
+ <hotseatSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="9999dp">
+ <hotseatQsbSpace launcher:fixedSize="36dp" />
+ </hotseatSpec>
+
+</hotseatSpecs>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 694893a..dd79ca8 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -22,6 +22,7 @@
import android.util.DisplayMetrics
import android.view.Surface
import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.NavigationMode
import com.android.launcher3.util.WindowBounds
@@ -320,4 +321,12 @@
private fun writeToDevice(context: Context, fileName: String, content: String) {
File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content)
}
+
+ protected fun Float.dpToPx(): Float {
+ return ResourceUtils.pxFromDp(this, context!!.resources.displayMetrics).toFloat()
+ }
+
+ protected fun Int.dpToPx(): Int {
+ return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
+ }
}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
index 863cf76..f2a269a 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
@@ -21,7 +21,6 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
-import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
@@ -118,8 +117,4 @@
assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
}
}
-
- private fun Int.dpToPx(): Int {
- return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
- }
}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
new file mode 100644
index 0000000..0ecf7ba
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.launcher3.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedHotseatSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - skips the first breakpoint
+ */
+ @Test
+ fun normalPhone_returnsSecondBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ initializeVarsForPhone(deviceSpec)
+
+ // Hotseat uses the whole device height
+ val availableHeight = deviceSpec.naturalSize.second
+
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.hotseatQsbSpace).isEqualTo(95)
+ }
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - use the first breakpoint
+ */
+ @Test
+ fun smallPhone_returnsFirstBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ deviceSpec.densityDpi = 540 // larger display size
+ initializeVarsForPhone(deviceSpec)
+
+ // Hotseat uses the whole device height
+ val availableHeight = deviceSpec.naturalSize.second
+
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.hotseatQsbSpace).isEqualTo(81)
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
index e21af57..4b05949 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
@@ -22,7 +22,6 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
import com.google.common.truth.Truth.assertThat
@@ -249,12 +248,4 @@
val folderSpecs = FolderSpecs.create(resourceHelper)
folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
}
-
- private fun Float.dpToPx(): Float {
- return ResourceUtils.pxFromDp(this, context!!.resources.displayMetrics).toFloat()
- }
-
- private fun Int.dpToPx(): Int {
- return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
- }
}
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
new file mode 100644
index 0000000..c764e47
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.launcher3.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.android.systemui.util.dpToPx
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatSpecsTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun parseValidFile() {
+ val hotseatSpecs =
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
+ assertThat(hotseatSpecs.specs.size).isEqualTo(2)
+
+ val expectedSpecs =
+ listOf(
+ HotseatSpec(
+ maxAvailableSize = 847.dpToPx(),
+ specType = ResponsiveSpec.SpecType.HEIGHT,
+ hotseatQsbSpace = SizeSpec(24f.dpToPx())
+ ),
+ HotseatSpec(
+ maxAvailableSize = 9999.dpToPx(),
+ specType = ResponsiveSpec.SpecType.HEIGHT,
+ hotseatQsbSpace = SizeSpec(36f.dpToPx())
+ ),
+ )
+
+ assertThat(hotseatSpecs.specs.size).isEqualTo(expectedSpecs.size)
+ assertThat(hotseatSpecs.specs[0]).isEqualTo(expectedSpecs[0])
+ assertThat(hotseatSpecs.specs[1]).isEqualTo(expectedSpecs[1])
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
+ HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_hotseat_file_case_1))
+ }
+}
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
index 088cae1..8ca07c6 100644
--- a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
@@ -139,4 +139,20 @@
assertThat(instance.isValid()).isEqualTo(false)
}
}
+
+ @Test
+ fun onlyFixedSize() {
+ assertThat(SizeSpec(fixedSize = 16f).onlyFixedSize()).isEqualTo(true)
+
+ val combinations =
+ listOf(
+ SizeSpec(0f, 1.1f, 0f, false),
+ SizeSpec(0f, 0f, 1.1f, false),
+ SizeSpec(0f, 0f, 0f, true)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.onlyFixedSize()).isEqualTo(false)
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 38de2fa..83d9f4a 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -110,6 +110,7 @@
DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
RECENTS_DRAG_LAYER + "ArrowTipView",
+ DRAG_LAYER + "ArrowTipView",
DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
DRAG_LAYER
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index d9517b0..4210d06 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -41,12 +41,10 @@
DRAG_LAYER
+ "SearchContainerView:id/apps_view|AllAppsRecyclerView:id/apps_list_view"
+ "|BubbleTextView:id/icon",
- DRAG_LAYER + "LauncherDragView|ImageView",
DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
DRAG_LAYER
+ "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
+ "/apps_list_view|BubbleTextView:id/icon",
- DRAG_LAYER + "LauncherDragView|View",
CONTENT
+ "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+ "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
@@ -61,7 +59,8 @@
RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView|IconView:id/icon",
DRAG_LAYER
+ "SearchContainerView:id/apps_view|UniversalSearchInputView:id"
- + "/search_container_all_apps|View:id/ripple"
+ + "/search_container_all_apps|View:id/ripple",
+ DRAG_LAYER + "LauncherDragView"
));
// Per-AnalysisNode data that's specific to this detector.