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

Commit 027f0a46 authored by Matías Hernández's avatar Matías Hernández
Browse files

Settings: don't try to allow NLSes with too-long component names

* NotificationAccessConfirmationActivity (triggered through CompanionDeviceManager) -> Don't show the dialog, bail out early similarly to other invalid inputs.
* NotificationAccessSettings (from Special App Access) -> No changes, but use the canonical constant now.
* ApprovalPreferenceController (used in NotificationAccessDetails) -> Disable the toggle, unless the NLS was previously approved (in which case it can still be removed).

Fixes: 260570119
Fixes: 286043036
Test: atest + manually
Change-Id: Ifc048311746c027e3683cdcf65f1079d04cf7c56
parent 08366227
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -81,6 +81,8 @@ public class ApprovalPreferenceController extends BasePreferenceController {
        final RestrictedSwitchPreference preference =
                (RestrictedSwitchPreference) pref;
        final CharSequence label = mPkgInfo.applicationInfo.loadLabel(mPm);
        final boolean isAllowedCn = mCn.flattenToShortString().length()
                <= NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH;
        final boolean isEnabled = isServiceEnabled(mCn);
        preference.setChecked(isEnabled);
        preference.setOnPreferenceChangeListener((p, newValue) -> {
@@ -105,7 +107,8 @@ public class ApprovalPreferenceController extends BasePreferenceController {
                return false;
            }
        });
        preference.updateState(mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isEnabled);
        preference.updateState(
                mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
    }

    public void disable(final ComponentName cn) {
+3 −1
Original line number Diff line number Diff line
@@ -67,7 +67,9 @@ public class NotificationAccessConfirmationActivity extends Activity
        mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL);
        CharSequence mAppLabel;

        if (mComponentName == null || mComponentName.getPackageName() == null) {
        if (mComponentName == null || mComponentName.getPackageName() == null
                || mComponentName.flattenToString().length()
                > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
            finish();
            return;
        }
+2 −2
Original line number Diff line number Diff line
@@ -66,7 +66,6 @@ public class NotificationAccessSettings extends EmptyTextSettings {
    private static final String TAG = "NotifAccessSettings";
    static final String ALLOWED_KEY = "allowed";
    static final String NOT_ALLOWED_KEY = "not_allowed";
    private static final int MAX_CN_LENGTH = 500;

    private static final ManagedServiceSettings.Config CONFIG =
            new ManagedServiceSettings.Config.Builder()
@@ -150,7 +149,8 @@ public class NotificationAccessSettings extends EmptyTextSettings {
        for (ServiceInfo service : services) {
            final ComponentName cn = new ComponentName(service.packageName, service.name);
            boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn);
            if (!isAllowed && cn.flattenToString().length() > MAX_CN_LENGTH) {
            if (!isAllowed && cn.flattenToString().length()
                    > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) {
                continue;
            }

+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.settings.notification;

import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME;

import static com.google.common.truth.Truth.assertThat;

import static org.robolectric.Shadows.shadowOf;

import android.annotation.Nullable;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.widget.TextView;

import com.android.settings.R;

import com.google.common.base.Strings;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

@RunWith(RobolectricTestRunner.class)
public class NotificationAccessConfirmationActivityTest {

    @Test
    public void start_showsDialog() {
        ComponentName cn = new ComponentName("com.example", "com.example.SomeService");
        installPackage(cn.getPackageName(), "X");

        NotificationAccessConfirmationActivity activity = startActivityWithIntent(cn);

        assertThat(activity.isFinishing()).isFalse();
        assertThat(getDialogText(activity)).isEqualTo(
                activity.getString(R.string.notification_listener_security_warning_summary, "X"));
    }

    @Test
    public void start_withMissingPackage_finishes() {
        ComponentName cn = new ComponentName("com.example", "com.example.SomeService");

        NotificationAccessConfirmationActivity activity = startActivityWithIntent(cn);

        assertThat(getDialogText(activity)).isNull();
        assertThat(activity.isFinishing()).isTrue();
    }

    @Test
    public void start_componentNameTooLong_finishes() {
        ComponentName longCn = new ComponentName("com.example", Strings.repeat("Blah", 150));
        installPackage(longCn.getPackageName(), "<Unused>");

        NotificationAccessConfirmationActivity activity = startActivityWithIntent(longCn);

        assertThat(getDialogText(activity)).isNull();
        assertThat(activity.isFinishing()).isTrue();
    }

    private static NotificationAccessConfirmationActivity startActivityWithIntent(
            ComponentName cn) {
        return Robolectric.buildActivity(
                        NotificationAccessConfirmationActivity.class,
                        new Intent().putExtra(EXTRA_COMPONENT_NAME, cn))
                .setup()
                .get();
    }

    private static void installPackage(String packageName, String appName) {
        PackageInfo pi = new PackageInfo();
        pi.packageName = packageName;
        pi.applicationInfo = new ApplicationInfo();
        pi.applicationInfo.packageName = packageName;
        pi.applicationInfo.name = appName;
        shadowOf(RuntimeEnvironment.application.getPackageManager()).installPackage(pi);
    }

    @Nullable
    private static String getDialogText(Activity activity) {
        TextView tv = activity.getWindow().findViewById(android.R.id.message);
        CharSequence text = (tv != null ? tv.getText() : null);
        return text != null ? text.toString() : null;
    }

}
+30 −0
Original line number Diff line number Diff line
@@ -83,6 +83,36 @@ public class ApprovalPreferenceControllerTest {

    }

    @Test
    public void updateState_enabled() {
        when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn(
                AppOpsManager.MODE_ALLOWED);
        when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true);
        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(
                mContext);
        pref.setAppOps(mAppOpsManager);

        mController.updateState(pref);

        assertThat(pref.isEnabled()).isTrue();
    }

    @Test
    public void updateState_invalidCn_disabled() {
        ComponentName longCn = new ComponentName("com.example.package",
                com.google.common.base.Strings.repeat("Blah", 150));
        mController.setCn(longCn);
        when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn(
                AppOpsManager.MODE_ALLOWED);
        RestrictedSwitchPreference pref = new RestrictedSwitchPreference(
                mContext);
        pref.setAppOps(mAppOpsManager);

        mController.updateState(pref);

        assertThat(pref.isEnabled()).isFalse();
    }

    @Test
    public void updateState_checked() {
        when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn(