diff options
3 files changed, 340 insertions, 154 deletions
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/ArchiveManager.java index 5082be6020a4..99479f088577 100644 --- a/services/core/java/com/android/server/pm/ArchiveManager.java +++ b/services/core/java/com/android/server/pm/ArchiveManager.java @@ -16,14 +16,28 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.DELETE_KEEP_DATA; + import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; import android.content.IntentSender; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; import android.os.Binder; +import android.os.UserHandle; import android.text.TextUtils; +import com.android.internal.annotations.GuardedBy; +import com.android.server.pm.pkg.ArchiveState; +import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo; import com.android.server.pm.pkg.PackageStateInternal; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -35,37 +49,103 @@ import java.util.Objects; */ final class ArchiveManager { + private final Context mContext; private final PackageManagerService mPm; - ArchiveManager(PackageManagerService mPm) { + @Nullable + private LauncherApps mLauncherApps; + + ArchiveManager(Context context, PackageManagerService mPm) { + this.mContext = context; this.mPm = mPm; } void archiveApp( @NonNull String packageName, @NonNull String callerPackageName, - int userId, + @NonNull UserHandle user, @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException { Objects.requireNonNull(packageName); Objects.requireNonNull(callerPackageName); + Objects.requireNonNull(user); Objects.requireNonNull(intentSender); Computer snapshot = mPm.snapshotComputer(); int callingUid = Binder.getCallingUid(); + int userId = user.getIdentifier(); String callingPackageName = snapshot.getNameForUid(callingUid); - snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "archiveApp"); + snapshot.enforceCrossUserPermission(callingUid, userId, true, true, + "archiveApp"); verifyCaller(callerPackageName, callingPackageName); - PackageStateInternal ps = snapshot.getPackageStateInternal(packageName); + PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user); + verifyInstallOwnership(packageName, callingPackageName, ps.getInstallSource()); + + List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList( + ps.getPackageName(), + new UserHandle(userId)); + // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender + + storeArchiveState(ps, mainActivities, userId); + + // TODO(b/278553670) Add special strings for the delete dialog + mPm.mInstallerService.uninstall( + new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), + callerPackageName, DELETE_KEEP_DATA, intentSender, userId); + } + + @NonNull + private static PackageStateInternal getPackageState(String packageName, + Computer snapshot, int callingUid, UserHandle user) + throws PackageManager.NameNotFoundException { + PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid, + user.getIdentifier()); if (ps == null) { throw new PackageManager.NameNotFoundException( TextUtils.formatSimple("Package %s not found.", packageName)); } + return ps; + } + + private LauncherApps getLauncherApps() { + if (mLauncherApps == null) { + mLauncherApps = mContext.getSystemService(LauncherApps.class); + } + return mLauncherApps; + } - verifyInstallOwnership(packageName, callingPackageName, ps); + private void storeArchiveState(PackageStateInternal ps, + List<LauncherActivityInfo> mainActivities, int userId) + throws PackageManager.NameNotFoundException { + List<ArchiveActivityInfo> activityInfos = new ArrayList<>(); + for (int i = 0; i < mainActivities.size(); i++) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( + mainActivities.get(i).getLabel().toString(), + Path.of("/TODO"), null); + activityInfos.add(activityInfo); + } + // TODO(b/278553670) Adapt installer check verifyInstallOwnership and check for null there + InstallSource installSource = ps.getInstallSource(); + String installerPackageName = installSource.mUpdateOwnerPackageName != null + ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName; - // TODO(b/278553670) Complete implementations - throw new UnsupportedOperationException("Method not implemented."); + synchronized (mPm.mLock) { + getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState( + new ArchiveState(activityInfos, installerPackageName)); + } + } + + @NonNull + @GuardedBy("mPm.mLock") + private PackageSetting getPackageSetting(String packageName, int userId) + throws PackageManager.NameNotFoundException { + PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) { + throw new PackageManager.NameNotFoundException( + TextUtils.formatSimple("Package %s not found.", packageName)); + } + return ps; } private static void verifyCaller(String callerPackageName, String callingPackageName) { @@ -80,14 +160,14 @@ final class ArchiveManager { } private static void verifyInstallOwnership(String packageName, String callingPackageName, - PackageStateInternal ps) { - if (!TextUtils.equals(ps.getInstallSource().mInstallerPackageName, + InstallSource installSource) { + if (!TextUtils.equals(installSource.mInstallerPackageName, callingPackageName)) { throw new SecurityException( TextUtils.formatSimple("Caller is not the installer of record for %s.", packageName)); } - String updateOwnerPackageName = ps.getInstallSource().mUpdateOwnerPackageName; + String updateOwnerPackageName = installSource.mUpdateOwnerPackageName; if (updateOwnerPackageName != null && !TextUtils.equals(updateOwnerPackageName, callingPackageName)) { throw new SecurityException( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java new file mode 100644 index 000000000000..7b1654549841 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2023 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.server.pm; + +import static android.content.pm.PackageManager.DELETE_KEEP_DATA; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.IntentSender; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; +import android.os.Binder; +import android.os.Build; +import android.os.Process; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.pm.pkg.ArchiveState; +import com.android.server.pm.pkg.PackageStateInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ArchiveManagerTest { + + private static final String PACKAGE = "com.example"; + private static final String CALLER_PACKAGE = "com.vending"; + + @Rule + public final MockSystemRule mMockSystem = new MockSystemRule(); + + @Mock + private IntentSender mIntentSender; + @Mock + private Computer mComputer; + @Mock + private Context mContext; + @Mock + private LauncherApps mLauncherApps; + @Mock + private PackageInstallerService mInstallerService; + @Mock + private PackageStateInternal mPackageState; + + private final InstallSource mInstallSource = + InstallSource.create( + CALLER_PACKAGE, + CALLER_PACKAGE, + CALLER_PACKAGE, + Binder.getCallingUid(), + CALLER_PACKAGE, + /* installerAttributionTag= */ null, + /* packageSource= */ 0); + + private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities(); + + private PackageSetting mPackageSetting; + + private PackageManagerService mPm; + private ArchiveManager mArchiveManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mMockSystem.system().stageNominalSystemState(); + when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn( + mInstallerService); + mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(), + /* factoryTest= */false, + MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint, + /* isEngBuild= */ false, + /* isUserDebugBuild= */false, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL)); + + when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn( + mPackageState); + when(mPackageState.getPackageName()).thenReturn(PACKAGE); + when(mPackageState.getInstallSource()).thenReturn(mInstallSource); + mPackageSetting = createBasicPackageSetting(); + when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn( + mPackageSetting); + when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps); + when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn( + mLauncherActivityInfos); + doReturn(mComputer).when(mPm).snapshotComputer(); + when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE); + mArchiveManager = new ArchiveManager(mContext, mPm); + } + + @Test + public void archiveApp_callerPackageNameIncorrect() { + Exception e = assertThrows( + SecurityException.class, + () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT, + mIntentSender) + ); + assertThat(e).hasMessageThat().isEqualTo( + String.format( + "The callerPackageName %s set by the caller doesn't match the " + + "caller's own package name %s.", + "different", + CALLER_PACKAGE)); + } + + @Test + public void archiveApp_packageNotInstalled() { + when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn( + null); + + Exception e = assertThrows( + PackageManager.NameNotFoundException.class, + () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, + mIntentSender) + ); + assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE)); + } + + @Test + public void archiveApp_packageNotInstalledForUser() { + mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false); + + Exception e = assertThrows( + PackageManager.NameNotFoundException.class, + () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, + mIntentSender) + ); + assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE)); + } + + @Test + public void archiveApp_callerNotInstallerOfRecord() { + InstallSource otherInstallSource = + InstallSource.create( + CALLER_PACKAGE, + CALLER_PACKAGE, + /* installerPackageName= */ "different", + Binder.getCallingUid(), + CALLER_PACKAGE, + /* installerAttributionTag= */ null, + /* packageSource= */ 0); + when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); + + Exception e = assertThrows( + SecurityException.class, + () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(), + mIntentSender) + ); + assertThat(e).hasMessageThat().isEqualTo( + String.format("Caller is not the installer of record for %s.", PACKAGE)); + } + + @Test + public void archiveApp_callerNotUpdateOwner() { + InstallSource otherInstallSource = + InstallSource.create( + CALLER_PACKAGE, + CALLER_PACKAGE, + CALLER_PACKAGE, + Binder.getCallingUid(), + /* updateOwnerPackageName= */ "different", + /* installerAttributionTag= */ null, + /* packageSource= */ 0); + when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); + + Exception e = assertThrows( + SecurityException.class, + () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, + mIntentSender) + ); + assertThat(e).hasMessageThat().isEqualTo( + String.format("Caller is not the update owner for %s.", PACKAGE)); + } + + @Test + public void archiveApp_success() throws PackageManager.NameNotFoundException { + List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>(); + for (LauncherActivityInfo mainActivity : createLauncherActivities()) { + // TODO(b/278553670) Extract and store launcher icons + ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo( + mainActivity.getLabel().toString(), + Path.of("/TODO"), null); + activityInfos.add(activityInfo); + } + + mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender); + verify(mInstallerService).uninstall( + eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)), + eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender), + eq(UserHandle.CURRENT.getIdentifier())); + assertThat(mPackageSetting.readUserState( + UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo( + new ArchiveState(activityInfos, CALLER_PACKAGE)); + } + + private static List<LauncherActivityInfo> createLauncherActivities() { + LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class); + when(activity1.getLabel()).thenReturn("activity1"); + LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class); + when(activity2.getLabel()).thenReturn("activity2"); + return List.of(activity1, activity2); + } + + private PackageSetting createBasicPackageSetting() { + return new PackageSettingBuilder() + .setName(PACKAGE).setCodePath("/data/app/" + PACKAGE + "-randompath") + .setInstallState(UserHandle.CURRENT.getIdentifier(), /* installed= */ true) + .build(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java deleted file mode 100644 index 73a6bb705916..000000000000 --- a/services/tests/servicestests/src/com/android/server/pm/ArchiveManagerTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2023 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.server.pm; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import android.content.IntentSender; -import android.content.pm.PackageManager; -import android.os.Binder; -import android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.server.pm.pkg.PackageStateInternal; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class ArchiveManagerTest { - - private static final String PACKAGE = "com.example"; - private static final String CALLER_PACKAGE = "com.vending"; - private static final int USER_ID = 1; - - @Mock private IntentSender mIntentSender; - @Mock private PackageManagerService mPm; - @Mock private Computer mComputer; - @Mock private PackageStateInternal mPackageState; - private InstallSource mInstallSource = - InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - CALLER_PACKAGE, - Binder.getCallingUid(), - CALLER_PACKAGE, - /* installerAttributionTag= */ null, - /* packageSource= */ 0); - - private ArchiveManager mArchiveManager; - private int mCallingUid; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mArchiveManager = new ArchiveManager(mPm); - mCallingUid = Binder.getCallingUid(); - when(mPm.snapshotComputer()).thenReturn(mComputer); - when(mComputer.getNameForUid(eq(mCallingUid))).thenReturn(CALLER_PACKAGE); - when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(mPackageState); - when(mPackageState.getInstallSource()).thenReturn(mInstallSource); - } - - @Test - public void archiveApp_callerPackageNameIncorrect() { - Exception e = assertThrows( - SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, "different", USER_ID, mIntentSender) - ); - assertThat(e).hasMessageThat().isEqualTo( - String.format( - "The callerPackageName %s set by the caller doesn't match the " - + "caller's own package name %s.", - "different", - CALLER_PACKAGE)); - } - - @Test - public void archiveApp_packageNotInstalled() { - when(mComputer.getPackageStateInternal(eq(PACKAGE))).thenReturn(null); - - Exception e = assertThrows( - PackageManager.NameNotFoundException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender) - ); - assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE)); - } - - @Test - public void archiveApp_callerNotInstallerOfRecord() { - InstallSource otherInstallSource = - InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - /* installerPackageName= */ "different", - Binder.getCallingUid(), - CALLER_PACKAGE, - /* installerAttributionTag= */ null, - /* packageSource= */ 0); - when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); - - Exception e = assertThrows( - SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender) - ); - assertThat(e).hasMessageThat().isEqualTo( - String.format("Caller is not the installer of record for %s.", PACKAGE)); - } - - @Test - public void archiveApp_callerNotUpdateOwner() { - InstallSource otherInstallSource = - InstallSource.create( - CALLER_PACKAGE, - CALLER_PACKAGE, - CALLER_PACKAGE, - Binder.getCallingUid(), - /* updateOwnerPackageName= */ "different", - /* installerAttributionTag= */ null, - /* packageSource= */ 0); - when(mPackageState.getInstallSource()).thenReturn(otherInstallSource); - - Exception e = assertThrows( - SecurityException.class, - () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, USER_ID, mIntentSender) - ); - assertThat(e).hasMessageThat().isEqualTo( - String.format("Caller is not the update owner for %s.", PACKAGE)); - } -} |