1 /* 2 * Copyright (C) 2020 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 17 package com.android.server.pm.parsing 18 19 import android.content.pm.ApplicationInfo 20 import android.content.pm.PackageParser 21 import android.os.Environment 22 import android.os.UserHandle 23 import android.platform.test.annotations.Presubmit 24 import com.google.common.truth.Truth.assertWithMessage 25 import org.junit.Test 26 27 /** 28 * As a performance optimization, the new parsing code builds the user data directories manually 29 * using string concatenation. This tries to mirror the logic that [Environment] uses, but it is 30 * still fragile to changes and potentially different device configurations. 31 * 32 * This compares the resultant values against the old [PackageParser] outputs as well as 33 * [ApplicationInfo]'s own [ApplicationInfo.initForUser]. 34 */ 35 @Presubmit 36 class PackageInfoUserFieldsTest : AndroidPackageParsingTestBase() { 37 38 @Test 39 fun userEnvironmentValues() { 40 // Specifically use a large user ID to test assumptions about single character IDs 41 val userId = 110 42 43 oldPackages.zip(newPackages) 44 .map { (old, new) -> 45 (old to oldAppInfo(pkg = old, userId = userId)!!) to 46 (new to newAppInfo(pkg = new, userId = userId)!!) 47 } 48 .forEach { (oldPair, newPair) -> 49 val (oldPkg, oldInfo) = oldPair 50 val (newPkg, newInfo) = newPair 51 52 val oldValuesActual = extractActual(oldInfo) 53 val newValuesActual = extractActual(newInfo) 54 val oldValuesExpected: Values 55 val newValuesExpected: Values 56 57 val packageName = oldPkg.packageName 58 if (packageName == "android") { 59 val systemDataDir = Environment.getDataSystemDirectory().absolutePath 60 oldValuesExpected = Values( 61 uid = UserHandle.getUid(userId, 62 UserHandle.getAppId(oldPkg.applicationInfo.uid)), 63 userDe = null, 64 userCe = null, 65 dataDir = systemDataDir 66 ) 67 newValuesExpected = Values( 68 uid = UserHandle.getUid(userId, UserHandle.getAppId(newPkg.uid)), 69 userDe = null, 70 userCe = null, 71 dataDir = systemDataDir 72 ) 73 } else { 74 oldValuesExpected = extractExpected(oldInfo, oldInfo.uid, userId) 75 newValuesExpected = extractExpected(newInfo, newPkg.uid, userId) 76 } 77 78 // Calls the internal ApplicationInfo logic to compare against. This must be 79 // done after saving the original values, since this will overwrite them. 80 oldInfo.initForUser(userId) 81 newInfo.initForUser(userId) 82 83 val oldInitValues = extractActual(oldInfo) 84 val newInitValues = extractActual(newInfo) 85 86 // The optimization is also done for the no state API that isn't used by the 87 // system. This API is still exposed publicly, so for this test we should 88 // verify it. 89 val newNoStateValues = extractActual( 90 newAppInfoWithoutState(newPkg, 0, userId)!!) 91 92 assertAllEquals(packageName, 93 oldValuesActual, oldValuesExpected, oldInitValues, 94 newValuesActual, newValuesExpected, newInitValues, newNoStateValues) 95 } 96 } 97 98 private fun assertAllEquals(packageName: String, vararg values: Values) { 99 // Local function to avoid accidentally calling wrong type 100 fun assertAllEquals(message: String, vararg values: Any?) { 101 values.forEachIndexed { index, value -> 102 if (index == 0) return@forEachIndexed 103 assertWithMessage("$message $index").that(values[0]).isEqualTo(value) 104 } 105 } 106 107 assertAllEquals("$packageName mismatched uid", values.map { it.uid }) 108 assertAllEquals("$packageName mismatched userDe", values.map { it.userDe }) 109 assertAllEquals("$packageName mismatched userCe", values.map { it.userCe }) 110 assertAllEquals("$packageName mismatched dataDir", values.map { it.dataDir }) 111 } 112 113 private fun extractActual(appInfo: ApplicationInfo) = Values( 114 uid = appInfo.uid, 115 userDe = appInfo.deviceProtectedDataDir, 116 userCe = appInfo.credentialProtectedDataDir, 117 dataDir = appInfo.dataDir 118 ) 119 120 private fun extractExpected(appInfo: ApplicationInfo, appIdUid: Int, userId: Int): Values { 121 val userDe = Environment.getDataUserDePackageDirectory(appInfo.volumeUuid, userId, 122 appInfo.packageName).absolutePath 123 val userCe = Environment.getDataUserCePackageDirectory(appInfo.volumeUuid, userId, 124 appInfo.packageName).absolutePath 125 val dataDir = if (appInfo.isDefaultToDeviceProtectedStorage) { 126 appInfo.deviceProtectedDataDir 127 } else { 128 appInfo.credentialProtectedDataDir 129 } 130 131 return Values( 132 uid = UserHandle.getUid(userId, UserHandle.getAppId(appIdUid)), 133 userDe = userDe, 134 userCe = userCe, 135 dataDir = dataDir 136 ) 137 } 138 139 data class Values( 140 val uid: Int, 141 val userDe: String?, 142 val userCe: String?, 143 val dataDir: String? 144 ) 145 } 146