| /* |
| * Copyright (C) 2019 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 org.junit.Assert.assertArrayEquals; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; |
| import static org.xmlpull.v1.XmlPullParser.START_TAG; |
| |
| import android.content.pm.PackageInstaller; |
| import android.util.AtomicFile; |
| import android.util.Slog; |
| import android.util.Xml; |
| |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.internal.os.BackgroundThread; |
| import com.android.internal.util.FastXmlSerializer; |
| |
| import libcore.io.IoUtils; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class PackageInstallerSessionTest { |
| @Rule |
| public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); |
| |
| private File mTmpDir; |
| private AtomicFile mSessionsFile; |
| private static final String TAG_SESSIONS = "sessions"; |
| |
| @Mock |
| PackageManagerService mMockPackageManagerInternal; |
| |
| @Before |
| public void setUp() throws Exception { |
| mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest"); |
| mSessionsFile = new AtomicFile( |
| new File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session"); |
| MockitoAnnotations.initMocks(this); |
| } |
| |
| @Test |
| public void testWriteAndRestoreSessionXmlSimpleSession() { |
| PackageInstallerSession session = createSimpleSession(); |
| dumpSession(session); |
| List<PackageInstallerSession> restored = restoreSessions(); |
| assertEquals(1, restored.size()); |
| assertSessionsEquivalent(session, restored.get(0)); |
| } |
| |
| @Test |
| public void testWriteAndRestoreSessionXmlStagedSession() { |
| PackageInstallerSession session = createStagedSession(); |
| dumpSession(session); |
| List<PackageInstallerSession> restored = restoreSessions(); |
| assertEquals(1, restored.size()); |
| assertSessionsEquivalent(session, restored.get(0)); |
| } |
| |
| @Test |
| public void testWriteAndRestoreSessionXmlGrantedPermission() { |
| PackageInstallerSession session = createSessionWithGrantedPermissions(); |
| dumpSession(session); |
| List<PackageInstallerSession> restored = restoreSessions(); |
| assertEquals(1, restored.size()); |
| assertSessionsEquivalent(session, restored.get(0)); |
| } |
| |
| @Test |
| public void testWriteAndRestoreSessionXmlMultiPackageSessions() { |
| PackageInstallerSession session = createMultiPackageParentSession(123, new int[]{234, 345}); |
| PackageInstallerSession childSession1 = createMultiPackageChildSession(234, 123); |
| PackageInstallerSession childSession2 = createMultiPackageChildSession(345, 123); |
| List<PackageInstallerSession> sessionGroup = |
| Arrays.asList(session, childSession1, childSession2); |
| dumpSessions(sessionGroup); |
| List<PackageInstallerSession> restored = restoreSessions(); |
| assertEquals(3, restored.size()); |
| assertSessionsEquivalent(sessionGroup, restored); |
| } |
| |
| private PackageInstallerSession createSimpleSession() { |
| return createSession(false, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID, |
| null); |
| } |
| |
| private PackageInstallerSession createStagedSession() { |
| return createSession(true, false, 123, false, PackageInstaller.SessionInfo.INVALID_ID, |
| null); |
| } |
| |
| private PackageInstallerSession createSessionWithGrantedPermissions() { |
| return createSession(false, true, 123, false, PackageInstaller.SessionInfo.INVALID_ID, |
| null); |
| } |
| |
| private PackageInstallerSession createMultiPackageParentSession(int sessionId, |
| int[] childSessionIds) { |
| return createSession(false, false, sessionId, true, |
| PackageInstaller.SessionInfo.INVALID_ID, childSessionIds); |
| } |
| |
| private PackageInstallerSession createMultiPackageChildSession(int sessionId, |
| int parentSessionId) { |
| return createSession(false, false, sessionId, false, parentSessionId, null); |
| } |
| |
| private PackageInstallerSession createSession(boolean staged, boolean withGrantedPermissions, |
| int sessionId, boolean isMultiPackage, |
| int parentSessionId, int[] childSessionIds) { |
| PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( |
| PackageInstaller.SessionParams.MODE_FULL_INSTALL); |
| if (staged) { |
| params.isStaged = true; |
| } |
| if (withGrantedPermissions) { |
| params.grantedRuntimePermissions = new String[]{"permission1", "permission2"}; |
| } |
| if (isMultiPackage) { |
| params.isMultiPackage = true; |
| } |
| InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller"); |
| return new PackageInstallerSession( |
| /* callback */ null, |
| /* context */null, |
| /* pm */ mMockPackageManagerInternal, |
| /* sessionProvider */ null, |
| /* looper */ BackgroundThread.getHandler().getLooper(), |
| /* stagingManager */ null, |
| /* sessionId */ sessionId, |
| /* userId */ 456, |
| /* installerUid */ -1, |
| /* installSource */ installSource, |
| /* sessionParams */ params, |
| /* createdMillis */ 0L, |
| /* stageDir */ mTmpDir, |
| /* stageCid */ null, |
| /* files */ null, |
| /* prepared */ true, |
| /* committed */ true, |
| /* sealed */ false, // Setting to true would trigger some PM logic. |
| /* childSessionIds */ childSessionIds != null ? childSessionIds : new int[0], |
| /* parentSessionId */ parentSessionId, |
| /* isReady */ staged ? true : false, |
| /* isFailed */ false, |
| /* isApplied */false, |
| /* stagedSessionErrorCode */ |
| PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED, |
| /* stagedSessionErrorMessage */ "some error"); |
| } |
| |
| private void dumpSession(PackageInstallerSession session) { |
| dumpSessions(Arrays.asList(session)); |
| } |
| |
| private void dumpSessions(List<PackageInstallerSession> sessions) { |
| FileOutputStream fos = null; |
| try { |
| fos = mSessionsFile.startWrite(); |
| |
| XmlSerializer out = new FastXmlSerializer(); |
| out.setOutput(fos, StandardCharsets.UTF_8.name()); |
| out.startDocument(null, true); |
| out.startTag(null, TAG_SESSIONS); |
| for (PackageInstallerSession session : sessions) { |
| session.write(out, mTmpDir); |
| } |
| out.endTag(null, TAG_SESSIONS); |
| out.endDocument(); |
| |
| mSessionsFile.finishWrite(fos); |
| Slog.d("PackageInstallerSessionTest", new String(mSessionsFile.readFully())); |
| } catch (IOException e) { |
| if (fos != null) { |
| mSessionsFile.failWrite(fos); |
| } |
| } |
| } |
| |
| // This is roughly the logic used in PackageInstallerService to read the session. Note that |
| // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the |
| // PackageInstallerService portion of the parsing. |
| private List<PackageInstallerSession> restoreSessions() { |
| List<PackageInstallerSession> ret = new ArrayList<>(); |
| FileInputStream fis = null; |
| try { |
| fis = mSessionsFile.openRead(); |
| final XmlPullParser in = Xml.newPullParser(); |
| in.setInput(fis, StandardCharsets.UTF_8.name()); |
| |
| int type; |
| while ((type = in.next()) != END_DOCUMENT) { |
| if (type == START_TAG) { |
| final String tag = in.getName(); |
| if (PackageInstallerSession.TAG_SESSION.equals(tag)) { |
| final PackageInstallerSession session; |
| try { |
| session = PackageInstallerSession.readFromXml(in, null, |
| null, mMockPackageManagerInternal, |
| BackgroundThread.getHandler().getLooper(), null, |
| mTmpDir, null); |
| ret.add(session); |
| } catch (Exception e) { |
| Slog.e("PackageInstallerSessionTest", "Exception ", e); |
| continue; |
| } |
| } |
| } |
| } |
| } catch (FileNotFoundException e) { |
| // Missing sessions are okay, probably first boot |
| } catch (IOException | XmlPullParserException e) { |
| |
| } finally { |
| IoUtils.closeQuietly(fis); |
| } |
| return ret; |
| } |
| |
| private void assertSessionParamsEquivalent(PackageInstaller.SessionParams expected, |
| PackageInstaller.SessionParams actual) { |
| assertEquals(expected.mode, actual.mode); |
| assertEquals(expected.installFlags, actual.installFlags); |
| assertEquals(expected.installLocation, actual.installLocation); |
| assertEquals(expected.installReason, actual.installReason); |
| assertEquals(expected.sizeBytes, actual.sizeBytes); |
| assertEquals(expected.appPackageName, actual.appPackageName); |
| assertEquals(expected.appIcon, actual.appIcon); |
| assertEquals(expected.originatingUri, actual.originatingUri); |
| assertEquals(expected.originatingUid, actual.originatingUid); |
| assertEquals(expected.referrerUri, actual.referrerUri); |
| assertEquals(expected.abiOverride, actual.abiOverride); |
| assertEquals(expected.volumeUuid, actual.volumeUuid); |
| assertArrayEquals(expected.grantedRuntimePermissions, actual.grantedRuntimePermissions); |
| assertEquals(expected.installerPackageName, actual.installerPackageName); |
| assertEquals(expected.isMultiPackage, actual.isMultiPackage); |
| assertEquals(expected.isStaged, actual.isStaged); |
| } |
| |
| private void assertSessionsEquivalent(List<PackageInstallerSession> expected, |
| List<PackageInstallerSession> actual) { |
| assertEquals(expected.size(), actual.size()); |
| for (PackageInstallerSession expectedSession : expected) { |
| boolean foundSession = false; |
| for (PackageInstallerSession actualSession : actual) { |
| if (expectedSession.sessionId == actualSession.sessionId) { |
| // We should only encounter each expected session once. |
| assertFalse(foundSession); |
| foundSession = true; |
| assertSessionsEquivalent(expectedSession, actualSession); |
| } |
| } |
| assertTrue(foundSession); |
| } |
| } |
| |
| private void assertSessionsEquivalent(PackageInstallerSession expected, |
| PackageInstallerSession actual) { |
| assertEquals(expected.sessionId, actual.sessionId); |
| assertEquals(expected.userId, actual.userId); |
| assertSessionParamsEquivalent(expected.params, actual.params); |
| assertEquals(expected.getInstallerUid(), actual.getInstallerUid()); |
| assertEquals(expected.stageDir.getAbsolutePath(), actual.stageDir.getAbsolutePath()); |
| assertEquals(expected.stageCid, actual.stageCid); |
| assertEquals(expected.isPrepared(), actual.isPrepared()); |
| assertEquals(expected.isStaged(), actual.isStaged()); |
| assertEquals(expected.isStagedSessionApplied(), actual.isStagedSessionApplied()); |
| assertEquals(expected.isStagedSessionFailed(), actual.isStagedSessionFailed()); |
| assertEquals(expected.isStagedSessionReady(), actual.isStagedSessionReady()); |
| assertEquals(expected.getStagedSessionErrorCode(), actual.getStagedSessionErrorCode()); |
| assertEquals(expected.getStagedSessionErrorMessage(), |
| actual.getStagedSessionErrorMessage()); |
| assertEquals(expected.isPrepared(), actual.isPrepared()); |
| assertEquals(expected.isCommitted(), actual.isCommitted()); |
| assertEquals(expected.isSealed(), actual.isSealed()); |
| assertEquals(expected.isMultiPackage(), actual.isMultiPackage()); |
| assertEquals(expected.hasParentSessionId(), actual.hasParentSessionId()); |
| assertEquals(expected.getParentSessionId(), actual.getParentSessionId()); |
| assertArrayEquals(expected.getChildSessionIds(), actual.getChildSessionIds()); |
| } |
| } |