diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 213e5a4b191..e57705e1f99 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2595,7 +2595,7 @@ android:exported="true" android:permission="android.permission.NETWORK_SETTINGS"> - + diff --git a/res/layout/slice_preference_layout.xml b/res/layout/slice_preference_layout.xml index 4cea9c04678..ae589014dd9 100644 --- a/res/layout/slice_preference_layout.xml +++ b/res/layout/slice_preference_layout.xml @@ -25,5 +25,5 @@ + android:layout_height="@dimen/slice_preference_group_height"/> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 1a3d6ffae1a..8b535e3f321 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -370,4 +370,6 @@ 264dp 360dp + + 360dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 9f5b562c2be..bf0e8a55a49 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7147,7 +7147,7 @@ Screen locking sounds - + Charging sounds and vibration @@ -7355,6 +7355,12 @@ Turn off now + + Live Caption + + + Auto-convert on-device audio to captions + Do Not Disturb is on until %s diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index cc07ce119e9..b3e1704967c 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -124,6 +124,12 @@ android:key="audio_and_captions_category" android:title="@string/audio_and_captions_category_title"> + + resolved = + mPackageManager.queryIntentActivities(LIVE_CAPTION_INTENT, 0 /* flags */); + return resolved != null && !resolved.isEmpty() + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + preference.setIntent(LIVE_CAPTION_INTENT); + } +} diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index f8740799acc..11858a79b6c 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -369,11 +369,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mPreferenceControllers.values()) { for (AbstractPreferenceController controller : controllerList) { final String key = controller.getPreferenceKey(); - if(TextUtils.isEmpty(key)) { - Log.d(TAG, String.format("Preference key is %s in Controller %s", - key, controller.getClass().getSimpleName())); - continue; - } final Preference preference = screen.findPreference(key); if (preference != null) { preference.setVisible(visible && controller.isAvailable()); diff --git a/src/com/android/settings/notification/ChannelSummaryPreference.java b/src/com/android/settings/notification/ChannelSummaryPreference.java new file mode 100644 index 00000000000..c716038f60f --- /dev/null +++ b/src/com/android/settings/notification/ChannelSummaryPreference.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 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 android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.CheckBox; + +import com.android.settings.R; +import com.android.settingslib.TwoTargetPreference; + +import androidx.preference.PreferenceViewHolder; + +/** + * A custom preference that provides inline checkbox and tappable target. + */ +public class ChannelSummaryPreference extends TwoTargetPreference { + + private Context mContext; + private Intent mIntent; + private CheckBox mCheckBox; + private boolean mChecked; + private boolean mEnableCheckBox = true; + + public ChannelSummaryPreference(Context context) { + super(context); + setLayoutResource(R.layout.preference_checkable_two_target); + mContext = context; + setWidgetLayoutResource(R.layout.zen_rule_widget); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + View settingsWidget = view.findViewById(android.R.id.widget_frame); + View divider = view.findViewById(R.id.two_target_divider); + if (mIntent != null) { + divider.setVisibility(View.VISIBLE); + settingsWidget.setVisibility(View.VISIBLE); + settingsWidget.setOnClickListener(v -> mContext.startActivity(mIntent)); + } else { + divider.setVisibility(View.GONE); + settingsWidget.setVisibility(View.GONE); + settingsWidget.setOnClickListener(null); + } + + View checkboxContainer = view.findViewById(R.id.checkbox_container); + if (checkboxContainer != null) { + checkboxContainer.setOnClickListener(mOnCheckBoxClickListener); + } + mCheckBox = (CheckBox) view.findViewById(com.android.internal.R.id.checkbox); + if (mCheckBox != null) { + mCheckBox.setChecked(mChecked); + mCheckBox.setEnabled(mEnableCheckBox); + } + } + + public boolean isChecked() { + return mChecked; + } + + @Override + public void setIntent(Intent intent) { + mIntent = intent; + } + + @Override + public void onClick() { + mOnCheckBoxClickListener.onClick(null); + } + + public void setChecked(boolean checked) { + mChecked = checked; + if (mCheckBox != null) { + mCheckBox.setChecked(checked); + } + } + + public void setCheckBoxEnabled(boolean enabled) { + mEnableCheckBox = enabled; + if (mCheckBox != null) { + mCheckBox.setEnabled(enabled); + } + } + + private View.OnClickListener mOnCheckBoxClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mCheckBox != null && !mCheckBox.isEnabled()) { + return; + } + setChecked(!mChecked); + if (!callChangeListener(mChecked)) { + setChecked(!mChecked); + } else { + persistBoolean(mChecked); + } + } + }; +} diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index ed0b1235ef4..7053bb341e2 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -273,8 +273,7 @@ abstract public class NotificationSettingsBase extends DashboardFragment { protected Preference populateSingleChannelPrefs(PreferenceGroup parent, final NotificationChannel channel, final boolean groupBlocked) { - MasterCheckBoxPreference channelPref = new MasterCheckBoxPreference( - getPrefContext()); + ChannelSummaryPreference channelPref = new ChannelSummaryPreference(getPrefContext()); channelPref.setCheckBoxEnabled(mSuspendedAppsAdmin == null && isChannelBlockable(channel) && isChannelConfigurable(channel) diff --git a/src/com/android/settings/slices/SlicePreference.java b/src/com/android/settings/slices/SlicePreference.java index 37a53f4d581..a88ae768db6 100644 --- a/src/com/android/settings/slices/SlicePreference.java +++ b/src/com/android/settings/slices/SlicePreference.java @@ -44,6 +44,7 @@ public class SlicePreference extends LayoutPreference { private void init() { mSliceView = findViewById(R.id.slice_view); mSliceView.showTitleItems(true); + mSliceView.setScrollable(false); } public void onSliceUpdated(Slice slice) { diff --git a/src/com/android/settings/slices/SlicePreferenceController.java b/src/com/android/settings/slices/SlicePreferenceController.java index 93ba6520f3e..d7fcc18a832 100644 --- a/src/com/android/settings/slices/SlicePreferenceController.java +++ b/src/com/android/settings/slices/SlicePreferenceController.java @@ -50,7 +50,6 @@ public class SlicePreferenceController extends BasePreferenceController implemen @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - mSlicePreference = screen.findPreference(getPreferenceKey()); } diff --git a/tests/robotests/src/com/android/settings/accessibility/LiveCaptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/LiveCaptionPreferenceControllerTest.java new file mode 100644 index 00000000000..f6160b285cb --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/LiveCaptionPreferenceControllerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 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.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.pm.ResolveInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowPackageManager; + +import java.util.Collections; + +@RunWith(RobolectricTestRunner.class) +public class LiveCaptionPreferenceControllerTest { + + private LiveCaptionPreferenceController mController; + + @Before + public void setUp() { + mController = new LiveCaptionPreferenceController(RuntimeEnvironment.application, + "test_key"); + } + + @Test + public void getAvailabilityStatus_canResolveIntent_shouldReturnAvailable() { + final ShadowPackageManager pm = Shadows.shadowOf( + RuntimeEnvironment.application.getPackageManager()); + pm.addResolveInfoForIntent(LiveCaptionPreferenceController.LIVE_CAPTION_INTENT, + new ResolveInfo()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_noResolveIntent_shouldReturnUnavailable() { + final ShadowPackageManager pm = Shadows.shadowOf( + RuntimeEnvironment.application.getPackageManager()); + pm.setResolveInfosForIntent(LiveCaptionPreferenceController.LIVE_CAPTION_INTENT, + Collections.emptyList()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/ChannelSummaryPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/ChannelSummaryPreferenceTest.java new file mode 100644 index 00000000000..408b2b66f25 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/ChannelSummaryPreferenceTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2019 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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.LinearLayout; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +@RunWith(RobolectricTestRunner.class) +public class ChannelSummaryPreferenceTest { + + private Context mContext; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + } + + @Test + public void createNewPreference_shouldSetLayout() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + assertThat(preference.getLayoutResource()).isEqualTo( + R.layout.preference_checkable_two_target); + assertThat(preference.getWidgetLayoutResource()).isEqualTo( + R.layout.zen_rule_widget); + } + + @Test + public void setChecked_shouldUpdateButtonCheckedState() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.preference_checkable_two_target, null)); + final LinearLayout widgetView = holder.itemView.findViewById(R.id.checkbox_container); + inflater.inflate(R.layout.preference_widget_checkbox, widgetView, true); + final CheckBox toggle = (CheckBox) holder.findViewById(com.android.internal.R.id.checkbox); + preference.onBindViewHolder(holder); + + preference.setChecked(true); + assertThat(toggle.isChecked()).isTrue(); + + preference.setChecked(false); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void setCheckboxEnabled_shouldUpdateButtonEnabledState() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.preference_checkable_two_target, null)); + final LinearLayout widgetView = holder.itemView.findViewById(R.id.checkbox_container); + inflater.inflate(R.layout.preference_widget_checkbox, widgetView, true); + final CheckBox toggle = (CheckBox) holder.findViewById(com.android.internal.R.id.checkbox); + preference.onBindViewHolder(holder); + + preference.setCheckBoxEnabled(true); + assertThat(toggle.isEnabled()).isTrue(); + + preference.setCheckBoxEnabled(false); + assertThat(toggle.isEnabled()).isFalse(); + } + + @Test + public void setCheckBoxEnabled_shouldUpdateButtonEnabledState_beforeViewBound() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.preference_checkable_two_target, null)); + final LinearLayout widgetView = holder.itemView.findViewById(R.id.checkbox_container); + inflater.inflate(R.layout.preference_widget_checkbox, widgetView, true); + final CheckBox toggle = (CheckBox) holder.findViewById(com.android.internal.R.id.checkbox); + + preference.setCheckBoxEnabled(false); + preference.onBindViewHolder(holder); + assertThat(toggle.isEnabled()).isFalse(); + } + + @Test + public void clickWidgetView_shouldToggleButton() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.preference_checkable_two_target, null)); + final LinearLayout widgetView = holder.itemView.findViewById(R.id.checkbox_container); + assertThat(widgetView).isNotNull(); + + inflater.inflate(R.layout.preference_widget_checkbox, widgetView, true); + final CheckBox toggle = (CheckBox) holder.findViewById(com.android.internal.R.id.checkbox); + preference.onBindViewHolder(holder); + + widgetView.performClick(); + assertThat(toggle.isChecked()).isTrue(); + + widgetView.performClick(); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickWidgetView_shouldNotToggleButtonIfDisabled() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final LayoutInflater inflater = LayoutInflater.from(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + inflater.inflate(R.layout.preference_checkable_two_target, null)); + final LinearLayout widgetView = holder.itemView.findViewById(R.id.checkbox_container); + assertThat(widgetView).isNotNull(); + + inflater.inflate(R.layout.preference_widget_checkbox, widgetView, true); + final CheckBox toggle = (CheckBox) holder.findViewById(com.android.internal.R.id.checkbox); + preference.onBindViewHolder(holder); + toggle.setEnabled(false); + + widgetView.performClick(); + assertThat(toggle.isChecked()).isFalse(); + } + + @Test + public void clickWidgetView_shouldNotifyPreferenceChanged() { + final ChannelSummaryPreference preference = new ChannelSummaryPreference(mContext); + final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate( + R.layout.preference_checkable_two_target, null)); + final View widgetView = holder.findViewById(R.id.checkbox_container); + final Preference.OnPreferenceChangeListener + listener = mock(Preference.OnPreferenceChangeListener.class); + preference.setOnPreferenceChangeListener(listener); + preference.onBindViewHolder(holder); + + preference.setChecked(false); + widgetView.performClick(); + verify(listener).onPreferenceChange(preference, true); + + preference.setChecked(true); + widgetView.performClick(); + verify(listener).onPreferenceChange(preference, false); + } +}