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

Commit 32a39428 authored by Menghan Li's avatar Menghan Li
Browse files

refactor(A11yHaTS): Extract notif. logic from SurveyNotificaitonReceiver

Bug: 380346799
Test: atest NotificationHelperTest
            SurveyNotificationReceiverTest
Flag: com.android.server.accessibility.enable_low_vision_hats
Change-Id: I85aab3ec8bff95a7be02cb5221619bc065f33f60
parent 61658f5a
Loading
Loading
Loading
Loading
+153 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.accessibility.notification;

import static com.android.internal.accessibility.common.NotificationConstants.EXTRA_SOURCE;
import static com.android.internal.accessibility.common.NotificationConstants.SOURCE_START_SURVEY;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;

import androidx.annotation.NonNull;

import com.android.settings.R;

/**
 * Helper class for creating and managing survey notifications.
 */
public class NotificationHelper {

    public static final String NOTIFICATION_CHANNEL_ID =
            "com.android.settings.accessibility.notification.SurveyNotificationService";

    // The base ID for notifications is derived from the bug component ID.
    // Page-specific notification IDs are generated by adding the respective pageId to this base.
    private static final int NOTIFICATION_ID_BASE = 751131;
    private static final int START_SURVEY_REQUEST_CODE = 1;

    private final Context mContext;
    private final NotificationManager mNotificationManager;

    /**
     * Constructs a new NotificationHelper.
     * <p>
     * Initializes the helper with the application context and gets the system's
     * NotificationManager service. It also ensures that the notification channel
     * for survey notifications is created if it doesn't already exist.
     * </p>
     *
     * @param context The context used to access system services and resources.
     */
    public NotificationHelper(@NonNull Context context) {
        mContext = context.getApplicationContext();
        mNotificationManager = mContext.getSystemService(NotificationManager.class);
        if (mNotificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) {
            final NotificationChannel channel = new NotificationChannel(
                    NOTIFICATION_CHANNEL_ID,
                    mContext.getString(R.string.accessibility_send_survey_notification_channel),
                    NotificationManager.IMPORTANCE_LOW);
            mNotificationManager.createNotificationChannel(channel);
        }
    }

    private PendingIntent createNotifyPendingIntent(Intent notifyIntent) {
        notifyIntent.putExtra(EXTRA_SOURCE, SOURCE_START_SURVEY);
        notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        return PendingIntent.getActivity(
                mContext,
                START_SURVEY_REQUEST_CODE,
                notifyIntent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
    }

    private Notification.Action createAction(PendingIntent pendingIntent, String actionText) {
        return new Notification.Action.Builder(
                /* icon= */ null,
                actionText,
                pendingIntent)
                .build();
    }

    private Notification.Builder createNotificationBuilder(String title, String text,
            PendingIntent notifyPendingIntent) {
        return new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                .setContentTitle(title)
                .setContentText(text)
                .setSmallIcon(R.drawable.ic_settings_24dp)
                .setContentIntent(notifyPendingIntent)
                .setFlag(Notification.FLAG_NO_CLEAR, true)
                .setAutoCancel(true);
    }

    private void showNotification(int notificationId, String title, String text,
            String actionText, Intent notifyIntent) {
        final PendingIntent contentPendingIntent = createNotifyPendingIntent(notifyIntent);
        final Notification.Action action = createAction(contentPendingIntent, actionText);
        final Notification notification =
                createNotificationBuilder(title, text, contentPendingIntent)
                        .addAction(action)
                        .build();
        mNotificationManager.notify(notificationId, notification);
    }

    /**
     * Determines the specific survey notification to show based on the page ID and displays it.
     *
     * @param pageId The ID of the settings page for which to show the survey.
     */
    public void cancelNotification(int pageId) {
        mNotificationManager.cancel(getNotificationId(pageId));
    }

    /**
     * Generates a notification ID based on a base ID and a page ID. This ensures that notifications
     * for different pages will have distinct IDs.
     *
     * @param pageId The unique identifier of the page for which to generate a notification ID.
     * @return A unique integer representing the notification ID for the given page.
     */
    public int getNotificationId(int pageId) {
        return NOTIFICATION_ID_BASE + pageId;
    }

    /**
     * Determines the specific survey notification to show based on the page ID and displays it.
     *
     * @param pageId The ID of the settings page for which to show the survey.
     */
    public void handleSurveyNotification(int pageId) {
        if (pageId == SettingsEnums.DARK_UI_SETTINGS) {
            final Intent settingsIntent = new Intent(Settings.ACTION_DARK_THEME_SETTINGS)
                    .setPackage(mContext.getPackageName());
            showNotification(
                    getNotificationId(pageId),
                    mContext.getString(R.string.dark_theme_survey_notification_title),
                    mContext.getString(R.string.dark_theme_survey_notification_summary),
                    mContext.getString(R.string.dark_theme_survey_notification_action),
                    settingsIntent
            );
        }
    }
}
+6 −103
Original line number Diff line number Diff line
@@ -16,136 +16,39 @@

package com.android.settings.accessibility.notification;

import static com.android.internal.accessibility.common.NotificationConstants.ACTION_SURVEY_NOTIFICATION_SHOWN;
import static com.android.internal.accessibility.common.NotificationConstants.ACTION_SURVEY_NOTIFICATION_DISMISSED;
import static com.android.internal.accessibility.common.NotificationConstants.ACTION_SURVEY_NOTIFICATION_SHOWN;
import static com.android.internal.accessibility.common.NotificationConstants.EXTRA_PAGE_ID;
import static com.android.internal.accessibility.common.NotificationConstants.EXTRA_SOURCE;
import static com.android.internal.accessibility.common.NotificationConstants.SOURCE_START_SURVEY;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.server.accessibility.Flags;
import com.android.settings.R;

/**
 * Service to display survey notifications.
 */
public class SurveyNotificationReceiver extends BroadcastReceiver {

    public static final String NOTIFICATION_CHANNEL_ID =
            "com.android.settings.accessibility.notification.SurveyNotificationService";

    // The base ID for notifications is derived from the bug component ID.
    // Page-specific notification IDs are generated by adding the respective pageId to this base.
    private static final int NOTIFICATION_ID_BASE = 751131;
    private static final int START_SURVEY_REQUEST_CODE = 1;

    @Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        if (!Flags.enableLowVisionHats()) {
            return;
        }

        final NotificationManager notificationManager =
                context.getSystemService(NotificationManager.class);
        if (notificationManager == null) {
        int pageId = intent.getIntExtra(EXTRA_PAGE_ID, SettingsEnums.PAGE_UNKNOWN);
        if (pageId == SettingsEnums.PAGE_UNKNOWN) {
            return;
        }

        int pageId = intent.getIntExtra(EXTRA_PAGE_ID, SettingsEnums.PAGE_UNKNOWN);
        int notificationId = getNotificationId(pageId);
        final NotificationHelper notificationHelper = new NotificationHelper(context);
        if (ACTION_SURVEY_NOTIFICATION_SHOWN.equals(intent.getAction())) {
            // create a notification channel to post persistent notification
            final NotificationChannel channel =
                    new NotificationChannel(
                            NOTIFICATION_CHANNEL_ID,
                            context.getString(
                                    R.string.accessibility_send_survey_notification_channel),
                            NotificationManager.IMPORTANCE_LOW);
            notificationManager.createNotificationChannel(channel);
            final Notification notification = createNotification(context, pageId);
            if (notification != null) {
                notificationManager.notify(notificationId, notification);
            }
            notificationHelper.handleSurveyNotification(pageId);
        } else if (ACTION_SURVEY_NOTIFICATION_DISMISSED.equals(intent.getAction())) {
            notificationManager.cancel(notificationId);
        }
    }

    @Nullable
    private Notification createNotification(Context context, int pageId) {
        if (pageId == SettingsEnums.DARK_UI_SETTINGS) {
            return buildNotification(
                    context,
                    context.getString(R.string.dark_theme_survey_notification_title),
                    context.getString(R.string.dark_theme_survey_notification_summary),
                    context.getString(R.string.dark_theme_survey_notification_action),
                    new Intent(Settings.ACTION_DARK_THEME_SETTINGS).setPackage(
                            context.getPackageName()));
        }
        return null;
            notificationHelper.cancelNotification(pageId);
        }

    private Notification buildNotification(Context context, String title, String text,
            String actionText, Intent notifyIntent) {
        final PendingIntent notifyPendingIntent = createNotifyPendingIntent(context, notifyIntent);
        final Notification.Action action = createAction(notifyPendingIntent, actionText);
        return createNotificationBuilder(context, title, text,
                notifyPendingIntent, action).build();
    }

    private PendingIntent createNotifyPendingIntent(Context context, Intent notifyIntent) {
        notifyIntent.putExtra(EXTRA_SOURCE, SOURCE_START_SURVEY);
        notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        return PendingIntent.getActivity(
                context,
                START_SURVEY_REQUEST_CODE,
                notifyIntent,
                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
    }

    private Notification.Action createAction(PendingIntent rateUsPendingIntent,
            String actionText) {
        return new Notification.Action.Builder(
                /* icon= */ null,
                actionText,
                rateUsPendingIntent)
                .build();
    }

    private Notification.Builder createNotificationBuilder(Context context, String title,
            String text, PendingIntent notifyPendingIntent, Notification.Action action) {
        return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                .setContentTitle(title)
                .setContentText(text)
                .setSmallIcon(R.drawable.ic_settings_24dp)
                .setContentIntent(notifyPendingIntent)
                .addAction(action)
                .setFlag(Notification.FLAG_NO_CLEAR, true)
                .setAutoCancel(true);
    }

    /**
     * Generates a notification ID based on a base ID and a page ID. This ensures that notifications
     * for different pages will have distinct IDs.
     *
     * @param pageId The unique identifier of the page for which to generate a notification ID.
     * @return A unique integer representing the notification ID for the given page.
     */
    public static int getNotificationId(int pageId) {
        return NOTIFICATION_ID_BASE + pageId;
    }
}
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.accessibility.notification;

import static com.android.internal.accessibility.common.NotificationConstants.EXTRA_SOURCE;
import static com.android.internal.accessibility.common.NotificationConstants.SOURCE_START_SURVEY;

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

import static org.mockito.Mockito.spy;
import static org.robolectric.Shadows.shadowOf;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;

import com.android.settings.R;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowNotification;
import org.robolectric.shadows.ShadowNotificationManager;
import org.robolectric.shadows.ShadowPendingIntent;

/** Tests for {@link NotificationHelper}. */
@RunWith(RobolectricTestRunner.class)
public class NotificationHelperTest {

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    private final Context mContext = spy(RuntimeEnvironment.getApplication());
    private NotificationHelper mNotificationHelper;
    private ShadowNotificationManager mShadowNotificationManager;

    @Before
    public void setUp() {
        mNotificationHelper = new NotificationHelper(mContext);
        NotificationManager notificationManager =
                mContext.getSystemService(NotificationManager.class);
        mShadowNotificationManager = shadowOf(notificationManager);
    }

    @Test
    public void constructor_createNotificationChannel() {
        assertThat(mShadowNotificationManager.getNotificationChannels()).isNotEmpty();
        assertThat(mShadowNotificationManager.getNotificationChannels().getFirst().getId())
                .isEqualTo(NotificationHelper.NOTIFICATION_CHANNEL_ID);
    }

    @Test
    public void handleSurveyNotification_withDarkUiSettingsPageId_createAndPostsNotification() {
        mNotificationHelper.handleSurveyNotification(SettingsEnums.DARK_UI_SETTINGS);

        int notificationId = mNotificationHelper.getNotificationId(SettingsEnums.DARK_UI_SETTINGS);
        Notification notification = mShadowNotificationManager.getNotification(notificationId);

        assertThat(notification).isNotNull();
        ShadowNotification shadowNotification = shadowOf(notification);
        assertThat(shadowNotification.getContentTitle().toString()).isEqualTo(
                mContext.getString(R.string.dark_theme_survey_notification_title));
        assertThat(shadowNotification.getContentText().toString()).isEqualTo(
                mContext.getString(R.string.dark_theme_survey_notification_summary));
        assertThat(notification.actions.length).isEqualTo(1);
        assertThat(notification.actions[0].title.toString()).isEqualTo(
                mContext.getString(R.string.dark_theme_survey_notification_action));

        // Verify content intent
        PendingIntent contentPendingIntent = notification.contentIntent;
        ShadowPendingIntent shadowContentPendingIntent = shadowOf(contentPendingIntent);
        Intent contentIntent = shadowContentPendingIntent.getSavedIntent();
        assertThat(contentIntent.getAction()).isEqualTo(Settings.ACTION_DARK_THEME_SETTINGS);
        assertThat(contentIntent.getPackage()).isEqualTo(mContext.getPackageName());
        assertThat(contentIntent.getStringExtra(EXTRA_SOURCE)).isEqualTo(SOURCE_START_SURVEY);
        assertThat(contentIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        // Verify action intent
        PendingIntent actionPendingIntent = notification.actions[0].actionIntent;
        ShadowPendingIntent shadowActionPendingIntent = shadowOf(actionPendingIntent);
        Intent actionIntent = shadowActionPendingIntent.getSavedIntent();
        assertThat(actionIntent.getAction()).isEqualTo(Settings.ACTION_DARK_THEME_SETTINGS);
        assertThat(actionIntent.getPackage()).isEqualTo(mContext.getPackageName());
        assertThat(actionIntent.getStringExtra(EXTRA_SOURCE)).isEqualTo(SOURCE_START_SURVEY);
        assertThat(actionIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_CLEAR_TOP
                | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    }

    @Test
    public void cancelNotification_withDarkUiSettingsPageId_cancelExpectedNotification() {
        mNotificationHelper.handleSurveyNotification(SettingsEnums.DARK_UI_SETTINGS);
        int notificationId = mNotificationHelper.getNotificationId(SettingsEnums.DARK_UI_SETTINGS);

        mNotificationHelper.cancelNotification(SettingsEnums.DARK_UI_SETTINGS);

        assertThat(mShadowNotificationManager.getNotification(notificationId)).isNull();
    }

    @Test
    public void getNotificationId_returnCorrectId() {
        int pageId = 123;
        // NOTIFICATION_ID_BASE from NotificationHelper is 751131
        int expectedNotificationId = 751131 + pageId;

        assertThat(mNotificationHelper.getNotificationId(pageId)).isEqualTo(expectedNotificationId);
    }
}
+17 −73

File changed.

Preview size limit exceeded, changes collapsed.