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);
+ }
+}