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.FakeRemoteSdkCallback; 27 import android.app.usage.StorageStats; 28 import android.app.usage.StorageStatsManager; 29 import android.content.Context; 30 import android.os.Binder; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.Process; 34 import android.os.UserHandle; 35 36 import androidx.test.core.app.ApplicationProvider; 37 import androidx.test.platform.app.InstrumentationRegistry; 38 39 import junit.framework.AssertionFailedError; 40 41 import org.junit.Before; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 import org.junit.runners.JUnit4; 45 46 import java.io.File; 47 import java.io.FileInputStream; 48 import java.io.FileNotFoundException; 49 50 @RunWith(JUnit4.class) 51 public class SdkSandboxStorageTestApp { 52 53 private static final String CODE_PROVIDER_PACKAGE = 54 "com.android.tests.codeprovider.storagetest"; 55 private static final String CODE_PROVIDER_KEY = "sdk-provider-class"; 56 private static final String CODE_PROVIDER_CLASS = 57 "com.android.tests.codeprovider.storagetest.StorageTestSandboxedSdkProvider"; 58 59 private static final String BUNDLE_KEY_PHASE_NAME = "phase-name"; 60 61 private static final String JAVA_FILE_PERMISSION_DENIED_MSG = 62 "open failed: EACCES (Permission denied)"; 63 private static final String JAVA_FILE_NOT_FOUND_MSG = 64 "open failed: ENOENT (No such file or directory)"; 65 66 private SdkSandboxManager mSdkSandboxManager; 67 68 @Before setup()69 public void setup() { 70 Context context = ApplicationProvider.getApplicationContext(); 71 mSdkSandboxManager = context.getSystemService( 72 SdkSandboxManager.class); 73 assertThat(mSdkSandboxManager).isNotNull(); 74 } 75 76 // Run a phase of the test inside the code loaded for this app runPhaseInsideCode(IBinder token, String phaseName)77 private void runPhaseInsideCode(IBinder token, String phaseName) { 78 Bundle bundle = new Bundle(); 79 bundle.putString(BUNDLE_KEY_PHASE_NAME, phaseName); 80 mSdkSandboxManager.requestSurfacePackage(token, new Binder(), 0, bundle); 81 } 82 83 @Test testSdkSandboxDataRootDirectory_IsNotAccessibleByApps()84 public void testSdkSandboxDataRootDirectory_IsNotAccessibleByApps() throws Exception { 85 assertDirIsNotAccessible("/data/misc_ce/0/sdksandbox"); 86 assertDirIsNotAccessible("/data/misc_de/0/sdksandbox"); 87 } 88 89 @Test testSdkSandboxDataAppDirectory_SharedStorageIsUsable()90 public void testSdkSandboxDataAppDirectory_SharedStorageIsUsable() throws Exception { 91 // First load code 92 Bundle params = new Bundle(); 93 params.putString(CODE_PROVIDER_KEY, CODE_PROVIDER_CLASS); 94 FakeRemoteSdkCallback callback = new FakeRemoteSdkCallback(); 95 mSdkSandboxManager.loadSdk(CODE_PROVIDER_PACKAGE, params, callback); 96 IBinder codeToken = callback.getSdkToken(); 97 98 // Run phase inside the code 99 runPhaseInsideCode(codeToken, "testSdkSandboxDataAppDirectory_SharedStorageIsUsable"); 100 101 // Wait for code to finish handling the request 102 assertThat(callback.isRequestSurfacePackageSuccessful()).isFalse(); 103 } 104 105 @Test testSdkDataIsAttributedToApp()106 public void testSdkDataIsAttributedToApp() throws Exception { 107 // First load sdk 108 Bundle params = new Bundle(); 109 params.putString(CODE_PROVIDER_KEY, CODE_PROVIDER_CLASS); 110 FakeRemoteSdkCallback callback = new FakeRemoteSdkCallback(); 111 mSdkSandboxManager.loadSdk(CODE_PROVIDER_PACKAGE, params, callback); 112 IBinder codeToken = callback.getSdkToken(); 113 114 final StorageStatsManager stats = InstrumentationRegistry.getInstrumentation().getContext() 115 .getSystemService(StorageStatsManager.class); 116 int uid = Process.myUid(); 117 UserHandle user = Process.myUserHandle(); 118 119 // Have the sdk use up space 120 final StorageStats initialAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid); 121 final StorageStats initialUserStats = stats.queryStatsForUser(UUID_DEFAULT, user); 122 runPhaseInsideCode(codeToken, "testSdkDataIsAttributedToApp"); 123 124 // Wait for sdk to finish handling the request 125 callback.isRequestSurfacePackageSuccessful(); 126 final StorageStats finalAppStats = stats.queryStatsForUid(UUID_DEFAULT, uid); 127 final StorageStats finalUserStats = stats.queryStatsForUser(UUID_DEFAULT, user); 128 129 // Verify the space used with a few hundred kilobytes error margin 130 long deltaAppSize = 2000000; 131 long deltaCacheSize = 1000000; 132 long errorMarginSize = 100000; 133 assertMostlyEquals(deltaAppSize, 134 finalAppStats.getDataBytes() - initialAppStats.getDataBytes(), 135 errorMarginSize); 136 assertMostlyEquals(deltaAppSize, 137 finalUserStats.getDataBytes() - initialUserStats.getDataBytes(), 138 errorMarginSize); 139 assertMostlyEquals(deltaCacheSize, 140 finalAppStats.getCacheBytes() - initialAppStats.getCacheBytes(), 141 errorMarginSize); 142 assertMostlyEquals(deltaCacheSize, 143 finalUserStats.getCacheBytes() - initialUserStats.getCacheBytes(), 144 errorMarginSize); 145 } 146 assertDirIsNotAccessible(String path)147 private static void assertDirIsNotAccessible(String path) { 148 // Trying to access a file that does not exist in that directory, it should return 149 // permission denied not file not found. 150 Exception exception = assertThrows(FileNotFoundException.class, () -> { 151 new FileInputStream(new File(path, "FILE_DOES_NOT_EXIST")); 152 }); 153 assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG); 154 assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG); 155 156 assertThat(new File(path).canExecute()).isFalse(); 157 } 158 assertMostlyEquals(long expected, long actual, long delta)159 public static void assertMostlyEquals(long expected, long actual, long delta) { 160 if (Math.abs(expected - actual) > delta) { 161 throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual); 162 } 163 } 164 } 165