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

Commit 3a738b18 authored by Fabian Kozynski's avatar Fabian Kozynski
Browse files

Auto-deny requests after some user denials

After the user denies the request to add a tile (a particular
ComponentName) a certain number of times, auto deny further requests for
that user,component pair. Cancelling the dialog (by going back/click
outside) don't count against.

This information is reset when the package is uninstalled or data is
cleared for the package.

In the future, this information will be persisted across restarts.

Fixes: 208671346
Test: manual
Test: atest com.android.server.statusbar

Change-Id: I1828d1d3e462fc9b4af94c4350df895994d02b6c
parent f4c585f9
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -626,6 +626,9 @@ public class StatusBarManager {
     * foreground ({@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND}
     * and the {@link android.service.quicksettings.TileService} must be exported.
     *
     * Note: the system can choose to auto-deny a request if the user has denied that specific
     * request (user, ComponentName) enough times before.
     *
     * @param tileServiceComponentName {@link ComponentName} of the
     *        {@link android.service.quicksettings.TileService} for the request.
     * @param tileLabel label of the tile to show to the user.
+22 −0
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -139,6 +140,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
    private int mCurrentUserId;
    private boolean mTracingEnabled;

    private final TileRequestTracker mTileRequestTracker;

    private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
    @GuardedBy("mLock")
    private IUdfpsHbmListener mUdfpsHbmListener;
@@ -245,6 +248,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
        mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);

        mTileRequestTracker = new TileRequestTracker(mContext);
    }

    @Override
@@ -1765,11 +1770,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
            mCurrentRequestAddTilePackages.put(packageName, currentTime);
        }

        if (mTileRequestTracker.shouldBeDenied(userId, componentName)) {
            if (clearTileAddRequest(packageName)) {
                try {
                    callback.onTileRequest(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
                } catch (RemoteException e) {
                    Slog.e(TAG, "requestAddTile - callback", e);
                }
            }
            return;
        }

        IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() {
            @Override
            public void onTileRequest(int i) {
                if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) {
                    i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED;
                } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED) {
                    mTileRequestTracker.addDenial(userId, componentName);
                } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED) {
                    mTileRequestTracker.resetRequests(userId, componentName);
                }
                if (clearTileAddRequest(packageName)) {
                    try {
@@ -1961,6 +1981,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
                pw.println("    " + requests.get(i) + ",");
            }
            pw.println("  ]");
            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
            mTileRequestTracker.dump(fd, ipw.increaseIndent(), args);
        }
    }

+138 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.server.statusbar;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.SparseArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.io.FileDescriptor;

/**
 * Tracks user denials of requests from {@link StatusBarManagerService#requestAddTile}.
 *
 * After a certain number of denials for a particular pair (user,ComponentName), requests will be
 * auto-denied without showing a dialog to the user.
 */
public class TileRequestTracker {

    @VisibleForTesting
    static final int MAX_NUM_DENIALS = 3;

    private final Context mContext;
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final SparseArrayMap<ComponentName, Integer> mTrackingMap = new SparseArrayMap<>();
    @GuardedBy("mLock")
    private final ArraySet<ComponentName> mComponentsToRemove = new ArraySet<>();

    private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                return;
            }

            Uri data = intent.getData();
            String packageName = data.getEncodedSchemeSpecificPart();

            if (!intent.hasExtra(Intent.EXTRA_UID)) {
                return;
            }
            int userId = UserHandle.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
            synchronized (mLock) {
                mComponentsToRemove.clear();
                final int elementsForUser = mTrackingMap.numElementsForKey(userId);
                final int userKeyIndex = mTrackingMap.indexOfKey(userId);
                for (int compKeyIndex = 0; compKeyIndex < elementsForUser; compKeyIndex++) {
                    ComponentName c = mTrackingMap.keyAt(userKeyIndex, compKeyIndex);
                    if (c.getPackageName().equals(packageName)) {
                        mComponentsToRemove.add(c);
                    }
                }
                final int compsToRemoveNum = mComponentsToRemove.size();
                for (int i = 0; i < compsToRemoveNum; i++) {
                    ComponentName c = mComponentsToRemove.valueAt(i);
                    mTrackingMap.delete(userId, c);
                }
            }
        }
    };

    TileRequestTracker(Context context) {
        mContext = context;

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
        intentFilter.addDataScheme("package");
        mContext.registerReceiverAsUser(mUninstallReceiver, UserHandle.ALL, intentFilter, null,
                null);
    }

    /**
     * Return whether this combination of {@code userId} and {@link ComponentName} should be
     * auto-denied.
     */
    boolean shouldBeDenied(int userId, ComponentName componentName) {
        synchronized (mLock) {
            return mTrackingMap.getOrDefault(userId, componentName, 0) >= MAX_NUM_DENIALS;
        }
    }

    /**
     * Add a new denial instance for a given {@code userId} and {@link ComponentName}.
     */
    void addDenial(int userId, ComponentName componentName) {
        synchronized (mLock) {
            int current = mTrackingMap.getOrDefault(userId, componentName, 0);
            mTrackingMap.add(userId, componentName, current + 1);
        }
    }

    /**
     * Reset the number of denied request for a given {@code userId} and {@link ComponentName}.
     */
    void resetRequests(int userId, ComponentName componentName) {
        synchronized (mLock) {
            mTrackingMap.delete(userId, componentName);
        }
    }

    void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
        pw.println("TileRequestTracker:");
        pw.increaseIndent();
        synchronized (mLock) {
            mTrackingMap.forEach((user, componentName, value) -> {
                pw.println("user=" + user + ", " + componentName.toShortString() + ": " + value);
            });
        }
        pw.decreaseIndent();
    }
}
+3 −2
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.TestableContext;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -34,9 +35,9 @@ import java.util.ArrayList;
 *
 * Instead, it keeps a list of the registrations for querying.
 */
class NoBroadcastContextWrapper extends ContextWrapper {
class NoBroadcastContextWrapper extends TestableContext {

    private ArrayList<BroadcastReceiverRegistration> mRegistrationList =
    ArrayList<BroadcastReceiverRegistration> mRegistrationList =
            new ArrayList<>();

    NoBroadcastContextWrapper(Context context) {
+58 −2
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -82,8 +83,7 @@ public class StatusBarManagerServiceTest {

    @Rule
    public final TestableContext mContext =
            new TestableContext(
                    new NoBroadcastContextWrapper(InstrumentationRegistry.getContext()), null);
            new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());

    @Mock
    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -519,6 +519,62 @@ public class StatusBarManagerServiceTest {
                callback.mUserResponse);
    }

    @Test
    public void testInstaDenialAfterManyDenials() throws RemoteException {
        int user = 10;
        mockEverything(user);

        for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
            mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
                    new Callback());

            verify(mMockStatusBar, times(i + 1)).requestAddTile(
                    eq(TEST_COMPONENT),
                    eq(APP_NAME),
                    eq(TILE_LABEL),
                    eq(mIcon),
                    mAddTileResultCallbackCaptor.capture()
            );
            mAddTileResultCallbackCaptor.getValue().onTileRequest(
                    StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
        }

        Callback callback = new Callback();
        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);

        // Only called MAX_NUM_DENIALS times
        verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
                any(),
                any(),
                any(),
                any(),
                mAddTileResultCallbackCaptor.capture()
        );
        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
                callback.mUserResponse);
    }

    @Test
    public void testDialogDismissalNotCountingAgainstDenials() throws RemoteException {
        int user = 10;
        mockEverything(user);

        for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS * 2; i++) {
            mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
                    new Callback());

            verify(mMockStatusBar, times(i + 1)).requestAddTile(
                    eq(TEST_COMPONENT),
                    eq(APP_NAME),
                    eq(TILE_LABEL),
                    eq(mIcon),
                    mAddTileResultCallbackCaptor.capture()
            );
            mAddTileResultCallbackCaptor.getValue().onTileRequest(
                    StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
        }
    }

    private void mockUidCheck() {
        mockUidCheck(TEST_PACKAGE);
    }
Loading