Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7dc0ce40 authored by Charlie Anderson's avatar Charlie Anderson Committed by Android (Google) Code Review
Browse files

Merge "[Test Week] Add tests for each operation in PackageUpdatedTask" into main

parents 300ca342 98c3cafc
Loading
Loading
Loading
Loading
+48 −48
Original line number Diff line number Diff line
@@ -109,7 +109,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
        final IconCache iconCache = app.getIconCache();

        final String[] packages = mPackages;
        final int N = packages.length;
        final int packageCount = packages.length;
        final FlagOp flagOp;
        final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
        final Predicate<ItemInfo> matcher = mOp == OP_USER_AVAILABILITY_CHANGE
@@ -123,7 +123,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
        }
        switch (mOp) {
            case OP_ADD: {
                for (int i = 0; i < N; i++) {
                for (int i = 0; i < packageCount; i++) {
                    iconCache.updateIconsForPkg(packages[i], mUser);
                    if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
                        if (DEBUG) {
@@ -146,7 +146,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                            + " Look for earlier AllAppsList logs to find more information.");
                    removedComponents.add(a.componentName);
                })) {
                    for (int i = 0; i < N; i++) {
                    for (int i = 0; i < packageCount; i++) {
                        iconCache.updateIconsForPkg(packages[i], mUser);
                        activitiesLists.put(packages[i],
                                appsList.updatePackage(context, packages[i], mUser));
@@ -156,13 +156,13 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                break;
            case OP_REMOVE: {
                for (int i = 0; i < N; i++) {
                for (int i = 0; i < packageCount; i++) {
                    iconCache.removeIconsForPkg(packages[i], mUser);
                }
                // Fall through
            }
            case OP_UNAVAILABLE:
                for (int i = 0; i < N; i++) {
                for (int i = 0; i < packageCount; i++) {
                    if (DEBUG) {
                        Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
                    }
@@ -217,44 +217,44 @@ public class PackageUpdatedTask implements ModelUpdateTask {
            // For system apps, package manager send OP_UPDATE when an app is enabled.
            final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
            synchronized (dataModel) {
                dataModel.forAllWorkspaceItemInfos(mUser, si -> {
                dataModel.forAllWorkspaceItemInfos(mUser, itemInfo -> {

                    boolean infoUpdated = false;
                    boolean shortcutUpdated = false;

                    ComponentName cn = si.getTargetComponent();
                    if (cn != null && matcher.test(si)) {
                    ComponentName cn = itemInfo.getTargetComponent();
                    if (cn != null && matcher.test(itemInfo)) {
                        String packageName = cn.getPackageName();

                        if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
                            forceKeepShortcuts.add(si.id);
                        if (itemInfo.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)) {
                            forceKeepShortcuts.add(itemInfo.id);
                            if (mOp == OP_REMOVE) {
                                return;
                            }
                        }

                        if (si.isPromise() && isNewApkAvailable) {
                        if (itemInfo.isPromise() && isNewApkAvailable) {
                            boolean isTargetValid = !cn.getClassName().equals(
                                    IconCache.EMPTY_CLASS_NAME);
                            if (si.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                            if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                                List<ShortcutInfo> shortcut =
                                        new ShortcutRequest(context, mUser)
                                                .forPackage(cn.getPackageName(),
                                                        si.getDeepShortcutId())
                                                        itemInfo.getDeepShortcutId())
                                                .query(ShortcutRequest.PINNED);
                                if (shortcut.isEmpty()) {
                                    isTargetValid = false;
                                    if (DEBUG) {
                                        Log.d(TAG, "Pinned Shortcut not found for updated"
                                                + " package=" + si.getTargetPackage());
                                                + " package=" + itemInfo.getTargetPackage());
                                    }
                                } else {
                                    if (DEBUG) {
                                        Log.d(TAG, "Found pinned shortcut for updated"
                                                + " package=" + si.getTargetPackage()
                                                + " package=" + itemInfo.getTargetPackage()
                                                + ", isTargetValid=" + isTargetValid);
                                    }
                                    si.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                    itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                    infoUpdated = true;
                                }
                            } else if (isTargetValid) {
@@ -262,39 +262,39 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                                        .isActivityEnabled(cn, mUser);
                            }

                            if (!isTargetValid && (si.hasStatusFlag(
                            if (!isTargetValid && (itemInfo.hasStatusFlag(
                                    FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
                                    || si.isArchived())) {
                                if (updateWorkspaceItemIntent(context, si, packageName)) {
                                    || itemInfo.isArchived())) {
                                if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
                                    infoUpdated = true;
                                } else if (si.hasPromiseIconUi()) {
                                    removedShortcuts.add(si.id);
                                } else if (itemInfo.hasPromiseIconUi()) {
                                    removedShortcuts.add(itemInfo.id);
                                    if (DEBUG) {
                                        FileLog.w(TAG, "Removing restored shortcut promise icon"
                                                + " that no longer points to valid component."
                                                + " id=" + si.id
                                                + ", package=" + si.getTargetPackage()
                                                + ", status=" + si.status
                                                + ", isArchived=" + si.isArchived());
                                                + " id=" + itemInfo.id
                                                + ", package=" + itemInfo.getTargetPackage()
                                                + ", status=" + itemInfo.status
                                                + ", isArchived=" + itemInfo.isArchived());
                                    }
                                    return;
                                }
                            } else if (!isTargetValid) {
                                removedShortcuts.add(si.id);
                                removedShortcuts.add(itemInfo.id);
                                if (DEBUG) {
                                    FileLog.w(TAG, "Removing shortcut that no longer points to"
                                            + " valid component."
                                            + " id=" + si.id
                                            + " package=" + si.getTargetPackage()
                                            + " status=" + si.status);
                                            + " id=" + itemInfo.id
                                            + " package=" + itemInfo.getTargetPackage()
                                            + " status=" + itemInfo.status);
                                }
                                return;
                            } else {
                                si.status = WorkspaceItemInfo.DEFAULT;
                                itemInfo.status = WorkspaceItemInfo.DEFAULT;
                                infoUpdated = true;
                            }
                        } else if (isNewApkAvailable && removedComponents.contains(cn)) {
                            if (updateWorkspaceItemIntent(context, si, packageName)) {
                            if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
                                infoUpdated = true;
                            }
                        }
@@ -304,7 +304,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                                    packageName);
                            // TODO: See if we can migrate this to
                            //  AppInfo#updateRuntimeFlagsForActivityTarget
                            si.setProgressLevel(
                            itemInfo.setProgressLevel(
                                    activities == null || activities.isEmpty()
                                            ? 100
                                            : PackageManagerHelper.getLoadingProgress(
@@ -313,35 +313,35 @@ public class PackageUpdatedTask implements ModelUpdateTask {
                            // In case an app is archived, we need to make sure that archived state
                            // in WorkspaceItemInfo is refreshed.
                            if (Flags.enableSupportForArchiving() && !activities.isEmpty()) {
                                boolean newArchivalState = activities.get(
                                        0).getActivityInfo().isArchived;
                                if (newArchivalState != si.isArchived()) {
                                    si.runtimeStatusFlags ^= FLAG_ARCHIVED;
                                boolean newArchivalState = activities.get(0)
                                        .getActivityInfo().isArchived;
                                if (newArchivalState != itemInfo.isArchived()) {
                                    itemInfo.runtimeStatusFlags ^= FLAG_ARCHIVED;
                                    infoUpdated = true;
                                }
                            }
                            if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                            if (itemInfo.itemType == Favorites.ITEM_TYPE_APPLICATION) {
                                if (activities != null && !activities.isEmpty()) {
                                    si.setNonResizeable(ApiWrapper.INSTANCE.get(context)
                                    itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
                                            .isNonResizeableActivity(activities.get(0)));
                                }
                                iconCache.getTitleAndIcon(si, si.usingLowResIcon());
                                iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon());
                                infoUpdated = true;
                            }
                        }

                        int oldRuntimeFlags = si.runtimeStatusFlags;
                        si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
                        if (si.runtimeStatusFlags != oldRuntimeFlags) {
                        int oldRuntimeFlags = itemInfo.runtimeStatusFlags;
                        itemInfo.runtimeStatusFlags = flagOp.apply(itemInfo.runtimeStatusFlags);
                        if (itemInfo.runtimeStatusFlags != oldRuntimeFlags) {
                            shortcutUpdated = true;
                        }
                    }

                    if (infoUpdated || shortcutUpdated) {
                        updatedWorkspaceItems.add(si);
                        updatedWorkspaceItems.add(itemInfo);
                    }
                    if (infoUpdated && si.id != ItemInfo.NO_ID) {
                        taskController.getModelWriter().updateItemInDatabase(si);
                    if (infoUpdated && itemInfo.id != ItemInfo.NO_ID) {
                        taskController.getModelWriter().updateItemInDatabase(itemInfo);
                    }
                });

@@ -391,7 +391,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
        } else if (mOp == OP_UPDATE) {
            // Mark disabled packages in the broadcast to be removed
            final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
            for (int i=0; i<N; i++) {
            for (int i = 0; i < packageCount; i++) {
                if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
                    if (DEBUG) {
                        Log.d(TAG, "OP_UPDATE:"
@@ -423,7 +423,7 @@ public class PackageUpdatedTask implements ModelUpdateTask {
        if (mOp == OP_ADD) {
            // Load widgets for the new package. Changes due to app updates are handled through
            // AppWidgetHost events, this is just to initialize the long-press options.
            for (int i = 0; i < N; i++) {
            for (int i = 0; i < packageCount; i++) {
                dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
            }
            taskController.bindUpdatedWidgets(dataModel);
+235 −0
Original line number Diff line number Diff line
/*
 * 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.model

import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps
import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.AppFilter
import com.android.launcher3.Flags.FLAG_ENABLE_PRIVATE_SPACE
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.PackageUpdatedTask.OP_ADD
import com.android.launcher3.model.PackageUpdatedTask.OP_REMOVE
import com.android.launcher3.model.PackageUpdatedTask.OP_SUSPEND
import com.android.launcher3.model.PackageUpdatedTask.OP_UNAVAILABLE
import com.android.launcher3.model.PackageUpdatedTask.OP_UNSUSPEND
import com.android.launcher3.model.PackageUpdatedTask.OP_UPDATE
import com.android.launcher3.model.PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.util.Executors
import com.android.launcher3.util.LauncherModelHelper
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@RunWith(AndroidJUnit4::class)
class PackageUpdatedTaskTest {

    @get:Rule val setFlagsRule = SetFlagsRule()

    private val mUser = UserHandle(0)
    private val mDataModel: BgDataModel = BgDataModel()
    private val mLauncherModelHelper = LauncherModelHelper()
    private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
    private val mAppState: LauncherAppState = spy(LauncherAppState.getInstance(mContext))

    private val expectedPackage = "Test.Package"
    private val expectedComponent = ComponentName(expectedPackage, "TestClass")
    private val expectedActivityInfo: LauncherActivityInfo = mock<LauncherActivityInfo>()
    private val expectedWorkspaceItem = spy(WorkspaceItemInfo())

    private val mockIconCache: IconCache = mock()
    private val mockTaskController: ModelTaskController = mock<ModelTaskController>()
    private val mockAppFilter: AppFilter = mock<AppFilter>()
    private val mockApplicationInfo: ApplicationInfo = mock<ApplicationInfo>()
    private val mockActivityInfo: ActivityInfo = mock<ActivityInfo>()

    private lateinit var mAllAppsList: AllAppsList

    @Before
    fun setup() {
        mAllAppsList = spy(AllAppsList(mockIconCache, mockAppFilter))
        mLauncherModelHelper.sandboxContext.spyService(LauncherApps::class.java).apply {
            whenever(getActivityList(expectedPackage, mUser))
                .thenReturn(listOf(expectedActivityInfo))
        }
        whenever(mAppState.iconCache).thenReturn(mockIconCache)
        whenever(mockTaskController.app).thenReturn(mAppState)
        whenever(mockAppFilter.shouldShowApp(expectedComponent)).thenReturn(true)
        mockApplicationInfo.apply {
            uid = 1
            isArchived = false
        }
        mockActivityInfo.isArchived = false
        expectedActivityInfo.apply {
            whenever(applicationInfo).thenReturn(mockApplicationInfo)
            whenever(activityInfo).thenReturn(mockActivityInfo)
            whenever(componentName).thenReturn(expectedComponent)
        }
        expectedWorkspaceItem.apply {
            itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
            container = LauncherSettings.Favorites.CONTAINER_DESKTOP
            user = mUser
            whenever(targetPackage).thenReturn(expectedPackage)
            whenever(targetComponent).thenReturn(expectedComponent)
        }
    }

    @After
    fun tearDown() {
        mLauncherModelHelper.destroy()
    }

    @Test
    fun `OP_ADD triggers model callbacks and adds new items to AllAppsList`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_ADD, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
        verify(mAllAppsList).addPackage(mContext, expectedPackage, mUser)
        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
        verify(mockTaskController).bindUpdatedWidgets(mDataModel)
        assertThat(mAllAppsList.data.firstOrNull()?.componentName)
            .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
    }

    @Test
    fun `OP_UPDATE triggers model callbacks and updates items in AllAppsList`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_UPDATE, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mockIconCache).updateIconsForPkg(expectedPackage, mUser)
        verify(mAllAppsList).updatePackage(mContext, expectedPackage, mUser)
        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
        assertThat(mAllAppsList.data.firstOrNull()?.componentName)
            .isEqualTo(AppInfo(mContext, expectedActivityInfo, mUser).componentName)
    }

    @Test
    fun `OP_REMOVE triggers model callbacks and removes packages and icons`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_REMOVE, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mockIconCache).removeIconsForPkg(expectedPackage, mUser)
        verify(mAllAppsList).removePackage(expectedPackage, mUser)
        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
        assertThat(mAllAppsList.data).isEmpty()
    }

    @Test
    fun `OP_UNAVAILABLE triggers model callbacks and removes package from AllAppsList`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_UNAVAILABLE, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mAllAppsList).removePackage(expectedPackage, mUser)
        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
        assertThat(mAllAppsList.data).isEmpty()
    }

    @Test
    fun `OP_SUSPEND triggers model callbacks and updates flags in AllAppsList`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_SUSPEND, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        mAllAppsList.add(AppInfo(mContext, expectedActivityInfo, mUser), expectedActivityInfo)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mAllAppsList).updateDisabledFlags(any(), any())
        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWorkspaceItem))
        assertThat(mAllAppsList.getAndResetChangeFlag()).isTrue()
    }

    @Test
    fun `OP_UNSUSPEND triggers no callbacks when app not suspended`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_UNSUSPEND, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mAllAppsList).updateDisabledFlags(any(), any())
        verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
        assertThat(mAllAppsList.getAndResetChangeFlag()).isFalse()
    }

    @EnableFlags(FLAG_ENABLE_PRIVATE_SPACE)
    @Test
    fun `OP_USER_AVAILABILITY_CHANGE triggers no callbacks if current user not work or private`() {
        // Given
        val taskUnderTest = PackageUpdatedTask(OP_USER_AVAILABILITY_CHANGE, mUser, expectedPackage)
        // When
        mDataModel.addItem(mContext, expectedWorkspaceItem, true)
        TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
            taskUnderTest.execute(mockTaskController, mDataModel, mAllAppsList)
        }
        mLauncherModelHelper.loadModelSync()
        // Then
        verify(mAllAppsList).updateDisabledFlags(any(), any())
        verify(mockTaskController).bindUpdatedWorkspaceItems(emptyList())
        assertThat(mAllAppsList.data).isEmpty()
    }
}