1 /* 2 * Copyright (C) 2022 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.tests.sdksandbox; 18 19 import static android.os.storage.StorageManager.UUID_DEFAULT; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertThrows; 24 25 import android.app.sdksandbox.SdkSandboxManager; 26 import android.app.sdksandbox.testutils.EmptyActivity; 27 import android.app.sdksandbox.testutils.FakeLoadSdkCallback; 28 import android.app.sdksandbox.testutils.SdkLifecycleHelper; 29 import android.app.usage.StorageStats; 30 import android.app.usage.StorageStatsManager; 31 import android.content.Context; 32 import android.os.Bundle; 33 import android.os.Process; 34 import android.os.UserHandle; 35 36 import androidx.test.core.app.ApplicationProvider; 37 import androidx.test.ext.junit.rules.ActivityScenarioRule; 38 import androidx.test.platform.app.InstrumentationRegistry; 39 import androidx.test.uiautomator.UiDevice; 40 41 import com.android.tests.codeprovider.storagetest_1.IStorageTestSdk1Api; 42 43 import junit.framework.AssertionFailedError; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.JUnit4; 51 52 import java.io.File; 53 import java.io.FileInputStream; 54 import java.io.FileNotFoundException; 55 56 @RunWith(JUnit4.class) 57 public class SdkSandboxStorageTestApp { 58 59 private static final String TAG = "SdkSandboxStorageTestApp"; 60 61 private static final String SDK_NAME = "com.android.tests.codeprovider.storagetest"; 62 private static final String BUNDLE_KEY_PHASE_NAME = "phase-name"; 63 64 private static final String JAVA_FILE_PERMISSION_DENIED_MSG = 65 "open failed: EACCES (Permission denied)"; 66 private static final String JAVA_FILE_NOT_FOUND_MSG = 67 "open failed: ENOENT (No such file or directory)"; 68 69 @Rule public final ActivityScenarioRule mRule = new ActivityScenarioRule<>(EmptyActivity.class); 70 71 private final Context mContext = ApplicationProvider.getApplicationContext(); 72 private final SdkLifecycleHelper mSdkLifecycleHelper = new SdkLifecycleHelper(mContext); 73 74 private SdkSandboxManager mSdkSandboxManager; 75 private IStorageTestSdk1Api mSdk; 76 private UiDevice mUiDevice; 77 78 @Before setup()79 public void setup() { 80 mSdkSandboxManager = mContext.getSystemService(SdkSandboxManager.class); 81 assertThat(mSdkSandboxManager).isNotNull(); 82 mRule.getScenario(); 83 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 84 85 // unload SDK to fix flakiness 86 mSdkLifecycleHelper.unloadSdk(SDK_NAME); 87 } 88 89 @After tearDown()90 public void tearDown() { 91 // unload SDK to fix flakiness 92 mSdkLifecycleHelper.unloadSdk(SDK_NAME); 93 } 94 95 @Test loadSdk()96 public void loadSdk() throws Exception { 97 FakeLoadSdkCallback callback = new FakeLoadSdkCallback(); 98 mSdkSandboxManager.loadSdk(SDK_NAME, new Bundle(), Runnable::run, callback); 99 callback.assertLoadSdkIsSuccessful(); 100 101 // Store the returned SDK interface so that we can interact with it later. 102 mSdk = IStorageTestSdk1Api.Stub.asInterface(callback.getSandboxedSdk().getInterface()); 103 } 104 105 @Test testSdkSandboxDataRootDirectory_IsNotAccessibleByApps()106 public void testSdkSandboxDataRootDirectory_IsNotAccessibleByApps() throws Exception { 107 assertDirIsNotAccessible("/data/misc_ce/0/sdksandbox"); 108 assertDirIsNotAccessible("/data/misc_de/0/sdksandbox"); 109 } 110 111 @Test testSdkDataPackageDirectory_SharedStorageIsUsable()112 public void testSdkDataPackageDirectory_SharedStorageIsUsable() throws Exception { 113 loadSdk(); 114 115 mSdk.verifySharedStorageIsUsable(); 116 } 117 118 @Test testSdkDataSubDirectory_PerSdkStorageIsUsable()119 public void testSdkDataSubDirectory_PerSdkStorageIsUsable() throws Exception { 120 loadSdk(); 121 122 mSdk.verifyPerSdkStorageIsUsable(); 123 } 124 125 @Test testSdkDataIsAttributedToApp()126 public void testSdkDataIsAttributedToApp() throws Exception { 127 loadSdk(); 128 129 final StorageStatsManager stats = InstrumentationRegistry.getInstrumentation().getContext() 130 .getSystemService(StorageStatsManager.class); 131 int uid = Process.myUid(); 132 UserHandle user = Process.myUserHandle(); 133 134 final StorageStats initialAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid); 135 final StorageStats initialUserStats = stats.queryStatsForUser(UUID_DEFAULT, user); 136 137 // Have the sdk use up space 138 final int sizeInBytes = 10000000; // 10 MB 139 mSdk.createFilesInStorage(sizeInBytes); 140 141 final StorageStats finalAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid); 142 final StorageStats finalUserStats = stats.queryStatsForUser(UUID_DEFAULT, user); 143 144 long deltaAppSize = 4 * sizeInBytes; 145 long deltaCacheSize = 2 * sizeInBytes; 146 147 // Assert app size is same 148 final long appSizeAppStats = finalAppStats.getDataBytes() - initialAppStats.getDataBytes(); 149 final long appSizeUserStats = 150 finalUserStats.getDataBytes() - initialUserStats.getDataBytes(); 151 152 // We can't guarantee that the initial app/user size we captured will not increase/decrease 153 // in between final capture. For exampel, some of use cache can be deleted by system in 154 // need of space. We therefore check for delta with some margin of error. 155 assertMostlyEquals("App size", deltaAppSize, appSizeAppStats, 10); 156 assertMostlyEquals("User size", deltaAppSize, appSizeUserStats, 20); 157 158 // Assert cache size is same 159 final long cacheSizeAppStats = 160 finalAppStats.getCacheBytes() - initialAppStats.getCacheBytes(); 161 final long cacheSizeUserStats = 162 finalUserStats.getCacheBytes() - initialUserStats.getCacheBytes(); 163 assertMostlyEquals("App cache", deltaCacheSize, cacheSizeAppStats, 10); 164 assertMostlyEquals("User cache", deltaCacheSize, cacheSizeUserStats, 20); 165 } 166 assertDirIsNotAccessible(String path)167 private static void assertDirIsNotAccessible(String path) { 168 // Trying to access a file that does not exist in that directory, it should return 169 // permission denied not file not found. 170 Exception exception = assertThrows(FileNotFoundException.class, () -> { 171 new FileInputStream(new File(path, "FILE_DOES_NOT_EXIST")); 172 }); 173 assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG); 174 assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG); 175 176 assertThat(new File(path).canExecute()).isFalse(); 177 } 178 assertMostlyEquals( String noun, long expected, long actual, long errorMarginInPercentage)179 private static void assertMostlyEquals( 180 String noun, long expected, long actual, long errorMarginInPercentage) { 181 final double diffInSize = Math.abs(expected - actual); 182 final double diffInPercentage = (diffInSize / expected) * 100; 183 if (diffInPercentage > errorMarginInPercentage) { 184 throw new AssertionFailedError( 185 noun 186 + " was expected to be roughly " 187 + expected 188 + " but was " 189 + actual 190 + ". Diff in percentage: " 191 + Math.round(diffInPercentage * 100) / 100.00); 192 } 193 } 194 } 195