From 82f7b5ad445c73af78e13e933eb6b9dd4b709a9f Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Wed, 10 Jul 2024 13:19:52 +0000 Subject: [PATCH 01/22] Fix onUserInteraction not called with 3-button-nav and enableOnBackInvokedCallback=true We should ignore KeyEvents with the FLAG_CANCELED flag in log analysis for Launcher tests as these KeyEvents should not have any effect. Bug: 346943119 Flag: com.android.window.flags.predictive_back_system_anims Test: atest BackNavigationTests Test: atest OnBackInvokedCallbackGestureTest Test: Manual, i.e. verifying in a test app that onUserInteraction is called when pressing back key in 3-button-nav and when enableOnBackInvokedCallback=true Change-Id: Icff47009757c49cdd8998244dd3e9459cc6ee085 --- .../launcher3/tapl/LauncherInstrumentation.java | 11 +++++++++++ .../android/launcher3/tapl/LogEventChecker.java | 14 +++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index f02a0c2296..3e0bdac013 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -98,6 +98,7 @@ import java.util.concurrent.TimeoutException; import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Supplier; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -2402,6 +2403,16 @@ public final class LauncherInstrumentation { disableSensorRotation(); final Integer initialPid = getPid(); final LogEventChecker eventChecker = new LogEventChecker(this); + eventChecker.setLogExclusionRule(event -> { + Matcher matcher = Pattern.compile("KeyEvent.*flags=0x([0-9a-fA-F]+)").matcher(event); + if (matcher.find()) { + int keyEventFlags = Integer.parseInt(matcher.group(1), 16); + // ignore KeyEvents with FLAG_CANCELED + return (keyEventFlags & KeyEvent.FLAG_CANCELED) != 0; + } + return false; + }); + if (eventChecker.start()) mEventChecker = eventChecker; return () -> { diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java index 055a357049..3a49160088 100644 --- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java +++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java @@ -35,6 +35,8 @@ public class LogEventChecker { // Map from an event sequence name to an ordered list of expected events in that sequence. private final ListMap mExpectedEvents = new ListMap<>(); + private LogExclusionRule mLogExclusionRule = null; + LogEventChecker(LauncherInstrumentation launcher) { mLauncher = launcher; } @@ -48,6 +50,10 @@ public class LogEventChecker { mExpectedEvents.add(sequence, pattern); } + void setLogExclusionRule(LogExclusionRule logExclusionRule) { + mLogExclusionRule = logExclusionRule; + } + // Waits for the expected number of events and returns them. private ListMap finishSync(long waitForExpectedCountMs) { final long startTime = SystemClock.uptimeMillis(); @@ -74,7 +80,9 @@ public class LogEventChecker { final ListMap eventSequences = new ListMap<>(); for (String rawEvent : rawEvents) { final String[] split = rawEvent.split("/"); - eventSequences.add(split[0], split[1]); + if (mLogExclusionRule == null || !mLogExclusionRule.shouldExclude(split[1])) { + eventSequences.add(split[0], split[1]); + } } return eventSequences; } @@ -175,4 +183,8 @@ public class LogEventChecker { return list; } } + + interface LogExclusionRule { + boolean shouldExclude(String event); + } } From 5c44a7a19c5ca9d9647157f7fbd5f028be18697f Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Wed, 26 Jun 2024 17:09:12 -0700 Subject: [PATCH 02/22] Disable gesture nav while PiP anim is running This is a re-land of ag/28058564 To prevent the Recents transition that may conflict with PiP Flag: EXEMPT bug fix Bug: 340724371 Test: Swipe to show TaskBar while expanding PiP to FS mode Change-Id: I8395f40bf64bc69af0e3c39789f3a727b03fae0d --- .../com/android/quickstep/RecentsAnimationDeviceState.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index cd62265751..f902284fcb 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; @@ -416,7 +417,8 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, E | SYSUI_STATE_QUICK_SETTINGS_EXPANDED | SYSUI_STATE_MAGNIFICATION_OVERLAP | SYSUI_STATE_DEVICE_DREAMING - | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION; + | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION + | SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING; return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled; } From a393f2b6781c8323247bf478304c938da5209f2c Mon Sep 17 00:00:00 2001 From: Ikram Gabiyev Date: Wed, 17 Jul 2024 12:38:23 -0700 Subject: [PATCH 03/22] Pass empty srcRectHint if overlay is present Launcher should pass an empty srcRectHint to Shell via stopSwipePipToHome if overlay is present, so that Shell know that srcRectHint is being "simulated" for the enter PiP animation with an app icon overlay. Shell will recalculate the simulated src-rect based on appBounds and aspect ratio of the destination bounds. Bug: 352773027 Flag: EXEMPT bugfix Test: swipe PiP to home from Maps with fixed rotation (90 and 270) Change-Id: I5f89c5af9d6b8a66725e346d09e7b3d84044bb4c --- quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index f020c8f170..20eaddc6f9 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -1631,14 +1631,17 @@ public abstract class AbsSwipeUpHandler Date: Mon, 15 Jul 2024 19:10:21 -0700 Subject: [PATCH 04/22] Don't add floatingMaskView if it will not fit. See this screenshot for math explanation: https://screenshot.googleplex.com/9x256x5uk7qNZDg We use the Y values of the views to determine if the header will be clipped by the floating mask view. Also added logs for private space. bug:343478928 Test manually before: see bug. after: https://drive.google.com/file/d/1FZXDQG-hV-V3wErVR0-nVCMgjUDk910Z/view?usp=sharing Flag: com.android.launcher3.enable_private_space Change-Id: I025e4e62f9ba60e8493d2aceb139d2c4ed6a84dd --- .../allapps/PrivateProfileManager.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java index aefb7e92d5..c1264d6135 100644 --- a/src/com/android/launcher3/allapps/PrivateProfileManager.java +++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java @@ -364,6 +364,7 @@ public class PrivateProfileManager extends UserProfileManager { /** Add Private Space Header view elements based upon {@link UserProfileState} */ public void bindPrivateSpaceHeaderViewElements(RelativeLayout parent) { mPSHeader = parent; + Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Binding private space."); updateView(); if (mOnPSHeaderAdded != null) { MAIN_EXECUTOR.execute(mOnPSHeaderAdded); @@ -376,6 +377,8 @@ public class PrivateProfileManager extends UserProfileManager { if (mPSHeader == null) { return; } + Log.d(TAG, "bindPrivateSpaceHeaderViewElements: " + "Updating view with state: " + + getCurrentState()); mPSHeader.setAlpha(1); ViewGroup lockPill = mPSHeader.findViewById(R.id.ps_lock_unlock_button); assert lockPill != null; @@ -761,6 +764,9 @@ public class PrivateProfileManager extends UserProfileManager { alphaAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { + if (mFloatingMaskView == null) { + return; + } mFloatingMaskView.setTranslationY((float) valueAnimator.getAnimatedValue()); } }); @@ -844,8 +850,22 @@ public class PrivateProfileManager extends UserProfileManager { if (!Flags.privateSpaceAddFloatingMaskView()) { return; } + // Use getLocationOnScreen() as simply checking for mPSHeader.getBottom() is only relative + // to its parent. + int[] psHeaderLocation = new int[2]; + mPSHeader.getLocationOnScreen(psHeaderLocation); + int psHeaderBottomY = psHeaderLocation[1] + mPsHeaderHeight; + // Calculate the topY of the floatingMaskView as if it was added. + int floatingMaskViewBottomBoxTopY = + (int) (mAllApps.getBottom() - getMainRecyclerView().getPaddingBottom()); + // Don't attach if the header will be clipped by the floating mask view. + if (psHeaderBottomY > floatingMaskViewBottomBoxTopY) { + mFloatingMaskView = null; + return; + } mFloatingMaskView = (FloatingMaskView) mAllApps.getLayoutInflater().inflate( R.layout.private_space_mask_view, mAllApps, false); + assert mFloatingMaskView != null; mAllApps.addView(mFloatingMaskView); // Translate off the screen first if its collapsing so this header view isn't visible to // user when animation starts. From 5c4c3bcbf3a976a893116cfbd32d9d3200ae222b Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 18 Jul 2024 09:20:49 -0700 Subject: [PATCH 05/22] [Test Week] Move SimpleBroadcastReceiver to multivalentTests folder Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: I82a59b6d090df7a246ba283bbfa286c99417c876 --- .../src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ => multivalentTests}/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt (100%) diff --git a/tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt similarity index 100% rename from tests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt rename to tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt From f667c3bc3bf924a43b167a6171dcbb40f492b828 Mon Sep 17 00:00:00 2001 From: mpodolian Date: Wed, 17 Jul 2024 22:21:18 +0100 Subject: [PATCH 06/22] Prepare BubbleBarViewController to support persistent task bar. Added required resources for the transient taskbar. Unified BubbleBarViewController logic for the persistent taskbar. Bug: 350065038 Flag: com.android.wm.shell.enable_bubble_bar Test: atest Change-Id: I24e6d2d43bf8145cdcc71c75da0cb6f6e84373da --- quickstep/res/values/dimens.xml | 2 + .../bubbles/BubbleBarViewController.java | 56 +++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index d4f66e2ee1..867ce1769d 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -449,11 +449,13 @@ 32dp 36dp + 28dp 24dp 12dp 16dp 6dp 8dp + @dimen/bubblebar_icon_spacing 12dp 1dp diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 24b91395f8..94e9e94f9f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -31,6 +31,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.anim.AnimatedFloat; import com.android.launcher3.taskbar.TaskbarActivityContext; @@ -62,6 +63,7 @@ public class BubbleBarViewController { private final TaskbarActivityContext mActivity; private final BubbleBarView mBarView; private int mIconSize; + private int mBubbleBarPadding; // Initialized in init. private BubbleStashController mBubbleStashController; @@ -110,10 +112,9 @@ public class BubbleBarViewController { mTaskbarStashController = controllers.taskbarStashController; mTaskbarInsetsController = controllers.taskbarInsetsController; mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController); - + onBubbleBarConfigurationChanged(/* animate= */ false); mActivity.addOnDeviceProfileChangeListener( - dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true)); - updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false); + dp -> onBubbleBarConfigurationChanged(/* animate= */ true)); mBubbleBarScale.updateValue(1f); mBubbleClickListener = v -> onBubbleClicked(v); mBubbleBarClickListener = v -> onBubbleBarClicked(); @@ -334,27 +335,60 @@ public class BubbleBarViewController { // Modifying view related properties. // - private void updateBubbleBarIconSize(int newIconSize, boolean animate) { + /** Notifies controller of configuration change, so bubble bar can be adjusted */ + public void onBubbleBarConfigurationChanged(boolean animate) { + int newIconSize; + int newPadding; Resources res = mActivity.getResources(); + if (mBubbleStashController.isBubblesShowingOnHome()) { + newIconSize = getBubbleBarIconSizeFromDeviceProfile(res); + newPadding = getBubbleBarPaddingFromDeviceProfile(res); + } else { + // the bubble bar is shown inside the persistent task bar, use preset sizes + newIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_persistent_taskbar); + newPadding = res.getDimensionPixelSize( + R.dimen.bubblebar_icon_spacing_persistent_taskbar); + } + updateBubbleBarIconSizeAndPadding(newIconSize, newPadding, animate); + } + + + private int getBubbleBarIconSizeFromDeviceProfile(Resources res) { + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); DisplayMetrics dm = res.getDisplayMetrics(); float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, APP_ICON_SMALL_DP, dm); float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, APP_ICON_MEDIUM_DP, dm); - float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - APP_ICON_LARGE_DP, dm); float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f; - float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f; - mIconSize = newIconSize <= smallMediumThreshold + int taskbarIconSize = deviceProfile.taskbarIconSize; + return taskbarIconSize <= smallMediumThreshold ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) : res.getDimensionPixelSize(R.dimen.bubblebar_icon_size); - float bubbleBarPadding = newIconSize >= mediumLargeThreshold + + } + + private int getBubbleBarPaddingFromDeviceProfile(Resources res) { + DeviceProfile deviceProfile = mActivity.getDeviceProfile(); + DisplayMetrics dm = res.getDisplayMetrics(); + float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + APP_ICON_MEDIUM_DP, dm); + float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + APP_ICON_LARGE_DP, dm); + float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f; + return deviceProfile.taskbarIconSize >= mediumLargeThreshold ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) : res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing); + } + + private void updateBubbleBarIconSizeAndPadding(int iconSize, int padding, boolean animate) { + if (mIconSize == iconSize && mBubbleBarPadding == padding) return; + mIconSize = iconSize; + mBubbleBarPadding = padding; if (animate) { - mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding); + mBarView.animateBubbleBarIconSize(iconSize, padding); } else { - mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding); + mBarView.setIconSizeAndPadding(iconSize, padding); } } From c56711c6691e7957ed98a9de6b3acb79103abb66 Mon Sep 17 00:00:00 2001 From: "Liam, Lee Pong Lam" Date: Tue, 16 Jul 2024 22:24:53 +0000 Subject: [PATCH 07/22] Add unit tests for SettingsChangeLogger Bug: 353584526 Flag: EXEMPT bugfix Test: Unit tests Change-Id: Ib1bac2471d9cc467b8b6697ae4d79f561eea75e4 --- .../logging/SettingsChangeLogger.java | 27 +++- .../logging/SettingsChangeLoggerTest.kt | 131 ++++++++++++++++++ 2 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java index 5ac04da0ac..717f6c879b 100644 --- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java +++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java @@ -39,7 +39,8 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Xml; -import com.android.launcher3.AutoInstallsLayout; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.LauncherPrefs; import com.android.launcher3.R; import com.android.launcher3.logging.InstanceId; @@ -73,7 +74,6 @@ public class SettingsChangeLogger implements new MainThreadInitializedObject<>(SettingsChangeLogger::new); private static final String TAG = "SettingsChangeLogger"; - private static final String ROOT_TAG = "androidx.preference.PreferenceScreen"; private static final String BOOLEAN_PREF = "SwitchPreference"; private final Context mContext; @@ -85,8 +85,13 @@ public class SettingsChangeLogger implements private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent; private SettingsChangeLogger(Context context) { + this(context, StatsLogManager.newInstance(context)); + } + + @VisibleForTesting + SettingsChangeLogger(Context context, StatsLogManager statsLogManager) { mContext = context; - mStatsLogManager = StatsLogManager.newInstance(mContext); + mStatsLogManager = statsLogManager; mLoggablePrefs = loadPrefKeys(context); DisplayController.INSTANCE.get(context).addChangeListener(this); mNavMode = DisplayController.getNavigationMode(context); @@ -105,7 +110,13 @@ public class SettingsChangeLogger implements ArrayMap result = new ArrayMap<>(); try { - AutoInstallsLayout.beginDocument(parser, ROOT_TAG); + // Move cursor to first tag because it could be + // androidx.preference.PreferenceScreen or PreferenceScreen + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG + && eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG @@ -189,13 +200,19 @@ public class SettingsChangeLogger implements prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff)); } + @VisibleForTesting + ArrayMap getLoggingPrefs() { + return mLoggablePrefs; + } + @Override public void close() { getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this); getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this); } - private static class LoggablePref { + @VisibleForTesting + static class LoggablePref { public boolean defaultValue; public int eventIdOn; public int eventIdOff; diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt new file mode 100644 index 0000000000..070eeafc25 --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 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.quickstep.logging + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.backedUpItem +import com.android.launcher3.logging.InstanceId +import com.android.launcher3.logging.StatsLogManager +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.atLeastOnce +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@RunWith(AndroidJUnit4::class) +class SettingsChangeLoggerTest { + private val mContext: Context = ApplicationProvider.getApplicationContext() + + private val mInstanceId = InstanceId.fakeInstanceId(1) + + private lateinit var mSystemUnderTest: SettingsChangeLogger + + @Mock private lateinit var mStatsLogManager: StatsLogManager + + @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger + + @Captor private lateinit var mEventCaptor: ArgumentCaptor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(mStatsLogManager.logger()).doReturn(mMockLogger) + whenever(mStatsLogManager.logger().withInstanceId(any())).doReturn(mMockLogger) + + mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager) + } + + @After + fun tearDown() { + mSystemUnderTest.close() + } + + @Test + fun loggingPrefs_correctDefaultValue() { + assertThat(mSystemUnderTest.loggingPrefs["pref_allowRotation"]!!.defaultValue).isFalse() + assertThat(mSystemUnderTest.loggingPrefs["pref_add_icon_to_home"]!!.defaultValue).isTrue() + assertThat(mSystemUnderTest.loggingPrefs["pref_overview_action_suggestions"]!!.defaultValue) + .isTrue() + assertThat(mSystemUnderTest.loggingPrefs["pref_smartspace_home_screen"]!!.defaultValue) + .isTrue() + assertThat(mSystemUnderTest.loggingPrefs["pref_enable_minus_one"]!!.defaultValue).isTrue() + } + + @Test + fun logSnapshot_defaultValue() { + mSystemUnderTest.logSnapshot(mInstanceId) + + verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture()) + val capturedEvents = mEventCaptor.allValues + assertThat(capturedEvents.isNotEmpty()).isTrue() + verifyDefaultEvent(capturedEvents) + // pref_allowRotation false + assertThat(capturedEvents.any { it.id == 616 }).isTrue() + } + + @Test + fun logSnapshot_updateValue() { + LauncherPrefs.get(mContext) + .put( + item = + backedUpItem( + sharedPrefKey = "pref_allowRotation", + defaultValue = false, + ), + value = true + ) + + mSystemUnderTest.logSnapshot(mInstanceId) + + verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture()) + val capturedEvents = mEventCaptor.allValues + assertThat(capturedEvents.isNotEmpty()).isTrue() + verifyDefaultEvent(capturedEvents) + // pref_allowRotation true + assertThat(capturedEvents.any { it.id == 615 }).isTrue() + } + + private fun verifyDefaultEvent(capturedEvents: MutableList) { + // LAUNCHER_NOTIFICATION_DOT_ENABLED + assertThat(capturedEvents.any { it.id == 611 }).isTrue() + // LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON + assertThat(capturedEvents.any { it.id == 625 }).isTrue() + // LAUNCHER_THEMED_ICON_DISABLED + assertThat(capturedEvents.any { it.id == 837 }).isTrue() + // pref_add_icon_to_home true + assertThat(capturedEvents.any { it.id == 613 }).isTrue() + // pref_overview_action_suggestions true + assertThat(capturedEvents.any { it.id == 619 }).isTrue() + // pref_smartspace_home_screen true + assertThat(capturedEvents.any { it.id == 621 }).isTrue() + // pref_enable_minus_one true + assertThat(capturedEvents.any { it.id == 617 }).isTrue() + } +} From 54bd36ef81f507ff9c2635d4a7b35786337baa4b Mon Sep 17 00:00:00 2001 From: Shamali P Date: Thu, 18 Jul 2024 14:22:12 +0000 Subject: [PATCH 08/22] Move out picker related methods from widgets model * The WidgetsBaseEntry and related types are specific to picker UI. So, moved them to a entry builder class. Bug: 353347512 Flag: EXEMPT BUGFIX Test: Unit test Change-Id: I42b3083b42ee03dc8d548e7464689ea270a36f22 --- .../launcher3/WidgetPickerActivity.java | 47 +++-- .../launcher3/model/BaseLauncherBinder.java | 5 +- .../launcher3/model/ModelTaskController.kt | 5 +- .../android/launcher3/model/WidgetsModel.java | 52 +---- .../model/WidgetsListBaseEntriesBuilder.kt | 51 +++++ .../WidgetsListBaseEntriesBuilderTest.kt | 178 ++++++++++++++++++ 6 files changed, 262 insertions(+), 76 deletions(-) create mode 100644 src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt create mode 100644 tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index d0be2f3cd1..0b1863398e 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -47,11 +47,11 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetPredictionsRequester; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.popup.PopupDataProvider; -import com.android.launcher3.util.ComponentKey; import com.android.launcher3.widget.WidgetCell; +import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder; import com.android.launcher3.widget.model.WidgetsListBaseEntry; -import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.picker.WidgetsFullSheet; import java.util.ArrayList; @@ -60,10 +60,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; -import java.util.stream.Collectors; /** An Activity that can host Launcher's widget picker. */ public class WidgetPickerActivity extends BaseActivity { @@ -112,7 +110,8 @@ public class WidgetPickerActivity extends BaseActivity { private WidgetsModel mModel; private LauncherAppState mApp; private WidgetPredictionsRequester mWidgetPredictionsRequester; - private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {}); + private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> { + }); private int mDesiredWidgetWidth; private int mDesiredWidgetHeight; @@ -287,45 +286,41 @@ public class WidgetPickerActivity extends BaseActivity { }; } - /** Updates the model with widgets, applies filters and launches the widgets sheet once - * widgets are available */ + /** + * Updates the model with widgets, applies filters and launches the widgets sheet once + * widgets are available + */ private void refreshAndBindWidgets() { MODEL_EXECUTOR.execute(() -> { LauncherAppState app = LauncherAppState.getInstance(this); Context context = app.getContext(); mModel.update(app, null); - final List allWidgets = - mModel.getFilteredWidgetsListForPicker(context, mWidgetsFilter); - final List defaultWidgets = - shouldShowDefaultWidgets() ? mModel.getFilteredWidgetsListForPicker(context, - mDefaultWidgetsFilter) : List.of(); - bindWidgets(allWidgets, defaultWidgets); + bindWidgets(mModel.getWidgetsByPackageItem()); // Open sheet once widgets are available, so that it doesn't interrupt the open // animation. openWidgetsSheet(); if (mUiSurface != null) { - Map allWidgetItems = allWidgets.stream() - .filter(entry -> entry instanceof WidgetsListContentEntry) - .flatMap(entry -> entry.mWidgets.stream()) - .distinct() - .collect(Collectors.toMap( - widget -> new ComponentKey(widget.componentName, widget.user), - Function.identity() - )); mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(), - mUiSurface, allWidgetItems); + mUiSurface, mModel.getWidgetsByComponentKey()); mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets); } }); } - private void bindWidgets(List allWidgets, - List defaultWidgets) { + private void bindWidgets(Map> widgets) { + WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder( + mApp.getContext()); + + final List allWidgets = builder.build(widgets, mWidgetsFilter); + final List defaultWidgets = + shouldShowDefaultWidgets() ? builder.build(widgets, + mDefaultWidgetsFilter) : List.of(); + MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(allWidgets, defaultWidgets)); } - private void openWidgetsSheet() { + private void openWidgetsSheet() { MAIN_EXECUTOR.execute(() -> { mWidgetSheet = WidgetsFullSheet.show(this, true); mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription); @@ -392,7 +387,7 @@ public class WidgetPickerActivity extends BaseActivity { mActiveOnBackAnimationCallback.onBackCancelled(); mActiveOnBackAnimationCallback = null; } - }; + } private boolean shouldShowDefaultWidgets() { // If optional filters such as size filter are present, we display them as default widgets. diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java index e6ade610f7..5faa2b83c3 100644 --- a/src/com/android/launcher3/model/BaseLauncherBinder.java +++ b/src/com/android/launcher3/model/BaseLauncherBinder.java @@ -52,6 +52,7 @@ import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.RunnableList; +import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder; import com.android.launcher3.widget.model.WidgetsListBaseEntry; import java.util.ArrayList; @@ -195,8 +196,8 @@ public class BaseLauncherBinder { if (!WIDGETS_ENABLED) { return; } - final List widgets = - mBgDataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext()); + List widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext()) + .build(mBgDataModel.widgetsModel.getWidgetsByPackageItem()); executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor); } diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt index 266ed0c67a..cf2cadc094 100644 --- a/src/com/android/launcher3/model/ModelTaskController.kt +++ b/src/com/android/launcher3/model/ModelTaskController.kt @@ -24,6 +24,7 @@ import com.android.launcher3.model.BgDataModel.FixedContainerItems import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.WorkspaceItemInfo import com.android.launcher3.util.PackageUserKey +import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder import java.util.Objects import java.util.concurrent.Executor import java.util.function.Predicate @@ -78,7 +79,9 @@ class ModelTaskController( } fun bindUpdatedWidgets(dataModel: BgDataModel) { - val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context) + val widgets = + WidgetsListBaseEntriesBuilder(app.context) + .build(dataModel.widgetsModel.widgetsByPackageItem) scheduleCallbackTask { it.bindAllWidgets(widgets) } } diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 58ebf0fa13..35064cf094 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -26,7 +26,6 @@ import com.android.launcher3.AppFilter; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; -import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.ComponentWithLabelAndIcon; import com.android.launcher3.icons.IconCache; @@ -39,9 +38,6 @@ import com.android.launcher3.util.Preconditions; import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; import com.android.launcher3.widget.WidgetManagerHelper; import com.android.launcher3.widget.WidgetSections; -import com.android.launcher3.widget.model.WidgetsListBaseEntry; -import com.android.launcher3.widget.model.WidgetsListContentEntry; -import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.wm.shell.Flags; import java.util.ArrayList; @@ -75,6 +71,9 @@ public class WidgetsModel { * Returns all widgets keyed by their component key. */ public synchronized Map getWidgetsByComponentKey() { + if (!WIDGETS_ENABLED) { + return Collections.emptyMap(); + } return mWidgetsByPackageItem.values().stream() .flatMap(Collection::stream).distinct() .collect(Collectors.toMap( @@ -87,51 +86,10 @@ public class WidgetsModel { * Returns widgets grouped by the package item that they should belong to. */ public synchronized Map> getWidgetsByPackageItem() { - return mWidgetsByPackageItem; - } - - /** - * Returns a list of {@link WidgetsListBaseEntry} filtered using given widget item filter. All - * {@link WidgetItem}s in a single row are sorted (based on label and user), but the overall - * list of {@link WidgetsListBaseEntry}s is not sorted. - * - * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List) - */ - public synchronized ArrayList getFilteredWidgetsListForPicker( - Context context, - Predicate widgetItemFilter) { if (!WIDGETS_ENABLED) { - return new ArrayList<>(); + return Collections.emptyMap(); } - ArrayList result = new ArrayList<>(); - AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context); - - for (Map.Entry> entry : - mWidgetsByPackageItem.entrySet()) { - PackageItemInfo pkgItem = entry.getKey(); - List widgetItems = entry.getValue() - .stream() - .filter(widgetItemFilter).toList(); - if (!widgetItems.isEmpty()) { - String sectionName = (pkgItem.title == null) ? "" : - indexer.computeSectionName(pkgItem.title); - result.add(WidgetsListHeaderEntry.create(pkgItem, sectionName, widgetItems)); - result.add(new WidgetsListContentEntry(pkgItem, sectionName, widgetItems)); - } - } - return result; - } - - /** - * Returns a list of {@link WidgetsListBaseEntry}. All {@link WidgetItem} in a single row - * are sorted (based on label and user), but the overall list of - * {@link WidgetsListBaseEntry}s is not sorted. - * - * @see com.android.launcher3.widget.picker.WidgetsListAdapter#setWidgets(List) - */ - public synchronized ArrayList getWidgetsListForPicker(Context context) { - // return all items - return getFilteredWidgetsListForPicker(context, /*widgetItemFilter=*/ item -> true); + return mWidgetsByPackageItem; } /** diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt new file mode 100644 index 0000000000..1abe4da98b --- /dev/null +++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilder.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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.widget.model + +import android.content.Context +import com.android.launcher3.compat.AlphabeticIndexCompat +import com.android.launcher3.model.WidgetItem +import com.android.launcher3.model.data.PackageItemInfo +import java.util.function.Predicate + +/** + * A helper class that builds the list of [WidgetsListBaseEntry]s used by the UI to display widgets. + */ +class WidgetsListBaseEntriesBuilder(val context: Context) { + + /** Builds the widgets list entries in a format understandable by the widget picking UI. */ + @JvmOverloads + fun build( + widgetsByPackageItem: Map>, + widgetFilter: Predicate = Predicate { true }, + ): List { + val indexer = AlphabeticIndexCompat(context) + + return buildList { + for ((pkgItem, widgetItems) in widgetsByPackageItem.entries) { + val filteredWidgetItems = widgetItems.filter { widgetFilter.test(it) } + if (filteredWidgetItems.isNotEmpty()) { + // Enables fast scroll popup to show right characters in all locales. + val sectionName = pkgItem.title?.let { indexer.computeSectionName(it) } ?: "" + + add(WidgetsListHeaderEntry.create(pkgItem, sectionName, filteredWidgetItems)) + add(WidgetsListContentEntry(pkgItem, sectionName, filteredWidgetItems)) + } + } + } + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt new file mode 100644 index 0000000000..5df7caa372 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/widget/model/WidgetsListBaseEntriesBuilderTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2024 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.widget.model + +import android.content.ComponentName +import android.content.Context +import android.os.UserHandle +import android.platform.test.rule.AllowedDevices +import android.platform.test.rule.DeviceProduct +import android.platform.test.rule.LimitDevicesRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.launcher3.InvariantDeviceProfile +import com.android.launcher3.LauncherAppState +import com.android.launcher3.icons.ComponentWithLabel +import com.android.launcher3.icons.IconCache +import com.android.launcher3.model.WidgetItem +import com.android.launcher3.model.data.PackageItemInfo +import com.android.launcher3.util.ActivityContextWrapper +import com.android.launcher3.util.WidgetUtils +import com.android.launcher3.widget.LauncherAppWidgetProviderInfo +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.invocation.InvocationOnMock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer + +@RunWith(AndroidJUnit4::class) +@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC]) +class WidgetsListBaseEntriesBuilderTest { + @Rule @JvmField val limitDevicesRule = LimitDevicesRule() + @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Mock private lateinit var iconCache: IconCache + + private lateinit var userHandle: UserHandle + private lateinit var context: Context + private lateinit var testInvariantProfile: InvariantDeviceProfile + private lateinit var allWidgets: Map> + private lateinit var underTest: WidgetsListBaseEntriesBuilder + + @Before + fun setUp() { + userHandle = UserHandle.CURRENT + context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + testInvariantProfile = LauncherAppState.getIDP(context) + + doAnswer { invocation: InvocationOnMock -> + val componentWithLabel = invocation.getArgument(0) as ComponentWithLabel + componentWithLabel.getComponent().shortClassName + } + .`when`(iconCache) + .getTitleNoCache(any()) + underTest = WidgetsListBaseEntriesBuilder(context) + + allWidgets = + mapOf( + // app 1 + packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE) to + listOf( + createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME), + createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME) + ), + // app 2 + packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE) to + listOf(createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)), + // app 3 + packageItemInfoWithTitle(APP_3_PACKAGE_NAME, APP_3_PACKAGE_TITLE) to + listOf(createWidgetItem(APP_3_PACKAGE_NAME, APP_3_PROVIDER_1_CLASS_NAME)) + ) + } + + @Test + fun widgetsListBaseEntriesBuilder_addsHeaderAndContentEntries_withCorrectSectionName() { + val expectedWidgetsCountBySection = + listOf( + APP_1_EXPECTED_SECTION_NAME to 2, + APP_2_EXPECTED_SECTION_NAME to 1, + APP_3_EXPECTED_SECTION_NAME to 1 + ) + + val entries = underTest.build(allWidgets) + + assertThat(entries).hasSize(6) + val headerEntrySectionAndWidgetSizes = + entries.filterIsInstance().map { + it.mTitleSectionName to it.mWidgets.size + } + val contentEntrySectionAndWidgetSizes = + entries.filterIsInstance().map { + it.mTitleSectionName to it.mWidgets.size + } + assertThat(headerEntrySectionAndWidgetSizes) + .containsExactlyElementsIn(expectedWidgetsCountBySection) + assertThat(contentEntrySectionAndWidgetSizes) + .containsExactlyElementsIn(expectedWidgetsCountBySection) + } + + @Test + fun widgetsListBaseEntriesBuilder_withFilter_addsFilteredHeaderAndContentEntries() { + val allowList = listOf(APP_1_PROVIDER_1_CLASS_NAME, APP_3_PROVIDER_1_CLASS_NAME) + val expectedWidgetsCountBySection = + listOf( + APP_1_EXPECTED_SECTION_NAME to 1, // one widget filtered out + APP_3_EXPECTED_SECTION_NAME to 1 + ) + + val entries = + underTest.build(allWidgets) { w -> allowList.contains(w.componentName.shortClassName) } + + assertThat(entries).hasSize(4) // app 2 filtered out + val headerEntrySectionAndWidgetSizes = + entries.filterIsInstance().map { + it.mTitleSectionName to it.mWidgets.size + } + val contentEntrySectionAndWidgetSizes = + entries.filterIsInstance().map { + it.mTitleSectionName to it.mWidgets.size + } + assertThat(headerEntrySectionAndWidgetSizes) + .containsExactlyElementsIn(expectedWidgetsCountBySection) + assertThat(contentEntrySectionAndWidgetSizes) + .containsExactlyElementsIn(expectedWidgetsCountBySection) + } + + private fun packageItemInfoWithTitle(packageName: String, title: String): PackageItemInfo { + val packageItemInfo = PackageItemInfo(packageName, userHandle) + packageItemInfo.title = title + return packageItemInfo + } + + private fun createWidgetItem(packageName: String, widgetProviderName: String): WidgetItem { + val providerInfo = + WidgetUtils.createAppWidgetProviderInfo( + ComponentName.createRelative(packageName, widgetProviderName) + ) + val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo) + return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context) + } + + companion object { + const val APP_1_PACKAGE_NAME = "com.example.app1" + const val APP_1_PACKAGE_TITLE = "App1" + const val APP_1_EXPECTED_SECTION_NAME = "A" // for fast popup + const val APP_1_PROVIDER_1_CLASS_NAME = "app1Provider1" + const val APP_1_PROVIDER_2_CLASS_NAME = "app1Provider2" + + const val APP_2_PACKAGE_NAME = "com.example.app2" + const val APP_2_PACKAGE_TITLE = "SomeApp2" + const val APP_2_EXPECTED_SECTION_NAME = "S" // for fast popup + const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1" + + const val APP_3_PACKAGE_NAME = "com.example.app3" + const val APP_3_PACKAGE_TITLE = "OtherApp3" + const val APP_3_EXPECTED_SECTION_NAME = "O" // for fast popup + const val APP_3_PROVIDER_1_CLASS_NAME = "app3Provider1" + } +} From 6ace1c0d3ad660564d8334da2bcbe5b932438ce7 Mon Sep 17 00:00:00 2001 From: Andrew Cole Date: Wed, 17 Jul 2024 16:24:03 -0700 Subject: [PATCH 09/22] Test Week - AccessibleDragLstenerAdapterTest Refactored AccessibleDragListnerAdapter to kotlin for null safety and removed a explicit call to Launcher. Added tests for all public methods * Refactored 1 class * Tested 1 class previously untested * Tested 4 public methods Bug: 353303621 Test: AccessibleDragListnerAdapterTest Flag: TEST_ONLY Change-Id: I7db70330f5e76b8f7298304314584e88e067c139 --- .../AccessibleDragListenerAdapter.java | 89 -------------- .../AccessibleDragListenerAdapter.kt | 75 ++++++++++++ .../AccessibleDragListenerAdapterTest.kt | 110 ++++++++++++++++++ 3 files changed, 185 insertions(+), 89 deletions(-) delete mode 100644 src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java create mode 100644 src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt create mode 100644 tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java deleted file mode 100644 index 79b81871cc..0000000000 --- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2016 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.accessibility; - -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.OnHierarchyChangeListener; - -import androidx.annotation.Nullable; - -import com.android.launcher3.CellLayout; -import com.android.launcher3.DropTarget.DragObject; -import com.android.launcher3.Launcher; -import com.android.launcher3.dragndrop.DragController.DragListener; -import com.android.launcher3.dragndrop.DragOptions; - -import java.util.function.Function; - -/** - * Utility listener to enable/disable accessibility drag flags for a ViewGroup - * containing CellLayouts - */ -public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener { - - private final ViewGroup mViewGroup; - private final Function mDelegateFactory; - - /** - * @param parent the viewgroup containing all the children - * @param delegateFactory function to create no delegates - */ - public AccessibleDragListenerAdapter(ViewGroup parent, - Function delegateFactory) { - mViewGroup = parent; - mDelegateFactory = delegateFactory; - } - - @Override - public void onDragStart(DragObject dragObject, DragOptions options) { - mViewGroup.setOnHierarchyChangeListener(this); - enableAccessibleDrag(true, dragObject); - } - - @Override - public void onDragEnd() { - mViewGroup.setOnHierarchyChangeListener(null); - enableAccessibleDrag(false, null); - Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this); - } - - - @Override - public void onChildViewAdded(View parent, View child) { - if (parent == mViewGroup) { - setEnableForLayout((CellLayout) child, true); - } - } - - @Override - public void onChildViewRemoved(View parent, View child) { - if (parent == mViewGroup) { - setEnableForLayout((CellLayout) child, false); - } - } - - protected void enableAccessibleDrag(boolean enable, @Nullable DragObject dragObject) { - for (int i = 0; i < mViewGroup.getChildCount(); i++) { - setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable); - } - } - - protected final void setEnableForLayout(CellLayout layout, boolean enable) { - layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null); - } -} diff --git a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt new file mode 100644 index 0000000000..21c2cafa3c --- /dev/null +++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 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.accessibility + +import android.view.View +import android.view.ViewGroup +import com.android.launcher3.CellLayout +import com.android.launcher3.DropTarget.DragObject +import com.android.launcher3.dragndrop.DragController +import com.android.launcher3.dragndrop.DragOptions +import com.android.launcher3.views.ActivityContext +import java.util.function.Function + +/** + * Utility listener to enable/disable accessibility drag flags for a ViewGroup containing + * CellLayouts + */ +open class AccessibleDragListenerAdapter +/** + * @param parent the viewgroup containing all the children + * @param delegateFactory function to create no delegates + */ +( + private val mViewGroup: ViewGroup, + private val mDelegateFactory: Function +) : DragController.DragListener, ViewGroup.OnHierarchyChangeListener { + override fun onDragStart(dragObject: DragObject, options: DragOptions) { + mViewGroup.setOnHierarchyChangeListener(this) + enableAccessibleDrag(true, dragObject) + } + + override fun onDragEnd() { + mViewGroup.setOnHierarchyChangeListener(null) + enableAccessibleDrag(false, null) + val activityContext = ActivityContext.lookupContext(mViewGroup.context) as ActivityContext + activityContext.getDragController>()?.removeDragListener(this) + } + + override fun onChildViewAdded(parent: View, child: View) { + if (parent === mViewGroup) { + setEnableForLayout(child as CellLayout, true) + } + } + + override fun onChildViewRemoved(parent: View, child: View) { + if (parent === mViewGroup) { + setEnableForLayout(child as CellLayout, false) + } + } + + protected open fun enableAccessibleDrag(enable: Boolean, dragObject: DragObject?) { + for (i in 0 until mViewGroup.childCount) { + setEnableForLayout(mViewGroup.getChildAt(i) as CellLayout, enable) + } + } + + protected fun setEnableForLayout(layout: CellLayout, enable: Boolean) { + layout.setDragAndDropAccessibilityDelegate( + if (enable) mDelegateFactory.apply(layout) else null + ) + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt new file mode 100644 index 0000000000..e03ee46811 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapterTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 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.accessibility + +import android.content.Context +import android.view.ViewGroup +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.CellLayout +import com.android.launcher3.DropTarget.DragObject +import com.android.launcher3.dragndrop.DragOptions +import com.android.launcher3.util.ActivityContextWrapper +import java.util.function.Function +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.kotlin.any + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AccessibleDragListenerAdapterTest { + + private lateinit var mockViewGroup: ViewGroup + private lateinit var mockChildOne: CellLayout + private lateinit var mockChildTwo: CellLayout + private lateinit var mDelegateFactory: Function + private lateinit var adapter: AccessibleDragListenerAdapter + private lateinit var mContext: Context + + @Before + fun setup() { + mContext = ActivityContextWrapper(getApplicationContext()) + mockViewGroup = mock(ViewGroup::class.java) + mockChildOne = mock(CellLayout::class.java) + mockChildTwo = mock(CellLayout::class.java) + `when`(mockViewGroup.context).thenReturn(mContext) + `when`(mockViewGroup.childCount).thenReturn(2) + `when`(mockViewGroup.getChildAt(0)).thenReturn(mockChildOne) + `when`(mockViewGroup.getChildAt(1)).thenReturn(mockChildTwo) + // Mock Delegate factory + mDelegateFactory = + mock(Function::class.java) as Function + `when`(mDelegateFactory.apply(any())) + .thenReturn(mock(DragAndDropAccessibilityDelegate::class.java)) + adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory) + } + + @Test + fun `onDragStart enables accessible drag for all view children`() { + // Create mock view children + val mockDragObject = mock(DragObject::class.java) + val mockDragOptions = mock(DragOptions::class.java) + + // Action + adapter.onDragStart(mockDragObject, mockDragOptions) + + // Assertion + verify(mockChildOne).setDragAndDropAccessibilityDelegate(any()) + verify(mockChildTwo).setDragAndDropAccessibilityDelegate(any()) + } + + @Test + fun `onDragEnd removes the accessibility delegate`() { + // Action + adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory) + adapter.onDragEnd() + + // Assertion + verify(mockChildOne).setDragAndDropAccessibilityDelegate(null) + verify(mockChildTwo).setDragAndDropAccessibilityDelegate(null) + } + + @Test + fun `onChildViewAdded sets enabled as true for childview`() { + // Action + adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory) + adapter.onChildViewAdded(mockViewGroup, mockChildOne) + + // Assertion + verify(mockChildOne).setDragAndDropAccessibilityDelegate(any()) + } + + @Test + fun `onChildViewRemoved sets enabled as false for childview`() { + // Action + adapter = AccessibleDragListenerAdapter(mockViewGroup, mDelegateFactory) + adapter.onChildViewRemoved(mockViewGroup, mockChildOne) + + // Assertion + verify(mockChildOne).setDragAndDropAccessibilityDelegate(null) + } +} From 2eeb08f216eac051224ac13c9eff7aa1a75ff118 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 18 Jul 2024 15:02:24 -0700 Subject: [PATCH 10/22] [Test Week] Replace verifyZeroInteractions with verifyNoMoreInteractions in RunnableListTest Fix: 354021767 Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: I85945c0a0f64ca0a65d28d66f3c47d83d0943c3b --- .../src/com/android/launcher3/util/RunnableListTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt index 093afc9060..cc672fef81 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt @@ -24,7 +24,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.reset import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest class RunnableListTest { @@ -73,7 +73,7 @@ class RunnableListTest { underTest.executeAllAndDestroy() - verifyZeroInteractions(runnable1) + verifyNoMoreInteractions(runnable1) } @Test @@ -107,7 +107,7 @@ class RunnableListTest { underTest.remove(runnable1) underTest.executeAllAndClear() - verifyZeroInteractions(runnable1) + verifyNoMoreInteractions(runnable1) verify(runnable2).run() } } From 30d02c20bf1c089b3d2f2ec78792773dd182d51e Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Mon, 15 Jul 2024 10:20:27 -0700 Subject: [PATCH 11/22] [Test Week] Add AllAppsRecyclerViewPoolTest Number of new tested methods: 2 Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: Ic210b5ad7988c9e8d060c959fb2a425068c82511 --- .../recyclerview/AllAppsRecyclerViewPool.kt | 45 ++++--- .../AllAppsRecyclerViewPoolTest.kt | 116 ++++++++++++++++++ 2 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt index 6d6b3b6497..f895b302c5 100644 --- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt +++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt @@ -17,6 +17,9 @@ package com.android.launcher3.recyclerview import android.content.Context +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.Companion.PROTECTED import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import androidx.recyclerview.widget.RecyclerView.ViewHolder @@ -38,21 +41,18 @@ const val EXTRA_ICONS_COUNT = 2 * [RecyclerView]. The view inflation will happen on background thread and inflated [ViewHolder]s * will be added to [RecycledViewPool] on main thread. */ -class AllAppsRecyclerViewPool : RecycledViewPool() { +class AllAppsRecyclerViewPool : RecycledViewPool() where T : Context, T : ActivityContext { var hasWorkProfile = false - private var mCancellableTask: CancellableTask>? = null + @VisibleForTesting(otherwise = PROTECTED) + var mCancellableTask: CancellableTask>? = null /** * Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate. */ - fun preInflateAllAppsViewHolders(context: T) where T : Context, T : ActivityContext { + fun preInflateAllAppsViewHolders(context: T) { val appsView = context.appsView ?: return val activeRv: RecyclerView = appsView.activeRecyclerView ?: return - val preInflateCount = getPreinflateCount(context) - if (preInflateCount <= 0) { - return - } // Create a separate context dedicated for all apps preinflation thread. The goal is to // create a separate AssetManager obj internally to avoid lock contention with @@ -77,34 +77,51 @@ class AllAppsRecyclerViewPool : RecycledViewPool() { null ) { override fun setAppsPerRow(appsPerRow: Int) = Unit + override fun getLayoutManager(): RecyclerView.LayoutManager? = null } + preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) { + getPreinflateCount(context) + } + } + + @VisibleForTesting(otherwise = PROTECTED) + fun preInflateAllAppsViewHolders( + adapter: RecyclerView.Adapter<*>, + viewType: Int, + parent: ViewGroup, + preInflationCountProvider: () -> Int + ) { + val preinflationCount = preInflationCountProvider.invoke() + if (preinflationCount <= 0) { + return + } mCancellableTask?.cancel() var task: CancellableTask>? = null task = CancellableTask( { val list: ArrayList = ArrayList() - for (i in 0 until preInflateCount) { + for (i in 0 until preinflationCount) { if (task?.canceled == true) { break } - list.add( - adapter.createViewHolder(activeRv, BaseAllAppsAdapter.VIEW_TYPE_ICON) - ) + list.add(adapter.createViewHolder(parent, viewType)) } list }, MAIN_EXECUTOR, { viewHolders -> - for (i in 0 until minOf(viewHolders.size, getPreinflateCount(context))) { + // Run preInflationCountProvider again as the needed VH might have changed + val newPreinflationCount = preInflationCountProvider.invoke() + for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) { putRecycledView(viewHolders[i]) } } ) mCancellableTask = task - VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask) + VIEW_PREINFLATION_EXECUTOR.execute(mCancellableTask) } /** @@ -125,7 +142,7 @@ class AllAppsRecyclerViewPool : RecycledViewPool() { * app icons in size of one all apps pages, so that opening all apps don't need to inflate app * icons. */ - fun getPreinflateCount(context: T): Int where T : Context, T : ActivityContext { + fun getPreinflateCount(context: T): Int { var targetPreinflateCount = PREINFLATE_ICONS_ROW_COUNT * context.deviceProfile.numShownAllAppsColumns + EXTRA_ICONS_COUNT diff --git a/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt new file mode 100644 index 0000000000..82043130a9 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPoolTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 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.recyclerview + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.util.Executors +import com.android.launcher3.views.ActivityContext +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AllAppsRecyclerViewPoolTest where T : Context, T : ActivityContext { + + private lateinit var underTest: AllAppsRecyclerViewPool + private lateinit var adapter: RecyclerView.Adapter<*> + + @Mock private lateinit var parent: ViewGroup + @Mock private lateinit var itemView: View + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = spy(AllAppsRecyclerViewPool()) + adapter = + object : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + object : ViewHolder(itemView) {} + + override fun getItemCount() = 0 + + override fun onBindViewHolder(holder: ViewHolder, position: Int) {} + } + underTest.setMaxRecycledViews(VIEW_TYPE, 20) + } + + @Test + fun preinflate_success() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 } + + awaitTasksCompleted() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10) + } + + @Test + fun preinflate_not_triggered() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 } + + awaitTasksCompleted() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + @Test + fun preinflate_cancel_before_runOnMainThread() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 } + assertThat(underTest.mCancellableTask!!.canceled).isFalse() + + underTest.clear() + + awaitTasksCompleted() + verify(underTest, never()).putRecycledView(any(ViewHolder::class.java)) + assertThat(underTest.mCancellableTask!!.canceled).isTrue() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + @Test + fun preinflate_cancel_after_run() { + underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 } + assertThat(underTest.mCancellableTask!!.canceled).isFalse() + awaitTasksCompleted() + + underTest.clear() + + verify(underTest, times(10)).putRecycledView(any(ViewHolder::class.java)) + assertThat(underTest.mCancellableTask!!.canceled).isTrue() + assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0) + } + + private fun awaitTasksCompleted() { + Executors.VIEW_PREINFLATION_EXECUTOR.submit { null }.get() + Executors.MAIN_EXECUTOR.submit { null }.get() + } + + companion object { + private const val VIEW_TYPE: Int = 4 + } +} From 5ad7a20e9079fc642ba03441f41ba2c46c118669 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 18 Jul 2024 14:54:10 -0700 Subject: [PATCH 12/22] [Test Week] Add ViewPoolTest Number of new tested methods: 3 Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: I13f1a7bad41c07368cff3d544120d1f41c92b603 --- src/com/android/launcher3/util/ViewPool.java | 10 +- .../android/launcher3/util/ViewPoolTest.kt | 94 +++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java index e413d7ff9c..2fa8bf4681 100644 --- a/src/com/android/launcher3/util/ViewPool.java +++ b/src/com/android/launcher3/util/ViewPool.java @@ -24,6 +24,7 @@ import android.view.ViewGroup; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.util.ViewPool.Reusable; @@ -43,9 +44,16 @@ public class ViewPool { public ViewPool(Context context, @Nullable ViewGroup parent, int layoutId, int maxSize, int initialSize) { + this(LayoutInflater.from(context).cloneInContext(context), + parent, layoutId, maxSize, initialSize); + } + + @VisibleForTesting + ViewPool(LayoutInflater inflater, @Nullable ViewGroup parent, + int layoutId, int maxSize, int initialSize) { mLayoutId = layoutId; mParent = parent; - mInflater = LayoutInflater.from(context); + mInflater = inflater; mPool = new Object[maxSize]; if (initialSize > 0) { diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt new file mode 100644 index 0000000000..e535656815 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewPoolTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 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.util + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.test.annotation.UiThreadTest +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.same +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.verifyNoMoreInteractions + +@SmallTest +@RunWith(LauncherMultivalentJUnit::class) +@UiThreadTest +class ViewPoolTest { + + @Mock private lateinit var viewParent: ViewGroup + @Mock private lateinit var view: ReusableView + @Mock private lateinit var inflater: LayoutInflater + + private lateinit var underTest: ViewPool + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(inflater.cloneInContext(any())).thenReturn(inflater) + underTest = ViewPool(inflater, viewParent, LAYOUT_ID, 10, 0) + } + + @Test + fun get_view_from_empty_pool_inflate_new_view() { + underTest.view + + verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false)) + } + + @Test + fun recycle_view() { + underTest.recycle(view) + + val returnedView = underTest.view + + verify(view).onRecycle() + assertThat(returnedView).isSameInstanceAs(view) + verifyNoMoreInteractions(inflater) + } + + @Test + fun get_view_twice_from_view_pool_with_one_view() { + underTest.recycle(view) + underTest.view + verifyNoMoreInteractions(inflater) + + underTest.view + + verify(inflater).inflate(eq(LAYOUT_ID), same(viewParent), eq(false)) + } + + companion object { + private const val LAYOUT_ID = 1000 + } + + private inner class ReusableView(context: Context) : View(context), ViewPool.Reusable { + override fun onRecycle() { + TODO("Not yet implemented") + } + } +} From f257aef6d7905d8ebb0f2bb18c38caa71bad7d68 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 18 Jul 2024 10:34:21 -0700 Subject: [PATCH 13/22] [Test Week] Add SystemUiControllerTest Number of new tested methods: 4 Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: I2c2b8101853b4b5a6562fe26dfa6b7358f0be17e --- .../launcher3/util/SystemUiControllerTest.kt | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt new file mode 100644 index 0000000000..612fcd428f --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/util/SystemUiControllerTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 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.util + +import android.view.View +import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN +import android.view.Window +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV +import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV +import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS +import com.android.launcher3.util.SystemUiController.UI_STATE_BASE_WINDOW +import com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SystemUiControllerTest { + + @Mock private lateinit var window: Window + @Mock private lateinit var decorView: View + + private lateinit var underTest: SystemUiController + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(window.decorView).thenReturn(decorView) + underTest = SystemUiController(window) + } + + @Test + fun test_default_state() { + assertThat(underTest.toString()).isEqualTo("mStates=[0, 0, 0, 0, 0]") + } + + @Test + fun update_state_base_window_light() { + underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true) + + val visibility = + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + verify(decorView).systemUiVisibility = eq(visibility) + assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility) + val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS + assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]") + } + + @Test + fun update_state_scrim_view_light() { + underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ true) + + verify(decorView).systemUiVisibility = + eq(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) + assertThat(underTest.baseSysuiVisibility).isEqualTo(0) + val flag = FLAG_LIGHT_NAV or FLAG_LIGHT_STATUS + assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]") + } + + @Test + fun update_state_base_window_dark() { + underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ false) + + verify(decorView, never()).systemUiVisibility = anyInt() + assertThat(underTest.baseSysuiVisibility).isEqualTo(0) + val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS + assertThat(underTest.toString()).isEqualTo("mStates=[$flag, 0, 0, 0, 0]") + } + + @Test + fun update_state_scrim_view_dark() { + underTest.updateUiState(UI_STATE_SCRIM_VIEW, /* isLight= */ false) + + verify(decorView, never()).systemUiVisibility = anyInt() + assertThat(underTest.baseSysuiVisibility).isEqualTo(0) + val flag = FLAG_DARK_NAV or FLAG_DARK_STATUS + assertThat(underTest.toString()).isEqualTo("mStates=[0, $flag, 0, 0, 0]") + } + + @Test + fun get_base_sysui_visibility() { + `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + + assertThat(underTest.baseSysuiVisibility).isEqualTo(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + } + + @Test + fun update_state_base_window_light_with_existing_visibility() { + `when`(decorView.systemUiVisibility).thenReturn(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + + underTest.updateUiState(UI_STATE_BASE_WINDOW, /* isLight= */ true) + + val visibility = + View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR or + View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or + SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + assertThat(underTest.baseSysuiVisibility).isEqualTo(visibility) + verify(decorView).systemUiVisibility = eq(visibility) + } +} From cda3427fea3af866d1c0a45b8ccde07d1ad6263e Mon Sep 17 00:00:00 2001 From: Sebastian Franco Date: Wed, 17 Jul 2024 16:18:20 -0700 Subject: [PATCH 14/22] Refactoring FolderPagedView to make it more testable Bug: 353588686 Test: FolderPagedViewTest.kt Flag: TEST_ONLY Change-Id: I2a2efd9f9b2a6ac32153f8102613a3af5afc1ecf --- .../launcher3/folder/FolderGridOrganizer.java | 1 + .../launcher3/folder/FolderPagedViewTest.kt | 172 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java index 3669d8b614..a7ab7b92aa 100644 --- a/src/com/android/launcher3/folder/FolderGridOrganizer.java +++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java @@ -201,6 +201,7 @@ public class FolderGridOrganizer { int row = rank / mCountX; return col < PREVIEW_MAX_COLUMNS && row < PREVIEW_MAX_ROWS; } + // If we have less than 4 items do this return rank < MAX_NUM_ITEMS_IN_PREVIEW; } } diff --git a/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt new file mode 100644 index 0000000000..e8681dc4a5 --- /dev/null +++ b/tests/src/com/android/launcher3/folder/FolderPagedViewTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2024 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.folder + +import android.graphics.Point +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Test +import org.junit.runner.RunWith + +data class TestCase(val maxCountX: Int, val maxCountY: Int, val totalItems: Int) + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FolderPagedViewTest { + + companion object { + private fun makeFolderGridOrganizer(testCase: TestCase): FolderGridOrganizer { + val folderGridOrganizer = FolderGridOrganizer(testCase.maxCountX, testCase.maxCountY) + folderGridOrganizer.setContentSize(testCase.totalItems) + return folderGridOrganizer + } + } + + @Test + fun setContentSize() { + assertCountXandY( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22), + expectedCountX = 4, + expectedCountY = 3 + ) + assertCountXandY( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 8), + expectedCountX = 3, + expectedCountY = 3 + ) + assertCountXandY( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 3), + expectedCountX = 2, + expectedCountY = 2 + ) + } + + private fun assertCountXandY(testCase: TestCase, expectedCountX: Int, expectedCountY: Int) { + val folderGridOrganizer = makeFolderGridOrganizer(testCase) + assert(folderGridOrganizer.countX == expectedCountX) { + "Error on expected countX $expectedCountX got ${folderGridOrganizer.countX} using test case $testCase" + } + assert(folderGridOrganizer.countY == expectedCountY) { + "Error on expected countY $expectedCountY got ${folderGridOrganizer.countY} using test case $testCase" + } + } + + @Test + fun getPosForRank() { + assertFolderRank( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22), + expectedPos = Point(0, 0), + rank = 0 + ) + assertFolderRank( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22), + expectedPos = Point(1, 0), + rank = 1 + ) + assertFolderRank( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22), + expectedPos = Point(3, 0), + rank = 3 + ) + assertFolderRank( + TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22), + expectedPos = Point(2, 1), + rank = 6 + ) + val testCase = TestCase(maxCountX = 4, maxCountY = 3, totalItems = 22) + // Rank 16 and 38 should yield the same point since 38 % 12 == 2 + val folderGridOrganizer = makeFolderGridOrganizer(testCase) + assertFolderRank(testCase, expectedPos = folderGridOrganizer.getPosForRank(2), rank = 38) + } + + private fun assertFolderRank(testCase: TestCase, expectedPos: Point, rank: Int) { + val folderGridOrganizer = makeFolderGridOrganizer(testCase) + val pos = folderGridOrganizer.getPosForRank(rank) + assert(pos == expectedPos) { + "Expected pos = $expectedPos doesn't match pos = $pos for the given rank $rank and the give test case $testCase" + } + } + + @Test + fun isItemInPreview() { + val folderGridOrganizer = FolderGridOrganizer(5, 8) + folderGridOrganizer.setContentSize(ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1) + // Very few items + for (i in 0..3) { + assertItemsInPreview( + TestCase( + maxCountX = 5, + maxCountY = 8, + totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1 + ), + expectedIsInPreview = true, + page = 0, + rank = i + ) + } + for (i in 4..40) { + assertItemsInPreview( + TestCase( + maxCountX = 5, + maxCountY = 8, + totalItems = ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW - 1 + ), + expectedIsInPreview = false, + page = 0, + rank = i + ) + } + // Full of items + assertItemsInPreview( + TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40), + expectedIsInPreview = false, + page = 0, + rank = 2 + ) + assertItemsInPreview( + TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40), + expectedIsInPreview = false, + page = 0, + rank = 2 + ) + assertItemsInPreview( + TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40), + expectedIsInPreview = true, + page = 0, + rank = 5 + ) + assertItemsInPreview( + TestCase(maxCountX = 5, maxCountY = 8, totalItems = 40), + expectedIsInPreview = true, + page = 0, + rank = 6 + ) + } + + private fun assertItemsInPreview( + testCase: TestCase, + expectedIsInPreview: Boolean, + page: Int, + rank: Int + ) { + val folderGridOrganizer = makeFolderGridOrganizer(testCase) + val isInPreview = folderGridOrganizer.isItemInPreview(page, rank) + assert(isInPreview == expectedIsInPreview) { + "Item preview state should be $expectedIsInPreview but got $isInPreview, for page $page and rank $rank, for test case $testCase" + } + } +} From cda4805e628e543ac715014a1eeef53a906f26cd Mon Sep 17 00:00:00 2001 From: Sihua Ma Date: Thu, 18 Jul 2024 23:42:54 +0000 Subject: [PATCH 15/22] Add unit test for RoundedCornerEnforcement Number of methods covered: 4 Test: atest NexusLauncherRoboTests:RoundedCornerEnforcementTest Test: SysUI studio Flag: TEST_ONLY Bug: 353303621 Change-Id: I488ce4a6617b96d4937a7030cd25831bcefadca1 --- .../widget/BaseLauncherAppWidgetHostView.java | 2 +- .../widget/RoundedCornerEnforcement.java | 2 +- .../widget/RoundedCornerEnforcementTest.kt | 106 ++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt diff --git a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java index 104209ef53..856f4b36b6 100644 --- a/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/BaseLauncherAppWidgetHostView.java @@ -110,7 +110,7 @@ public abstract class BaseLauncherAppWidgetHostView extends NavigableAppWidgetHo } View background = RoundedCornerEnforcement.findBackground(this); if (background == null - || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { + || RoundedCornerEnforcement.hasAppWidgetOptedOut(background)) { resetRoundedCorners(); return; } diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java index 1e46ffd30c..2e5e251f01 100644 --- a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java +++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java @@ -67,7 +67,7 @@ public class RoundedCornerEnforcement { /** * Check whether the app widget has opted out of the enforcement. */ - public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) { + public static boolean hasAppWidgetOptedOut(@NonNull View background) { return background.getId() == android.R.id.background && background.getClipToOutline(); } diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt new file mode 100644 index 0000000000..db7770205e --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/widget/RoundedCornerEnforcementTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 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.widget + +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.R +import org.junit.Assert.assertEquals +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class RoundedCornerEnforcementTest { + + @Test + fun `Widget view has one background`() { + val mockWidgetView = mock(LauncherAppWidgetHostView::class.java) + + doReturn(android.R.id.background).whenever(mockWidgetView).id + + assertSame(RoundedCornerEnforcement.findBackground(mockWidgetView), mockWidgetView) + } + + @Test + fun `Widget opted out of rounded corner enforcement`() { + val mockView = mock(View::class.java) + + doReturn(android.R.id.background).whenever(mockView).id + doReturn(true).whenever(mockView).clipToOutline + + assertTrue(RoundedCornerEnforcement.hasAppWidgetOptedOut(mockView)) + } + + @Test + fun `Compute rect based on widget view with background`() { + val mockBackgroundView = mock(View::class.java) + val mockWidgetView = mock(ViewGroup::class.java) + val testRect = Rect(0, 0, 0, 0) + + doReturn(WIDTH).whenever(mockBackgroundView).width + doReturn(HEIGHT).whenever(mockBackgroundView).height + doReturn(LEFT).whenever(mockBackgroundView).left + doReturn(TOP).whenever(mockBackgroundView).top + doReturn(mockWidgetView).whenever(mockBackgroundView).parent + + RoundedCornerEnforcement.computeRoundedRectangle( + mockWidgetView, + mockBackgroundView, + testRect + ) + + assertEquals(Rect(50, 75, 250, 275), testRect) + } + + @Test + fun `Compute system radius`() { + val mockContext = mock(Context::class.java) + val mockRes = mock(Resources::class.java) + + doReturn(mockRes).whenever(mockContext).resources + doReturn(RADIUS) + .whenever(mockRes) + .getDimension(eq(android.R.dimen.system_app_widget_background_radius)) + doReturn(LAUNCHER_RADIUS) + .whenever(mockRes) + .getDimension(eq(R.dimen.enforced_rounded_corner_max_radius)) + + assertEquals(RADIUS, RoundedCornerEnforcement.computeEnforcedRadius(mockContext)) + } + + companion object { + const val WIDTH = 200 + const val HEIGHT = 200 + const val LEFT = 50 + const val TOP = 75 + + const val RADIUS = 8f + const val LAUNCHER_RADIUS = 16f + } +} From cde962dcb71b83d4fe4e5dd2cc109c157d22e739 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 17 Jul 2024 15:17:08 -0700 Subject: [PATCH 16/22] Synchronize the app launch and taskbar stash animations. Introduce FLAG_IGNORE_IN_APP to prevent taskbar from stashing prematurely due to HomeTransitionListener signal. Bug: 345768019 Fixes: 319162553 Test: Launch an app, note the two animations are synced Verified by locally introducing a startDelay (since otherwise it is hard to tell) Flag: com.android.launcher3.enable_scaling_reveal_home_animation Change-Id: Ibb64e0321f33cf81e337fc7c1b2c3edfb23e6ba4 --- .../launcher3/QuickstepTransitionManager.java | 23 +++++++++++++++++++ .../taskbar/LauncherTaskbarUIController.java | 19 +++++++++++++++ .../taskbar/TaskbarStashController.java | 6 +++++ 3 files changed, 48 insertions(+) diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 5a74f4aafc..9faf39277d 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -356,6 +356,14 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener options.setOnAnimationAbortListener(endCallback); options.setOnAnimationFinishedListener(endCallback); + // Prepare taskbar for animation synchronization. This needs to happen here before any + // app transition is created. + LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); + if (enableScalingRevealHomeAnimation() && taskbarController != null) { + taskbarController.setIgnoreInAppFlagForSync(true); + onEndCallback.add(() -> taskbarController.setIgnoreInAppFlagForSync(false)); + } + IBinder cookie = mAppLaunchRunner.supportsReturnTransition() ? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null; addLaunchCookie(cookie, (ItemInfo) v.getTag(), options); @@ -1912,6 +1920,21 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener anim.addListener(mForceInvisibleListener); } + // Syncs the app launch animation and taskbar stash animation (if exists). + if (enableScalingRevealHomeAnimation()) { + LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); + if (taskbarController != null) { + taskbarController.setIgnoreInAppFlagForSync(false); + + if (launcherClosing) { + Animator taskbar = taskbarController.createAnimToApp(); + if (taskbar != null) { + anim.play(taskbar); + } + } + } + } + result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy, skipFirstFrame); } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 779009a6b0..0add1c41de 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -19,6 +19,7 @@ import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE; import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES; import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE; +import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IGNORE_IN_APP; import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS; import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity; @@ -256,6 +257,24 @@ public class LauncherTaskbarUIController extends TaskbarUIController { return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration); } + /** + * Create Taskbar animation to be played alongside the Launcher app launch animation. + */ + public @Nullable Animator createAnimToApp() { + TaskbarStashController stashController = mControllers.taskbarStashController; + stashController.updateStateForFlag(TaskbarStashController.FLAG_IN_APP, true); + return stashController.createApplyStateAnimator(stashController.getStashDuration()); + } + + /** + * Temporarily ignore FLAG_IN_APP for app launches to prevent premature taskbar stashing. + * This is needed because taskbar gets a signal to stash before we actually start the + * app launch animation. + */ + public void setIgnoreInAppFlagForSync(boolean enabled) { + mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled); + } + public void updateTaskbarLauncherStateGoingHome() { mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, true); mTaskbarLauncherStateController.applyState(); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 430c0032a6..0c5ad42147 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -95,6 +95,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,... public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ... public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview + public static final int FLAG_IGNORE_IN_APP = 1 << 12; // used to sync with app launch animation // If any of these flags are enabled, isInApp should return true. private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP; @@ -1263,6 +1264,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba */ @Nullable public Animator createSetStateAnimator(long flags, long duration) { + // We do this when we want to synchronize the app launch and taskbar stash animations. + if (hasAnyFlag(FLAG_IGNORE_IN_APP) && hasAnyFlag(flags, FLAG_IN_APP)) { + flags = flags & ~FLAG_IN_APP; + } + boolean isStashed = mStashCondition.test(flags); if (DEBUG) { From cda1c12ebe4156f6cd253881fb1e0ff9750c6911 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 18 Jul 2024 22:29:18 -0700 Subject: [PATCH 17/22] [Test Week] Add missing @RunWith(AndroidJUnit4.class) annotation Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: Ieb8f8ac3a8a94defe5f9ed2b8ca9812fd2ac1ba6 --- .../src/com/android/launcher3/util/RunnableListTest.kt | 3 +++ .../src/com/android/launcher3/util/ViewCacheTest.kt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt index cc672fef81..94bd7c96ba 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/RunnableListTest.kt @@ -16,10 +16,12 @@ package com.android.launcher3.util +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.reset @@ -27,6 +29,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoMoreInteractions @SmallTest +@RunWith(AndroidJUnit4::class) class RunnableListTest { @Mock private lateinit var runnable1: Runnable diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt index bad21c925d..d82818d6d6 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/util/ViewCacheTest.kt @@ -18,6 +18,7 @@ package com.android.launcher3.util import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.launcher3.R import com.android.launcher3.util.ViewCache.CacheEntry @@ -26,6 +27,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@SmallTest @RunWith(AndroidJUnit4::class) class ViewCacheTest { From b7b3137261f61cddd3d06f3f2dd1680766994a87 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Fri, 19 Jul 2024 00:46:57 -0700 Subject: [PATCH 18/22] [Test Week] Add ScreenOnTrackerTest Number of new tested methods: 5 Bug: 353303621 Test: This is the unit test Flag: NONE - unit test Change-Id: Ieae1ed9bb215a52a12fef919c8dd407291c2b0f9 --- .../launcher3/util/ScreenOnTracker.java | 23 +++- .../launcher3/util/ScreenOnTrackerTest.kt | 103 ++++++++++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java index 12eff612be..8ee799a2dd 100644 --- a/src/com/android/launcher3/util/ScreenOnTracker.java +++ b/src/com/android/launcher3/util/ScreenOnTracker.java @@ -24,7 +24,10 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.content.Context; import android.content.Intent; +import androidx.annotation.VisibleForTesting; + import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; /** * Utility class for tracking if the screen is currently on or off @@ -34,8 +37,7 @@ public class ScreenOnTracker implements SafeCloseable { public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(ScreenOnTracker::new); - private final SimpleBroadcastReceiver mReceiver = - new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive); + private final SimpleBroadcastReceiver mReceiver; private final CopyOnWriteArrayList mListeners = new CopyOnWriteArrayList<>(); private final Context mContext; @@ -44,8 +46,20 @@ public class ScreenOnTracker implements SafeCloseable { private ScreenOnTracker(Context context) { // Assume that the screen is on to begin with mContext = context; + mReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive); + init(); + } + + @VisibleForTesting + ScreenOnTracker(Context context, SimpleBroadcastReceiver receiver) { + mContext = context; + mReceiver = receiver; + init(); + } + + private void init() { mIsScreenOn = true; - mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT); + mReceiver.register(mContext, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT); } @Override @@ -53,7 +67,8 @@ public class ScreenOnTracker implements SafeCloseable { mReceiver.unregisterReceiverSafely(mContext); } - private void onReceive(Intent intent) { + @VisibleForTesting + void onReceive(Intent intent) { String action = intent.getAction(); if (ACTION_SCREEN_ON.equals(action)) { mIsScreenOn = true; diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt new file mode 100644 index 0000000000..430aad25e7 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024 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.util + +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_SCREEN_OFF +import android.content.Intent.ACTION_SCREEN_ON +import android.content.Intent.ACTION_USER_PRESENT +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.verifyNoMoreInteractions + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ScreenOnTrackerTest { + + @Mock private lateinit var receiver: SimpleBroadcastReceiver + @Mock private lateinit var context: Context + @Mock private lateinit var listener: ScreenOnTracker.ScreenOnListener + + private lateinit var underTest: ScreenOnTracker + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = ScreenOnTracker(context, receiver) + } + + @Test + fun test_default_state() { + verify(receiver).register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT) + assertThat(underTest.isScreenOn).isTrue() + } + + @Test + fun close_unregister_receiver() { + underTest.close() + + verify(receiver).unregisterReceiverSafely(context) + } + + @Test + fun add_listener_then_receive_screen_on_intent_notify_listener() { + underTest.addListener(listener) + + underTest.onReceive(Intent(ACTION_SCREEN_ON)) + + verify(listener).onScreenOnChanged(true) + assertThat(underTest.isScreenOn).isTrue() + } + + @Test + fun add_listener_then_receive_screen_off_intent_notify_listener() { + underTest.addListener(listener) + + underTest.onReceive(Intent(ACTION_SCREEN_OFF)) + + verify(listener).onScreenOnChanged(false) + assertThat(underTest.isScreenOn).isFalse() + } + + @Test + fun add_listener_then_receive_user_present_intent_notify_listener() { + underTest.addListener(listener) + + underTest.onReceive(Intent(ACTION_USER_PRESENT)) + + verify(listener).onUserPresent() + assertThat(underTest.isScreenOn).isTrue() + } + + @Test + fun remove_listener_then_receive_intent_noOp() { + underTest.addListener(listener) + + underTest.removeListener(listener) + + underTest.onReceive(Intent(ACTION_USER_PRESENT)) + verifyNoMoreInteractions(listener) + } +} From 3210f73125d066358c8e15d6c578fb231ff154e1 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Fri, 19 Jul 2024 09:21:40 +0000 Subject: [PATCH 19/22] Throw exception from SystemUiProxy.getRecentTasks for invalid result - The reason Recents is empty after a force-stop is when RecentsTasksList.getTasks is called first time after restart, the result is always invalid but we cached the invalid result with a chagneId(=1). The next time we call getTasks again, we think the cached result is valid, and returend an empty list. - The fix is to mark such result as invalid to avoid caching the wrong result Fix: 353926204 Test: RecentTasksListTest Flag: EXEMPT bugfix Change-Id: If15ab8fd7454db8a08c22b17eaac73f0c78aa75f --- .../android/quickstep/RecentTasksList.java | 16 +++++++++--- .../com/android/quickstep/SystemUiProxy.java | 26 +++++++++++++++---- .../quickstep/RecentTasksListTest.java | 25 ++++++++++++++---- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index 3d4167a25e..4989831744 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -300,8 +300,12 @@ public class RecentTasksList { @VisibleForTesting TaskLoadResult loadTasksInBackground(int numTasks, int requestId, boolean loadKeysOnly) { int currentUserId = Process.myUserHandle().getIdentifier(); - ArrayList rawTasks = - mSysUiProxy.getRecentTasks(numTasks, currentUserId); + ArrayList rawTasks; + try { + rawTasks = mSysUiProxy.getRecentTasks(numTasks, currentUserId); + } catch (SystemUiProxy.GetRecentTasksException e) { + return INVALID_RESULT; + } // The raw tasks are given in most-recent to least-recent order, we need to reverse it Collections.reverse(rawTasks); @@ -416,8 +420,12 @@ public class RecentTasksList { } writer.println(prefix + " ]"); int currentUserId = Process.myUserHandle().getIdentifier(); - ArrayList rawTasks = - mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); + ArrayList rawTasks; + try { + rawTasks = mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId); + } catch (SystemUiProxy.GetRecentTasksException e) { + rawTasks = new ArrayList<>(); + } writer.println(prefix + " rawTasks=["); for (GroupedRecentTaskInfo task : rawTasks) { TaskInfo taskInfo1 = task.getTaskInfo1(); diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index 66aa897794..3f73959254 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -1394,10 +1394,26 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable { } } - public ArrayList getRecentTasks(int numTasks, int userId) { + public static class GetRecentTasksException extends Exception { + public GetRecentTasksException(String message) { + super(message); + } + + public GetRecentTasksException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * Retrieves a list of Recent tasks from ActivityManager. + * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get + * RemoteException from server side + */ + public ArrayList getRecentTasks(int numTasks, int userId) + throws GetRecentTasksException { if (mRecentTasks == null) { - Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks"); - return new ArrayList<>(); + Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks"); + throw new GetRecentTasksException("null mRecentTasks"); } try { final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks, @@ -1407,8 +1423,8 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable { } return new ArrayList<>(Arrays.asList(rawTasks)); } catch (RemoteException e) { - Log.w(TAG, "Failed call getRecentTasks", e); - return new ArrayList<>(); + Log.e(TAG, "Failed call getRecentTasks", e); + throw new GetRecentTasksException("Failed call getRecentTasks", e); } } diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java index 5d00255882..d049fbc192 100644 --- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.TestCase.assertNull; import static org.junit.Assert.assertEquals; @@ -69,14 +71,14 @@ public class RecentTasksListTest { } @Test - public void onRecentTasksChanged_doesNotFetchTasks() { + public void onRecentTasksChanged_doesNotFetchTasks() throws Exception { mRecentTasksList.onRecentTasksChanged(); verify(mockSystemUiProxy, times(0)) .getRecentTasks(anyInt(), anyInt()); } @Test - public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { + public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception { GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forSplitTasks( new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null); when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) @@ -91,7 +93,19 @@ public class RecentTasksListTest { } @Test - public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() { + public void loadTasksInBackground_GetRecentTasksException() throws Exception { + when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt())) + .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed")); + + RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground( + Integer.MAX_VALUE, -1, false); + + assertThat(taskList.mRequestId).isEqualTo(-1); + assertThat(taskList).isEmpty(); + } + + @Test + public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception { String taskDescription = "Wheeee!"; ActivityManager.RecentTaskInfo task1 = new ActivityManager.RecentTaskInfo(); task1.taskDescription = new ActivityManager.TaskDescription(taskDescription); @@ -111,7 +125,7 @@ public class RecentTasksListTest { } @Test - public void loadTasksInBackground_freeformTask_createsDesktopTask() { + public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception { ActivityManager.RecentTaskInfo[] tasks = { createRecentTaskInfo(1 /* taskId */), createRecentTaskInfo(4 /* taskId */), @@ -134,7 +148,8 @@ public class RecentTasksListTest { } @Test - public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() { + public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() + throws Exception { ActivityManager.RecentTaskInfo[] tasks = { createRecentTaskInfo(1 /* taskId */), createRecentTaskInfo(4 /* taskId */), From 426f74369acba0c765f39faf5a8b4151b7f88013 Mon Sep 17 00:00:00 2001 From: Jordan Silva Date: Thu, 18 Jul 2024 19:18:00 +0100 Subject: [PATCH 20/22] Introducing Manual DI for Overview This CL adds the RecentsDependencyContainer to maintain singletons and dependencies related to RecentsView. RecentsDependencies is a singleton that requires the application context to be initialized. Unlike regular singletons, this class has an initialize function where it gets initialized and can be retrieved with getInstance without providing the appContext each time. - We've updated the refactored classes behind the -enable_refactor_task_thumbnail flag to use this DI solution rather than relying on .parent or recreating dependencies. - To inject dependencies, you can use RecentsDependencies.inject for lazy initialization or RecentsDependencies.get for eager initialization. - At the moment, we don't have a singleton or factory definition. All dependencies created by RecentsDependencies will be stored in a specific scope, making the instance a singleton within that scope. - You can create or retrieve a dependency in a particular scope by calling RecentsDependencies.inject(scopeId). - If you don't need the dependency to be stored in RecentsDependencies, you can create it manually in your code and inject the necessary parameters from RecentsDependencies (see the viewModel in TaskOverlayHelper). - Handling the cleaning/resetting of dependencies will be addressed in b/353917593. RecentsView lifecycle is more complex and doesn't get recreated every time. We need to determine which dependencies or scopes can be destroyed and recreated. Fix: 349055024 Test: NONE Flag: com.android.launcher3.enable_refactor_task_thumbnail Change-Id: I27b92e3038f1cce0fd53b637dba5054c05b40283 --- .../recents/di/RecentsDependencies.kt | 251 ++++++++++++++++++ .../recents/di/RecentsDependenciesExtras.kt | 27 ++ .../recents/di/RecentsDependenciesScope.kt | 71 +++++ .../recents/viewmodel/RecentsViewModel.kt | 48 ++++ .../task/thumbnail/TaskThumbnailView.kt | 28 +- .../quickstep/task/util/TaskOverlayHelper.kt | 27 +- .../task/viewmodel/TaskOverlayViewModel.kt | 6 +- .../TaskThumbnailViewModel.kt | 6 +- .../quickstep/task/viewmodel/TaskViewModel.kt | 25 ++ .../android/quickstep/views/RecentsView.java | 175 +++++++----- .../android/quickstep/views/TaskContainer.kt | 32 ++- .../com/android/quickstep/views/TaskView.kt | 17 +- .../thumbnail/TaskThumbnailViewModelTest.kt | 1 + .../viewmodel/TaskOverlayViewModelTest.kt | 2 +- 14 files changed, 574 insertions(+), 142 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt create mode 100644 quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt create mode 100644 quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt create mode 100644 quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt rename quickstep/src/com/android/quickstep/task/{thumbnail => viewmodel}/TaskThumbnailViewModel.kt (96%) create mode 100644 quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt new file mode 100644 index 0000000000..7a5a71481c --- /dev/null +++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2024 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.quickstep.recents.di + +import android.content.Context +import android.util.Log +import android.view.View +import com.android.quickstep.RecentsModel +import com.android.quickstep.recents.data.RecentTasksRepository +import com.android.quickstep.recents.data.TasksRepository +import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase +import com.android.quickstep.recents.usecase.GetThumbnailUseCase +import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.task.viewmodel.TaskContainerData +import com.android.quickstep.task.viewmodel.TaskOverlayViewModel +import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel +import com.android.quickstep.task.viewmodel.TaskViewData +import com.android.quickstep.task.viewmodel.TaskViewModel +import com.android.quickstep.views.TaskViewType +import com.android.systemui.shared.recents.model.Task +import java.util.logging.Level + +internal typealias RecentsScopeId = String + +class RecentsDependencies private constructor(private val appContext: Context) { + private val scopes = mutableMapOf() + + init { + startDefaultScope(appContext) + } + + /** + * This function initialised the default scope with RecentsView dependencies. These dependencies + * are used multiple times and should be a singleton to share across Recents classes. + */ + private fun startDefaultScope(appContext: Context) { + createScope(DEFAULT_SCOPE_ID).apply { + set(RecentsViewData::class.java.simpleName, RecentsViewData()) + + // Create RecentsTaskRepository singleton + val recentTasksRepository: RecentTasksRepository = + with(RecentsModel.INSTANCE.get(appContext)) { + TasksRepository(this, thumbnailCache, iconCache) + } + set(RecentTasksRepository::class.java.simpleName, recentTasksRepository) + } + } + + inline fun inject( + scopeId: RecentsScopeId = "", + extras: RecentsDependenciesExtras = RecentsDependenciesExtras(), + noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null, + ): T = inject(T::class.java, scopeId = scopeId, extras = extras, factory = factory) + + @Suppress("UNCHECKED_CAST") + @JvmOverloads + fun inject( + modelClass: Class, + scopeId: RecentsScopeId = DEFAULT_SCOPE_ID, + extras: RecentsDependenciesExtras = RecentsDependenciesExtras(), + factory: ((extras: RecentsDependenciesExtras) -> T)? = null, + ): T { + val currentScopeId = scopeId.ifEmpty { DEFAULT_SCOPE_ID } + val scope = scopes[currentScopeId] ?: createScope(currentScopeId) + + log("inject ${modelClass.simpleName} into ${scope.scopeId}", Log.INFO) + var instance: T? + synchronized(this) { + instance = getDependency(scope, modelClass) + log("found instance? $instance", Log.INFO) + if (instance == null) { + instance = + factory?.invoke(extras) as T ?: createDependency(modelClass, scopeId, extras) + scope[modelClass.simpleName] = instance!! + } + } + return instance!! + } + + inline fun provide(scopeId: RecentsScopeId = "", noinline factory: () -> T): T = + provide(T::class.java, scopeId = scopeId, factory = factory) + + @JvmOverloads + fun provide( + modelClass: Class, + scopeId: RecentsScopeId = DEFAULT_SCOPE_ID, + factory: () -> T, + ) = inject(modelClass, scopeId, factory = { factory.invoke() }) + + private fun getDependency(scope: RecentsDependenciesScope, modelClass: Class): T? { + var instance: T? = scope[modelClass.simpleName] as T? + if (instance == null) { + instance = + scope.scopeIdsLinked.firstNotNullOfOrNull { scopeId -> + getScope(scopeId)[modelClass.simpleName] + } as T? + } + if (instance != null) log("Found dependency: $instance", Log.INFO) + return instance + } + + fun getScope(scope: Any): RecentsDependenciesScope { + val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString() + return getScope(scopeId) + } + + fun getScope(scopeId: RecentsScopeId): RecentsDependenciesScope = + scopes[scopeId] ?: createScope(scopeId) + + // TODO(b/353912757): Create a factory so we can prevent this method of growing indefinitely. + // Each class should be responsible for providing a factory function to create a new instance. + @Suppress("UNCHECKED_CAST") + private fun createDependency( + modelClass: Class, + scopeId: RecentsScopeId, + extras: RecentsDependenciesExtras, + ): T { + log("createDependency ${modelClass.simpleName} with $scopeId and $extras", Log.WARN) + val instance: Any = + when (modelClass) { + RecentTasksRepository::class.java -> { + with(RecentsModel.INSTANCE.get(appContext)) { + TasksRepository(this, thumbnailCache, iconCache) + } + } + RecentsViewData::class.java -> RecentsViewData() + TaskViewModel::class.java -> TaskViewModel(taskViewData = inject(scopeId, extras)) + TaskViewData::class.java -> { + val taskViewType = extras["TaskViewType"] as TaskViewType + TaskViewData(taskViewType) + } + TaskContainerData::class.java -> TaskContainerData() + TaskThumbnailViewModel::class.java -> + TaskThumbnailViewModel( + recentsViewData = inject(), + taskViewData = inject(scopeId, extras), + taskContainerData = inject(), + getThumbnailPositionUseCase = inject(), + tasksRepository = inject() + ) + TaskOverlayViewModel::class.java -> { + val task = extras["Task"] as Task + TaskOverlayViewModel( + task = task, + recentsViewData = inject(), + recentTasksRepository = inject(), + getThumbnailPositionUseCase = inject() + ) + } + GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject()) + GetThumbnailPositionUseCase::class.java -> + GetThumbnailPositionUseCase( + deviceProfileRepository = inject(), + rotationStateRepository = inject(), + tasksRepository = inject() + ) + else -> { + log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR) + error("Factory for ${modelClass.simpleName} not defined!") + } + } + return instance as T + } + + private fun createScope(scopeId: RecentsScopeId): RecentsDependenciesScope { + return RecentsDependenciesScope(scopeId).also { scopes[scopeId] = it } + } + + private fun log(message: String, @Log.Level level: Int = Log.DEBUG) { + if (DEBUG) { + when (level) { + Log.WARN -> Log.w(TAG, message) + Log.VERBOSE -> Log.v(TAG, message) + Log.INFO -> Log.i(TAG, message) + Log.ERROR -> Log.e(TAG, message) + else -> Log.d(TAG, message) + } + } + } + + companion object { + private const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope" + private const val TAG = "RecentsDependencies" + private const val DEBUG = false + + @Volatile private lateinit var instance: RecentsDependencies + + fun initialize(view: View): RecentsDependencies = initialize(view.context) + + fun initialize(context: Context): RecentsDependencies { + synchronized(this) { + if (!Companion::instance.isInitialized) { + instance = RecentsDependencies(context.applicationContext) + } + } + return instance + } + + fun getInstance(): RecentsDependencies { + if (!Companion::instance.isInitialized) { + throw UninitializedPropertyAccessException( + "Recents dependencies are not initialized. " + + "Call `RecentsDependencies.initialize` before using this container." + ) + } + return instance + } + + fun destroy() { + instance.scopes.clear() + instance.startDefaultScope(instance.appContext) + } + } +} + +inline fun RecentsDependencies.Companion.inject( + scope: Any = "", + vararg extras: Pair, + noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null, +): Lazy = lazy { get(scope, RecentsDependenciesExtras(extras), factory) } + +inline fun RecentsDependencies.Companion.get( + scope: Any = "", + extras: RecentsDependenciesExtras = RecentsDependenciesExtras(), + noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null, +): T { + val scopeId: RecentsScopeId = scope as? RecentsScopeId ?: scope.hashCode().toString() + return getInstance().inject(scopeId, extras, factory) +} + +inline fun RecentsDependencies.Companion.get( + scope: Any = "", + vararg extras: Pair, + noinline factory: ((extras: RecentsDependenciesExtras) -> T)? = null, +): T = get(scope, RecentsDependenciesExtras(extras), factory) + +fun RecentsDependencies.Companion.getScope(scopeId: Any) = getInstance().getScope(scopeId) diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt new file mode 100644 index 0000000000..753cb6e7c9 --- /dev/null +++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesExtras.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.quickstep.recents.di + +data class RecentsDependenciesExtras(private val data: MutableMap = mutableMapOf()) { + constructor(value: Array>) : this(value.toMap().toMutableMap()) + + operator fun get(key: String) = data[key] + + operator fun set(key: String, value: Any) { + data[key] = value + } +} diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt new file mode 100644 index 0000000000..56bb1edfe9 --- /dev/null +++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependenciesScope.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 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.quickstep.recents.di + +import android.util.Log + +class RecentsDependenciesScope( + val scopeId: RecentsScopeId, + private val dependencies: MutableMap = mutableMapOf(), + private val scopeIds: MutableList = mutableListOf() +) { + val scopeIdsLinked: List + get() = scopeIds.toList() + + operator fun get(identifier: String): Any? { + log("get $identifier") + return dependencies[identifier] + } + + operator fun set(key: String, value: Any) { + synchronized(this) { + log("set $key") + dependencies[key] = value + } + } + + fun remove(key: String): Any? { + synchronized(this) { + log("remove $key") + return dependencies.remove(key) + } + } + + fun linkTo(scope: RecentsDependenciesScope) { + log("linking to ${scope.scopeId}") + scopeIds += scope.scopeId + } + + fun close() { + log("reset") + synchronized(this) { dependencies.clear() } + } + + private fun log(message: String) { + if (DEBUG) Log.d(TAG, "[scopeId=$scopeId] $message") + } + + override fun toString(): String = + "scopeId: $scopeId" + + "\n dependencies: ${dependencies.map { "${it.key}=${it.value}" }.joinToString(", ")}" + + "\n linked to: ${scopeIds.joinToString(", ")}" + + private companion object { + private const val TAG = "RecentsDependenciesScope" + private const val DEBUG = false + } +} diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt new file mode 100644 index 0000000000..8b03a84362 --- /dev/null +++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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.quickstep.recents.viewmodel + +import com.android.quickstep.recents.data.RecentTasksRepository + +class RecentsViewModel( + private val recentsTasksRepository: RecentTasksRepository, + private val recentsViewData: RecentsViewData +) { + fun refreshAllTaskData() { + recentsTasksRepository.getAllTaskData(true) + } + + fun updateVisibleTasks(visibleTaskIdList: List) { + recentsTasksRepository.setVisibleTasks(visibleTaskIdList) + } + + fun updateScale(scale: Float) { + recentsViewData.scale.value = scale + } + + fun updateFullscreenProgress(fullscreenProgress: Float) { + recentsViewData.fullscreenProgress.value = fullscreenProgress + } + + fun updateTasksFullyVisible(taskIds: Set) { + recentsViewData.settledFullyVisibleTaskIds.value = taskIds + } + + fun setOverlayEnabled(isOverlayEnabled: Boolean) { + recentsViewData.overlayEnabled.value = isOverlayEnabled + } +} diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt index d1be3fbf56..fcc2af36b4 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt @@ -31,15 +31,14 @@ import androidx.core.view.isVisible import com.android.launcher3.R import com.android.launcher3.Utilities import com.android.launcher3.util.ViewPool -import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.recents.di.inject import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized +import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel import com.android.quickstep.util.TaskCornerRadius -import com.android.quickstep.views.RecentsView -import com.android.quickstep.views.RecentsViewContainer -import com.android.quickstep.views.TaskView import com.android.systemui.shared.system.QuickStepContract import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -50,25 +49,8 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach class TaskThumbnailView : FrameLayout, ViewPool.Reusable { - // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped - // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] - // This is using a lazy for now because the dependencies cannot be obtained without DI. - val viewModel by lazy { - val recentsView = - RecentsViewContainer.containerFromContext(context) - .getOverviewPanel>() - TaskThumbnailViewModel( - recentsView.mRecentsViewData!!, - (parent as TaskView).taskViewData, - (parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData, - recentsView.mTasksRepository!!, - GetThumbnailPositionUseCase( - recentsView.mDeviceProfileRepository!!, - recentsView.mOrientedStateRepository!!, - recentsView.mTasksRepository!! - ) - ) - } + + private val viewModel: TaskThumbnailViewModel by RecentsDependencies.inject(this) private lateinit var viewAttachedScope: CoroutineScope diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt index cc53be94ad..9253dbf945 100644 --- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt +++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt @@ -18,13 +18,12 @@ package com.android.quickstep.task.util import android.util.Log import com.android.quickstep.TaskOverlayFactory -import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.recents.di.get import com.android.quickstep.task.thumbnail.TaskOverlayUiState import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled import com.android.quickstep.task.viewmodel.TaskOverlayViewModel -import com.android.quickstep.views.RecentsView -import com.android.quickstep.views.RecentsViewContainer import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -42,24 +41,12 @@ class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOver private lateinit var overlayInitializedScope: CoroutineScope private var uiState: TaskOverlayUiState = Disabled - // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped - // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] - // This is using a lazy for now because the dependencies cannot be obtained without DI. - private val viewModel by lazy { - val recentsView = - RecentsViewContainer.containerFromContext( - overlay.taskView.context - ) - .getOverviewPanel>() + private val viewModel: TaskOverlayViewModel by lazy { TaskOverlayViewModel( - task, - recentsView.mRecentsViewData!!, - recentsView.mTasksRepository!!, - GetThumbnailPositionUseCase( - recentsView.mDeviceProfileRepository!!, - recentsView.mOrientedStateRepository!!, - recentsView.mTasksRepository - ) + task = task, + recentsViewData = RecentsDependencies.get(), + getThumbnailPositionUseCase = RecentsDependencies.get(), + recentTasksRepository = RecentsDependencies.get() ) } diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt index ca3bbb463f..4e13d1ce26 100644 --- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt +++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt @@ -34,14 +34,14 @@ import kotlinx.coroutines.runBlocking class TaskOverlayViewModel( private val task: Task, recentsViewData: RecentsViewData, - tasksRepository: RecentTasksRepository, - private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase + private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase, + recentTasksRepository: RecentTasksRepository, ) { val overlayState = combine( recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) }, - tasksRepository.getThumbnailById(task.key.id) + recentTasksRepository.getThumbnailById(task.key.id) ) { isOverlayEnabled, isFullyVisible, thumbnailData -> if (isOverlayEnabled && isFullyVisible) { Enabled( diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt similarity index 96% rename from quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt rename to quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt index e03e1142d7..64656452e7 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt +++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.quickstep.task.thumbnail +package com.android.quickstep.task.viewmodel import android.annotation.ColorInt import android.graphics.Matrix @@ -23,12 +23,12 @@ import com.android.quickstep.recents.data.RecentTasksRepository import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase import com.android.quickstep.recents.usecase.ThumbnailPositionState import com.android.quickstep.recents.viewmodel.RecentsViewData +import com.android.quickstep.task.thumbnail.TaskThumbnail +import com.android.quickstep.task.thumbnail.TaskThumbnailUiState import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized -import com.android.quickstep.task.viewmodel.TaskContainerData -import com.android.quickstep.task.viewmodel.TaskViewData import com.android.systemui.shared.recents.model.Task import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt new file mode 100644 index 0000000000..ec75d59c34 --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewModel.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 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.quickstep.task.viewmodel + +import androidx.lifecycle.ViewModel + +class TaskViewModel(private val taskViewData: TaskViewData) : ViewModel() { + fun updateScale(scale: Float) { + taskViewData.scale.value = scale + } +} diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 1db1447303..a3d635914e 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -191,10 +191,12 @@ import com.android.quickstep.TaskViewUtils; import com.android.quickstep.TopTaskTracker; import com.android.quickstep.ViewUtils; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; +import com.android.quickstep.recents.data.RecentTasksRepository; import com.android.quickstep.recents.data.RecentsDeviceProfileRepository; import com.android.quickstep.recents.data.RecentsRotationStateRepository; -import com.android.quickstep.recents.data.TasksRepository; +import com.android.quickstep.recents.di.RecentsDependencies; import com.android.quickstep.recents.viewmodel.RecentsViewData; +import com.android.quickstep.recents.viewmodel.RecentsViewModel; import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.AnimUtils; @@ -241,8 +243,9 @@ import java.util.stream.Collectors; /** * A list of recent tasks. + * * @param : the container that should host recents view - * @param : the type of base state that will be used + * @param : the type of base state that will be used */ public abstract class RecentsView mSizeStrategy; @Nullable @@ -726,10 +720,12 @@ public abstract class RecentsView new RecentsRotationStateRepository(mOrientationState)); + + recentsDependencies.provide(RecentsDeviceProfileRepository.class, + () -> new RecentsDeviceProfileRepository(mContainer)); + } else { + mRecentsViewModel = null; + } + mScrollHapticMinGapMillis = getResources() .getInteger(R.integer.recentsScrollHapticMinGapMillis); mFastFlingVelocity = getResources() .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); mModel = RecentsModel.INSTANCE.get(context); mIdp = InvariantDeviceProfile.INSTANCE.get(context); - if (enableRefactorTaskThumbnail()) { - mTasksRepository = new TasksRepository( - mModel, mModel.getThumbnailCache(), mModel.getIconCache()); - mOrientedStateRepository = new RecentsRotationStateRepository(mOrientationState); - mDeviceProfileRepository = new RecentsDeviceProfileRepository(mContainer); - } else { - mTasksRepository = null; - mOrientedStateRepository = null; - mDeviceProfileRepository = null; - } mClearAllButton = (ClearAllButton) LayoutInflater.from(context) .inflate(R.layout.overview_clear_all_button, this, false); @@ -1077,6 +1084,7 @@ public abstract class RecentsView= screenEnd - mPageSpacing; @@ -4651,6 +4671,7 @@ public abstract class RecentsView{ + builder.addOnFrameCallback(() -> { // TODO(b/334826842): Handle splash icon for new TTV. if (!enableRefactorTaskThumbnail()) { taskContainer.getThumbnailViewDeprecated().refreshSplashView(); @@ -4896,17 +4917,21 @@ public abstract class RecentsView { // Once we pass a certain threshold, update the sysui flags to match the target @@ -5390,7 +5415,7 @@ public abstract class RecentsView 0 - ? (int) Utilities.mapRange( - mAdjacentPageHorizontalOffset, - getOverScrollShift(), - getUndampedOverScrollShift()) - : getOverScrollShift(); + ? (int) Utilities.mapRange( + mAdjacentPageHorizontalOffset, + getOverScrollShift(), + getUndampedOverScrollShift()) + : getOverScrollShift(); return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this) + overScrollShift + getOffsetFromScrollPosition(pageIndex); } @@ -5942,7 +5968,10 @@ public abstract class RecentsView = taskOverlayFactory.createOverlay(this) - val taskContainerData = TaskContainerData() - - private val getThumbnailUseCase by lazy { - // TODO(b/335649589): Ideally create and obtain this from DI. - val recentsView = - RecentsViewContainer.containerFromContext( - overlay.taskView.context - ) - .getOverviewPanel>() - GetThumbnailUseCase(recentsView.mTasksRepository!!) - } + lateinit var taskContainerData: TaskContainerData + private val getThumbnailUseCase: GetThumbnailUseCase by RecentsDependencies.inject() + private val taskThumbnailViewModel: TaskThumbnailViewModel by + RecentsDependencies.inject(snapshotView) init { if (enableRefactorTaskThumbnail()) { require(snapshotView is TaskThumbnailView) + taskContainerData = RecentsDependencies.get(this) + RecentsDependencies.getScope(snapshotView).apply { + val taskViewScope = RecentsDependencies.getScope(taskView) + linkTo(taskViewScope) + + val taskContainerScope = RecentsDependencies.getScope(this) + linkTo(taskContainerScope) + } } else { require(snapshotView is TaskThumbnailViewDeprecated) } @@ -146,12 +152,10 @@ class TaskContainer( overlay.destroy() } - // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM - // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView fun bindThumbnailView() { // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but // this should be decided inside TaskThumbnailViewModel. - thumbnailView.viewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask)) + taskThumbnailViewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask)) } fun setOverlayEnabled(enabled: Boolean) { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 004003c014..f2f036a874 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -80,8 +80,10 @@ import com.android.quickstep.TaskAnimationManager import com.android.quickstep.TaskOverlayFactory import com.android.quickstep.TaskViewUtils import com.android.quickstep.orientation.RecentsPagedOrientationHandler +import com.android.quickstep.recents.di.RecentsDependencies +import com.android.quickstep.recents.di.get import com.android.quickstep.task.thumbnail.TaskThumbnailView -import com.android.quickstep.task.viewmodel.TaskViewData +import com.android.quickstep.task.viewmodel.TaskViewModel import com.android.quickstep.util.ActiveGestureErrorDetector import com.android.quickstep.util.ActiveGestureLog import com.android.quickstep.util.BorderAnimator @@ -115,7 +117,8 @@ constructor( @IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS) annotation class TaskDataChanges - val taskViewData = TaskViewData(type) + private lateinit var taskViewModel: TaskViewModel + val taskIds: IntArray /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ get() = taskContainers.map { it.task.key.id }.toIntArray() @@ -441,6 +444,11 @@ constructor( init { setOnClickListener { _ -> onClick() } + + if (enableRefactorTaskThumbnail()) { + taskViewModel = RecentsDependencies.get(this, "TaskViewType" to type) + } + val keyboardFocusHighlightEnabled = (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline()) val cursorHoverStatesEnabled = enableCursorHoverStates() @@ -638,6 +646,7 @@ constructor( orientedState: RecentsOrientedState, taskOverlayFactory: TaskOverlayFactory ) { + cancelPendingLoadTasks() taskContainers = listOf( @@ -1404,7 +1413,7 @@ constructor( scaleX = scale scaleY = scale if (enableRefactorTaskThumbnail()) { - taskViewData.scale.value = scale + taskViewModel.updateScale(scale) } updateSnapshotRadius() } @@ -1483,8 +1492,6 @@ constructor( /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */ fun notifyIsRunningTaskUpdated() { - // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM - // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView taskContainers.forEach { it.bindThumbnailView() } } diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt index fddc740590..754c9d1e73 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized import com.android.quickstep.task.viewmodel.TaskContainerData +import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel import com.android.quickstep.task.viewmodel.TaskViewData import com.android.quickstep.views.TaskViewType import com.android.systemui.shared.recents.model.Task diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt index 64937b4c15..d0887df9bf 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt @@ -59,7 +59,7 @@ class TaskOverlayViewModelTest { private val tasksRepository = FakeTasksRepository() private val mGetThumbnailPositionUseCase = mock() private val systemUnderTest = - TaskOverlayViewModel(task, recentsViewData, tasksRepository, mGetThumbnailPositionUseCase) + TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository) @Test fun initialStateIsDisabled() = runTest { From b6e8ab0d2173b78fd485a610a1a61e6dcf1f767f Mon Sep 17 00:00:00 2001 From: Charlie Anderson Date: Thu, 18 Jul 2024 15:42:26 -0400 Subject: [PATCH 21/22] [Test week] add tests for InstallSessionHelper 9 public methods tested no previous test class Bug: 353303621 Test: unit tests Flag: TEST_ONLY Change-Id: Ib113c00c10ed0aca8c996db493a84077026bf6c8 --- .../launcher3/pm/InstallSessionHelperTest.kt | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt new file mode 100644 index 0000000000..3dd8dbceec --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2024 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.pm + +import android.content.pm.ApplicationInfo +import android.content.pm.ApplicationInfo.FLAG_INSTALLED +import android.content.pm.LauncherApps +import android.content.pm.PackageInstaller +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.LauncherPrefs +import com.android.launcher3.LauncherPrefs.Companion.PROMISE_ICON_IDS +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.IntArray +import com.android.launcher3.util.LauncherModelHelper +import com.android.launcher3.util.TestUtil +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class InstallSessionHelperTest { + + private val launcherModelHelper = LauncherModelHelper() + private val sandboxContext = spy(launcherModelHelper.sandboxContext) + private val packageManager = sandboxContext.packageManager + private val expectedAppPackage = "expectedAppPackage" + private val expectedInstallerPackage = "expectedInstallerPackage" + private val mockPackageInstaller: PackageInstaller = mock() + + private lateinit var installSessionHelper: InstallSessionHelper + private lateinit var launcherApps: LauncherApps + + @Before + fun setup() { + whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller) + whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage) + launcherApps = sandboxContext.spyService(LauncherApps::class.java) + installSessionHelper = InstallSessionHelper(sandboxContext) + } + + @Test + fun `getActiveSessions fetches verified install sessions from LauncherApps`() { + // Given + val expectedVerifiedSession1 = + PackageInstaller.SessionInfo().apply { + sessionId = 0 + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + userId = 0 + } + val expectedVerifiedSession2 = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "app2" + userId = 0 + } + val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2) + whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions) + // When + val actualSessions = installSessionHelper.getActiveSessions() + // Then + assertThat(actualSessions.values.toList()).isEqualTo(expectedSessions) + } + + @Test + fun `getActiveSessionInfo fetches verified install sessions for given user and pkg`() { + // Given + val expectedVerifiedSession = + PackageInstaller.SessionInfo().apply { + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + userId = 0 + } + whenever(launcherApps.allPackageInstallerSessions) + .thenReturn(listOf(expectedVerifiedSession)) + // When + val actualSession = + installSessionHelper.getActiveSessionInfo(UserHandle(0), expectedAppPackage) + // Then + assertThat(actualSession).isEqualTo(expectedVerifiedSession) + } + + @Test + fun `getVerifiedSessionInfo verifies and returns session for given id`() { + // Given + val expectedSession = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + userId = 0 + } + whenever(mockPackageInstaller.getSessionInfo(1)).thenReturn(expectedSession) + // When + val actualSession = installSessionHelper.getVerifiedSessionInfo(1) + // Then + assertThat(actualSession).isEqualTo(expectedSession) + } + + @Test + fun `isTrustedPackage returns true if LauncherApps finds ApplicationInfo`() { + // Given + val expectedApplicationInfo = + ApplicationInfo().apply { + flags = flags or FLAG_INSTALLED + enabled = true + } + doReturn(expectedApplicationInfo) + .whenever(launcherApps) + .getApplicationInfo(expectedAppPackage, ApplicationInfo.FLAG_SYSTEM, UserHandle(0)) + // When + val actualResult = installSessionHelper.isTrustedPackage(expectedAppPackage, UserHandle(0)) + // Then + assertThat(actualResult).isTrue() + } + + @Test + fun `getAllVerifiedSessions verifies and returns all active install sessions`() { + // Given + val expectedVerifiedSession1 = + PackageInstaller.SessionInfo().apply { + sessionId = 0 + installerPackageName = expectedInstallerPackage + appPackageName = expectedAppPackage + userId = 0 + } + val expectedVerifiedSession2 = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "app2" + userId = 0 + } + val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2) + whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions) + // When + val actualSessions = installSessionHelper.allVerifiedSessions + // Then + assertThat(actualSessions).isEqualTo(expectedSessions) + } + + @Test + fun `promiseIconAddedForId returns true if there is a promiseIcon with the session id`() { + // Given + val expectedIdString = IntArray().apply { add(1) }.toConcatString() + LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString)) + val expectedSession = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "app2" + userId = 0 + } + whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession)) + // When + var actualResult = false + TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { + actualResult = installSessionHelper.promiseIconAddedForId(1) + } + // Then + assertThat(actualResult).isTrue() + } + + @Test + fun `removePromiseIconId removes promiseIconId for given Session id`() { + // Given + val expectedIdString = IntArray().apply { add(1) }.toConcatString() + LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString)) + val expectedSession = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "app2" + userId = 0 + } + whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession)) + // When + var actualResult = true + TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { + installSessionHelper.removePromiseIconId(1) + actualResult = installSessionHelper.promiseIconAddedForId(1) + } + // Then + assertThat(actualResult).isFalse() + } + + @Test + fun `tryQueuePromiseAppIcon will update promise icon ids from eligible sessions`() { + // Given + val expectedIdString = IntArray().apply { add(1) }.toConcatString() + LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString)) + val expectedSession = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "appPackage" + userId = 0 + appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) + appLabel = "appLabel" + installReason = PackageManager.INSTALL_REASON_USER + } + whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession)) + // When + var wasPromiseIconAdded = false + var actualPromiseIconIds = "" + TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { + installSessionHelper.removePromiseIconId(1) + installSessionHelper.tryQueuePromiseAppIcon(expectedSession) + wasPromiseIconAdded = installSessionHelper.promiseIconAddedForId(1) + actualPromiseIconIds = LauncherPrefs.get(sandboxContext).get(PROMISE_ICON_IDS) + } + // Then + assertThat(wasPromiseIconAdded).isTrue() + assertThat(actualPromiseIconIds).isEqualTo(expectedIdString) + } + + @Test + fun `verifySessionInfo is true if can verify given SessionInfo`() { + // Given + val expectedIdString = IntArray().apply { add(1) }.toConcatString() + LauncherPrefs.get(sandboxContext).putSync(Pair(PROMISE_ICON_IDS, expectedIdString)) + val expectedSession = + PackageInstaller.SessionInfo().apply { + sessionId = 1 + installerPackageName = expectedInstallerPackage + appPackageName = "appPackage" + userId = 0 + appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) + appLabel = "appLabel" + installReason = PackageManager.INSTALL_REASON_USER + } + whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession)) + // When + var actualResult = false + TestUtil.runOnExecutorSync(MODEL_EXECUTOR) { + actualResult = installSessionHelper.verifySessionInfo(expectedSession) + } + // Then + assertThat(actualResult).isTrue() + } +} From f38d98d1a678cbf4481a7411bd2f4a452b4a3604 Mon Sep 17 00:00:00 2001 From: Marcelo Arteiro Date: Mon, 1 Jul 2024 18:18:08 +0000 Subject: [PATCH 22/22] Align color resources in launcher projects. This is an effort to align/unify color usage across all launcher projects and the system itself. This fix recreates all dynamic color attributes present in the OS since many launcher areas cannot access private attributes. It is recomemnded to use these new local attributes in all launcher areas and avoid creating new color resources when these can be used directly. Bug: 352690172 Test: Presubmits Flag: EXEMPT bugfix Change-Id: Ic36b8bf70ee37f2fdd32e459e66f0e2d001d6ba2 --- go/quickstep/res/values/styles.xml | 2 +- quickstep/res/color/all_set_bg_primary.xml | 2 +- quickstep/res/color/all_set_bg_tertiary.xml | 2 +- .../color/bubblebar_drop_target_bg_color.xml | 2 +- .../res/color/menu_item_hover_state_color.xml | 4 +- .../drawable/bg_bubble_bar_drop_target.xml | 2 +- .../bg_bubble_expanded_view_drop_target.xml | 2 +- .../drawable/bg_floating_desktop_select.xml | 2 +- .../drawable/bg_overview_clear_all_button.xml | 2 +- .../res/drawable/bg_taskbar_edu_tooltip.xml | 2 +- quickstep/res/drawable/bg_wellbeing_toast.xml | 2 +- quickstep/res/drawable/ic_chevron_down.xml | 2 +- ...uick_switch_overview_button_background.xml | 2 +- .../res/drawable/rotate_tutorial_warning.xml | 2 +- .../keyboard_quick_switch_taskview.xml | 2 +- .../gesture_tutorial_step_menu.xml | 4 +- .../res/layout/digital_wellbeing_toast.xml | 2 +- .../res/layout/gesture_tutorial_step_menu.xml | 4 +- quickstep/res/layout/icon_app_chip_view.xml | 2 +- .../layout/keyboard_quick_switch_taskview.xml | 2 +- ...eyboard_quick_switch_textonly_taskview.xml | 4 +- .../res/layout/keyboard_quick_switch_view.xml | 2 +- .../res/layout/overview_clear_all_button.xml | 4 +- quickstep/res/layout/task.xml | 4 +- quickstep/res/layout/task_desktop.xml | 4 +- quickstep/res/layout/task_grouped.xml | 4 +- .../res/layout/task_view_menu_option.xml | 4 +- quickstep/res/values-night/colors.xml | 4 +- quickstep/res/values-night/styles.xml | 20 +-- quickstep/res/values/colors.xml | 4 +- quickstep/res/values/styles.xml | 30 ++-- .../appprediction/AppsDividerView.java | 7 +- .../launcher3/taskbar/TaskbarEduTooltip.kt | 3 +- .../taskbar/bubbles/BubbleBarController.java | 4 +- .../material_color_surface.xml | 19 --- .../material_color_surface_bright.xml | 19 --- .../material_color_surface_container.xml | 19 --- .../material_color_surface_container_high.xml | 19 --- ...terial_color_surface_container_highest.xml | 19 --- .../material_color_surface_container_low.xml | 19 --- ...aterial_color_surface_container_lowest.xml | 19 --- .../material_color_surface_dim.xml | 19 --- .../material_color_surface_inverse.xml | 19 --- .../material_color_surface_variant.xml | 19 --- res/color-night-v31/popup_shade_first.xml | 5 +- res/color-v31/material_color_surface.xml | 19 --- .../material_color_surface_bright.xml | 19 --- .../material_color_surface_container.xml | 19 --- .../material_color_surface_container_high.xml | 19 --- ...terial_color_surface_container_highest.xml | 19 --- .../material_color_surface_container_low.xml | 19 --- ...aterial_color_surface_container_lowest.xml | 19 --- res/color-v31/material_color_surface_dim.xml | 19 --- .../material_color_surface_inverse.xml | 20 --- .../material_color_surface_variant.xml | 19 --- res/color-v31/popup_shade_first.xml | 5 +- res/color/overview_button.xml | 7 +- res/color/popup_shade_first.xml | 5 +- res/drawable/add_item_dialog_background.xml | 2 +- res/drawable/all_apps_tabs_background.xml | 4 +- .../bg_rounded_corner_bottom_sheet_handle.xml | 3 +- .../button_top_rounded_bordered_ripple.xml | 2 +- res/drawable/ic_close_work_edu.xml | 2 +- .../ic_private_space_with_background.xml | 3 +- res/drawable/icon_menu_arrow_background.xml | 5 +- .../private_space_install_app_icon.xml | 4 +- res/drawable/rounded_action_button.xml | 7 +- res/drawable/work_card.xml | 2 +- res/layout/private_space_header.xml | 4 +- res/values-night-v31/colors.xml | 35 ---- res/values-night/colors.xml | 64 ------- res/values-night/styles.xml | 49 ++++++ res/values-v30/styles.xml | 1 + res/values-v31/colors.xml | 35 ---- res/values-v34/colors.xml | 101 +++++++++++ res/values/attrs.xml | 94 ++++++----- res/values/colors.xml | 159 ++++++++++++------ res/values/styles.xml | 117 ++++++------- .../allapps/SectionDecorationHandler.java | 9 +- .../util/ActivityContextWrapper.java | 8 +- .../allapps/PrivateSpaceHeaderViewTest.java | 3 +- 81 files changed, 477 insertions(+), 783 deletions(-) delete mode 100644 res/color-night-v31/material_color_surface.xml delete mode 100644 res/color-night-v31/material_color_surface_bright.xml delete mode 100644 res/color-night-v31/material_color_surface_container.xml delete mode 100644 res/color-night-v31/material_color_surface_container_high.xml delete mode 100644 res/color-night-v31/material_color_surface_container_highest.xml delete mode 100644 res/color-night-v31/material_color_surface_container_low.xml delete mode 100644 res/color-night-v31/material_color_surface_container_lowest.xml delete mode 100644 res/color-night-v31/material_color_surface_dim.xml delete mode 100644 res/color-night-v31/material_color_surface_inverse.xml delete mode 100644 res/color-night-v31/material_color_surface_variant.xml delete mode 100644 res/color-v31/material_color_surface.xml delete mode 100644 res/color-v31/material_color_surface_bright.xml delete mode 100644 res/color-v31/material_color_surface_container.xml delete mode 100644 res/color-v31/material_color_surface_container_high.xml delete mode 100644 res/color-v31/material_color_surface_container_highest.xml delete mode 100644 res/color-v31/material_color_surface_container_low.xml delete mode 100644 res/color-v31/material_color_surface_container_lowest.xml delete mode 100644 res/color-v31/material_color_surface_dim.xml delete mode 100644 res/color-v31/material_color_surface_inverse.xml delete mode 100644 res/color-v31/material_color_surface_variant.xml delete mode 100644 res/values-night/colors.xml diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml index c659331bde..2524c760f4 100644 --- a/go/quickstep/res/values/styles.xml +++ b/go/quickstep/res/values/styles.xml @@ -16,7 +16,7 @@ --> - \ No newline at end of file diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml index 0f997f9b77..0bb971e166 100644 --- a/quickstep/res/values/colors.xml +++ b/quickstep/res/values/colors.xml @@ -95,6 +95,6 @@ ?androidprv:attr/colorAccentPrimaryVariant - ?androidprv:attr/materialColorPrimaryFixedDim - ?androidprv:attr/materialColorOnPrimaryFixed + ?attr/materialColorPrimaryFixedDim + ?attr/materialColorOnPrimaryFixed \ No newline at end of file diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index 153bc99e90..1f4720c255 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -124,7 +124,7 @@ diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java index 84c2ed252b..7a8b58e58f 100644 --- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java +++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java @@ -31,12 +31,12 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import androidx.annotation.ColorInt; -import androidx.core.content.ContextCompat; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; +import com.android.launcher3.util.Themes; /** * A view which shows a horizontal divider @@ -84,10 +84,9 @@ public class AppsDividerView extends View implements FloatingHeaderRow { getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height) }; - mStrokeColor = ContextCompat.getColor(context, R.color.material_color_outline_variant); + mStrokeColor = Themes.getAttrColor(context, R.attr.materialColorOutlineVariant); - mAllAppsLabelTextColor = ContextCompat.getColor(context, - R.color.material_color_on_surface_variant); + mAllAppsLabelTextColor = Themes.getAttrColor(context, R.attr.materialColorOnSurfaceVariant); mAccessibilityManager = AccessibilityManager.getInstance(context); setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context)); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt index 7f9d8a390e..19e987234f 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt @@ -53,8 +53,7 @@ constructor( private val activityContext: ActivityContext = ActivityContext.lookupContext(context) - private val backgroundColor = - Themes.getAttrColor(context, com.android.internal.R.attr.materialColorSurfaceBright) + private val backgroundColor = Themes.getAttrColor(context, R.attr.materialColorSurfaceBright) private val tooltipCornerRadius = Themes.getDialogCornerRadius(context) private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width) diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 83d4df29c8..5be01718e6 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -635,8 +635,8 @@ public class BubbleBarController extends IBubblesListener.Stub { final TypedArray ta = mContext.obtainStyledAttributes( new int[]{ - com.android.internal.R.attr.materialColorOnPrimaryFixed, - com.android.internal.R.attr.materialColorPrimaryFixed + R.attr.materialColorOnPrimaryFixed, + R.attr.materialColorPrimaryFixed }); int overflowIconColor = ta.getColor(0, Color.WHITE); int overflowBackgroundColor = ta.getColor(1, Color.BLACK); diff --git a/res/color-night-v31/material_color_surface.xml b/res/color-night-v31/material_color_surface.xml deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_bright.xml b/res/color-night-v31/material_color_surface_bright.xml deleted file mode 100644 index f34ed6c548..0000000000 --- a/res/color-night-v31/material_color_surface_bright.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container.xml b/res/color-night-v31/material_color_surface_container.xml deleted file mode 100644 index 002b88eba4..0000000000 --- a/res/color-night-v31/material_color_surface_container.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_high.xml b/res/color-night-v31/material_color_surface_container_high.xml deleted file mode 100644 index edd36fcd23..0000000000 --- a/res/color-night-v31/material_color_surface_container_high.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_highest.xml b/res/color-night-v31/material_color_surface_container_highest.xml deleted file mode 100644 index e54f95380e..0000000000 --- a/res/color-night-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_low.xml b/res/color-night-v31/material_color_surface_container_low.xml deleted file mode 100644 index 40f0d4c9de..0000000000 --- a/res/color-night-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_container_lowest.xml b/res/color-night-v31/material_color_surface_container_lowest.xml deleted file mode 100644 index 24f559b494..0000000000 --- a/res/color-night-v31/material_color_surface_container_lowest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_dim.xml b/res/color-night-v31/material_color_surface_dim.xml deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface_dim.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_inverse.xml b/res/color-night-v31/material_color_surface_inverse.xml deleted file mode 100644 index ac63072a9e..0000000000 --- a/res/color-night-v31/material_color_surface_inverse.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/material_color_surface_variant.xml b/res/color-night-v31/material_color_surface_variant.xml deleted file mode 100644 index a645f24dd9..0000000000 --- a/res/color-night-v31/material_color_surface_variant.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-night-v31/popup_shade_first.xml b/res/color-night-v31/popup_shade_first.xml index 83822a62bc..28995e32bd 100644 --- a/res/color-night-v31/popup_shade_first.xml +++ b/res/color-night-v31/popup_shade_first.xml @@ -12,7 +12,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color-v31/material_color_surface.xml b/res/color-v31/material_color_surface.xml deleted file mode 100644 index b049851ff4..0000000000 --- a/res/color-v31/material_color_surface.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_bright.xml b/res/color-v31/material_color_surface_bright.xml deleted file mode 100644 index b049851ff4..0000000000 --- a/res/color-v31/material_color_surface_bright.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container.xml b/res/color-v31/material_color_surface_container.xml deleted file mode 100644 index b031c081a9..0000000000 --- a/res/color-v31/material_color_surface_container.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_high.xml b/res/color-v31/material_color_surface_container_high.xml deleted file mode 100644 index a996d51eba..0000000000 --- a/res/color-v31/material_color_surface_container_high.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_highest.xml b/res/color-v31/material_color_surface_container_highest.xml deleted file mode 100644 index e7a535af53..0000000000 --- a/res/color-v31/material_color_surface_container_highest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_low.xml b/res/color-v31/material_color_surface_container_low.xml deleted file mode 100644 index b8fe01e484..0000000000 --- a/res/color-v31/material_color_surface_container_low.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_container_lowest.xml b/res/color-v31/material_color_surface_container_lowest.xml deleted file mode 100644 index 25e8666862..0000000000 --- a/res/color-v31/material_color_surface_container_lowest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_dim.xml b/res/color-v31/material_color_surface_dim.xml deleted file mode 100644 index e2d226fa89..0000000000 --- a/res/color-v31/material_color_surface_dim.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_inverse.xml b/res/color-v31/material_color_surface_inverse.xml deleted file mode 100644 index e189862856..0000000000 --- a/res/color-v31/material_color_surface_inverse.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/res/color-v31/material_color_surface_variant.xml b/res/color-v31/material_color_surface_variant.xml deleted file mode 100644 index e2d226fa89..0000000000 --- a/res/color-v31/material_color_surface_variant.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/res/color-v31/popup_shade_first.xml b/res/color-v31/popup_shade_first.xml index 1278bb494a..be73698c2e 100644 --- a/res/color-v31/popup_shade_first.xml +++ b/res/color-v31/popup_shade_first.xml @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/color/overview_button.xml b/res/color/overview_button.xml index 1dd8da60c7..0b317bd395 100644 --- a/res/color/overview_button.xml +++ b/res/color/overview_button.xml @@ -1,12 +1,11 @@ - + \ No newline at end of file diff --git a/res/color/popup_shade_first.xml b/res/color/popup_shade_first.xml index 1278bb494a..be73698c2e 100644 --- a/res/color/popup_shade_first.xml +++ b/res/color/popup_shade_first.xml @@ -13,7 +13,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - + + diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml index e279fa051a..be4765a45b 100644 --- a/res/drawable/add_item_dialog_background.xml +++ b/res/drawable/add_item_dialog_background.xml @@ -1,7 +1,7 @@ - + diff --git a/res/drawable/all_apps_tabs_background.xml b/res/drawable/all_apps_tabs_background.xml index 1e7cff2e93..62927afeb7 100644 --- a/res/drawable/all_apps_tabs_background.xml +++ b/res/drawable/all_apps_tabs_background.xml @@ -30,7 +30,7 @@ android:state_selected="false"> - + @@ -39,7 +39,7 @@ android:state_selected="true"> - + diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml index 379e0e53d6..a19465dba1 100644 --- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml +++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml @@ -15,8 +15,7 @@ --> - + diff --git a/res/drawable/button_top_rounded_bordered_ripple.xml b/res/drawable/button_top_rounded_bordered_ripple.xml index f5b68866cb..13959f6925 100644 --- a/res/drawable/button_top_rounded_bordered_ripple.xml +++ b/res/drawable/button_top_rounded_bordered_ripple.xml @@ -25,7 +25,7 @@ android:topRightRadius="12dp" android:bottomLeftRadius="4dp" android:bottomRightRadius="4dp" /> - + diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml index f336eea897..e4053e3ba7 100644 --- a/res/drawable/ic_close_work_edu.xml +++ b/res/drawable/ic_close_work_edu.xml @@ -20,6 +20,6 @@ android:viewportWidth="960" android:viewportHeight="960"> diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml index cb37c9add5..cc73f6dafe 100644 --- a/res/drawable/ic_private_space_with_background.xml +++ b/res/drawable/ic_private_space_with_background.xml @@ -13,14 +13,13 @@ limitations under the License. --> + android:fillColor="?attr/materialColorSurfaceContainerLowest" /> + android:centerColor="?attr/materialColorSurfaceBright" + android:endColor="?attr/materialColorSurfaceBright" /> \ No newline at end of file diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml index cfec2b126d..12c4a82f39 100644 --- a/res/drawable/private_space_install_app_icon.xml +++ b/res/drawable/private_space_install_app_icon.xml @@ -23,9 +23,9 @@ android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" /> + android:fillColor="?attr/materialColorSurfaceContainerLowest" /> + android:fillColor="?attr/materialColorOnSurface" /> diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml index e283d3fee3..ddd3042cb9 100644 --- a/res/drawable/rounded_action_button.xml +++ b/res/drawable/rounded_action_button.xml @@ -7,7 +7,7 @@ ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ - ~ Unless required by applicable law or agreed to in writing, software + ~ Unless required by applicable law or agreed to in writing, soft]ware ~ 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 @@ -16,13 +16,12 @@ - + + android:color="?attr/materialColorSurfaceContainerLow" /> diff --git a/res/drawable/work_card.xml b/res/drawable/work_card.xml index 9bf2b8d0fc..01ec947158 100644 --- a/res/drawable/work_card.xml +++ b/res/drawable/work_card.xml @@ -17,7 +17,7 @@ - + diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml index 52180cff77..e68f0f3d33 100644 --- a/res/layout/private_space_header.xml +++ b/res/layout/private_space_header.xml @@ -61,7 +61,7 @@ android:layout_marginBottom="@dimen/ps_lock_icon_margin_bottom" android:importantForAccessibility="no" android:src="@drawable/ic_lock" - app:tint="@color/material_color_primary_fixed_dim" + app:tint="?attr/materialColorPrimaryFixedDim" android:scaleType="center"/> @android:color/system_accent1_900 - - @android:color/system_accent2_700 - @android:color/system_accent3_700 - @android:color/system_accent1_700 - @android:color/system_accent2_100 - @android:color/system_accent3_100 - @android:color/system_accent1_100 - @android:color/system_accent2_200 - #FFDAD5 - @android:color/system_accent2_900 - @android:color/system_neutral1_900 - @android:color/system_accent3_200 - @android:color/system_accent3_900 - @android:color/system_accent1_200 - @android:color/system_accent2_700 - #930001 - @android:color/system_accent1_900 - @android:color/system_accent1_600 - @android:color/system_accent2_100 - @android:color/system_accent3_700 - @android:color/system_accent3_100 - @android:color/system_accent1_700 - @android:color/system_neutral1_800 - @android:color/system_accent1_100 - @android:color/system_accent2_800 - @android:color/system_accent3_800 - #690001 - @android:color/system_neutral2_200 - @android:color/system_neutral2_400 - @android:color/system_neutral2_700 - @android:color/system_accent1_800 - @android:color/system_neutral1_100 - @android:color/system_accent1_200 - @android:color/system_accent2_200 - @android:color/system_accent3_200 \ No newline at end of file diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml deleted file mode 100644 index 95b3a630ca..0000000000 --- a/res/values-night/colors.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - #3F4759 - #583E5B - #0D0E11 - #2B4678 - #DBE2F9 - #FBD7FC - #1B1B1F - #D8E2FF - #BFC6DC - #FFDAD5 - #141B2C - #1B1B1F - #DEBCDF - #29132D - #ADC6FF - #3F4759 - #930001 - #001A41 - #445E91 - #DBE2F9 - #FAF9FD - #44474F - #583E5B - #FBD7FC - #2B4678 - #E3E2E6 - #D8E2FF - #293041 - #402843 - #121316 - #38393C - #690001 - #121316 - #292A2D - #343538 - #C4C6D0 - #72747D - #444746 - #102F60 - #E3E2E6 - #1F1F23 - #ADC6FF - #BFC6DC - #DEBCDF - diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml index 9d50d07d53..06f0eee50f 100644 --- a/res/values-night/styles.xml +++ b/res/values-night/styles.xml @@ -26,7 +26,56 @@ true + + diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml index 727036680d..5c81d496f7 100644 --- a/res/values-v31/colors.xml +++ b/res/values-v31/colors.xml @@ -110,39 +110,4 @@ @android:color/system_accent1_900 @android:color/system_neutral1_1000 - - @android:color/system_accent2_700 - @android:color/system_accent3_700 - @android:color/system_accent1_700 - @android:color/system_accent2_900 - @android:color/system_accent3_900 - @android:color/system_accent1_900 - @android:color/system_accent2_200 - #410000 - @android:color/system_accent2_900 - @android:color/system_neutral1_100 - @android:color/system_accent3_200 - @android:color/system_accent3_900 - @android:color/system_accent1_200 - @android:color/system_accent2_100 - #FFDAD5 - @android:color/system_accent1_900 - @android:color/system_accent1_200 - @android:color/system_accent2_100 - @android:color/system_accent3_100 - @android:color/system_accent3_100 - @android:color/system_accent1_100 - @android:color/system_neutral1_50 - @android:color/system_accent1_100 - @android:color/system_accent2_0 - @android:color/system_accent3_0 - #FFFFFF - @android:color/system_neutral2_700 - @android:color/system_neutral2_500 - @android:color/system_neutral2_200 - @android:color/system_accent1_0 - @android:color/system_neutral1_900 - @android:color/system_accent1_600 - @android:color/system_accent2_600 - @android:color/system_accent3_600 diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml index d19d4a6ffd..4f3a769a61 100644 --- a/res/values-v34/colors.xml +++ b/res/values-v34/colors.xml @@ -30,4 +30,105 @@ @android:color/system_on_surface_variant_light + + @android:color/system_primary_container_light + @android:color/system_on_primary_container_light + @android:color/system_primary_light + @android:color/system_on_primary_light + @android:color/system_secondary_container_light + @android:color/system_on_secondary_container_light + @android:color/system_secondary_light + @android:color/system_on_secondary_light + @android:color/system_tertiary_container_light + @android:color/system_on_tertiary_container_light + @android:color/system_tertiary_light + @android:color/system_on_tertiary_light + @android:color/system_background_light + @android:color/system_on_background_light + @android:color/system_surface_light + @android:color/system_on_surface_light + @android:color/system_surface_container_low_light + @android:color/system_surface_container_lowest_light + @android:color/system_surface_container_light + @android:color/system_surface_container_high_light + @android:color/system_surface_container_highest_light + @android:color/system_surface_bright_light + @android:color/system_surface_dim_light + @android:color/system_surface_variant_light + @android:color/system_on_surface_variant_light + @android:color/system_outline_light + @android:color/system_outline_variant_light + @android:color/system_error_light + @android:color/system_on_error_light + @android:color/system_error_container_light + @android:color/system_on_error_container_light + @android:color/system_control_activated_light + @android:color/system_control_normal_light + @android:color/system_control_highlight_light + @android:color/system_text_primary_inverse_light + @android:color/system_text_secondary_and_tertiary_inverse_light + @android:color/system_text_primary_inverse_disable_only_light + @android:color/system_text_secondary_and_tertiary_inverse_disabled_light + @android:color/system_text_hint_inverse_light + @android:color/system_palette_key_color_primary_light + @android:color/system_palette_key_color_secondary_light + @android:color/system_palette_key_color_tertiary_light + @android:color/system_palette_key_color_neutral_light + @android:color/system_palette_key_color_neutral_variant_light + @android:color/system_primary_container_dark + @android:color/system_on_primary_container_dark + @android:color/system_primary_dark + @android:color/system_on_primary_dark + @android:color/system_secondary_container_dark + @android:color/system_on_secondary_container_dark + @android:color/system_secondary_dark + @android:color/system_on_secondary_dark + @android:color/system_tertiary_container_dark + @android:color/system_on_tertiary_container_dark + @android:color/system_tertiary_dark + @android:color/system_on_tertiary_dark + @android:color/system_background_dark + @android:color/system_on_background_dark + @android:color/system_surface_dark + @android:color/system_on_surface_dark + @android:color/system_surface_container_low_dark + @android:color/system_surface_container_lowest_dark + @android:color/system_surface_container_dark + @android:color/system_surface_container_high_dark + @android:color/system_surface_container_highest_dark + @android:color/system_surface_bright_dark + @android:color/system_surface_dim_dark + @android:color/system_surface_variant_dark + @android:color/system_on_surface_variant_dark + @android:color/system_outline_dark + @android:color/system_outline_variant_dark + @android:color/system_error_dark + @android:color/system_on_error_dark + @android:color/system_error_container_dark + @android:color/system_on_error_container_dark + @android:color/system_control_activated_dark + @android:color/system_control_normal_dark + @android:color/system_control_highlight_dark + @android:color/system_text_primary_inverse_dark + @android:color/system_text_secondary_and_tertiary_inverse_dark + @android:color/system_text_primary_inverse_disable_only_dark + @android:color/system_text_secondary_and_tertiary_inverse_disabled_dark + @android:color/system_text_hint_inverse_dark + @android:color/system_palette_key_color_primary_dark + @android:color/system_palette_key_color_secondary_dark + @android:color/system_palette_key_color_tertiary_dark + @android:color/system_palette_key_color_neutral_dark + @android:color/system_palette_key_color_neutral_variant_dark + @android:color/system_primary_fixed + @android:color/system_primary_fixed_dim + @android:color/system_on_primary_fixed + @android:color/system_on_primary_fixed_variant + @android:color/system_secondary_fixed + @android:color/system_secondary_fixed_dim + @android:color/system_on_secondary_fixed + @android:color/system_on_secondary_fixed_variant + @android:color/system_tertiary_fixed + @android:color/system_tertiary_fixed_dim + @android:color/system_on_tertiary_fixed + @android:color/system_on_tertiary_fixed_variant diff --git a/res/values/attrs.xml b/res/values/attrs.xml index eda36474b6..6151b5fb93 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -45,6 +45,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -582,51 +631,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/res/values/colors.xml b/res/values/colors.xml index 02f69f6e48..c82358b5da 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -17,8 +17,7 @@ ** limitations under the License. */ --> - + #FFC1C1C1 @@ -105,7 +104,7 @@ #FAF9F8 #1F1F1F #4C4D50 - @color/material_color_on_surface_variant + @color/system_on_surface_variant_light #1F1F1F #444746 #C2E7FF @@ -119,14 +118,14 @@ #C4C7C5 #0B57D0 #0B57D0 - @color/material_color_on_surface - @color/material_color_on_surface_variant + @color/system_on_surface_light + @color/system_on_surface_variant_light #1F2020 #393939 #E3E3E3 #CCCDCF - @color/material_color_on_surface_variant + @color/system_on_surface_variant_dark #E3E3E3 #C4C7C5 #004A77 @@ -140,51 +139,107 @@ #444746 #062E6F #FFFFFF - @color/material_color_on_surface - @color/material_color_on_surface_variant + @color/system_on_surface_dark + @color/system_on_surface_variant_dark - #3F4759 - #583E5B - #FFFFFF - #2B4678 - #141B2C - #29132D - #F5F3F7 - #001A41 - #BFC6DC - #410000 - #141B2C - #E3E2E6 - #DEBCDF - #29132D - #ADC6FF - #DBE2F9 - #FFDAD5 - #001A41 - #ADC6FF - #DBE2F9 - #121316 - #E1E2EC - #FBD7FC - #FBD7FC - #D8E2FF - #1B1B1F - #D8E2FF - #FFFFFF - #FFFFFF - #DBD9DD - #FAF9FD - #FFFFFF - #FAF9FD - #E9E7EC - #E3E2E6 - #44474F - #72747D - #C4C7C5 - #FFFFFF - #1B1B1F - #EFEDF1 - #445E91 - #575E71 - #715573 + #D9E2FF + #001945 + #475D92 + #FFFFFF + #DCE2F9 + #151B2C + #575E71 + #FFFFFF + #FDD7FA + #2A122C + #725572 + #FFFFFF + #FAF8FF + #1A1B20 + #FAF8FF + #1A1B20 + #F4F3FA + #FFFFFF + #EEEDF4 + #E8E7EF + #E2E2E9 + #FAF8FF + #DAD9E0 + #E1E2EC + #44464F + #757780 + #C5C6D0 + #BA1A1A + #FFFFFF + #FFDAD6 + #410002 + #D9E2FF + #44464F + #000000 + #E2E2E9 + #C5C6D0 + #E2E2E9 + #E2E2E9 + #E2E2E9 + #6076AC + #70778B + #8C6D8C + #76777D + #757780 + #2F4578 + #D9E2FF + #B0C6FF + #152E60 + #404659 + #DCE2F9 + #C0C6DC + #2A3042 + #593D59 + #FDD7FA + #E0BBDD + #412742 + #121318 + #E2E2E9 + #121318 + #E2E2E9 + #1A1B20 + #0C0E13 + #1E1F25 + #282A2F + #33343A + #38393F + #121318 + #44464F + #C5C6D0 + #8F9099 + #44464F + #FFB4AB + #690005 + #93000A + #FFDAD6 + #2F4578 + #C5C6D0 + #FFFFFF + #1A1B20 + #44464F + #1A1B20 + #1A1B20 + #1A1B20 + #6076AC + #70778B + #8C6D8C + #76777D + #757780 + #D9E2FF + #B0C6FF + #001945 + #2F4578 + #DCE2F9 + #C0C6DC + #151B2C + #404659 + #FDD7FA + #E0BBDD + #2A122C + #593D59 diff --git a/res/values/styles.xml b/res/values/styles.xml index 7b43a3bd2a..ee7ed2687f 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -17,7 +17,7 @@ */ --> - + - + + + diff --git a/src/com/android/launcher3/allapps/SectionDecorationHandler.java b/src/com/android/launcher3/allapps/SectionDecorationHandler.java index ac9b146f12..eaeb8bbdd3 100644 --- a/src/com/android/launcher3/allapps/SectionDecorationHandler.java +++ b/src/com/android/launcher3/allapps/SectionDecorationHandler.java @@ -25,7 +25,6 @@ import android.graphics.drawable.InsetDrawable; import android.view.View; import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import com.android.launcher3.R; import com.android.launcher3.util.Themes; @@ -61,10 +60,10 @@ public class SectionDecorationHandler { mContext = context; mFillAlpha = fillAlpha; - mFocusColor = ContextCompat.getColor(context, - R.color.material_color_surface_bright); // UX recommended - mFillColor = ContextCompat.getColor(context, - R.color.material_color_surface_container_high); // UX recommended + mFocusColor = Themes.getAttrColor(context, + R.attr.materialColorSurfaceBright); // UX recommended + mFillColor = Themes.getAttrColor(context, + R.attr.materialColorSurfaceContainerHigh); // UX recommended mIsTopLeftRound = isTopLeftRound; mIsTopRightRound = isTopRightRound; diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java index 191d284939..4217d22790 100644 --- a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java +++ b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.util; +import android.R; import android.content.Context; import android.content.ContextWrapper; import android.view.ContextThemeWrapper; @@ -39,11 +40,16 @@ public class ActivityContextWrapper extends ContextThemeWrapper implements Activ private final MyDragLayer mMyDragLayer; public ActivityContextWrapper(Context base) { - super(base, android.R.style.Theme_DeviceDefault); + this(base, R.style.Theme_DeviceDefault); + } + + public ActivityContextWrapper(Context base, int theme) { + super(base, theme); mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base); mMyDragLayer = new MyDragLayer(this); } + @Override public BaseDragLayer getDragLayer() { return mMyDragLayer; diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java index eac7f63fb9..de48432492 100644 --- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java +++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java @@ -116,7 +116,8 @@ public class PrivateSpaceHeaderViewTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new ActivityContextWrapper(getApplicationContext()); + mContext = new ActivityContextWrapper(getApplicationContext(), + R.style.DynamicColorsBaseLauncherTheme); when(mAllApps.getContext()).thenReturn(mContext); when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO); when(mUserCache.getUserProfiles())