Loading core/java/android/permission/PermissionManager.java +35 −12 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.app.IActivityManager; import android.app.PropertyInvalidatedCache; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; Loading @@ -42,9 +43,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.location.LocationManager; import android.media.AudioManager; import android.content.AttributionSource; import android.os.Build; import android.os.Handler; import android.os.Looper; Loading @@ -52,8 +51,8 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; Loading Loading @@ -103,6 +102,20 @@ public final class PermissionManager { @EnabledAfter(targetSdkVersion = S) 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 * {@link #shouldTraceGrant}. Loading Loading @@ -873,21 +886,31 @@ public final class PermissionManager { } /** * Check if this package/op combination is exempted from indicators * @return * Determine if a package should be shown in indicators. Only a select few roles, and the * system app itself, are hidden. These values are updated at most every 15 seconds. * @hide */ public static boolean isSpecialCaseShownIndicator(@NonNull Context context, public static boolean shouldShowPackageForIndicatorCached(@NonNull Context context, @NonNull String packageName) { if (packageName.equals(SYSTEM_PKG)) { if (SYSTEM_PKG.equals(packageName)) { 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", false) || packageName.equals(context.getString(R.string.config_systemSpeechRecognizer)) || context.getSystemService(LocationManager.class).isProviderPackage(packageName); return true; } /** * Gets the list of packages that have permissions that specified Loading core/java/android/permission/PermissionUsageHelper.java +5 −22 Original line number Diff line number Diff line Loading @@ -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_RECORD_AUDIO; 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.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; Loading Loading @@ -284,10 +282,6 @@ public class PermissionUsageHelper { continue; } if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) { continue; } boolean isRunning = attrOpEntry.isRunning() || lastAccessTime >= runningThreshold; Loading Loading @@ -375,7 +369,7 @@ public class PermissionUsageHelper { // for it's uid and package name, save it. int usageId = usage.getPackageIdHash(); OpUsage lastMostRecent = mostRecentUsages.get(usageId); if (!usage.packageName.equals(SYSTEM_PKG) && (lastMostRecent == null if (shouldShowPackage(usage.packageName) && (lastMostRecent == null || usage.lastAccessTime > lastMostRecent.lastAccessTime)) { mostRecentUsages.put(usageId, usage); } Loading @@ -401,8 +395,7 @@ public class PermissionUsageHelper { // 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. OpUsage proxy = currentUsage.proxy; if (PermissionManager.isSpecialCaseShownIndicator(mContext, proxy.packageName) || isUserSensitive(proxy.packageName, proxy.getUser(), proxy.op)) { if (shouldShowPackage(proxy.packageName)) { currentUsage = proxy; // We've effectively added one usage, so increment the max number of usages maxUsages++; Loading @@ -411,7 +404,6 @@ public class PermissionUsageHelper { } } if (currentUsage == null || iterNum == maxUsages || currentUsage.getPackageIdHash() == start.getPackageIdHash()) { // We have an invalid state, or a cycle, so break Loading @@ -421,7 +413,7 @@ public class PermissionUsageHelper { proxyPackages.add(currentUsage.getPackageIdHash()); // Don't add an app label for the main app, or the system app if (!currentUsage.packageName.equals(start.packageName) && !currentUsage.packageName.equals(SYSTEM_PKG)) { && shouldShowPackage(currentUsage.packageName)) { try { PackageManager userPkgManager = getUserContext(currentUsage.getUser()).getPackageManager(); Loading Loading @@ -451,17 +443,8 @@ public class PermissionUsageHelper { return usagesAndLabels; } private boolean isUserSensitive(String packageName, UserHandle user, String op) { if (op.equals(OPSTR_PHONE_CALL_CAMERA) || op.equals(OPSTR_PHONE_CALL_MICROPHONE)) { 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; private boolean shouldShowPackage(String packageName) { return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } /** Loading packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +6 −114 Original line number Diff line number Diff line Loading @@ -25,24 +25,20 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.AudioRecordingConfiguration; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.provider.DeviceConfig; import android.permission.PermissionManager; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.annotation.WorkerThread; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; Loading Loading @@ -83,19 +79,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final Context mContext; private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; private final IndividualSensorPrivacyController mSensorPrivacyController; 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 final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>(); private final PermissionFlagsCache mFlagsCache; private boolean mListening; private boolean mMicMuted; private boolean mCameraDisabled; Loading Loading @@ -124,7 +113,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon Context context, @Background Looper bgLooper, DumpManager dumpManager, PermissionFlagsCache cache, AudioManager audioManager, IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher, Loading @@ -132,7 +120,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon ) { mDispatcher = dispatcher; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mFlagsCache = cache; mBGHandler = new H(bgLooper); final int numOps = OPS.length; for (int i = 0; i < numOps; i++) { Loading @@ -143,7 +130,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mMicMuted = audioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mContext = context; mClock = clock; dumpManager.registerDumpable(TAG, this); Loading Loading @@ -308,102 +294,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return createdNew; } /** * Does the app-op code refer to a user sensitive permission for the specified user id * 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); private boolean isUserVisible(String packageName) { return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } /** Loading Loading @@ -438,7 +330,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item) && !item.isDisabled()) { && isUserVisible(item.getPackageName()) && !item.isDisabled()) { list.add(item); } } Loading @@ -449,7 +341,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mNotedItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item)) { && isUserVisible(item.getPackageName())) { list.add(item); } } Loading Loading @@ -503,7 +395,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } 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); for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); Loading packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.ktdeleted 100644 → 0 +0 −88 Original line number Diff line number Diff line /* * 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)) } } No newline at end of file packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +18 −13 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -49,6 +50,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; Loading @@ -71,6 +73,7 @@ import java.util.List; public class AppOpsControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; 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_OTHER = UserHandle.getUid(1, 0); private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); Loading @@ -84,8 +87,6 @@ public class AppOpsControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock private PermissionFlagsCache mFlagsCache; @Mock private PackageManager mPackageManager; @Mock private IndividualSensorPrivacyController mSensorPrivacyController; Loading @@ -101,22 +102,19 @@ public class AppOpsControllerTest extends SysuiTestCase { private AppOpsControllerImpl mController; private TestableLooper mTestableLooper; private String mExemptedRolePkgName; @Before public void setUp() { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); mExemptedRolePkgName = getContext().getString(R.string.config_systemUiIntelligence); getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager); // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of // TEST_UID_NON_USER_SENSITIVE are user sensitive. 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)) .when(mAudioManager).registerAudioRecordingCallback(any(), any()); Loading @@ -135,7 +133,6 @@ public class AppOpsControllerTest extends SysuiTestCase { mContext, mTestableLooper.getLooper(), mDumpManager, mFlagsCache, mAudioManager, mSensorPrivacyController, mDispatcher, Loading Loading @@ -242,18 +239,26 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test public void nonUserSensitiveOpsAreIgnored() { public void systemAndExemptedRolesAreIgnored() { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); 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( UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); } @Test public void nonUserSensitiveOpsNotNotified() { public void exemptedRoleNotNotified() { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); mTestableLooper.processAllMessages(); Loading Loading
core/java/android/permission/PermissionManager.java +35 −12 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.app.IActivityManager; import android.app.PropertyInvalidatedCache; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; import android.content.AttributionSource; import android.content.Context; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; Loading @@ -42,9 +43,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.location.LocationManager; import android.media.AudioManager; import android.content.AttributionSource; import android.os.Build; import android.os.Handler; import android.os.Looper; Loading @@ -52,8 +51,8 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; Loading Loading @@ -103,6 +102,20 @@ public final class PermissionManager { @EnabledAfter(targetSdkVersion = S) 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 * {@link #shouldTraceGrant}. Loading Loading @@ -873,21 +886,31 @@ public final class PermissionManager { } /** * Check if this package/op combination is exempted from indicators * @return * Determine if a package should be shown in indicators. Only a select few roles, and the * system app itself, are hidden. These values are updated at most every 15 seconds. * @hide */ public static boolean isSpecialCaseShownIndicator(@NonNull Context context, public static boolean shouldShowPackageForIndicatorCached(@NonNull Context context, @NonNull String packageName) { if (packageName.equals(SYSTEM_PKG)) { if (SYSTEM_PKG.equals(packageName)) { 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", false) || packageName.equals(context.getString(R.string.config_systemSpeechRecognizer)) || context.getSystemService(LocationManager.class).isProviderPackage(packageName); return true; } /** * Gets the list of packages that have permissions that specified Loading
core/java/android/permission/PermissionUsageHelper.java +5 −22 Original line number Diff line number Diff line Loading @@ -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_RECORD_AUDIO; 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.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; Loading Loading @@ -284,10 +282,6 @@ public class PermissionUsageHelper { continue; } if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) { continue; } boolean isRunning = attrOpEntry.isRunning() || lastAccessTime >= runningThreshold; Loading Loading @@ -375,7 +369,7 @@ public class PermissionUsageHelper { // for it's uid and package name, save it. int usageId = usage.getPackageIdHash(); OpUsage lastMostRecent = mostRecentUsages.get(usageId); if (!usage.packageName.equals(SYSTEM_PKG) && (lastMostRecent == null if (shouldShowPackage(usage.packageName) && (lastMostRecent == null || usage.lastAccessTime > lastMostRecent.lastAccessTime)) { mostRecentUsages.put(usageId, usage); } Loading @@ -401,8 +395,7 @@ public class PermissionUsageHelper { // 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. OpUsage proxy = currentUsage.proxy; if (PermissionManager.isSpecialCaseShownIndicator(mContext, proxy.packageName) || isUserSensitive(proxy.packageName, proxy.getUser(), proxy.op)) { if (shouldShowPackage(proxy.packageName)) { currentUsage = proxy; // We've effectively added one usage, so increment the max number of usages maxUsages++; Loading @@ -411,7 +404,6 @@ public class PermissionUsageHelper { } } if (currentUsage == null || iterNum == maxUsages || currentUsage.getPackageIdHash() == start.getPackageIdHash()) { // We have an invalid state, or a cycle, so break Loading @@ -421,7 +413,7 @@ public class PermissionUsageHelper { proxyPackages.add(currentUsage.getPackageIdHash()); // Don't add an app label for the main app, or the system app if (!currentUsage.packageName.equals(start.packageName) && !currentUsage.packageName.equals(SYSTEM_PKG)) { && shouldShowPackage(currentUsage.packageName)) { try { PackageManager userPkgManager = getUserContext(currentUsage.getUser()).getPackageManager(); Loading Loading @@ -451,17 +443,8 @@ public class PermissionUsageHelper { return usagesAndLabels; } private boolean isUserSensitive(String packageName, UserHandle user, String op) { if (op.equals(OPSTR_PHONE_CALL_CAMERA) || op.equals(OPSTR_PHONE_CALL_MICROPHONE)) { 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; private boolean shouldShowPackage(String packageName) { return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } /** Loading
packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +6 −114 Original line number Diff line number Diff line Loading @@ -25,24 +25,20 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.location.LocationManager; import android.media.AudioManager; import android.media.AudioRecordingConfiguration; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.provider.DeviceConfig; import android.permission.PermissionManager; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; import androidx.annotation.WorkerThread; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.Dumpable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; Loading Loading @@ -83,19 +79,12 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon private final Context mContext; private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; private final IndividualSensorPrivacyController mSensorPrivacyController; 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 final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>(); private final PermissionFlagsCache mFlagsCache; private boolean mListening; private boolean mMicMuted; private boolean mCameraDisabled; Loading Loading @@ -124,7 +113,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon Context context, @Background Looper bgLooper, DumpManager dumpManager, PermissionFlagsCache cache, AudioManager audioManager, IndividualSensorPrivacyController sensorPrivacyController, BroadcastDispatcher dispatcher, Loading @@ -132,7 +120,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon ) { mDispatcher = dispatcher; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mFlagsCache = cache; mBGHandler = new H(bgLooper); final int numOps = OPS.length; for (int i = 0; i < numOps; i++) { Loading @@ -143,7 +130,6 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon mMicMuted = audioManager.isMicrophoneMute() || mSensorPrivacyController.isSensorBlocked(MICROPHONE); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mLocationManager = context.getSystemService(LocationManager.class); mContext = context; mClock = clock; dumpManager.registerDumpable(TAG, this); Loading Loading @@ -308,102 +294,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return createdNew; } /** * Does the app-op code refer to a user sensitive permission for the specified user id * 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); private boolean isUserVisible(String packageName) { return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } /** Loading Loading @@ -438,7 +330,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item) && !item.isDisabled()) { && isUserVisible(item.getPackageName()) && !item.isDisabled()) { list.add(item); } } Loading @@ -449,7 +341,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mNotedItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) && isUserVisible(item)) { && isUserVisible(item.getPackageName())) { list.add(item); } } Loading Loading @@ -503,7 +395,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon } 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); for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); Loading
packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.ktdeleted 100644 → 0 +0 −88 Original line number Diff line number Diff line /* * 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)) } } No newline at end of file
packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +18 −13 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -49,6 +50,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; Loading @@ -71,6 +73,7 @@ import java.util.List; public class AppOpsControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; 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_OTHER = UserHandle.getUid(1, 0); private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); Loading @@ -84,8 +87,6 @@ public class AppOpsControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock private PermissionFlagsCache mFlagsCache; @Mock private PackageManager mPackageManager; @Mock private IndividualSensorPrivacyController mSensorPrivacyController; Loading @@ -101,22 +102,19 @@ public class AppOpsControllerTest extends SysuiTestCase { private AppOpsControllerImpl mController; private TestableLooper mTestableLooper; private String mExemptedRolePkgName; @Before public void setUp() { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); mExemptedRolePkgName = getContext().getString(R.string.config_systemUiIntelligence); getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager); // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of // TEST_UID_NON_USER_SENSITIVE are user sensitive. 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)) .when(mAudioManager).registerAudioRecordingCallback(any(), any()); Loading @@ -135,7 +133,6 @@ public class AppOpsControllerTest extends SysuiTestCase { mContext, mTestableLooper.getLooper(), mDumpManager, mFlagsCache, mAudioManager, mSensorPrivacyController, mDispatcher, Loading Loading @@ -242,18 +239,26 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test public void nonUserSensitiveOpsAreIgnored() { public void systemAndExemptedRolesAreIgnored() { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); 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( UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); } @Test public void nonUserSensitiveOpsNotNotified() { public void exemptedRoleNotNotified() { assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals("")); mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); mTestableLooper.processAllMessages(); Loading