1 /*
<lambda>null2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server.pm
17 
18 import android.content.Context
19 import android.content.pm.PackageInstaller
20 import android.content.pm.PackageInstaller.SessionParams
21 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT
22 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED
23 import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED
24 import android.content.pm.PackageManager
25 import android.content.pm.verify.domain.DomainSet
26 import android.os.Parcel
27 import android.os.Process
28 import android.platform.test.annotations.Presubmit
29 import android.util.AtomicFile
30 import android.util.Slog
31 import android.util.Xml
32 import com.android.internal.os.BackgroundThread
33 import com.android.server.testutils.whenever
34 import com.google.common.truth.Truth.assertThat
35 import libcore.io.IoUtils
36 import org.junit.Before
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.rules.TemporaryFolder
40 import org.mockito.ArgumentMatchers.anyInt
41 import org.mockito.ArgumentMatchers.anyLong
42 import org.mockito.ArgumentMatchers.anyString
43 import org.mockito.Mock
44 import org.mockito.Mockito.mock
45 import org.mockito.MockitoAnnotations
46 import org.xmlpull.v1.XmlPullParser
47 import org.xmlpull.v1.XmlPullParserException
48 import java.io.File
49 import java.io.FileInputStream
50 import java.io.FileNotFoundException
51 import java.io.FileOutputStream
52 import java.io.IOException
53 
54 @Presubmit
55 class PackageInstallerSessionTest {
56 
57     companion object {
58         private const val TAG_SESSIONS = "sessions"
59     }
60 
61     @JvmField
62     @Rule
63     var mTemporaryFolder = TemporaryFolder()
64 
65     private lateinit var mTmpDir: File
66     private lateinit var mSessionsFile: AtomicFile
67 
68     @Mock
69     lateinit var mMockPackageManagerInternal: PackageManagerService
70 
71     @Mock
72     lateinit var mSnapshot: Computer
73 
74     @Before
75     @Throws(Exception::class)
76     fun setUp() {
77         mTmpDir = mTemporaryFolder.newFolder("PackageInstallerSessionTest")
78         mSessionsFile = AtomicFile(
79             File(mTmpDir.getAbsolutePath() + "/sessions.xml"), "package-session"
80         )
81         MockitoAnnotations.initMocks(this)
82         whenever(mSnapshot.getPackageUid(anyString(), anyLong(), anyInt())) { 0 }
83         whenever(mMockPackageManagerInternal.snapshotComputer()) { mSnapshot }
84     }
85 
86     @Test
87     fun testWriteAndRestoreSessionXmlSimpleSession() {
88         writeRestoreAssert(listOf(createSession()))
89     }
90 
91     @Test
92     fun testWriteAndRestoreSessionXmlStagedSession() {
93         writeRestoreAssert(listOf(createSession(staged = true)))
94     }
95 
96     @Test
97     fun testWriteAndRestoreSessionXmlLegacyGrantedPermission() {
98         val sessions = createSession {
99             @Suppress("DEPRECATION")
100             it.setGrantedRuntimePermissions(arrayOf("permission1", "permission2"))
101         }.let(::listOf)
102 
103         val restored = writeRestoreAssert(sessions)
104         assertThat(restored.single().params.legacyGrantedRuntimePermissions).asList()
105             .containsExactly("permission1", "permission2")
106     }
107 
108     @Test
109     fun testWriteAndRestoreSessionXmlPermissionState() {
110         val sessions = createSession {
111             it.setPermissionState("grantPermission", PERMISSION_STATE_GRANTED)
112                 .setPermissionState("denyPermission", PERMISSION_STATE_DENIED)
113                 .setPermissionState("grantToDefaultPermission", PERMISSION_STATE_GRANTED)
114                 .setPermissionState("grantToDefaultPermission", PERMISSION_STATE_DEFAULT)
115                 .setPermissionState("denyToDefaultPermission", PERMISSION_STATE_DENIED)
116                 .setPermissionState("denyToDefaultPermission", PERMISSION_STATE_DEFAULT)
117                 .setPermissionState("grantToDenyPermission", PERMISSION_STATE_GRANTED)
118                 .setPermissionState("grantToDenyPermission", PERMISSION_STATE_DENIED)
119                 .setPermissionState("denyToGrantPermission", PERMISSION_STATE_DENIED)
120                 .setPermissionState("denyToGrantPermission", PERMISSION_STATE_GRANTED)
121         }.let(::listOf)
122 
123         writeRestoreAssert(sessions).single().params.run {
124             assertThat(legacyGrantedRuntimePermissions).asList()
125                 .containsExactly("grantPermission", "denyToGrantPermission")
126             assertThat(permissionStates)
127                 .containsExactlyEntriesIn(mapOf(
128                     "grantPermission" to PERMISSION_STATE_GRANTED,
129                     "denyToGrantPermission" to PERMISSION_STATE_GRANTED,
130                     "denyPermission" to PERMISSION_STATE_DENIED,
131                     "grantToDenyPermission" to PERMISSION_STATE_DENIED,
132                 ))
133         }
134     }
135 
136     @Test
137     fun testWriteAndRestoreSessionXmlMultiPackageSessions() {
138         val session = createSession(
139             sessionId = 123,
140             multiPackage = true,
141             childSessionIds = listOf(234, 345)
142         )
143         val childSession1 = createSession(sessionId = 234, parentSessionId = 123)
144         val childSession2 = createSession(sessionId = 345, parentSessionId = 123)
145         writeRestoreAssert(listOf(session, childSession1, childSession2))
146     }
147 
148     private fun createSession(
149         staged: Boolean = false,
150         sessionId: Int = 123,
151         multiPackage: Boolean = false,
152         parentSessionId: Int = PackageInstaller.SessionInfo.INVALID_ID,
153         childSessionIds: List<Int> = emptyList(),
154         block: (SessionParams) -> Unit = {},
155     ): PackageInstallerSession {
156         val params = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
157             isStaged = staged
158             isMultiPackage = multiPackage
159             block(this)
160         }
161 
162         val installSource = InstallSource.create(
163             "testInstallInitiator",
164             "testInstallOriginator", "testInstaller", -1, "testUpdateOwner",
165             "testAttributionTag", PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED
166         )
167 
168         return PackageInstallerSession(
169             /* callback */ null,
170             /* context */ null,
171             /* pm */ mMockPackageManagerInternal,
172             /* sessionProvider */ null,
173             /* silentUpdatePolicy */ null,
174             /* looper */ BackgroundThread.getHandler().looper,
175             /* stagingManager */ null,
176             /* sessionId */ sessionId,
177             /* userId */ 456,
178             /* installerUid */ Process.myUid(),
179             /* installSource */ installSource,
180             /* sessionParams */ params,
181             /* createdMillis */ 0L,
182             /* committedMillis */ 0L,
183             /* stageDir */ mTmpDir,
184             /* stageCid */ null,
185             /* files */ null,
186             /* checksums */ null,
187             /* prepared */ true,
188             /* committed */ false,
189             /* destroyed */ false,
190             /* sealed */ false, // Setting to true would trigger some PM logic.
191             /* childSessionIds */ childSessionIds.toIntArray(),
192             /* parentSessionId */ parentSessionId,
193             /* isReady */ staged,
194             /* isFailed */ false,
195             /* isApplied */ false,
196             /* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
197             /* stagedSessionErrorMessage */ "some error",
198             /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar"))
199         )
200     }
201 
202     private fun writeRestoreAssert(sessions: List<PackageInstallerSession>) =
203         writeSessions(sessions)
204             .run { restoreSessions() }
205             .also { assertEquals(sessions, it) }
206 
207     private fun writeSessions(sessions: List<PackageInstallerSession>) {
208         var fos: FileOutputStream? = null
209         try {
210             fos = mSessionsFile.startWrite()
211             Xml.resolveSerializer(fos).apply {
212                 startDocument(null, true)
213                 startTag(null, TAG_SESSIONS)
214                 for (session in sessions) {
215                     session.write(this, mTmpDir)
216                 }
217                 endTag(null, TAG_SESSIONS)
218                 endDocument()
219             }
220             mSessionsFile.finishWrite(fos)
221             Slog.d("PackageInstallerSessionTest", String(mSessionsFile.readFully()))
222         } catch (e: IOException) {
223             mSessionsFile.failWrite(fos)
224         }
225     }
226 
227     // This is roughly the logic used in PackageInstallerService to read the session. Note that
228     // this test stresses readFromXml method from PackageInstallerSession, and doesn't cover the
229     // PackageInstallerService portion of the parsing.
230     private fun restoreSessions(): List<PackageInstallerSession> {
231         val ret: MutableList<PackageInstallerSession> = ArrayList()
232         var fis: FileInputStream? = null
233         try {
234             fis = mSessionsFile.openRead()
235             val parser = Xml.resolvePullParser(fis)
236             var type: Int
237             while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
238                 if (type == XmlPullParser.START_TAG) {
239                     val tag = parser.name
240                     if (PackageInstallerSession.TAG_SESSION == tag) {
241                         val session: PackageInstallerSession
242                         try {
243                             session = PackageInstallerSession.readFromXml(
244                                 parser,
245                                 mock(PackageInstallerService.InternalCallback::class.java),
246                                 mock(Context::class.java),
247                                 mMockPackageManagerInternal,
248                                 BackgroundThread.getHandler().looper,
249                                 mock(StagingManager::class.java),
250                                 mTmpDir,
251                                 mock(PackageSessionProvider::class.java),
252                                 mock(SilentUpdatePolicy::class.java)
253                             )
254                             ret.add(session)
255                         } catch (e: Exception) {
256                             Slog.e("PackageInstallerSessionTest", "Exception ", e)
257                             continue
258                         }
259                     }
260                 }
261             }
262         } catch (_: FileNotFoundException) {
263             // Missing sessions are okay, probably first boot
264         } catch (_: IOException) {
265         } catch (_: XmlPullParserException) {
266         } finally {
267             IoUtils.closeQuietly(fis)
268         }
269         return ret
270     }
271 
272     private fun assertSessionParamsEquivalent(expected: SessionParams, actual: SessionParams) {
273         assertThat(expected.mode).isEqualTo(actual.mode)
274         assertThat(expected.installFlags).isEqualTo(actual.installFlags)
275         assertThat(expected.installLocation).isEqualTo(actual.installLocation)
276         assertThat(expected.installReason).isEqualTo(actual.installReason)
277         assertThat(expected.sizeBytes).isEqualTo(actual.sizeBytes)
278         assertThat(expected.appPackageName).isEqualTo(actual.appPackageName)
279         assertThat(expected.appIcon).isEqualTo(actual.appIcon)
280         assertThat(expected.originatingUri).isEqualTo(actual.originatingUri)
281         assertThat(expected.originatingUid).isEqualTo(actual.originatingUid)
282         assertThat(expected.referrerUri).isEqualTo(actual.referrerUri)
283         assertThat(expected.abiOverride).isEqualTo(actual.abiOverride)
284         assertThat(expected.volumeUuid).isEqualTo(actual.volumeUuid)
285         assertThat(expected.permissionStates).isEqualTo(actual.permissionStates)
286         assertThat(expected.installerPackageName).isEqualTo(actual.installerPackageName)
287         assertThat(expected.isMultiPackage).isEqualTo(actual.isMultiPackage)
288         assertThat(expected.isStaged).isEqualTo(actual.isStaged)
289     }
290 
291     private fun assertEquals(
292         expected: List<PackageInstallerSession>,
293         actual: List<PackageInstallerSession>
294     ) {
295         assertThat(expected).hasSize(actual.size)
296         expected.sortedBy { it.sessionId }.zip(actual.sortedBy { it.sessionId })
297             .forEach { (expected, actual) ->
298                 assertEquals(expected, actual)
299             }
300     }
301 
302     private fun assertEquals(expected: PackageInstallerSession, actual: PackageInstallerSession) {
303         // Check both the restored params and an unparcelized variant to ensure parcelling works
304         assertSessionParamsEquivalent(expected.params, actual.params)
305         assertSessionParamsEquivalent(expected.params, actual.params.let {
306             val parcel = Parcel.obtain()
307             it.writeToParcel(parcel, 0)
308             parcel.setDataPosition(0)
309             SessionParams.CREATOR.createFromParcel(parcel).also {
310                 parcel.recycle()
311             }
312         })
313 
314         assertThat(expected.sessionId).isEqualTo(actual.sessionId)
315         assertThat(expected.userId).isEqualTo(actual.userId)
316         assertThat(expected.installerUid).isEqualTo(actual.installerUid)
317         assertThat(expected.installerPackageName).isEqualTo(actual.installerPackageName)
318         assertInstallSourcesEquivalent(expected.installSource, actual.installSource)
319         assertThat(expected.stageDir.absolutePath).isEqualTo(actual.stageDir.absolutePath)
320         assertThat(expected.stageCid).isEqualTo(actual.stageCid)
321         assertThat(expected.isPrepared).isEqualTo(actual.isPrepared)
322         assertThat(expected.isStaged).isEqualTo(actual.isStaged)
323         assertThat(expected.isSessionApplied).isEqualTo(actual.isSessionApplied)
324         assertThat(expected.isSessionFailed).isEqualTo(actual.isSessionFailed)
325         assertThat(expected.isSessionReady).isEqualTo(actual.isSessionReady)
326         assertThat(expected.sessionErrorCode).isEqualTo(actual.sessionErrorCode)
327         assertThat(expected.sessionErrorMessage).isEqualTo(actual.sessionErrorMessage)
328         assertThat(expected.isPrepared).isEqualTo(actual.isPrepared)
329         assertThat(expected.isCommitted).isEqualTo(actual.isCommitted)
330         assertThat(expected.isPreapprovalRequested).isEqualTo(actual.isPreapprovalRequested)
331         assertThat(expected.createdMillis).isEqualTo(actual.createdMillis)
332         assertThat(expected.isSealed).isEqualTo(actual.isSealed)
333         assertThat(expected.isMultiPackage).isEqualTo(actual.isMultiPackage)
334         assertThat(expected.hasParentSessionId()).isEqualTo(actual.hasParentSessionId())
335         assertThat(expected.parentSessionId).isEqualTo(actual.parentSessionId)
336         assertThat(expected.childSessionIds).asList()
337             .containsExactlyElementsIn(actual.childSessionIds.toList())
338         assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains)
339     }
340 
341     private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) {
342         assertThat(expected.mInstallerPackageName).isEqualTo(actual.mInstallerPackageName)
343         assertThat(expected.mInitiatingPackageName).isEqualTo(actual.mInitiatingPackageName)
344         assertThat(expected.mOriginatingPackageName).isEqualTo(actual.mOriginatingPackageName)
345     }
346 }