blob: 748442d872ea637fc2006d651c59e043be0c3682 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.app.cts;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OPSTR_CAMERA;
import static android.app.AppOpsManager.OPSTR_COARSE_LOCATION;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_TOP;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOp;
import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.HistoricalOpsRequest;
import android.app.Instrumentation;
import android.app.cts.android.app.cts.tools.WaitForBroadcast;
import android.app.cts.android.app.cts.tools.WatchUidRunner;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.permission.cts.PermissionUtils;
import android.provider.DeviceConfig;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
* AppOpsManager.MODE_FOREGROUND is introduced in API level 29. This test class specifically tests
* ActivityManagerService's interaction with AppOpsService regarding MODE_FOREGROUND operation.
* If an operation's mode is MODE_FOREGROUND, this operation is allowed only when the process is in
* one of the foreground state (including foreground_service state), this operation will be denied
* when the process is in background state.
*/
@RunWith(AndroidJUnit4.class)
//@Suppress
public class ActivityManagerApi29Test {
private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
private static final String ACTION_SIMPLE_ACTIVITY_START_RESULT =
"android.app.cts.activitymanager.api29.SimpleActivity.RESULT";
private static final String ACTION_SERVICE_START_RESULT =
"android.app.cts.activitymanager.api29.LocationForegroundService.RESULT";
private static final String SERVICE_NAME = ".LocationForegroundService";
private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
private static final int WAITFOR_MSEC = 10000;
private static final int NOTEOP_COUNT = 5;
//TODO: remove this when development is done.
private static final int DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1 << 31;
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
private static final Context sContext = sInstrumentation.getContext();
private static final Context sTargetContext = sInstrumentation.getTargetContext();
private static final AppOpsManager sAppOps =
(AppOpsManager) sContext.getSystemService(AppOpsManager.class);
private static Intent sActivityIntent;
private static Intent sServiceIntent = new Intent().setClassName(
PACKAGE_NAME, PACKAGE_NAME + SERVICE_NAME);
private static int sUid;
static {
try {
sUid = sContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).uid;
} catch (NameNotFoundException e) {
throw new RuntimeException("NameNotFoundException:" + e);
}
}
private String mOldAppOpsSettings;
private boolean mWasPermissionsHubEnabled = false;
private WatchUidRunner mUidWatcher;
@Before
public void setUp() throws Exception {
CtsAppTestUtils.turnScreenOn(sInstrumentation, sContext);
CtsAppTestUtils.makeUidIdle(sInstrumentation, PACKAGE_NAME);
// PACKAGE_NAME's targetSdkVersion is 29, when ACCESS_COARSE_LOCATION is granted, appOp is
// MODE_FOREGROUND (In API level lower than 29, appOp is MODE_ALLOWED).
assertEquals(MODE_FOREGROUND,
PermissionUtils.getAppOp(PACKAGE_NAME, ACCESS_COARSE_LOCATION));
runWithShellPermissionIdentity(()-> {
mOldAppOpsSettings = Settings.Global.getString(sContext.getContentResolver(),
Settings.Global.APP_OPS_CONSTANTS);
Settings.Global.putString(sContext.getContentResolver(),
Settings.Global.APP_OPS_CONSTANTS,
"top_state_settle_time=0,fg_service_state_settle_time=0,"
+ "bg_state_settle_time=0");
mWasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_PERMISSIONS_HUB_ENABLED, false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(true), false);
sAppOps.clearHistory();
sAppOps.resetHistoryParameters(); }
);
mUidWatcher = new WatchUidRunner(sInstrumentation, sUid, WAITFOR_MSEC);
}
@After
public void tearDown() throws Exception {
CtsAppTestUtils.makeUidIdle(sInstrumentation, PACKAGE_NAME);
runWithShellPermissionIdentity(() -> {
// restore old AppOps settings.
Settings.Global.putString(sContext.getContentResolver(),
Settings.Global.APP_OPS_CONSTANTS, mOldAppOpsSettings);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(mWasPermissionsHubEnabled),
false);
sAppOps.clearHistory();
sAppOps.resetHistoryParameters(); }
);
mUidWatcher.finish();
}
private void startSimpleActivity() {
sActivityIntent = new Intent(Intent.ACTION_MAIN)
.setClassName(PACKAGE_NAME, PACKAGE_NAME + SIMPLE_ACTIVITY)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
sTargetContext.startActivity(sActivityIntent);
}
private void stopSimpleActivity() {
sActivityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
.putExtra("finish", true);
sTargetContext.startActivity(sActivityIntent);
}
private void startSimpleService() {
sTargetContext.startForegroundService(sServiceIntent);
}
private void stopSimpleService() {
sTargetContext.stopService(sServiceIntent);
}
/**
* This tests app in PROCESS_STATE_TOP state can have location access.
* The app's permission is AppOpsManager.MODE_FOREGROUND. If the process is in PROCESS_STATE_TOP
* , even its capability is zero, it still has location access.
* @throws Exception
*/
@Test
public void testTopActivityWithAppOps() throws Exception {
startSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
// AppOps location access should be allowed.
assertEquals(MODE_ALLOWED, noteOp(OPSTR_COARSE_LOCATION));
stopSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
// AppOps location access should be denied.
assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
}
/**
* When ActivityManagerService process states and capability changes, it updates AppOpsService.
* This test starts a foreground service with location type, it updates AppOpsService with
* PROCESS_STATE_FOREGROUND_SERVICE and PROCESS_CAPABILITY_FOREGROUND_LOCATION, then check if
* AppOpsManager allow ACCESS_COARSE_LOCATION of MODE_FOREGROUND.
*
* The "android.app.cts.activitymanager.api29" package's targetSdkVersion is 29.
* @throws Exception
*/
@Test
public void testFgsLocationWithAppOps() throws Exception {
// Start a foreground service with location
startSimpleService();
// Wait for state and capability change.
// BG started FGS does not have location capability.
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
startSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
// AppOps location access should be allowed.
assertEquals(MODE_ALLOWED, noteOp(OPSTR_COARSE_LOCATION));
stopSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
// AppOps location access should be denied.
assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
stopSimpleService();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
// AppOps location access should be denied.
assertEquals(MODE_IGNORED, noteOp(OPSTR_COARSE_LOCATION));
}
/**
* After calling AppOpsManager.noteOp() interface multiple times in different process states,
* this test calls AppOpsManager.getHistoricalOps() and check the access count and reject count
* in HistoricalOps.
*
* @throws Exception
*/
@Test
public void testAppOpsHistoricalOps() throws Exception {
runWithShellPermissionIdentity(
() -> sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
1000, 10)
);
WaitForBroadcast waiter = new WaitForBroadcast(sInstrumentation.getTargetContext());
waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_RESULT);
startSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
waiter.doWait(WAITFOR_MSEC);
waiter = new WaitForBroadcast(sInstrumentation.getTargetContext());
waiter.prepare(ACTION_SERVICE_START_RESULT);
startSimpleService();
waiter.doWait(WAITFOR_MSEC);
for (int i = 0; i < NOTEOP_COUNT; i++) {
noteOp(OPSTR_COARSE_LOCATION);
}
stopSimpleActivity();
// The callingPackage to start FGS is in background.
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
for (int i = 0; i < NOTEOP_COUNT; i++) {
noteOp(OPSTR_COARSE_LOCATION);
}
stopSimpleService();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
for (int i = 0; i < NOTEOP_COUNT; i++) {
noteOp(OPSTR_COARSE_LOCATION);
}
runWithShellPermissionIdentity(() -> {
CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
HistoricalOpsRequest histOpsRequest = new HistoricalOpsRequest.Builder(
Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli(),
Long.MAX_VALUE)
.setUid(sUid)
.setPackageName(PACKAGE_NAME)
.setOpNames(Arrays.asList(OPSTR_COARSE_LOCATION))
.setFlags(OP_FLAGS_ALL)
.build();
sAppOps.getHistoricalOps(histOpsRequest, sContext.getMainExecutor(), ops::complete);
HistoricalOp hOp = ops.get(5000, TimeUnit.MILLISECONDS)
.getUidOps(sUid).getPackageOps(PACKAGE_NAME)
.getOp(OPSTR_COARSE_LOCATION);
assertEquals(NOTEOP_COUNT, hOp.getAccessCount(UID_STATE_TOP,
UID_STATE_FOREGROUND_SERVICE, AppOpsManager.OP_FLAGS_ALL));
assertEquals(NOTEOP_COUNT, hOp.getForegroundAccessCount(OP_FLAGS_ALL));
assertEquals(NOTEOP_COUNT, hOp.getForegroundRejectCount(OP_FLAGS_ALL));
assertEquals(0, hOp.getBackgroundAccessCount(OP_FLAGS_ALL));
// denied access one time in background.
assertEquals(NOTEOP_COUNT, hOp.getBackgroundRejectCount(OP_FLAGS_ALL)); }
);
}
/**
* Only FGS started by TOP app can have OP_CAMERA and OP_RECORD_AUDIO.
* @throws Exception
*/
@Test
public void testCameraWithAppOps() throws Exception {
startSimpleService();
// Wait for state and capability change.
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
// Non-Top started FGS do not have while-in-use permission, camera/microphone access is
// denied.
assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
// Start an activity, put app in TOP.
startSimpleActivity();
// TOP process has all capabilities.
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
new Integer(PROCESS_CAPABILITY_ALL));
// Camera/microphone access is allowed because the app is TOP.
assertEquals(MODE_ALLOWED, noteOp(OPSTR_CAMERA));
assertEquals(MODE_ALLOWED, noteOp(OPSTR_RECORD_AUDIO));
// Tell the activity to finalize.
stopSimpleActivity();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
new Integer(DEBUG_PROCESS_CAPABILITY_FOREGROUND_LOCATION));
// App not in Top, camera/microphone access should be denied.
assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
// Stop the foreground service.
stopSimpleService();
mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
new Integer(PROCESS_CAPABILITY_NONE));
assertEquals(MODE_IGNORED, noteOp(OPSTR_CAMERA));
assertEquals(MODE_IGNORED, noteOp(OPSTR_RECORD_AUDIO));
}
private int noteOp(String opStr) throws Exception {
return callWithShellPermissionIdentity(
() -> sAppOps.noteOp(opStr, sUid, PACKAGE_NAME,
opStr, ""));
}
}