blob: 99e59223746bced3e1a697607969ba0148376c94 [file] [log] [blame]
/*
* Copyright (C) 2020 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.cts.appdataisolation.appa;
import static com.android.cts.appdataisolation.common.FileUtils.APPA_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.APPB_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.CE_DATA_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.DE_DATA_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.EXTERNAL_DATA_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.NOT_INSTALLED_PKG;
import static com.android.cts.appdataisolation.common.FileUtils.OBB_FILE_NAME;
import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsNotAccessible;
import static com.android.cts.appdataisolation.common.FileUtils.assertFileDoesNotExist;
import static com.android.cts.appdataisolation.common.FileUtils.assertFileExists;
import static com.android.cts.appdataisolation.common.FileUtils.touchFile;
import static com.android.cts.appdataisolation.common.UserUtils.getCurrentUserId;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
import android.support.test.uiautomator.UiDevice;
import android.view.KeyEvent;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.cts.appdataisolation.common.FileUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/*
* This class is a helper class for AppDataIsolationTests to assert and check data stored in app.
*/
@SmallTest
public class AppATests {
private static final long BIND_SERVICE_TIMEOUT_MS = 5000;
private Context mContext;
private UiDevice mDevice;
private String mCePath;
private String mDePath;
private String mExternalDataPath;
private String mObbPath;
private volatile CountDownLatch mServiceConnectedLatch;
private IIsolatedService mService;
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IIsolatedService.Stub.asInterface(service);
mServiceConnectedLatch.countDown();
}
};
@Before
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mCePath = mContext.getApplicationInfo().dataDir;
mDePath = mContext.getApplicationInfo().deviceProtectedDataDir;
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
setUpExternalStoragePaths();
}
private void setUpExternalStoragePaths() {
File externalFilesDir = mContext.getExternalFilesDir("");
if (externalFilesDir != null) {
mExternalDataPath = mContext.getExternalFilesDir("").getAbsolutePath();
}
File obbDir = mContext.getObbDir();
if (obbDir != null) {
mObbPath = obbDir.getAbsolutePath();
}
}
@Test
public void testCreateCeDeAppData() throws Exception {
assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
touchFile(mCePath, CE_DATA_FILE_NAME);
touchFile(mDePath, DE_DATA_FILE_NAME);
assertFileExists(mCePath, CE_DATA_FILE_NAME);
assertFileDoesNotExist(mCePath, DE_DATA_FILE_NAME);
assertFileDoesNotExist(mDePath, CE_DATA_FILE_NAME);
assertFileExists(mDePath, DE_DATA_FILE_NAME);
}
@Test
public void testCreateExternalDirs() throws Exception {
assertFileDoesNotExist(mExternalDataPath, EXTERNAL_DATA_FILE_NAME);
assertFileDoesNotExist(mObbPath, OBB_FILE_NAME);
touchFile(mExternalDataPath, EXTERNAL_DATA_FILE_NAME);
touchFile(mObbPath, OBB_FILE_NAME);
assertFileExists(mExternalDataPath, EXTERNAL_DATA_FILE_NAME);
assertFileExists(mObbPath, OBB_FILE_NAME);
}
@Test
public void testAppACeDataExists() {
assertFileExists(mCePath, CE_DATA_FILE_NAME);
}
@Test
public void testAppACeDataDoesNotExist() {
assertFileDoesNotExist(mCePath, CE_DATA_FILE_NAME);
}
@Test
public void testAppADeDataExists() {
assertFileExists(mDePath, DE_DATA_FILE_NAME);
}
@Test
public void testAppADeDataDoesNotExist() {
assertFileDoesNotExist(mDePath, DE_DATA_FILE_NAME);
}
@Test
public void testAppAExternalDirsDoExist() {
assertFileExists(mExternalDataPath, EXTERNAL_DATA_FILE_NAME);
assertFileExists(mObbPath, OBB_FILE_NAME);
}
@Test
public void testAppAExternalDirsDoNotExist() {
assertFileDoesNotExist(mExternalDataPath, EXTERNAL_DATA_FILE_NAME);
assertFileDoesNotExist(mObbPath, OBB_FILE_NAME);
}
@Test
public void testAppAExternalDirsUnavailable() {
assertNull(mContext.getExternalFilesDir(""));
assertNull(mContext.getObbDir());
}
@Test
public void testAppACurProfileDataAccessible() {
assertDirIsAccessible("/data/misc/profiles/cur/"+ getCurrentUserId() + "/"
+ mContext.getPackageName());
}
@Test
public void testAppARefProfileDataAccessible() {
assertDirIsAccessible("/data/misc/profiles/ref/"
+ mContext.getPackageName());
}
@Test
public void testCannotAccessAppBDataDir() throws Exception {
ApplicationInfo applicationInfo = mContext.getPackageManager()
.getApplicationInfo(APPB_PKG, 0);
assertDirDoesNotExist(applicationInfo.dataDir);
assertDirDoesNotExist(applicationInfo.deviceProtectedDataDir);
assertDirDoesNotExist("/data/data/" + APPB_PKG);
assertDirDoesNotExist("/data/misc/profiles/cur/" + getCurrentUserId() + "/" + APPB_PKG);
assertDirDoesNotExist("/data/misc/profiles/ref/" + APPB_PKG);
}
@Test
public void testUnlockDevice() throws Exception {
mDevice.wakeUp();
mDevice.waitForIdle();
mDevice.pressMenu();
mDevice.waitForIdle();
mDevice.pressKeyCode(KeyEvent.KEYCODE_1);
mDevice.pressKeyCode(KeyEvent.KEYCODE_2);
mDevice.pressKeyCode(KeyEvent.KEYCODE_3);
mDevice.pressKeyCode(KeyEvent.KEYCODE_4);
mDevice.pressKeyCode(KeyEvent.KEYCODE_5);
mDevice.waitForIdle();
mDevice.pressEnter();
mDevice.waitForIdle();
mDevice.pressHome();
mDevice.waitForIdle();
}
private void testCanNotAccessAppBExternalDirs() {
String appBExternalDir = FileUtils.replacePackageAWithPackageB(
mContext.getExternalFilesDir("").getParentFile().getAbsolutePath());
String appBObbDir = FileUtils.replacePackageAWithPackageB(
mContext.getObbDir().getAbsolutePath());
assertDirDoesNotExist(appBExternalDir);
assertDirDoesNotExist(appBObbDir);
}
@Test
public void testAppAUnlockDeviceAndVerifyCeDeExternalDataExist() throws Exception {
final CountDownLatch unlocked = new CountDownLatch(1);
final CountDownLatch bootCompleted = new CountDownLatch(1);
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_USER_UNLOCKED:
unlocked.countDown();
break;
case Intent.ACTION_BOOT_COMPLETED:
bootCompleted.countDown();
break;
}
}
};
mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
testUnlockDevice();
assertTrue("User not unlocked", unlocked.await(1, TimeUnit.MINUTES));
assertTrue("No locked boot complete", bootCompleted.await(1, TimeUnit.MINUTES));
setUpExternalStoragePaths();
// The test app process should be still running, make sure CE DE now is available
testAppACeDataExists();
testAppADeDataExists();
testAppAExternalDirsDoExist();
testAppACurProfileDataAccessible();
testAppARefProfileDataAccessible();
// Verify after unlocking device, app a has still no access to app b dir.
testCannotAccessAppBDataDir();
testCanNotAccessAppBExternalDirs();
}
@Test
public void testIsolatedProcess() throws Exception {
mServiceConnectedLatch = new CountDownLatch(1);
Intent serviceIntent = new Intent(mContext, IsolatedService.class);
try {
mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
assertTrue("Timed out while waiting to bind to isolated service",
mServiceConnectedLatch.await(BIND_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS));
mService.assertDataIsolated();
} finally {
mContext.unbindService(mServiceConnection);
}
}
@Test
public void testAppZygoteIsolatedProcess() throws Exception {
mServiceConnectedLatch = new CountDownLatch(1);
Intent serviceIntent = new Intent(mContext, AppZygoteIsolatedService.class);
try {
mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
assertTrue("Timed out while waiting to bind to isolated service",
mServiceConnectedLatch.await(BIND_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS));
mService.assertDataIsolated();
} finally {
mContext.unbindService(mServiceConnection);
}
}
@Test
public void testOtherUserDirsNotPresent() throws Exception {
final Bundle arguments = InstrumentationRegistry.getArguments();
final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
final String ceDataRoot = "/data/user/" + otherUserId;
final String deDataRoot = "/data/user_de/" + otherUserId;
final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
assertDirDoesNotExist(ceDataRoot);
assertDirDoesNotExist(deDataRoot);
assertDirDoesNotExist(profileRoot);
}
@Test
public void testOtherUserDirsNotAccessible() throws Exception {
final Bundle arguments = InstrumentationRegistry.getArguments();
final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
final String ceDataRoot = "/data/user/" + otherUserId;
final String deDataRoot = "/data/user_de/" + otherUserId;
final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
// APPA (this app) is installed in this user but not the other one.
// APPB is installed in this user and the other one.
// NOT_INSTALLED_PKG isn't installed anywhere.
// We must get the same answer for all of them, so we can't infer if any of them are or
// are not installed in the other user.
assertDirIsNotAccessible(ceDataRoot);
assertDirIsNotAccessible(ceDataRoot + "/" + APPA_PKG);
assertDirIsNotAccessible(ceDataRoot + "/" + APPB_PKG);
assertDirIsNotAccessible(ceDataRoot + "/" + NOT_INSTALLED_PKG);
assertDirIsNotAccessible(deDataRoot);
assertDirIsNotAccessible(deDataRoot + "/" + APPA_PKG);
assertDirIsNotAccessible(deDataRoot + "/" + APPB_PKG);
assertDirIsNotAccessible(deDataRoot + "/" + NOT_INSTALLED_PKG);
// If the vendor policy is pre-R then backward compatibility rules apply.
if (isVendorPolicyNewerThanR()) {
assertDirIsNotAccessible(profileRoot);
assertDirIsNotAccessible(profileRoot + "/" + APPA_PKG);
assertDirIsNotAccessible(profileRoot + "/" + APPB_PKG);
assertDirIsNotAccessible(profileRoot + "/" + NOT_INSTALLED_PKG);
}
}
private boolean isVendorPolicyNewerThanR() {
if (SystemProperties.get("ro.vndk.version").equals("S")) {
// Vendor build is S, but before the API level bump - good enough for us.
return true;
}
return PropertyUtil.isVendorApiLevelNewerThan(Build.VERSION_CODES.R);
}
}