Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1cbf9f39 authored by Nate Myren's avatar Nate Myren Committed by Android (Google) Code Review
Browse files

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

parents 293c2796 2d400b87
Loading
Loading
Loading
Loading
+35 −12
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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}.
@@ -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
+5 −22
Original line number Diff line number Diff line
@@ -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;

@@ -284,10 +282,6 @@ public class PermissionUsageHelper {
                        continue;
                    }

                    if (!shouldShowPermissionsHub() && !isUserSensitive(packageName, user, op)) {
                        continue;
                    }

                    boolean isRunning = attrOpEntry.isRunning()
                            || lastAccessTime >= runningThreshold;

@@ -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);
            }
@@ -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++;
@@ -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
@@ -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();
@@ -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);
    }

    /**
+6 −114
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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,
@@ -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++) {
@@ -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);
@@ -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);
    }

    /**
@@ -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);
                }
            }
@@ -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);
                }
            }
@@ -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);
+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
+18 −13
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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);
@@ -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;
@@ -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());
@@ -135,7 +133,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
                mContext,
                mTestableLooper.getLooper(),
                mDumpManager,
                mFlagsCache,
                mAudioManager,
                mSensorPrivacyController,
                mDispatcher,
@@ -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