Snap for 11992154 from 622a7e37c5 to 24Q4-release

Change-Id: I765e9a3b2d6ec681c3501fd8d56222b9d12f5fbc
This commit is contained in:
Android Build Coastguard Worker
2024-06-19 23:22:05 +00:00
5 changed files with 223 additions and 35 deletions
@@ -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
+5 -3
View File
@@ -2797,9 +2797,11 @@ public class Launcher extends StatefulActivity<LauncherState>
}
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();
@@ -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<FirstScreenBroadcastModel> broadcastModels =
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
mPmHelper,
@@ -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<LauncherAppWidgetHostView> mViews = new SparseArray<>();
protected final List<ProviderChangedListener> 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();
}
@@ -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() {