diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig index 21b98631aa..a7796415e2 100644 --- a/aconfig/launcher.aconfig +++ b/aconfig/launcher.aconfig @@ -316,3 +316,16 @@ flag { description: "Archived apps will use new icon in app title" bug: "350758155" } + +flag { + name: "enable_multi_instance_menu_taskbar" + namespace: "launcher" + description: "Menu in Taskbar with options to launch and manage multiple instances of the same app" + bug: "355237285" +} +flag { + name: "navigate_to_child_preference" + namespace: "launcher" + description: "Settings screen supports navigating to child preference if the key is not on the screen" + bug: "293390881" +} diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index a16031d804..92d9516836 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -290,6 +290,9 @@ public class PredictionRowView writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled); writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused); writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow); - writer.println(prefix + "\tmPredictedApps: " + mPredictedApps); + writer.println(prefix + "\tmPredictedApps: " + mPredictedApps.size()); + for (WorkspaceItemInfo info : mPredictedApps) { + writer.println(prefix + "\t\t" + info); + } } } diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index de974ecae1..c50e82dd91 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -536,6 +536,9 @@ public class HotseatPredictionController implements DragController.DragListener, writer.println(prefix + "HotseatPredictionController"); writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags)); writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount); - writer.println(prefix + "\tmPredictedItems: " + mPredictedItems); + writer.println(prefix + "\tmPredictedItems: " + mPredictedItems.size()); + for (ItemInfo info : mPredictedItems) { + writer.println(prefix + "\t\t" + info); + } } } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 93a023dbdd..252ebf7d47 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -272,6 +272,10 @@ public class LauncherTaskbarUIController extends TaskbarUIController { * app launch animation. */ public void setIgnoreInAppFlagForSync(boolean enabled) { + if (mControllers == null) { + // This method can be called before init() is called. + return; + } mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled); } diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt index fdb62dfb3e..0d38fb9655 100644 --- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt +++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt @@ -31,4 +31,7 @@ class RecentsViewData { // The settled set of visible taskIds that is updated after RecentsView scroll settles. val settledFullyVisibleTaskIds = MutableStateFlow(emptySet()) + + // Color tint on foreground scrim + val tintAmount = MutableStateFlow(0f) } diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt index 8b03a84362..edeca2d3f3 100644 --- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt +++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt @@ -45,4 +45,8 @@ class RecentsViewModel( fun setOverlayEnabled(isOverlayEnabled: Boolean) { recentsViewData.overlayEnabled.value = isOverlayEnabled } + + fun setTintAmount(tintAmount: Float) { + recentsViewData.tintAmount.value = tintAmount + } } diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt index fcc2af36b4..8659734e7e 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt @@ -98,10 +98,7 @@ class TaskThumbnailView : FrameLayout, ViewPool.Reusable { } .launchIn(viewAttachedScope) viewModel.dimProgress - .onEach { dimProgress -> - // TODO(b/348195366) Add fade in/out for scrim - scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA - } + .onEach { dimProgress -> scrimView.alpha = dimProgress } .launchIn(viewAttachedScope) viewModel.cornerRadiusProgress.onEach { invalidateOutline() }.launchIn(viewAttachedScope) viewModel.inheritedScale @@ -176,8 +173,4 @@ class TaskThumbnailView : FrameLayout, ViewPool.Reusable { overviewCornerRadius, fullscreenCornerRadius ) / inheritedScale - - private companion object { - const val MAX_SCRIM_ALPHA = 0.4f - } } diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt index 64656452e7..0753bb9d04 100644 --- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt +++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt @@ -30,6 +30,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.systemui.shared.recents.model.Task +import kotlin.math.max import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -63,7 +64,12 @@ class TaskThumbnailViewModel( combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale -> recentsScale * taskScale } - val dimProgress: Flow = taskContainerData.taskMenuOpenProgress + val dimProgress: Flow = + combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) { + taskMenuOpenProgress, + tintAmount -> + max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount) + } val uiState: Flow = task .flatMapLatest { taskFlow -> @@ -110,4 +116,8 @@ class TaskThumbnailViewModel( } @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff) + + private companion object { + const val MAX_SCRIM_ALPHA = 0.4f + } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index d63ac56691..46f6feb0d4 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -6116,7 +6116,6 @@ public abstract class RecentsView lockingAction(/* lock */ true)); @@ -425,6 +426,7 @@ public class PrivateProfileManager extends UserProfileManager { lockPill.setContentDescription(mLockedStateContentDesc); mPrivateSpaceSettingsButton.setVisibility(GONE); + mPrivateSpaceSettingsButton.setClickable(false); transitionView.setVisibility(GONE); } case STATE_TRANSITION -> { @@ -660,10 +662,7 @@ public class PrivateProfileManager extends UserProfileManager { return; } attachFloatingMaskView(expand); - PropertySetter headerSetter = new AnimatedPropertySetter(); - headerSetter.add(updateSettingsGearAlpha(expand)); - headerSetter.add(updateLockTextAlpha(expand)); - AnimatorSet animatorSet = headerSetter.buildAnim(); + AnimatorSet animatorSet = new AnimatedPropertySetter().buildAnim(); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -708,12 +707,16 @@ public class PrivateProfileManager extends UserProfileManager { } })); if (expand) { - animatorSet.playTogether(animateAlphaOfIcons(true), + animatorSet.playTogether(updateSettingsGearAlpha(true), + updateLockTextAlpha(true), + animateAlphaOfIcons(true), animatePillTransition(true), translateFloatingMaskView(false)); } else { AnimatorSet parallelSet = new AnimatorSet(); - parallelSet.playTogether(animateAlphaOfIcons(false), + parallelSet.playTogether(updateSettingsGearAlpha(false), + updateLockTextAlpha(false), + animateAlphaOfIcons(false), animatePillTransition(false)); if (isPrivateSpaceHidden()) { animatorSet.playSequentially(parallelSet, @@ -794,6 +797,14 @@ public class PrivateProfileManager extends UserProfileManager { @Override public void onAnimationStart(Animator animator) { mPrivateSpaceSettingsButton.setVisibility(VISIBLE); + mPrivateSpaceSettingsButton.setClickable(false); + } + + @Override + public void onAnimationEnd(Animator animator) { + if (expand) { + mPrivateSpaceSettingsButton.setClickable(true); + } } }); return settingsAlphaAnim; diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index 52ce4e85a6..bd9298b99a 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -44,11 +44,13 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback; +import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceGroup.PreferencePositionCallback; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BuildConfig; +import com.android.launcher3.Flags; import com.android.launcher3.LauncherFiles; import com.android.launcher3.R; import com.android.launcher3.states.RotationHelper; @@ -165,6 +167,7 @@ public class SettingsActivity extends FragmentActivity private boolean mRestartOnResume = false; private String mHighLightKey; + private boolean mPreferenceHighlighted = false; @Override @@ -198,11 +201,62 @@ public class SettingsActivity extends FragmentActivity } } + // If the target preference is not in the current preference screen, find the parent + // preference screen that contains the target preference and set it as the preference + // screen. + if (Flags.navigateToChildPreference() + && mHighLightKey != null + && !isKeyInPreferenceGroup(mHighLightKey, screen)) { + final PreferenceScreen parentPreferenceScreen = + findParentPreference(screen, mHighLightKey); + if (parentPreferenceScreen != null && getActivity() != null) { + if (!TextUtils.isEmpty(parentPreferenceScreen.getTitle())) { + getActivity().setTitle(parentPreferenceScreen.getTitle()); + } + setPreferenceScreen(parentPreferenceScreen); + return; + } + } + if (getActivity() != null && !TextUtils.isEmpty(getPreferenceScreen().getTitle())) { getActivity().setTitle(getPreferenceScreen().getTitle()); } } + private boolean isKeyInPreferenceGroup(String targetKey, PreferenceGroup parent) { + for (int i = 0; i < parent.getPreferenceCount(); i++) { + Preference pref = parent.getPreference(i); + if (pref.getKey() != null && pref.getKey().equals(targetKey)) { + return true; + } + } + return false; + } + + /** + * Finds the parent preference screen for the given target key. + * + * @param parent the parent preference screen + * @param targetKey the key of the preference to find + * @return the parent preference screen that contains the target preference + */ + @Nullable + private PreferenceScreen findParentPreference(PreferenceScreen parent, String targetKey) { + for (int i = 0; i < parent.getPreferenceCount(); i++) { + Preference pref = parent.getPreference(i); + if (pref instanceof PreferenceScreen) { + PreferenceScreen foundKey = findParentPreference((PreferenceScreen) pref, + targetKey); + if (foundKey != null) { + return foundKey; + } + } else if (pref.getKey() != null && pref.getKey().equals(targetKey)) { + return parent; + } + } + return null; + } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); diff --git a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt b/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt new file mode 100644 index 0000000000..cf03adcc5b --- /dev/null +++ b/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt @@ -0,0 +1,66 @@ +/* + * 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.allapps + +import android.content.Context +import android.view.ViewGroup +import android.view.ViewGroup.MarginLayoutParams +import android.widget.ImageView +import androidx.test.core.app.ApplicationProvider +import com.android.launcher3.util.ActivityContextWrapper +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +class FloatingMaskViewTest { + @Mock + private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null + + @Mock + private val mockBottomBox: ImageView? = null + private var mVut: FloatingMaskView? = null + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) + mVut = FloatingMaskView(context) + mVut!!.layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + } + + @Test + fun setParameters_paramsMarginEqualRecyclerViewPadding() { + val floatingMaskView = Mockito.spy(mVut) + Mockito.`when`(mockAllAppsRecyclerView!!.paddingLeft).thenReturn(PADDING_PX) + Mockito.`when`(mockAllAppsRecyclerView.paddingRight).thenReturn(PADDING_PX) + Mockito.`when`(mockAllAppsRecyclerView.paddingBottom).thenReturn(PADDING_PX) + Mockito.`when`(floatingMaskView!!.bottomBox).thenReturn(mockBottomBox) + val lp = floatingMaskView.layoutParams as MarginLayoutParams + + floatingMaskView.setParameters(lp, mockAllAppsRecyclerView) + + Truth.assertThat(lp.leftMargin).isEqualTo(PADDING_PX) + Truth.assertThat(lp.rightMargin).isEqualTo(PADDING_PX) + Mockito.verify(mockBottomBox)?.minimumHeight = PADDING_PX + } + + companion object { + private const val PADDING_PX = 15 + } +}