From fe9a7f3f38519646893a0bcf76030898c158c5c2 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Thu, 13 Jun 2024 12:41:39 -0700 Subject: [PATCH 1/4] Fix flickering issues with divider during split animation This CL makes changes to the "split divider placeholder view", which was intended to cover up the backdrop a little during the split confirm animation. The placeholder view is now larger (fullscreen) and fades in with the animation movement, so there is no longer a period of time when it looks like an awkward rectangle. New timings and interpolators confirmed with UX. Also renamed some variables and added comments for clarity. Fixes: 344573331 Test: Manually verified that the visual bug no longer happens on large and small screen, and from desktop and Overview. Flag: EXEMPT bugfix Change-Id: I3b37f2b0478035d7a3181ae7c23962fe75a13b2c Merged-In: I3b37f2b0478035d7a3181ae7c23962fe75a13b2c (cherry picked from commit 91fb2f2e5e2c1f61adb78fbcfebc22cdb9210b3a) --- .../util/PhoneSplitToConfirmTimings.java | 2 + .../util/SplitAnimationController.kt | 59 ++++++++----------- .../quickstep/util/SplitToConfirmTimings.java | 10 ++++ .../util/SplitToWorkspaceController.java | 7 +-- .../util/TabletSplitToConfirmTimings.java | 2 + .../android/quickstep/views/RecentsView.java | 13 ++-- 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java index 3d9e09ee81..cbe0f19c01 100644 --- a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java +++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java @@ -27,6 +27,8 @@ public class PhoneSplitToConfirmTimings public int getPlaceholderIconFadeInEnd() { return 133; } public int getStagedRectSlideStart() { return 0; } public int getStagedRectSlideEnd() { return 333; } + public int getBackingScrimFadeInStart() { return 0; } + public int getBackingScrimFadeInEnd() { return 266; } public int getDuration() { return PHONE_CONFIRM_DURATION; } } diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt index 1d7c11bf37..f5ccab2211 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -275,48 +275,41 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC } /** - * Creates and returns a view to fade in at .4 animation progress and adds it to the provided - * [pendingAnimation]. Assumes that animation will be the final split placeholder launch anim. - * - * [secondPlaceholderEndingBounds] refers to the second placeholder view that gets added on - * screen, not the logical second app. - * For landscape it's the left app and for portrait the top one. + * Creates and returns a fullscreen scrim to fade in behind the split confirm animation, and + * adds it to the provided [pendingAnimation]. */ - fun addDividerPlaceholderViewToAnim(pendingAnimation: PendingAnimation, - launcher: StatefulActivity<*>, - secondPlaceholderEndingBounds: Rect, - context: Context) : View { - val mSplitDividerPlaceholderView = View(context) + fun addScrimBehindAnim( + pendingAnimation: PendingAnimation, + launcher: StatefulActivity<*>, + context: Context + ): View { + val scrim = View(context) val recentsView = launcher.getOverviewPanel>() val dp : com.android.launcher3.DeviceProfile = launcher.getDeviceProfile() // Add it before/under the most recently added first floating taskView val firstAddedSplitViewIndex: Int = launcher.getDragLayer().indexOfChild( recentsView.splitSelectController.firstFloatingTaskView) - launcher.getDragLayer().addView(mSplitDividerPlaceholderView, firstAddedSplitViewIndex) - val lp = mSplitDividerPlaceholderView.layoutParams as InsettableFrameLayout.LayoutParams + launcher.getDragLayer().addView(scrim, firstAddedSplitViewIndex) + // Make the scrim fullscreen + val lp = scrim.layoutParams as InsettableFrameLayout.LayoutParams lp.topMargin = 0 + lp.height = dp.heightPx + lp.width = dp.widthPx - if (dp.isLeftRightSplit) { - lp.height = secondPlaceholderEndingBounds.height() - lp.width = launcher.resources - .getDimensionPixelSize(R.dimen.split_divider_handle_region_height) - mSplitDividerPlaceholderView.translationX = secondPlaceholderEndingBounds.right - lp.width / 2f - mSplitDividerPlaceholderView.translationY = 0f - } else { - lp.height = launcher.resources - .getDimensionPixelSize(R.dimen.split_divider_handle_region_height) - lp.width = secondPlaceholderEndingBounds.width() - mSplitDividerPlaceholderView.translationY = secondPlaceholderEndingBounds.top - lp.height / 2f - mSplitDividerPlaceholderView.translationX = 0f - } + scrim.alpha = 0f + scrim.setBackgroundColor(launcher.resources.getColor(R.color.taskbar_background_dark)) + val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) as SplitToConfirmTimings + pendingAnimation.setViewAlpha( + scrim, + 1f, + Interpolators.clampToProgress( + timings.backingScrimFadeInterpolator, + timings.backingScrimFadeInStartOffset, + timings.backingScrimFadeInEndOffset + ) + ) - mSplitDividerPlaceholderView.alpha = 0f - mSplitDividerPlaceholderView.setBackgroundColor(launcher.resources - .getColor(R.color.taskbar_background_dark)) - val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) - pendingAnimation.setViewAlpha(mSplitDividerPlaceholderView, 1f, - Interpolators.clampToProgress(timings.stagedRectScaleXInterpolator, 0.4f, 1f)) - return mSplitDividerPlaceholderView + return scrim } /** Does not play any animation if user is not currently in split selection state. */ diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java index d1ec2b6f23..7557ad1b86 100644 --- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java +++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java @@ -17,6 +17,7 @@ package com.android.quickstep.util; import static com.android.app.animation.Interpolators.EMPHASIZED; +import static com.android.app.animation.Interpolators.LINEAR; import android.view.animation.Interpolator; @@ -31,6 +32,8 @@ abstract class SplitToConfirmTimings implements SplitAnimationTimings { abstract public int getPlaceholderIconFadeInEnd(); abstract public int getStagedRectSlideStart(); abstract public int getStagedRectSlideEnd(); + abstract public int getBackingScrimFadeInStart(); + abstract public int getBackingScrimFadeInEnd(); // Common timings public int getInstructionsFadeStart() { return 0; } @@ -39,6 +42,7 @@ abstract class SplitToConfirmTimings implements SplitAnimationTimings { public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; } public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; } public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; } + public Interpolator getBackingScrimFadeInterpolator() { return LINEAR; } abstract public int getDuration(); @@ -48,4 +52,10 @@ abstract class SplitToConfirmTimings implements SplitAnimationTimings { public float getInstructionsFadeEndOffset() { return (float) getInstructionsFadeEnd() / getDuration(); } + public float getBackingScrimFadeInStartOffset() { + return (float) getBackingScrimFadeInStart() / getDuration(); + } + public float getBackingScrimFadeInEndOffset() { + return (float) getBackingScrimFadeInEnd() / getDuration(); + } } diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java index 2d29f4b969..2307a44130 100644 --- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java +++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java @@ -163,9 +163,8 @@ public class SplitToWorkspaceController { new RectF(firstTaskStartingBounds), firstTaskEndingBounds, false /* fadeWithThumbnail */, true /* isStagedTask */); - View mSplitDividerPlaceholderView = recentsView.getSplitSelectController() - .getSplitAnimationController().addDividerPlaceholderViewToAnim(pendingAnimation, - mLauncher, secondTaskEndingBounds, view.getContext()); + View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController() + .addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext()); FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher, view, bitmap, icon, secondTaskStartingBounds); @@ -197,7 +196,7 @@ public class SplitToWorkspaceController { private void cleanUp() { mLauncher.getDragLayer().removeView(firstFloatingTaskView); mLauncher.getDragLayer().removeView(secondFloatingTaskView); - mLauncher.getDragLayer().removeView(mSplitDividerPlaceholderView); + mLauncher.getDragLayer().removeView(backingScrim); mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher); mController.resetState(); } diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java index 3756b4af59..d74473ceaf 100644 --- a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java +++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java @@ -27,6 +27,8 @@ public class TabletSplitToConfirmTimings public int getPlaceholderIconFadeInEnd() { return 250; } public int getStagedRectSlideStart() { return 0; } public int getStagedRectSlideEnd() { return 500; } + public int getBackingScrimFadeInStart() { return 0; } + public int getBackingScrimFadeInEnd() { return 400; } public int getDuration() { return TABLET_CONFIRM_DURATION; } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index f44455c2dd..0043bc59fc 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -721,7 +721,11 @@ public abstract class RecentsView Date: Tue, 18 Jun 2024 13:22:01 -0400 Subject: [PATCH 2/4] Only send new first screen broadcast on first load after restore Flag: EXEMPT launcher_broadcast_installed_apps Bug: 322314760 Test: manually tested Change-Id: I28a3ee1fb2c5c382ea8789b6e966c80db1abf0e0 --- .../android/launcher3/model/LoaderTask.java | 2 +- .../android/launcher3/model/LoaderTaskTest.kt | 171 +++++++++++++++++- 2 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index dc6968c7d9..312c6f412a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -209,7 +209,7 @@ public class LoaderTask implements Runnable { mApp.getContext().getContentResolver(), "launcher_broadcast_installed_apps", /* def= */ 0); - if (launcherBroadcastInstalledApps == 1) { + if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) { List broadcastModels = FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( mPmHelper, diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt index 28a001f990..d16674c6c2 100644 --- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt +++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt @@ -1,10 +1,13 @@ package com.android.launcher3.model import android.appwidget.AppWidgetManager +import android.content.Intent import android.os.UserHandle import android.platform.test.flag.junit.SetFlagsRule +import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.launcher3.Flags import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.LauncherAppState @@ -14,6 +17,7 @@ import com.android.launcher3.icons.IconCache import com.android.launcher3.icons.cache.CachingLogic import com.android.launcher3.icons.cache.IconCacheUpdateHandler import com.android.launcher3.pm.UserCache +import com.android.launcher3.provider.RestoreDbTask import com.android.launcher3.ui.TestViewHelpers import com.android.launcher3.util.Executors.MODEL_EXECUTOR import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext @@ -21,21 +25,30 @@ import com.android.launcher3.util.LooperIdleLock import com.android.launcher3.util.UserIconInfo import com.google.common.truth.Truth import java.util.concurrent.CountDownLatch +import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyList +import org.mockito.ArgumentMatchers.anyMap import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.mockito.MockitoSession import org.mockito.Spy +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql" @@ -43,6 +56,20 @@ private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql" @RunWith(AndroidJUnit4::class) class LoaderTaskTest { private var context = SandboxModelContext() + private val expectedBroadcastModel = + FirstScreenBroadcastModel( + installerPackage = "installerPackage", + pendingCollectionItems = mutableSetOf("pendingCollectionItem"), + pendingWidgetItems = mutableSetOf("pendingWidgetItem"), + pendingHotseatItems = mutableSetOf("pendingHotseatItem"), + pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"), + installedHotseatItems = mutableSetOf("installedHotseatItem"), + installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"), + firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"), + secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget") + ) + private lateinit var mockitoSession: MockitoSession + @Mock private lateinit var app: LauncherAppState @Mock private lateinit var bgAllAppsList: AllAppsList @Mock private lateinit var modelDelegate: ModelDelegate @@ -61,7 +88,11 @@ class LoaderTaskTest { @Before fun setup() { MockitoAnnotations.initMocks(this) - + mockitoSession = + ExtendedMockito.mockitoSession() + .strictness(Strictness.LENIENT) + .mockStatic(FirstScreenBroadcastHelper::class.java) + .startMocking() val idp = InvariantDeviceProfile().apply { numRows = 5 @@ -90,6 +121,7 @@ class LoaderTaskTest { @After fun tearDown() { context.onDestroy() + mockitoSession.finishMocking() } @Test @@ -166,6 +198,141 @@ class LoaderTaskTest { verify(bgAllAppsList, Mockito.never()) .setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true) } + + @Test + fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) + RestoreDbTask.setPending(spyContext) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java) + verify(spyContext).sendBroadcast(argumentCaptor.capture()) + val actualBroadcastIntent = argumentCaptor.value + assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`) + assertEquals( + ArrayList(expectedBroadcastModel.installedWorkspaceItems), + actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems") + ) + assertEquals( + ArrayList(expectedBroadcastModel.installedHotseatItems), + actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems") + ) + assertEquals( + ArrayList( + expectedBroadcastModel.firstScreenInstalledWidgets + + expectedBroadcastModel.secondaryScreenInstalledWidgets + ), + actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingCollectionItems), + actualBroadcastIntent.getStringArrayListExtra("folderItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingWorkspaceItems), + actualBroadcastIntent.getStringArrayListExtra("workspaceItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingHotseatItems), + actualBroadcastIntent.getStringArrayListExtra("hotseatItem") + ) + assertEquals( + ArrayList(expectedBroadcastModel.pendingWidgetItems), + actualBroadcastIntent.getStringArrayListExtra("widgetItem") + ) + } + + @Test + fun `When not a restore then installed item broadcast not sent`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) + } + + @Test + fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() { + // Given + val spyContext = spy(context) + `when`(app.context).thenReturn(spyContext) + whenever( + FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( + anyOrNull(), + anyList(), + anyMap(), + anyList() + ) + ) + .thenReturn(listOf(expectedBroadcastModel)) + + whenever( + FirstScreenBroadcastHelper.sendBroadcastsForModels( + spyContext, + listOf(expectedBroadcastModel) + ) + ) + .thenCallRealMethod() + + Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0) + RestoreDbTask.setPending(spyContext) + + // When + LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder) + .runSyncOnBackgroundThread() + + // Then + verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java)) + } } private fun LoaderTask.runSyncOnBackgroundThread() { From 880de362f17008c497265d7705790474d228567f Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Tue, 18 Jun 2024 11:53:40 -0700 Subject: [PATCH 3/4] [Launcher Jank] Avoid LauncherWidgetHolder and QuickstepWidgetHolder calling IAppWidgetService$Stub$Proxy.setAppWidgetHidden from main thread Fix: 347991067 Test: manual - jank fix Flag: NONE - jank fix Change-Id: I881e5961ee4422d89e761c95ee8371902ded7ae6 --- .../uioverrides/QuickstepWidgetHolder.java | 9 +-- .../widget/LauncherWidgetHolder.java | 68 ++++++++++++------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java index 01d5ff0e15..56fc4d1c0b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java @@ -17,7 +17,6 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; @@ -100,7 +99,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { // for concurrent modification. new ArrayList<>(h.mProviderChangedListeners).forEach( ProviderChangedListener::notifyWidgetProvidersChanged))), - UI_HELPER_EXECUTOR.getLooper()); + getWidgetHolderExecutor().getLooper()); if (WIDGETS_ENABLED) { sWidgetHost.startListening(); } @@ -199,8 +198,10 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder { return; } - sWidgetHost.setAppWidgetHidden(); - setListeningFlag(false); + getWidgetHolderExecutor().execute(() -> { + sWidgetHost.setAppWidgetHidden(); + setListeningFlag(false); + }); } @Override diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java index a5e22c5899..1fb8c8306f 100644 --- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java +++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java @@ -20,6 +20,7 @@ import static android.app.Activity.RESULT_CANCELED; import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED; import static com.android.launcher3.Flags.enableWorkspaceInflation; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo; import android.appwidget.AppWidgetHost; @@ -36,6 +37,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; @@ -44,6 +46,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.ResourceBasedOverride; import com.android.launcher3.util.SafeCloseable; import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView; @@ -51,6 +54,7 @@ import com.android.launcher3.widget.custom.CustomWidgetManager; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntConsumer; /** @@ -77,7 +81,7 @@ public class LauncherWidgetHolder { protected final SparseArray mViews = new SparseArray<>(); protected final List mProviderChangedListeners = new ArrayList<>(); - protected int mFlags = FLAG_STATE_IS_NORMAL; + protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL); // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle"; @@ -96,6 +100,10 @@ public class LauncherWidgetHolder { context, appWidgetRemovedCallback, mProviderChangedListeners); } + protected LooperExecutor getWidgetHolderExecutor() { + return UI_HELPER_EXECUTOR; + } + /** * Starts listening to the widget updates from the server side */ @@ -104,21 +112,23 @@ public class LauncherWidgetHolder { return; } - try { - mWidgetHost.startListening(); - } catch (Exception e) { - if (!Utilities.isBinderSizeError(e)) { - throw new RuntimeException(e); + getWidgetHolderExecutor().execute(() -> { + try { + mWidgetHost.startListening(); + } catch (Exception e) { + if (!Utilities.isBinderSizeError(e)) { + throw new RuntimeException(e); + } + // We're willing to let this slide. The exception is being caused by the list of + // RemoteViews which is being passed back. The startListening relationship will + // have been established by this point, and we will end up populating the + // widgets upon bind anyway. See issue 14255011 for more context. } - // We're willing to let this slide. The exception is being caused by the list of - // RemoteViews which is being passed back. The startListening relationship will - // have been established by this point, and we will end up populating the - // widgets upon bind anyway. See issue 14255011 for more context. - } - // TODO: Investigate why widgetHost.startListening() always return non-empty updates - setListeningFlag(true); + // TODO: Investigate why widgetHost.startListening() always return non-empty updates + setListeningFlag(true); - updateDeferredView(); + MAIN_EXECUTOR.execute(() -> updateDeferredView()); + }); } /** @@ -282,16 +292,23 @@ public class LauncherWidgetHolder { if (!WIDGETS_ENABLED) { return; } - mWidgetHost.stopListening(); - setListeningFlag(false); + getWidgetHolderExecutor().execute(() -> { + mWidgetHost.stopListening(); + setListeningFlag(false); + }); } + /** + * Update {@link FLAG_LISTENING} on {@link mFlags} after making binder calls from + * {@link sWidgetHost}. + */ + @WorkerThread protected void setListeningFlag(final boolean isListening) { if (isListening) { - mFlags |= FLAG_LISTENING; + mFlags.updateAndGet(old -> old | FLAG_LISTENING); return; } - mFlags &= ~FLAG_LISTENING; + mFlags.updateAndGet(old -> old & ~FLAG_LISTENING); } /** @@ -373,7 +390,7 @@ public class LauncherWidgetHolder { * as a result of using the same flow. */ protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) { - if ((mFlags & FLAG_LISTENING) == 0) { + if ((mFlags.get() & FLAG_LISTENING) == 0) { if (view instanceof PendingAppWidgetHostView pv && pv.isDeferredWidget()) { return view; } else { @@ -395,7 +412,7 @@ public class LauncherWidgetHolder { @NonNull protected LauncherAppWidgetHostView createViewInternal( int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) { - if ((mFlags & FLAG_LISTENING) == 0) { + if ((mFlags.get() & FLAG_LISTENING) == 0) { // Since the launcher hasn't started listening to widget updates, we can't simply call // host.createView here because the later will make a binder call to retrieve // RemoteViews from system process. @@ -460,7 +477,7 @@ public class LauncherWidgetHolder { * @return True if the host is listening to the updates, false otherwise */ public boolean isListening() { - return (mFlags & FLAG_LISTENING) != 0; + return (mFlags.get() & FLAG_LISTENING) != 0; } /** @@ -469,16 +486,17 @@ public class LauncherWidgetHolder { */ private void setShouldListenFlag(int flag, boolean on) { if (on) { - mFlags |= flag; + mFlags.updateAndGet(old -> old | flag); } else { - mFlags &= ~flag; + mFlags.updateAndGet(old -> old & ~flag); } final boolean listening = isListening(); - if (!listening && shouldListen(mFlags)) { + int currentFlag = mFlags.get(); + if (!listening && shouldListen(currentFlag)) { // Postpone starting listening until all flags are on. startListening(); - } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) { + } else if (listening && (currentFlag & FLAG_ACTIVITY_STARTED) == 0) { // Postpone stopping listening until the activity is stopped. stopListening(); } From f58d227f6129590b3a45599eb1cd31989b144970 Mon Sep 17 00:00:00 2001 From: Ivan Tkachenko Date: Wed, 19 Jun 2024 11:44:52 +0000 Subject: [PATCH 4/4] Fix back gesture navigation in Launcher with DW The windows back functionality with the `enable_desktop_windowing_wallpaper_activity` flag enabled works as expected and doesn't require changes in `#updateDisallowBack`. - Added the `enable_desktop_windowing_wallpaper_activity` flag check in `Launcher#updateDisallowBack` - The early return statement will get removed after flag is fully rolled out (b/333533253) Bug: 330183377 Test: manual Flag: com.android.window.flags.enable_desktop_windowing_wallpaper_activity Change-Id: I2eaaa44da82da6cbdfe534793830d0f44c4668ba --- src/com/android/launcher3/Launcher.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 4e566abddc..d90580158b 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2797,9 +2797,11 @@ public class Launcher extends StatefulActivity } private void updateDisallowBack() { - if (BuildCompat.isAtLeastV() && Flags.enableDesktopWindowingMode() - && mDeviceProfile.isTablet) { - // TODO(b/330183377) disable back in launcher when when we productionize + if (BuildCompat.isAtLeastV() + && Flags.enableDesktopWindowingMode() + && !Flags.enableDesktopWindowingWallpaperActivity() + && mDeviceProfile.isTablet) { + // TODO(b/333533253): Clean up after desktop wallpaper activity flag is rolled out return; } LauncherRootView rv = getRootView();