Merge "Exclude only system and device intelligence roles from indicators" into sc-dev

This commit is contained in:
Nate Myren
2021-04-07 22:02:30 +00:00
committed by Android (Google) Code Review
6 changed files with 64 additions and 394 deletions

View File

@@ -35,6 +35,7 @@ import android.app.IActivityManager;
import android.app.PropertyInvalidatedCache; import android.app.PropertyInvalidatedCache;
import android.compat.annotation.ChangeId; import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context; import android.content.Context;
import android.content.pm.IPackageManager; import android.content.pm.IPackageManager;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@@ -42,9 +43,7 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo; import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable; import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.location.LocationManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.content.AttributionSource;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -52,8 +51,8 @@ import android.os.Message;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.DebugUtils; import android.util.DebugUtils;
@@ -103,6 +102,20 @@ public final class PermissionManager {
@EnabledAfter(targetSdkVersion = S) @EnabledAfter(targetSdkVersion = S)
public static final long CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS = 146211400; public static final long CANNOT_INSTALL_WITH_BAD_PERMISSION_GROUPS = 146211400;
/**
* The time to wait in between refreshing the exempted indicator role packages
*/
private static final long EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS = 15000;
private static long sLastIndicatorUpdateTime = -1;
private static final int[] EXEMPTED_ROLES = {R.string.config_systemAmbientAudioIntelligence,
R.string.config_systemUiIntelligence, R.string.config_systemAudioIntelligence,
R.string.config_systemNotificationIntelligence, R.string.config_systemTextIntelligence,
R.string.config_systemVisualIntelligence};
private static final String[] INDICATOR_EXEMPTED_PACKAGES = new String[EXEMPTED_ROLES.length];
/** /**
* Note: Changing this won't do anything on its own - you should also change the filtering in * Note: Changing this won't do anything on its own - you should also change the filtering in
* {@link #shouldTraceGrant}. * {@link #shouldTraceGrant}.
@@ -873,21 +886,31 @@ public final class PermissionManager {
} }
/** /**
* Check if this package/op combination is exempted from indicators * Determine if a package should be shown in indicators. Only a select few roles, and the
* @return * system app itself, are hidden. These values are updated at most every 15 seconds.
* @hide * @hide
*/ */
public static boolean isSpecialCaseShownIndicator(@NonNull Context context, public static boolean shouldShowPackageForIndicatorCached(@NonNull Context context,
@NonNull String packageName) { @NonNull String packageName) {
if (SYSTEM_PKG.equals(packageName)) {
if (packageName.equals(SYSTEM_PKG)) {
return false; return false;
} }
long now = SystemClock.elapsedRealtime();
if (sLastIndicatorUpdateTime == -1
|| (now - sLastIndicatorUpdateTime) > EXEMPTED_INDICATOR_ROLE_UPDATE_FREQUENCY_MS) {
sLastIndicatorUpdateTime = now;
for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
INDICATOR_EXEMPTED_PACKAGES[i] = context.getString(EXEMPTED_ROLES[i]);
}
}
for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
String exemptedPackage = INDICATOR_EXEMPTED_PACKAGES[i];
if (exemptedPackage != null && exemptedPackage.equals(packageName)) {
return false;
}
}
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, "permissions_hub_2_enabled", return true;
false)
|| packageName.equals(context.getString(R.string.config_systemSpeechRecognizer))
|| context.getSystemService(LocationManager.class).isProviderPackage(packageName);
} }
/** /**
* Gets the list of packages that have permissions that specified * Gets the list of packages that have permissions that specified

View File

@@ -26,8 +26,6 @@ import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE; import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
import static android.app.AppOpsManager.opToPermission;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.media.AudioSystem.MODE_IN_COMMUNICATION; import static android.media.AudioSystem.MODE_IN_COMMUNICATION;
import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
@@ -284,10 +282,6 @@ public class PermissionUsageHelper {
continue; continue;
} }
if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) {
continue;
}
boolean isRunning = attrOpEntry.isRunning() boolean isRunning = attrOpEntry.isRunning()
|| lastAccessTime >= runningThreshold; || lastAccessTime >= runningThreshold;
@@ -375,7 +369,7 @@ public class PermissionUsageHelper {
// for it's uid and package name, save it. // for it's uid and package name, save it.
int usageId = usage.getPackageIdHash(); int usageId = usage.getPackageIdHash();
OpUsage lastMostRecent = mostRecentUsages.get(usageId); OpUsage lastMostRecent = mostRecentUsages.get(usageId);
if (!usage.packageName.equals(SYSTEM_PKG) && (lastMostRecent == null if (shouldShowPackage(usage.packageName) && (lastMostRecent == null
|| usage.lastAccessTime > lastMostRecent.lastAccessTime)) { || usage.lastAccessTime > lastMostRecent.lastAccessTime)) {
mostRecentUsages.put(usageId, usage); mostRecentUsages.put(usageId, usage);
} }
@@ -401,8 +395,7 @@ public class PermissionUsageHelper {
// We are missing the proxy usage. This may be because it's a one-step trusted // We are missing the proxy usage. This may be because it's a one-step trusted
// proxy. Check if we should show the proxy label, and show it, if so. // proxy. Check if we should show the proxy label, and show it, if so.
OpUsage proxy = currentUsage.proxy; OpUsage proxy = currentUsage.proxy;
if (PermissionManager.isSpecialCaseShownIndicator(mContext, proxy.packageName) if (shouldShowPackage(proxy.packageName)) {
|| isUserSensitive(proxy.packageName, proxy.getUser(), proxy.op)) {
currentUsage = proxy; currentUsage = proxy;
// We've effectively added one usage, so increment the max number of usages // We've effectively added one usage, so increment the max number of usages
maxUsages++; maxUsages++;
@@ -411,7 +404,6 @@ public class PermissionUsageHelper {
} }
} }
if (currentUsage == null || iterNum == maxUsages if (currentUsage == null || iterNum == maxUsages
|| currentUsage.getPackageIdHash() == start.getPackageIdHash()) { || currentUsage.getPackageIdHash() == start.getPackageIdHash()) {
// We have an invalid state, or a cycle, so break // We have an invalid state, or a cycle, so break
@@ -421,7 +413,7 @@ public class PermissionUsageHelper {
proxyPackages.add(currentUsage.getPackageIdHash()); proxyPackages.add(currentUsage.getPackageIdHash());
// Don't add an app label for the main app, or the system app // Don't add an app label for the main app, or the system app
if (!currentUsage.packageName.equals(start.packageName) if (!currentUsage.packageName.equals(start.packageName)
&& !currentUsage.packageName.equals(SYSTEM_PKG)) { && shouldShowPackage(currentUsage.packageName)) {
try { try {
PackageManager userPkgManager = PackageManager userPkgManager =
getUserContext(currentUsage.getUser()).getPackageManager(); getUserContext(currentUsage.getUser()).getPackageManager();
@@ -451,17 +443,8 @@ public class PermissionUsageHelper {
return usagesAndLabels; return usagesAndLabels;
} }
private boolean isUserSensitive(String packageName, UserHandle user, String op) { private boolean shouldShowPackage(String packageName) {
if (op.equals(OPSTR_PHONE_CALL_CAMERA) || op.equals(OPSTR_PHONE_CALL_MICROPHONE)) { return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName);
return true;
}
if (opToPermission(op) == null) {
return false;
}
int permFlags = mPkgManager.getPermissionFlags(opToPermission(op), packageName, user);
return (permFlags & FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
} }
/** /**

View File

@@ -25,24 +25,20 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioRecordingConfiguration; import android.media.AudioRecordingConfiguration;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.DeviceConfig; import android.permission.PermissionManager;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
@@ -83,19 +79,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
private final Context mContext; private final Context mContext;
private final AppOpsManager mAppOps; private final AppOpsManager mAppOps;
private final AudioManager mAudioManager; private final AudioManager mAudioManager;
private final LocationManager mLocationManager;
private final IndividualSensorPrivacyController mSensorPrivacyController; private final IndividualSensorPrivacyController mSensorPrivacyController;
private final SystemClock mClock; private final SystemClock mClock;
// mLocationProviderPackages are cached and updated only occasionally
private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
private long mLastLocationProviderPackageUpdate;
private List<String> mLocationProviderPackages;
private H mBGHandler; private H mBGHandler;
private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>(); private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
private final PermissionFlagsCache mFlagsCache;
private boolean mListening; private boolean mListening;
private boolean mMicMuted; private boolean mMicMuted;
private boolean mCameraDisabled; private boolean mCameraDisabled;
@@ -124,7 +113,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
Context context, Context context,
@Background Looper bgLooper, @Background Looper bgLooper,
DumpManager dumpManager, DumpManager dumpManager,
PermissionFlagsCache cache,
AudioManager audioManager, AudioManager audioManager,
IndividualSensorPrivacyController sensorPrivacyController, IndividualSensorPrivacyController sensorPrivacyController,
BroadcastDispatcher dispatcher, BroadcastDispatcher dispatcher,
@@ -132,7 +120,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
) { ) {
mDispatcher = dispatcher; mDispatcher = dispatcher;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mFlagsCache = cache;
mBGHandler = new H(bgLooper); mBGHandler = new H(bgLooper);
final int numOps = OPS.length; final int numOps = OPS.length;
for (int i = 0; i < numOps; i++) { for (int i = 0; i < numOps; i++) {
@@ -143,7 +130,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
mMicMuted = audioManager.isMicrophoneMute() mMicMuted = audioManager.isMicrophoneMute()
|| mSensorPrivacyController.isSensorBlocked(MICROPHONE); || mSensorPrivacyController.isSensorBlocked(MICROPHONE);
mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA);
mLocationManager = context.getSystemService(LocationManager.class);
mContext = context; mContext = context;
mClock = clock; mClock = clock;
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
@@ -308,102 +294,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
return createdNew; return createdNew;
} }
/** private boolean isUserVisible(String packageName) {
* Does the app-op code refer to a user sensitive permission for the specified user id return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName);
* and package. Only user sensitive permission should be shown to the user by default.
*
* @param appOpCode The code of the app-op.
* @param uid The uid of the user.
* @param packageName The name of the package.
*
* @return {@code true} iff the app-op item is user sensitive
*/
private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
String permission = AppOpsManager.opToPermission(appOpCode);
if (permission == null) {
return false;
}
int permFlags = mFlagsCache.getPermissionFlags(permission,
packageName, uid);
return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
}
/**
* Does the app-op item refer to an operation that should be shown to the user.
* Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
* permission should be shown to the user by default.
*
* @param item The item
*
* @return {@code true} iff the app-op item should be shown to the user
*/
private boolean isUserVisible(AppOpItem item) {
return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
}
/**
* Checks if a package is the current location provider.
*
* <p>Data is cached to avoid too many calls into system server
*
* @param packageName The package that might be the location provider
*
* @return {@code true} iff the package is the location provider.
*/
private boolean isLocationProvider(String packageName) {
long now = System.currentTimeMillis();
if (mLastLocationProviderPackageUpdate + LOCATION_PROVIDER_UPDATE_FREQUENCY_MS < now) {
mLastLocationProviderPackageUpdate = now;
mLocationProviderPackages = mLocationManager.getProviderPackages(
LocationManager.FUSED_PROVIDER);
}
return mLocationProviderPackages.contains(packageName);
}
private boolean isSpeechRecognizerUsage(int opCode, String packageName) {
if (AppOpsManager.OP_RECORD_AUDIO != opCode) {
return false;
}
return packageName.equals(
mContext.getString(R.string.config_systemSpeechRecognizer));
}
// TODO ntmyren: remove after teamfood is finished
private boolean showSystemApps() {
return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false);
}
/**
* Does the app-op, uid and package name, refer to an operation that should be shown to the
* user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
* ops that refer to user sensitive permission should be shown to the user by default.
*
* @param item The item
*
* @return {@code true} iff the app-op for should be shown to the user
*/
private boolean isUserVisible(int appOpCode, int uid, String packageName) {
// currently OP_SYSTEM_ALERT_WINDOW and OP_MONITOR_HIGH_POWER_LOCATION
// does not correspond to a platform permission
// which may be user sensitive, so for now always show it to the user.
if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW
|| appOpCode == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
|| appOpCode == AppOpsManager.OP_PHONE_CALL_CAMERA
|| appOpCode == AppOpsManager.OP_PHONE_CALL_MICROPHONE) {
return true;
}
// TODO ntmyren: Replace this with more robust check if this moves beyond teamfood
if (((showSystemApps() && !packageName.equals("android"))
|| appOpCode == AppOpsManager.OP_CAMERA && isLocationProvider(packageName))
|| isSpeechRecognizerUsage(appOpCode, packageName)) {
return true;
}
return isUserSensitive(appOpCode, uid, packageName);
} }
/** /**
@@ -438,7 +330,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
AppOpItem item = mActiveItems.get(i); AppOpItem item = mActiveItems.get(i);
if ((userId == UserHandle.USER_ALL if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId) || UserHandle.getUserId(item.getUid()) == userId)
&& isUserVisible(item) && !item.isDisabled()) { && isUserVisible(item.getPackageName()) && !item.isDisabled()) {
list.add(item); list.add(item);
} }
} }
@@ -449,7 +341,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
AppOpItem item = mNotedItems.get(i); AppOpItem item = mNotedItems.get(i);
if ((userId == UserHandle.USER_ALL if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId) || UserHandle.getUserId(item.getUid()) == userId)
&& isUserVisible(item)) { && isUserVisible(item.getPackageName())) {
list.add(item); list.add(item);
} }
} }
@@ -503,7 +395,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
} }
private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) { private void notifySuscribersWorker(int code, int uid, String packageName, boolean active) {
if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) { if (mCallbacksByCode.contains(code) && isUserVisible(packageName)) {
if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName); if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
for (Callback cb: mCallbacksByCode.get(code)) { for (Callback cb: mCallbacksByCode.get(code)) {
cb.onActiveStateChanged(code, uid, packageName, active); cb.onActiveStateChanged(code, uid, packageName, active);

View File

@@ -1,88 +0,0 @@
/*
* 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.systemui.appops
import android.content.pm.PackageManager
import android.os.UserHandle
import androidx.annotation.WorkerThread
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.Assert
import java.util.concurrent.Executor
import javax.inject.Inject
private data class PermissionFlagKey(
val permission: String,
val packageName: String,
val uid: Int
)
/**
* Cache for PackageManager's PermissionFlags.
*
* After a specific `{permission, package, uid}` has been requested, updates to it will be tracked,
* and changes to the uid will trigger new requests (in the background).
*/
@SysUISingleton
class PermissionFlagsCache @Inject constructor(
private val packageManager: PackageManager,
@Background private val executor: Executor
) : PackageManager.OnPermissionsChangedListener {
private val permissionFlagsCache =
mutableMapOf<Int, MutableMap<PermissionFlagKey, Int>>()
private var listening = false
override fun onPermissionsChanged(uid: Int) {
executor.execute {
// Only track those that we've seen before
val keys = permissionFlagsCache.get(uid)
if (keys != null) {
keys.mapValuesTo(keys) {
getFlags(it.key)
}
}
}
}
/**
* Retrieve permission flags from cache or PackageManager. There parameters will be passed
* directly to [PackageManager].
*
* Calls to this method should be done from a background thread (though it will only be
* enforced if the cache is not hit).
*/
@WorkerThread
fun getPermissionFlags(permission: String, packageName: String, uid: Int): Int {
if (!listening) {
listening = true
packageManager.addOnPermissionsChangeListener(this)
}
val key = PermissionFlagKey(permission, packageName, uid)
return permissionFlagsCache.getOrPut(uid, { mutableMapOf() }).get(key) ?: run {
getFlags(key).also {
Assert.isNotMainThread()
permissionFlagsCache.get(uid)?.put(key, it)
}
}
}
private fun getFlags(key: PermissionFlagKey): Int {
return packageManager.getPermissionFlags(key.permission, key.packageName,
UserHandle.getUserHandleForUid(key.uid))
}
}

View File

@@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +50,7 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
@@ -71,6 +73,7 @@ import java.util.List;
public class AppOpsControllerTest extends SysuiTestCase { public class AppOpsControllerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test"; private static final String TEST_PACKAGE_NAME = "test";
private static final String TEST_ATTRIBUTION_NAME = "attribution"; private static final String TEST_ATTRIBUTION_NAME = "attribution";
private static final String SYSTEM_PKG = "android";
private static final int TEST_UID = UserHandle.getUid(0, 0); private static final int TEST_UID = UserHandle.getUid(0, 0);
private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0); private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@@ -84,8 +87,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
@Mock @Mock
private DumpManager mDumpManager; private DumpManager mDumpManager;
@Mock @Mock
private PermissionFlagsCache mFlagsCache;
@Mock
private PackageManager mPackageManager; private PackageManager mPackageManager;
@Mock @Mock
private IndividualSensorPrivacyController mSensorPrivacyController; private IndividualSensorPrivacyController mSensorPrivacyController;
@@ -101,22 +102,19 @@ public class AppOpsControllerTest extends SysuiTestCase {
private AppOpsControllerImpl mController; private AppOpsControllerImpl mController;
private TestableLooper mTestableLooper; private TestableLooper mTestableLooper;
private String mExemptedRolePkgName;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this); mTestableLooper = TestableLooper.get(this);
mExemptedRolePkgName = getContext().getString(R.string.config_systemUiIntelligence);
getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager); getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
// All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
// TEST_UID_NON_USER_SENSITIVE are user sensitive. // TEST_UID_NON_USER_SENSITIVE are user sensitive.
getContext().setMockPackageManager(mPackageManager); getContext().setMockPackageManager(mPackageManager);
when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID))).thenReturn(
PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID_OTHER)))
.thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0);
doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0)) doAnswer((invocation) -> mRecordingCallback = invocation.getArgument(0))
.when(mAudioManager).registerAudioRecordingCallback(any(), any()); .when(mAudioManager).registerAudioRecordingCallback(any(), any());
@@ -135,7 +133,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
mContext, mContext,
mTestableLooper.getLooper(), mTestableLooper.getLooper(),
mDumpManager, mDumpManager,
mFlagsCache,
mAudioManager, mAudioManager,
mSensorPrivacyController, mSensorPrivacyController,
mDispatcher, mDispatcher,
@@ -242,18 +239,26 @@ public class AppOpsControllerTest extends SysuiTestCase {
} }
@Test @Test
public void nonUserSensitiveOpsAreIgnored() { public void systemAndExemptedRolesAreIgnored() {
assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals(""));
mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
assertEquals(0, mController.getActiveAppOpsForUser(
UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true);
assertEquals(0, mController.getActiveAppOpsForUser( assertEquals(0, mController.getActiveAppOpsForUser(
UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
} }
@Test @Test
public void nonUserSensitiveOpsNotNotified() { public void exemptedRoleNotNotified() {
assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals(""));
mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
mTestableLooper.processAllMessages(); mTestableLooper.processAllMessages();

View File

@@ -1,145 +0,0 @@
/*
* 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.systemui.appops
import android.content.pm.PackageManager
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class PermissionFlagsCacheTest : SysuiTestCase() {
companion object {
const val TEST_PERMISSION = "test_permission"
const val TEST_PACKAGE = "test_package"
const val TEST_UID1 = 1000
const val TEST_UID2 = UserHandle.PER_USER_RANGE + 1000
}
@Mock
private lateinit var packageManager: PackageManager
private lateinit var executor: FakeExecutor
private lateinit var flagsCache: PermissionFlagsCache
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(FakeSystemClock())
flagsCache = PermissionFlagsCache(packageManager, executor)
executor.runAllReady()
}
@Test
fun testNotListeningByDefault() {
verify(packageManager, never()).addOnPermissionsChangeListener(any())
}
@Test
fun testGetCorrectFlags() {
`when`(packageManager.getPermissionFlags(anyString(), anyString(), any())).thenReturn(0)
`when`(packageManager.getPermissionFlags(
TEST_PERMISSION,
TEST_PACKAGE,
UserHandle.getUserHandleForUid(TEST_UID1))
).thenReturn(1)
assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
assertNotEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2))
}
@Test
fun testFlagIsCached() {
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
verify(packageManager, times(1)).getPermissionFlags(
TEST_PERMISSION,
TEST_PACKAGE,
UserHandle.getUserHandleForUid(TEST_UID1)
)
}
@Test
fun testListeningAfterFirstRequest() {
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
verify(packageManager).addOnPermissionsChangeListener(any())
}
@Test
fun testListeningOnlyOnce() {
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2)
verify(packageManager, times(1)).addOnPermissionsChangeListener(any())
}
@Test
fun testUpdateFlag() {
assertEquals(0, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
`when`(packageManager.getPermissionFlags(
TEST_PERMISSION,
TEST_PACKAGE,
UserHandle.getUserHandleForUid(TEST_UID1))
).thenReturn(1)
flagsCache.onPermissionsChanged(TEST_UID1)
executor.runAllReady()
assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1))
}
@Test
fun testUpdateFlag_notUpdatedIfUidHasNotBeenRequestedBefore() {
flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)
flagsCache.onPermissionsChanged(TEST_UID2)
executor.runAllReady()
verify(packageManager, never()).getPermissionFlags(
TEST_PERMISSION,
TEST_PACKAGE,
UserHandle.getUserHandleForUid(TEST_UID2)
)
}
}