feat(HCT): Perform custom migration logic for existing HCT users
This logic is triggered by two scenarios: (A) During first bootup after OTA update to Android 16, if the user had HCT enabled. - Trigger: ACTION_PRE_BOOT_COMPLETED. - Migration: HCT is disabled and notification is shown. (B) Restore backup from Android 15 (or earlier), if the backup had HCT enabled and new device does not. - Trigger: SettingsProvider's restore process. - Migration: HCT is not restored and notification is shown. We store whether the user has seen this notification in a new secure setting ACCESSIBILITY_HCT_SHOW_PROMPT. This setting is also backed up. Bug: 369906140 Flag: com.android.graphics.hwui.flags.high_contrast_text_small_text_rect Test: atest SettingsRoboTests:com.android.settings.accessibility.HighContrastTextMigrationReceiverTest Test: flash an incremental update on a build with HCT enabled; observe HCT is disabled and a notification is sent. Test: flash an incremental update on a build with HCT disabled; observe no change to HCT and no notification. Change-Id: I4d294ffc0b2eabc59ee7988a579d678975a16380
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
import static com.android.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
|
||||
import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS;
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
|
||||
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.ACTION_RESTORED;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_CHANNEL;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.NOTIFICATION_ID;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_SHOWN;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.PROMPT_UNNECESSARY;
|
||||
import static com.android.settings.accessibility.HighContrastTextMigrationReceiver.PromptState.UNKNOWN;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Bundle;
|
||||
import android.platform.test.annotations.DisableFlags;
|
||||
import android.platform.test.annotations.EnableFlags;
|
||||
import android.platform.test.flag.junit.SetFlagsRule;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.graphics.hwui.flags.Flags;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.Shadows;
|
||||
import org.robolectric.shadows.ShadowNotification;
|
||||
import org.robolectric.shadows.ShadowNotificationManager;
|
||||
import org.robolectric.shadows.ShadowPackageManager;
|
||||
|
||||
/** Tests for {@link HighContrastTextMigrationReceiver}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HighContrastTextMigrationReceiverTest {
|
||||
|
||||
@Rule
|
||||
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private HighContrastTextMigrationReceiver mReceiver;
|
||||
private ShadowNotificationManager mShadowNotificationManager;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
NotificationManager notificationManager =
|
||||
mContext.getSystemService(NotificationManager.class);
|
||||
mShadowNotificationManager = Shadows.shadowOf(notificationManager);
|
||||
|
||||
// Setup Settings app as a system app
|
||||
ShadowPackageManager shadowPm = Shadows.shadowOf(mContext.getPackageManager());
|
||||
ComponentName textReadingComponent = new ComponentName(Utils.SETTINGS_PACKAGE_NAME,
|
||||
com.android.settings.Settings.TextReadingSettingsActivity.class.getName());
|
||||
ActivityInfo activityInfo = shadowPm.addActivityIfNotPresent(textReadingComponent);
|
||||
activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
shadowPm.addOrUpdateActivity(activityInfo);
|
||||
|
||||
mReceiver = new HighContrastTextMigrationReceiver();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onReceive_flagOff_settingsNotSet() {
|
||||
mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onRestored_hctStateOn_showPromptHctKeepsOn() {
|
||||
setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON);
|
||||
|
||||
mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, ON);
|
||||
verifyNotificationSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onRestored_hctStateOff_showPromptHctKeepsOff() {
|
||||
setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
|
||||
|
||||
mReceiver.onReceive(mContext, new Intent(ACTION_RESTORED));
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, OFF);
|
||||
verifyNotificationSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateUnknownHctOn_showPromptAndAutoDisableHct() {
|
||||
setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ ON);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
|
||||
verifyNotificationSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateUnknownAndHctOff_promptIsUnnecessaryHctKeepsOff() {
|
||||
setPromptStateAndHctState(/* promptState= */ UNKNOWN, /* hctState= */ OFF);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
|
||||
verifyNotificationNotSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateShownAndHctOn_promptStateUnchangedHctKeepsOn() {
|
||||
setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ ON);
|
||||
verifyNotificationNotSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateShownAndHctOff_promptStateUnchangedHctKeepsOff() {
|
||||
setPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_SHOWN, /* hctState= */ OFF);
|
||||
verifyNotificationNotSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateUnnecessaryAndHctOn_promptStateUnchangedHctKeepsOn() {
|
||||
setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ ON);
|
||||
verifyNotificationNotSent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnableFlags(Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
|
||||
public void onPreBootCompleted_promptStateUnnecessaryHctOff_promptStateUnchangedHctKeepsOff() {
|
||||
setPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
|
||||
mReceiver.onReceive(mContext, intent);
|
||||
|
||||
assertPromptStateAndHctState(/* promptState= */ PROMPT_UNNECESSARY, /* hctState= */ OFF);
|
||||
verifyNotificationNotSent();
|
||||
}
|
||||
|
||||
private void verifyNotificationNotSent() {
|
||||
Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID);
|
||||
assertThat(notification).isNull();
|
||||
}
|
||||
|
||||
private void verifyNotificationSent() {
|
||||
// Verify hct channel created
|
||||
assertThat(mShadowNotificationManager.getNotificationChannels().stream().anyMatch(
|
||||
channel -> channel.getId().equals(NOTIFICATION_CHANNEL))).isTrue();
|
||||
|
||||
// Verify hct notification is sent with correct content
|
||||
Notification notification = mShadowNotificationManager.getNotification(NOTIFICATION_ID);
|
||||
assertThat(notification).isNotNull();
|
||||
|
||||
ShadowNotification shadowNotification = Shadows.shadowOf(notification);
|
||||
assertThat(shadowNotification.getContentTitle()).isEqualTo(mContext.getString(
|
||||
R.string.accessibility_toggle_high_text_contrast_preference_title));
|
||||
assertThat(shadowNotification.getContentText()).isEqualTo(
|
||||
mContext.getString(R.string.accessibility_notification_high_contrast_text_content));
|
||||
|
||||
assertThat(notification.actions.length).isEqualTo(1);
|
||||
assertThat(notification.actions[0].title.toString()).isEqualTo(
|
||||
mContext.getString(R.string.accessibility_notification_high_contrast_text_action));
|
||||
|
||||
PendingIntent pendingIntent = notification.actions[0].actionIntent;
|
||||
Intent settingsIntent = Shadows.shadowOf(pendingIntent).getSavedIntent();
|
||||
Bundle fragmentArgs = settingsIntent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
|
||||
assertThat(fragmentArgs).isNotNull();
|
||||
assertThat(fragmentArgs.getString(EXTRA_FRAGMENT_ARG_KEY))
|
||||
.isEqualTo(TextReadingPreferenceFragment.HIGH_TEXT_CONTRAST_KEY);
|
||||
}
|
||||
|
||||
private void assertPromptStateAndHctState(
|
||||
@HighContrastTextMigrationReceiver.PromptState int promptState,
|
||||
@AccessibilityUtil.State int hctState) {
|
||||
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, UNKNOWN))
|
||||
.isEqualTo(promptState);
|
||||
assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, OFF))
|
||||
.isEqualTo(hctState);
|
||||
}
|
||||
|
||||
private void setPromptStateAndHctState(
|
||||
@HighContrastTextMigrationReceiver.PromptState int promptState,
|
||||
@AccessibilityUtil.State int hctState) {
|
||||
Settings.Secure.putInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_HCT_RECT_PROMPT_STATUS, promptState);
|
||||
Settings.Secure.putInt(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, hctState);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user