diff --git a/res/xml/captioning_appearance.xml b/res/xml/captioning_appearance.xml index 71566307eba..cfddff221e6 100644 --- a/res/xml/captioning_appearance.xml +++ b/res/xml/captioning_appearance.xml @@ -25,7 +25,8 @@ android:title="@string/summary_placeholder" android:layout="@layout/captioning_preview" android:selectable="false" - settings:searchable="false"/> + settings:searchable="false" + settings:controller="com.android.settings.accessibility.CaptionPreviewPreferenceController"/> mPreferenceList = new ArrayList<>(); - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final View.OnLayoutChangeListener mLayoutChangeListener = - new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - // Remove the listener once the callback is triggered. - mPreviewViewport.removeOnLayoutChangeListener(this); - mHandler.post(() ->refreshPreviewText()); - } - }; - @Override public int getMetricsCategory() { return SettingsEnums.ACCESSIBILITY_CAPTION_APPEARANCE; @@ -123,7 +97,6 @@ public class CaptionAppearanceFragment extends DashboardFragment updateAllPreferences(); refreshShowingCustom(); installUpdateListeners(); - refreshPreviewText(); } @Override @@ -136,83 +109,7 @@ public class CaptionAppearanceFragment extends DashboardFragment return TAG; } - private void refreshPreviewText() { - final Context context = getActivity(); - if (context == null) { - // We've been destroyed, abort! - return; - } - - final SubtitleView preview = mPreviewText; - if (preview != null) { - final int styleId = mCaptioningManager.getRawUserStyle(); - applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId); - - final Locale locale = mCaptioningManager.getLocale(); - if (locale != null) { - final CharSequence localizedText = AccessibilityUtils.getTextForLocale( - context, locale, R.string.captioning_preview_text); - preview.setText(localizedText); - } else { - preview.setText(R.string.captioning_preview_text); - } - - final CaptioningManager.CaptionStyle style = mCaptioningManager.getUserStyle(); - if (style.hasWindowColor()) { - mPreviewWindow.setBackgroundColor(style.windowColor); - } else { - final CaptioningManager.CaptionStyle defStyle = - CaptioningManager.CaptionStyle.DEFAULT; - mPreviewWindow.setBackgroundColor(defStyle.windowColor); - } - } - } - - /** - * Updates font style of captioning properties for preview screen. - * - * @param manager caption manager - * @param previewText preview text - * @param previewWindow preview window - * @param styleId font style id - */ - public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, - View previewWindow, int styleId) { - previewText.setStyle(styleId); - - final Context context = previewText.getContext(); - final ContentResolver cr = context.getContentResolver(); - final float fontScale = manager.getFontScale(); - if (previewWindow != null) { - // Assume the viewport is clipped with a 16:9 aspect ratio. - final float virtualHeight = Math.max(9 * previewWindow.getWidth(), - 16 * previewWindow.getHeight()) / 16.0f; - previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale); - } else { - final float textSize = context.getResources().getDimension( - R.dimen.caption_preview_text_size); - previewText.setTextSize(textSize * fontScale); - } - - final Locale locale = manager.getLocale(); - if (locale != null) { - final CharSequence localizedText = AccessibilityUtils.getTextForLocale( - context, locale, R.string.captioning_preview_characters); - previewText.setText(localizedText); - } else { - previewText.setText(R.string.captioning_preview_characters); - } - } - private void initializeAllPreferences() { - final LayoutPreference captionPreview = findPreference(PREF_CAPTION_PREVIEW); - - mPreviewText = captionPreview.findViewById(R.id.preview_text); - - mPreviewWindow = captionPreview.findViewById(R.id.preview_window); - - mPreviewViewport = captionPreview.findViewById(R.id.preview_viewport); - mPreviewViewport.addOnLayoutChangeListener(mLayoutChangeListener); final Resources res = getResources(); final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values); @@ -400,8 +297,6 @@ public class CaptionAppearanceFragment extends DashboardFragment } else if (mEdgeType == preference) { Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value); } - - refreshPreviewText(); enableCaptioningManager(); } @@ -411,13 +306,11 @@ public class CaptionAppearanceFragment extends DashboardFragment if (mTypeface == preference) { Settings.Secure.putString( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value); - refreshPreviewText(); enableCaptioningManager(); } else if (mFontSize == preference) { Settings.Secure.putFloat( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, Float.parseFloat((String) value)); - refreshPreviewText(); enableCaptioningManager(); } diff --git a/src/com/android/settings/accessibility/CaptionHelper.java b/src/com/android/settings/accessibility/CaptionHelper.java new file mode 100644 index 00000000000..378d4b3091c --- /dev/null +++ b/src/com/android/settings/accessibility/CaptionHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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 android.content.Context; +import android.view.View; +import android.view.accessibility.CaptioningManager; + +import com.android.internal.widget.SubtitleView; +import com.android.settings.R; +import com.android.settingslib.accessibility.AccessibilityUtils; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.Locale; + +/** + * Helper class for caption. + */ +public class CaptionHelper { + + /* WebVtt specifies line height as 5.3% of the viewport height. */ + @VisibleForTesting + static final float LINE_HEIGHT_RATIO = 0.0533f; + + private final Context mContext; + private final CaptioningManager mCaptioningManager; + + public CaptionHelper(Context context) { + mContext = context; + mCaptioningManager = context.getSystemService(CaptioningManager.class); + } + + /** + * Updates font style of captioning properties for preview screen. + * + * @param previewText preview text + * @param previewWindow preview window + * @param styleId font style id + */ + public void applyCaptionProperties(SubtitleView previewText, View previewWindow, + int styleId) { + previewText.setStyle(styleId); + + final float fontScale = mCaptioningManager.getFontScale(); + if (previewWindow != null) { + // Assume the viewport is clipped with a 16:9 aspect ratio. + final float virtualHeight = Math.max(9 * previewWindow.getWidth(), + 16 * previewWindow.getHeight()) / 16.0f; + previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale); + } else { + final float textSize = mContext.getResources().getDimension( + R.dimen.caption_preview_text_size); + previewText.setTextSize(textSize * fontScale); + } + + final Locale locale = mCaptioningManager.getLocale(); + if (locale != null) { + final CharSequence localizedText = AccessibilityUtils.getTextForLocale( + mContext, locale, R.string.captioning_preview_characters); + previewText.setText(localizedText); + } else { + previewText.setText(R.string.captioning_preview_characters); + } + } +} diff --git a/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java b/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java new file mode 100644 index 00000000000..a8187f18da9 --- /dev/null +++ b/src/com/android/settings/accessibility/CaptionPreviewPreferenceController.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022 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 android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.view.View; +import android.view.accessibility.CaptioningManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.internal.widget.SubtitleView; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.accessibility.AccessibilityUtils; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.LayoutPreference; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** Controller that shows the caption locale summary. */ +public class CaptionPreviewPreferenceController extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop { + + @VisibleForTesting + static final List CAPTIONING_FEATURE_KEYS = Arrays.asList( + Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, + Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, + Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, + Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, + Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, + Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, + Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, + Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE + ); + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @VisibleForTesting + AccessibilitySettingsContentObserver mSettingsContentObserver; + private CaptioningManager mCaptioningManager; + private CaptionHelper mCaptionHelper; + private LayoutPreference mPreference; + private SubtitleView mPreviewText; + private View mPreviewWindow; + private View mPreviewViewport; + + public CaptionPreviewPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mCaptioningManager = context.getSystemService(CaptioningManager.class); + mCaptionHelper = new CaptionHelper(context); + mSettingsContentObserver = new AccessibilitySettingsContentObserver(mHandler); + mSettingsContentObserver.registerKeysToObserverCallback(CAPTIONING_FEATURE_KEYS, + key -> refreshPreviewText()); + } + + @Override + public void onStart() { + mSettingsContentObserver.register(mContext.getContentResolver()); + } + + @Override + public void onStop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsContentObserver); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + mPreviewText = mPreference.findViewById(R.id.preview_text); + mPreviewWindow = mPreference.findViewById(R.id.preview_window); + mPreviewViewport = mPreference.findViewById(R.id.preview_viewport); + mPreviewViewport.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if ((oldRight - oldLeft) != (right - left)) { + // Remove the listener once the callback is triggered. + mPreviewViewport.removeOnLayoutChangeListener(this); + mHandler.post(() -> refreshPreviewText()); + } + } + }); + } + + private void refreshPreviewText() { + if (mPreviewText != null) { + final int styleId = mCaptioningManager.getRawUserStyle(); + mCaptionHelper.applyCaptionProperties(mPreviewText, mPreviewViewport, styleId); + + final Locale locale = mCaptioningManager.getLocale(); + if (locale != null) { + final CharSequence localizedText = AccessibilityUtils.getTextForLocale( + mContext, locale, R.string.captioning_preview_text); + mPreviewText.setText(localizedText); + } else { + mPreviewText.setText(R.string.captioning_preview_text); + } + + final CaptioningManager.CaptionStyle style = mCaptioningManager.getUserStyle(); + if (style.hasWindowColor()) { + mPreviewWindow.setBackgroundColor(style.windowColor); + } else { + final CaptioningManager.CaptionStyle defStyle = + CaptioningManager.CaptionStyle.DEFAULT; + mPreviewWindow.setBackgroundColor(defStyle.windowColor); + } + } + } +} diff --git a/src/com/android/settings/accessibility/PresetPreference.java b/src/com/android/settings/accessibility/PresetPreference.java index 680fcbc240a..a0ef7b63595 100644 --- a/src/com/android/settings/accessibility/PresetPreference.java +++ b/src/com/android/settings/accessibility/PresetPreference.java @@ -19,26 +19,24 @@ package com.android.settings.accessibility; import android.content.Context; import android.util.AttributeSet; import android.view.View; -import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import android.widget.TextView; import com.android.internal.widget.SubtitleView; import com.android.settings.R; +/** Grid preference that allows the user to pick a captioning preset type. */ public class PresetPreference extends ListDialogPreference { - private static final float DEFAULT_FONT_SIZE = 32f; - private final CaptioningManager mCaptioningManager; + private static final float DEFAULT_FONT_SIZE = 32f; + private final CaptionHelper mCaptionHelper; public PresetPreference(Context context, AttributeSet attrs) { super(context, attrs); + mCaptionHelper = new CaptionHelper(context); setDialogLayoutResource(R.layout.grid_picker_dialog); setListItemLayoutResource(R.layout.preset_picker_item); - - mCaptioningManager = (CaptioningManager) context.getSystemService( - Context.CAPTIONING_SERVICE); } @Override @@ -50,17 +48,16 @@ public class PresetPreference extends ListDialogPreference { @Override protected void onBindListItem(View view, int index) { final View previewViewport = view.findViewById(R.id.preview_viewport); - final SubtitleView previewText = (SubtitleView) view.findViewById(R.id.preview); + final SubtitleView previewText = view.findViewById(R.id.preview); final int value = getValueAt(index); - CaptionAppearanceFragment.applyCaptionProperties( - mCaptioningManager, previewText, previewViewport, value); + mCaptionHelper.applyCaptionProperties(previewText, previewViewport, value); final float density = getContext().getResources().getDisplayMetrics().density; previewText.setTextSize(DEFAULT_FONT_SIZE * density); final CharSequence title = getTitleAt(index); if (title != null) { - final TextView summary = (TextView) view.findViewById(R.id.summary); + final TextView summary = view.findViewById(R.id.summary); summary.setText(title); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptionHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptionHelperTest.java new file mode 100644 index 00000000000..02d1b187a73 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/CaptionHelperTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 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 org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.view.View; +import android.view.accessibility.CaptioningManager; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.widget.SubtitleView; +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +import java.util.Locale; + +/** Tests for {@link CaptionHelper}. */ +@RunWith(RobolectricTestRunner.class) +public class CaptionHelperTest { + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + private CaptioningManager mCaptioningManager; + @Mock + private SubtitleView mSubtitleView; + @Mock + private View mPreviewWindow; + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + private CaptionHelper mCaptionHelper; + + @Before + public void setUp() { + when(mContext.getSystemService(CaptioningManager.class)).thenReturn(mCaptioningManager); + mCaptionHelper = new CaptionHelper(mContext); + } + + @Test + public void applyCaptionProperties_verifyAction() { + final float fontScale = 1.0f; + when(mCaptioningManager.getFontScale()).thenReturn(fontScale); + final int windowSize = 100; + when(mPreviewWindow.getWidth()).thenReturn(windowSize); + when(mPreviewWindow.getHeight()).thenReturn(windowSize); + final float textSize = CaptionHelper.LINE_HEIGHT_RATIO * windowSize * fontScale; + + mCaptionHelper.applyCaptionProperties(mSubtitleView, mPreviewWindow, /* styleId= */ 0); + + verify(mSubtitleView).setTextSize(textSize); + verify(mSubtitleView).setText(R.string.captioning_preview_characters); + } + + @Test + public void applyCaptionProperties_withoutPreviewWindow_verifyAction() { + final float fontScale = 1.0f; + when(mCaptioningManager.getFontScale()).thenReturn(fontScale); + final float textSize = mContext.getResources().getDimension( + R.dimen.caption_preview_text_size) * fontScale; + + mCaptionHelper.applyCaptionProperties(mSubtitleView, /* PreviewWindow= */ null, + /* styleId= */ 0); + + verify(mSubtitleView).setTextSize(textSize); + verify(mSubtitleView).setText(R.string.captioning_preview_characters); + } + + @Test + public void applyCaptionProperties_localeUS_verifyAction() { + when(mCaptioningManager.getLocale()).thenReturn(Locale.US); + final String text = mContext.getString(R.string.captioning_preview_characters); + + mCaptionHelper.applyCaptionProperties(mSubtitleView, /* PreviewWindow= */ null, + /* styleId= */ 0); + + verify(mSubtitleView).setText(text); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java new file mode 100644 index 00000000000..2ca755ce999 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/CaptionPreviewPreferenceControllerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; +import android.view.View; + +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.widget.LayoutPreference; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link CaptionPreviewPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class CaptionPreviewPreferenceControllerTest { + + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock + private PreferenceScreen mScreen; + @Mock + private ContentResolver mContentResolver; + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + private CaptionPreviewPreferenceController mController; + private LayoutPreference mLayoutPreference; + + @Before + public void setUp() { + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new CaptionPreviewPreferenceController(mContext, + "captioning_preference_switch"); + final View view = new View(mContext); + mLayoutPreference = new LayoutPreference(mContext, view); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mLayoutPreference); + } + + @Test + public void getAvailabilityStatus_shouldReturnAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + public void onStart_registerSpecificContentObserverForSpecificKeys() { + mController.onStart(); + + for (String key : mController.CAPTIONING_FEATURE_KEYS) { + verify(mContentResolver).registerContentObserver(Settings.Secure.getUriFor(key), + /* notifyForDescendants= */ false, mController.mSettingsContentObserver); + } + } + + @Test + public void onPause_unregisterContentObserver() { + mController.onStop(); + + verify(mContentResolver).unregisterContentObserver(mController.mSettingsContentObserver); + } +}