From 8335ce30ad94dd4e6b96b5c8b14add2191083d86 Mon Sep 17 00:00:00 2001 From: Amin Shaikh Date: Fri, 5 Apr 2019 08:06:38 -0400 Subject: [PATCH 01/25] Create TintDrawable in Settings. This allows us to specify a drawable resource in xml that is simply another drawable with a different tint. This is necessary to overlay the settings wifi icons since the tint is resolved using a theme attribute. This change also replaces a few icons other duplicate icons to demonstrate usage. Fixes: 123363812 Test: make; use class in XML and run app Change-Id: Id80f318e96dab2e1ad9db8430e8371ac6497b753 --- res/drawable/ic_battery_saver_accent_24dp.xml | 18 +--- res/drawable/ic_delete_accent.xml | 20 +--- res/drawable/ic_settings_wireless_white.xml | 13 +-- res/drawable/ic_wifi_signal_0.xml | 14 +-- res/drawable/ic_wifi_signal_1.xml | 17 +-- res/drawable/ic_wifi_signal_2.xml | 17 +-- res/drawable/ic_wifi_signal_3.xml | 17 +-- res/drawable/ic_wifi_signal_4.xml | 13 +-- res/values/attrs.xml | 5 + .../android/settings/widget/TintDrawable.java | 101 ++++++++++++++++++ 10 files changed, 138 insertions(+), 97 deletions(-) create mode 100644 src/com/android/settings/widget/TintDrawable.java diff --git a/res/drawable/ic_battery_saver_accent_24dp.xml b/res/drawable/ic_battery_saver_accent_24dp.xml index 764402de5b8..31eb1930103 100644 --- a/res/drawable/ic_battery_saver_accent_24dp.xml +++ b/res/drawable/ic_battery_saver_accent_24dp.xml @@ -13,17 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - + diff --git a/res/drawable/ic_delete_accent.xml b/res/drawable/ic_delete_accent.xml index 86a9ccacd40..2f087e8e341 100644 --- a/res/drawable/ic_delete_accent.xml +++ b/res/drawable/ic_delete_accent.xml @@ -13,19 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - + diff --git a/res/drawable/ic_settings_wireless_white.xml b/res/drawable/ic_settings_wireless_white.xml index 3271b8b4f66..63be43b290b 100644 --- a/res/drawable/ic_settings_wireless_white.xml +++ b/res/drawable/ic_settings_wireless_white.xml @@ -13,12 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + \ No newline at end of file diff --git a/res/drawable/ic_wifi_signal_0.xml b/res/drawable/ic_wifi_signal_0.xml index 55faf64f675..4a3567afb3f 100644 --- a/res/drawable/ic_wifi_signal_0.xml +++ b/res/drawable/ic_wifi_signal_0.xml @@ -14,13 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - \ No newline at end of file + \ No newline at end of file diff --git a/res/drawable/ic_wifi_signal_1.xml b/res/drawable/ic_wifi_signal_1.xml index e0a207266d1..88a94688fe8 100644 --- a/res/drawable/ic_wifi_signal_1.xml +++ b/res/drawable/ic_wifi_signal_1.xml @@ -14,16 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + \ No newline at end of file diff --git a/res/drawable/ic_wifi_signal_2.xml b/res/drawable/ic_wifi_signal_2.xml index d0daa603604..b1c2859dfb7 100644 --- a/res/drawable/ic_wifi_signal_2.xml +++ b/res/drawable/ic_wifi_signal_2.xml @@ -14,16 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + diff --git a/res/drawable/ic_wifi_signal_3.xml b/res/drawable/ic_wifi_signal_3.xml index c542c69c520..b058eba1343 100644 --- a/res/drawable/ic_wifi_signal_3.xml +++ b/res/drawable/ic_wifi_signal_3.xml @@ -14,16 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - + \ No newline at end of file diff --git a/res/drawable/ic_wifi_signal_4.xml b/res/drawable/ic_wifi_signal_4.xml index bb7dbd04b29..e84066a9636 100644 --- a/res/drawable/ic_wifi_signal_4.xml +++ b/res/drawable/ic_wifi_signal_4.xml @@ -14,12 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + \ No newline at end of file diff --git a/res/values/attrs.xml b/res/values/attrs.xml index be6345bd6a8..83269cc4e04 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -164,6 +164,11 @@ + + + + + diff --git a/src/com/android/settings/widget/TintDrawable.java b/src/com/android/settings/widget/TintDrawable.java new file mode 100644 index 00000000000..13eb1211da7 --- /dev/null +++ b/src/com/android/settings/widget/TintDrawable.java @@ -0,0 +1,101 @@ +/* + * 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.widget; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.drawable.DrawableWrapper; +import android.util.AttributeSet; +import android.util.Log; + +import com.android.settings.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** + * A Drawable that tints a contained Drawable, overriding the existing tint specified in the + * underlying drawable. This class should only be used in XML. + * + * @attr ref android.R.styleable#DrawableWrapper_drawable + * @attr ref R.styleable#TintDrawable_tint + */ +public class TintDrawable extends DrawableWrapper { + private ColorStateList mTint; + private int[] mThemeAttrs; + + /** No-arg constructor used by drawable inflation. */ + public TintDrawable() { + super(null); + } + + @Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.TintDrawable); + + super.inflate(r, parser, attrs, theme); + + mThemeAttrs = a.extractThemeAttrs(); + updateStateFromTypedArray(a); + a.recycle(); + + applyTint(); + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + if (mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.TintDrawable); + updateStateFromTypedArray(a); + a.recycle(); + } + + // Ensure tint is reapplied after applying the theme to ensure this drawables' + // tint overrides the underlying drawables' tint. + applyTint(); + } + + @Override + public boolean canApplyTheme() { + return (mThemeAttrs != null && mThemeAttrs.length > 0) || super.canApplyTheme(); + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + if (a.hasValue(R.styleable.TintDrawable_android_drawable)) { + setDrawable(a.getDrawable(R.styleable.TintDrawable_android_drawable)); + } + if (a.hasValue(R.styleable.TintDrawable_android_tint)) { + mTint = a.getColorStateList(R.styleable.TintDrawable_android_tint); + } + } + + private void applyTint() { + if (getDrawable() != null && mTint != null) { + getDrawable().mutate().setTintList(mTint); + } + } +} From 17afd6633a74f9e82e85e38ec192bfc01e4a27de Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Wed, 10 Apr 2019 17:28:36 +0800 Subject: [PATCH 02/25] Correct the state when prevent ringing page is launched. When prevent ringing page is opened, the switch gets checked and is set to vibrate in default. Make it set to a correct state based on the value stored in Settings.Secure.VOLUME_HUSH_GESTURE. Fixes: 129728579 Test: robotests Change-Id: I1063db9fe79800eb2e400810e2414ee650076ea0 --- ...ventRingingSwitchPreferenceController.java | 9 ++- ...RingingSwitchPreferenceControllerTest.java | 72 +++++++++++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java index e21bb7563bf..9545939255b 100644 --- a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java +++ b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java @@ -102,8 +102,15 @@ public class PreventRingingSwitchPreferenceController extends AbstractPreference @Override public void onSwitchChanged(Switch switchView, boolean isChecked) { + final int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE); + final int newRingingSetting = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF + ? Settings.Secure.VOLUME_HUSH_VIBRATE + : preventRingingSetting; + Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, isChecked ? Settings.Secure.VOLUME_HUSH_VIBRATE + Settings.Secure.VOLUME_HUSH_GESTURE, isChecked + ? newRingingSetting : Settings.Secure.VOLUME_HUSH_OFF); } diff --git a/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java index 85eeacc4ce2..ccb2bf6a2b4 100644 --- a/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/PreventRingingSwitchPreferenceControllerTest.java @@ -16,6 +16,10 @@ package com.android.settings.gestures; +import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; +import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; +import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -43,6 +47,9 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class PreventRingingSwitchPreferenceControllerTest { + + private static final int UNKNOWN = -1; + private Context mContext; private Resources mResources; private PreventRingingSwitchPreferenceController mController; @@ -76,35 +83,88 @@ public class PreventRingingSwitchPreferenceControllerTest { } @Test - public void testOn_updateState_hushOff() { + public void updateState_hushOff_uncheck() { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_OFF); + VOLUME_HUSH_OFF); + mController.updateState(mPreference); + verify(mController.mSwitch, times(1)).setChecked(false); } @Test - public void testOn_updateState_hushVibrate() { + public void updateState_hushVibrate_setChecked() { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_VIBRATE); + VOLUME_HUSH_VIBRATE); + mController.updateState(mPreference); + verify(mController.mSwitch, times(1)).setChecked(true); } @Test - public void testOn_updateState_hushMute() { + public void updateState_hushMute_setChecked() { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_MUTE); + VOLUME_HUSH_MUTE); + mController.updateState(mPreference); + verify(mController.mSwitch, times(1)).setChecked(true); } + @Test + public void onSwitchChanged_wasHushOff_checked_returnHushVibrate() { + Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, + VOLUME_HUSH_OFF); + + mController.onSwitchChanged(null, true); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, UNKNOWN)).isEqualTo(VOLUME_HUSH_VIBRATE); + } + + @Test + public void onSwitchChanged_wasHushMute_unchecked_returnHushOff() { + Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, + VOLUME_HUSH_MUTE); + + mController.onSwitchChanged(null, false); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, UNKNOWN)).isEqualTo(VOLUME_HUSH_OFF); + } + + @Test + public void onSwitchChanged_wasHushMute_checked_returnHushMute() { + // this is the case for the page open + Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, + VOLUME_HUSH_MUTE); + + mController.onSwitchChanged(null, true); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, UNKNOWN)).isEqualTo(VOLUME_HUSH_MUTE); + } + + @Test + public void onSwitchChanged_wasHushVibrate_checked_returnHushVibrate() { + // this is the case for the page open + Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, + VOLUME_HUSH_VIBRATE); + + mController.onSwitchChanged(null, true); + + assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, UNKNOWN)).isEqualTo(VOLUME_HUSH_VIBRATE); + } + @Test public void testPreferenceClickListenerAttached() { PreferenceScreen preferenceScreen = mock(PreferenceScreen.class); LayoutPreference mLayoutPreference = mock(LayoutPreference.class); when(preferenceScreen.findPreference(mController.getPreferenceKey())).thenReturn( mLayoutPreference); + mController.displayPreference(preferenceScreen); verify(mLayoutPreference, times(1)) From 7df6445c3e2018468f80015a67d314b0066d4a5e Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Fri, 12 Apr 2019 15:31:25 -0700 Subject: [PATCH 03/25] Clean up DND slice in search. - Remove the controller in SoundSettings, just use the common one. - Update a bunch of IDs so ZenModeSliceBuilder maps to the toggle button in zen_mode_settings.xml This is needed so the slice intent (left target when shown in search) is the same as what we index from search side. Previously the search indexer is finding zen_mode from sound page but the slice itself is reporting a deep link into zenModeSettings page. Fixes: 130437726 Test: robotest Change-Id: Ic41d2d93afa1f748e3282e23010199a0fa078645 --- res/xml/sound_settings.xml | 2 +- res/xml/zen_mode_settings.xml | 8 +-- .../ZenModeButtonPreferenceController.java | 9 ++-- .../notification/ZenModeSettings.java | 1 - .../notification/ZenModeSliceBuilder.java | 11 ++-- ...ModeSoundSettingsPreferenceController.java | 33 ------------ .../settings/slices/CustomSliceRegistry.java | 5 +- ...SoundSettingsPreferenceControllerTest.java | 50 ------------------- 8 files changed, 19 insertions(+), 100 deletions(-) delete mode 100644 src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java delete mode 100644 tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml index e610df29658..d99cc00c8d3 100644 --- a/res/xml/sound_settings.xml +++ b/res/xml/sound_settings.xml @@ -103,7 +103,7 @@ settings:useAdminDisabledSummary="true" settings:keywords="@string/keywords_sounds_and_notifications_interruptions" settings:allowDividerAbove="true" - settings:controller="com.android.settings.notification.ZenModeSoundSettingsPreferenceController"/> + settings:controller="com.android.settings.notification.ZenModePreferenceController"/> + settings:searchable="false"> + settings:allowDividerBelow="true" + settings:keywords="@string/keywords_zen_mode_settings"/> diff --git a/src/com/android/settings/notification/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/ZenModeButtonPreferenceController.java index 4829a289584..3a9bcb7b17a 100644 --- a/src/com/android/settings/notification/ZenModeButtonPreferenceController.java +++ b/src/com/android/settings/notification/ZenModeButtonPreferenceController.java @@ -33,11 +33,12 @@ import com.android.settingslib.widget.LayoutPreference; public class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController implements PreferenceControllerMixin { + public static final String KEY = "zen_mode_toggle"; + private static final String TAG = "EnableZenModeButton"; - protected static final String KEY = "zen_mode_settings_button_container"; + private final FragmentManager mFragment; private Button mZenButtonOn; private Button mZenButtonOff; - private FragmentManager mFragment; public ZenModeButtonPreferenceController(Context context, Lifecycle lifecycle, FragmentManager fragment) { @@ -60,13 +61,13 @@ public class ZenModeButtonPreferenceController extends AbstractZenModePreference super.updateState(preference); if (null == mZenButtonOn) { - mZenButtonOn = (Button) ((LayoutPreference) preference) + mZenButtonOn = ((LayoutPreference) preference) .findViewById(R.id.zen_mode_settings_turn_on_button); updateZenButtonOnClickListener(); } if (null == mZenButtonOff) { - mZenButtonOff = (Button) ((LayoutPreference) preference) + mZenButtonOff = ((LayoutPreference) preference) .findViewById(R.id.zen_mode_settings_turn_off_button); mZenButtonOff.setOnClickListener(v -> { mMetricsFeatureProvider.action(mContext, diff --git a/src/com/android/settings/notification/ZenModeSettings.java b/src/com/android/settings/notification/ZenModeSettings.java index 9360a33521d..f35c649e0b7 100644 --- a/src/com/android/settings/notification/ZenModeSettings.java +++ b/src/com/android/settings/notification/ZenModeSettings.java @@ -328,7 +328,6 @@ public class ZenModeSettings extends ZenModeSettingsBase { public List getNonIndexableKeys(Context context) { List keys = super.getNonIndexableKeys(context); keys.add(ZenModeDurationPreferenceController.KEY); - keys.add(ZenModeButtonPreferenceController.KEY); return keys; } diff --git a/src/com/android/settings/notification/ZenModeSliceBuilder.java b/src/com/android/settings/notification/ZenModeSliceBuilder.java index e8b181a6e69..9e88cea7622 100644 --- a/src/com/android/settings/notification/ZenModeSliceBuilder.java +++ b/src/com/android/settings/notification/ZenModeSliceBuilder.java @@ -18,8 +18,6 @@ package com.android.settings.notification; import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; -import static com.android.settings.notification.ZenModeSoundSettingsPreferenceController.ZEN_MODE_KEY; - import android.annotation.ColorInt; import android.app.NotificationManager; import android.app.PendingIntent; @@ -47,6 +45,8 @@ public class ZenModeSliceBuilder { private static final String TAG = "ZenModeSliceBuilder"; + private static final String ZEN_MODE_SLICE_KEY = ZenModeButtonPreferenceController.KEY; + /** * Action notifying a change on the Zen Mode Slice. */ @@ -78,7 +78,8 @@ public class ZenModeSliceBuilder { final PendingIntent primaryAction = getPrimaryAction(context); final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, (IconCompat) null /* icon */, ListBuilder.ICON_IMAGE, title); - final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, null /* actionTitle */, + final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction, + null /* actionTitle */, isZenModeEnabled); return new ListBuilder(context, CustomSliceRegistry.ZEN_MODE_SLICE_URI, @@ -110,10 +111,10 @@ public class ZenModeSliceBuilder { } public static Intent getIntent(Context context) { - final Uri contentUri = new Uri.Builder().appendPath(ZEN_MODE_KEY).build(); + final Uri contentUri = new Uri.Builder().appendPath(ZEN_MODE_SLICE_KEY).build(); final String screenTitle = context.getText(R.string.zen_mode_settings_title).toString(); return SliceBuilderUtils.buildSearchResultPageIntent(context, - ZenModeSettings.class.getName(), ZEN_MODE_KEY, screenTitle, + ZenModeSettings.class.getName(), ZEN_MODE_SLICE_KEY, screenTitle, SettingsEnums.NOTIFICATION_ZEN_MODE) .setClassName(context.getPackageName(), SubSettings.class.getName()) .setData(contentUri); diff --git a/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java b/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java deleted file mode 100644 index 842c49d7b5a..00000000000 --- a/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceController.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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; - -public class ZenModeSoundSettingsPreferenceController extends ZenModePreferenceController { - - public static final String ZEN_MODE_KEY = "zen_mode"; - - public ZenModeSoundSettingsPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public int getAvailabilityStatus() { - return AVAILABLE; - } -} diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index e4008153954..dc3324b3d92 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -19,8 +19,6 @@ package com.android.settings.slices; import static android.provider.SettingsSlicesContract.KEY_LOCATION; import static android.provider.SettingsSlicesContract.KEY_WIFI; -import static com.android.settings.notification.ZenModeSoundSettingsPreferenceController.ZEN_MODE_KEY; - import android.content.ContentResolver; import android.net.Uri; import android.provider.SettingsSlicesContract; @@ -43,6 +41,7 @@ import com.android.settings.location.LocationSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.MediaOutputSlice; import com.android.settings.network.telephony.MobileDataSlice; +import com.android.settings.notification.ZenModeButtonPreferenceController; import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settings.wifi.slice.ContextualWifiSlice; import com.android.settings.wifi.slice.WifiSlice; @@ -298,7 +297,7 @@ public class CustomSliceRegistry { .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) - .appendPath(ZEN_MODE_KEY) + .appendPath(ZenModeButtonPreferenceController.KEY) .build(); /** diff --git a/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java deleted file mode 100644 index a08a4d72700..00000000000 --- a/tests/robotests/src/com/android/settings/notification/ZenModeSoundSettingsPreferenceControllerTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.android.settings.core.BasePreferenceController.AVAILABLE; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class ZenModeSoundSettingsPreferenceControllerTest { - - private Context mContext; - private ZenModeSoundSettingsPreferenceController mController; - private static final String KEY_ZEN_MODE = "zen_mode"; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = new ZenModeSoundSettingsPreferenceController(mContext, KEY_ZEN_MODE); - } - - @Test - public void getAvailabilityStatus_available() { - assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); - } -} From 1d2b51aeb0c40f71d50c59dfc3ade3e0fe18ed0a Mon Sep 17 00:00:00 2001 From: pastychang Date: Mon, 15 Apr 2019 11:58:28 +0800 Subject: [PATCH 04/25] Set WifiDialog to light theme in setup flow Screenshot: https://screenshot.googleplex.com/X6tkxihEWFY Test: Manual Bug: 130507879 Change-Id: Icca6b827cdb62387fde4c91435d4df10e2ff4d04 --- res/values/themes_suw.xml | 2 ++ src/com/android/settings/wifi/WifiDialog.java | 15 +++++++++-- .../settings/wifi/WifiDialogActivity.java | 10 ++++++-- .../settings/wifi/WifiDialogActivityTest.java | 25 ++++++++++++++++++- .../android/settings/wifi/WifiDialogTest.java | 13 ++++++++++ 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/res/values/themes_suw.xml b/res/values/themes_suw.xml index 9bb67cf6021..0df69913f3b 100644 --- a/res/values/themes_suw.xml +++ b/res/values/themes_suw.xml @@ -191,12 +191,14 @@ - From 30009434322e51f464b0e70230efd3a486ddbf51 Mon Sep 17 00:00:00 2001 From: Lei Yu Date: Mon, 15 Apr 2019 15:10:38 -0700 Subject: [PATCH 13/25] Hide Mobile preference for secondary user Fixes: 128917967 Test: RunSettingsRoboTests Change-Id: Ibb95e3f0f42a8c484807a286bc77e8764eb31fcf --- .../network/MobileNetworkSummaryController.java | 5 ++++- .../MobileNetworkSummaryControllerTest.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java index df743b3cee4..ae115ebb727 100644 --- a/src/com/android/settings/network/MobileNetworkSummaryController.java +++ b/src/com/android/settings/network/MobileNetworkSummaryController.java @@ -21,6 +21,7 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; import android.content.Context; import android.content.Intent; +import android.os.UserManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.euicc.EuiccManager; @@ -49,6 +50,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController private static final String KEY = "mobile_network_list"; private SubscriptionManager mSubscriptionManager; + private UserManager mUserManager; private SubscriptionsChangeListener mChangeListener; private AddPreference mPreference; @@ -70,6 +72,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController public MobileNetworkSummaryController(Context context, Lifecycle lifecycle) { super(context); mSubscriptionManager = context.getSystemService(SubscriptionManager.class); + mUserManager = context.getSystemService(UserManager.class); if (lifecycle != null) { mChangeListener = new SubscriptionsChangeListener(context, this); lifecycle.addObserver(this); @@ -162,7 +165,7 @@ public class MobileNetworkSummaryController extends AbstractPreferenceController @Override public boolean isAvailable() { - return !Utils.isWifiOnly(mContext); + return !Utils.isWifiOnly(mContext) && mUserManager.isAdminUser(); } @Override diff --git a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java index ad66a8f43d1..b8ba63c752c 100644 --- a/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MobileNetworkSummaryControllerTest.java @@ -31,6 +31,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; +import android.os.UserManager; import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; @@ -65,6 +66,8 @@ public class MobileNetworkSummaryControllerTest { private EuiccManager mEuiccManager; @Mock private PreferenceScreen mPreferenceScreen; + @Mock + private UserManager mUserManager; private AddPreference mPreference; private Context mContext; @@ -76,6 +79,7 @@ public class MobileNetworkSummaryControllerTest { mContext = spy(Robolectric.setupActivity(Activity.class)); when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); when(mContext.getSystemService(EuiccManager.class)).thenReturn(mEuiccManager); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); when(mTelephonyManager.getNetworkCountryIso()).thenReturn(""); when(mEuiccManager.isEnabled()).thenReturn(true); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1); @@ -97,9 +101,22 @@ public class MobileNetworkSummaryControllerTest { final ConnectivityManager cm = mock(ConnectivityManager.class); when(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false); when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(cm); + when(mUserManager.isAdminUser()).thenReturn(true); + assertThat(mController.isAvailable()).isFalse(); } + @Test + public void isAvailable_secondaryUser_notAvailable() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + when(cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true); + when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(cm); + when(mUserManager.isAdminUser()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test public void getSummary_noSubscriptions_correctSummaryAndClickHandler() { mController.displayPreference(mPreferenceScreen); From 000ba3fabef08c0cd3f27397427504df645b5bdb Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Mon, 15 Apr 2019 17:33:55 +0800 Subject: [PATCH 14/25] Remove error slice from Settings in rendering time We filter error slice in EligibleCardChecker but not filter it in SliceContextualCardRenderer, these two part should use the same logic. Fixes:128687331 Test: manual Change-Id: I217bc6b578ceb36b0a67b44dc8a08ecd02771d82 --- .../slices/SliceContextualCardRenderer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index 725f08752b5..9898834508b 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -16,6 +16,8 @@ package com.android.settings.homepage.contextualcards.slices; +import static android.app.slice.Slice.HINT_ERROR; + import android.content.ContentResolver; import android.content.Context; import android.net.Uri; @@ -117,6 +119,14 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life return; } + if (slice.hasHint(HINT_ERROR)) { + Log.w(TAG, "Slice has HINT_ERROR, skipping rendering. uri=" + slice.getUri()); + mSliceLiveDataMap.get(slice.getUri()).removeObservers(mLifecycleOwner); + mContext.getContentResolver().notifyChange(CardContentProvider.REFRESH_CARD_URI, + null); + return; + } + switch (holder.getItemViewType()) { case VIEW_TYPE_DEFERRED_SETUP: mDeferredSetupCardHelper.bindView(holder, card, slice); From 44eecb7375e8109d30d3aba50a724172bf19817c Mon Sep 17 00:00:00 2001 From: Sean Stout Date: Mon, 15 Apr 2019 15:34:09 -0700 Subject: [PATCH 15/25] Rename ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL is now ACTION_APP_BATTERY_SETTINGS Test: adb shell am start -a \ "android.settings.VIEW_ADVANCED_POWER_USAGE_DETAIL" -d \ "package:com.google.android.deskclock" --ez \ "request_ignore_background_restriction" 1 Bug: 129901520 Change-Id: Iec103ee46863c05497f9aa53f0ae81b105fdd309 --- AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a8375c98af4..3582d3d281b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2904,7 +2904,7 @@ android:launchMode="singleInstance" android:theme="@android:style/Theme.NoDisplay"> - + From 73454f2607dd69cc0494e1675046bcfab4d4ec3c Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Mon, 15 Apr 2019 12:50:47 +0800 Subject: [PATCH 16/25] "Disallow adjust volume" restricted dialog didn't show root casue: VolumeSeekBarPreference & SeekBarPreference will return false when call isSelectable() then this preference won't perform Click Fixes: 123745455 Test: Manual & robotest Change-Id: Iba27b503e8a272260c80bd41aecdd5cdbc39f962 --- .../notification/VolumeSeekBarPreference.java | 5 ----- .../settings/widget/SeekBarPreference.java | 2 +- .../widget/SeekBarPreferenceTest.java | 21 ++++++++++++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java index 7f36791e069..92b3cae61b9 100644 --- a/src/com/android/settings/notification/VolumeSeekBarPreference.java +++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java @@ -80,11 +80,6 @@ public class VolumeSeekBarPreference extends SeekBarPreference { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } - @Override - public boolean isSelectable() { - return false; - } - public void setStream(int stream) { mStream = stream; setMax(mAudioManager.getStreamMaxVolume(mStream)); diff --git a/src/com/android/settings/widget/SeekBarPreference.java b/src/com/android/settings/widget/SeekBarPreference.java index f4d2aac83d4..44def119ea2 100644 --- a/src/com/android/settings/widget/SeekBarPreference.java +++ b/src/com/android/settings/widget/SeekBarPreference.java @@ -93,7 +93,7 @@ public class SeekBarPreference extends RestrictedPreference @Override public boolean isSelectable() { - return false; + return isDisabledByAdmin(); } @Override diff --git a/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java index a675e0231bc..0a1d5d80de5 100644 --- a/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/widget/SeekBarPreferenceTest.java @@ -18,12 +18,16 @@ package com.android.settings.widget; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.content.Context; import android.os.Parcelable; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @@ -39,9 +43,10 @@ public class SeekBarPreferenceTest { @Before public void setUp() { + MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; - mSeekBarPreference = new SeekBarPreference(mContext); + mSeekBarPreference = spy(new SeekBarPreference(mContext)); mSeekBarPreference.setMax(MAX); mSeekBarPreference.setMin(MIN); mSeekBarPreference.setProgress(PROGRESS); @@ -59,4 +64,18 @@ public class SeekBarPreferenceTest { assertThat(preference.getMin()).isEqualTo(MIN); assertThat(preference.getProgress()).isEqualTo(PROGRESS); } + + @Test + public void isSelectable_disabledByAdmin_returnTrue() { + when(mSeekBarPreference.isDisabledByAdmin()).thenReturn(true); + + assertThat(mSeekBarPreference.isSelectable()).isTrue(); + } + + @Test + public void isSelectable_notDisabledByAdmin_returnFalse() { + when(mSeekBarPreference.isDisabledByAdmin()).thenReturn(false); + + assertThat(mSeekBarPreference.isSelectable()).isFalse(); + } } From ef7ce46657661ff51524f789cf8a7b025de882ea Mon Sep 17 00:00:00 2001 From: Salvador Martinez Date: Thu, 11 Apr 2019 14:06:08 -0700 Subject: [PATCH 17/25] Change dark theme screen to toggle The dark theme screen has been removed in favor of a simple toggle with a dialog that shows up the first time a user tries to enable dark theme when in light theme. Test: robotests pass Bug: 130251804 Change-Id: I48f8e24a2b2a117e5a8054c5bc0b240ba68fe1ad --- res/drawable/dark_theme.xml | 26 ++++ res/values/strings.xml | 11 +- res/xml/accessibility_settings.xml | 5 +- res/xml/display_settings.xml | 5 +- src/com/android/settings/DisplaySettings.java | 8 + .../accessibility/AccessibilitySettings.java | 4 +- .../display/DarkUIInfoDialogFragment.java | 93 +++++++++++ .../display/DarkUIPreferenceController.java | 53 +++++-- .../settings/display/DarkUISettings.java | 145 ------------------ .../DarkUISettingsRadioButtonsController.java | 86 ----------- .../AccessibilitySettingsTest.java | 13 -- .../display/DarkUIInfoDialogFragmentTest.java | 65 ++++++++ ...kUISettingsRadioButtonsControllerTest.java | 52 ------- 13 files changed, 243 insertions(+), 323 deletions(-) create mode 100644 res/drawable/dark_theme.xml create mode 100644 src/com/android/settings/display/DarkUIInfoDialogFragment.java delete mode 100644 src/com/android/settings/display/DarkUISettings.java delete mode 100644 src/com/android/settings/display/DarkUISettingsRadioButtonsController.java create mode 100644 tests/robotests/src/com/android/settings/display/DarkUIInfoDialogFragmentTest.java delete mode 100644 tests/robotests/src/com/android/settings/display/DarkUISettingsRadioButtonsControllerTest.java diff --git a/res/drawable/dark_theme.xml b/res/drawable/dark_theme.xml new file mode 100644 index 00000000000..3425002417a --- /dev/null +++ b/res/drawable/dark_theme.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index da1c158b36f..414cb6efde0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9900,17 +9900,14 @@ System UI demo mode - Theme - - - Choose Theme - - - This setting also applies to apps + Dark Theme Supported apps will also switch to dark theme + + Got it + Quick settings developer tiles diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index 26849d1fe41..b4968f8200f 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -58,11 +58,10 @@ android:title="@string/screen_zoom_title" settings:searchable="false"/> - + settings:searchable="false"/> - diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index b67e6e84f3a..eb77d4a3492 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -18,11 +18,13 @@ package com.android.settings; import android.app.settings.SettingsEnums; import android.content.Context; +import android.os.Bundle; import android.provider.SearchIndexableResource; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.display.BrightnessLevelPreferenceController; import com.android.settings.display.CameraGesturePreferenceController; +import com.android.settings.display.DarkUIPreferenceController; import com.android.settings.display.LiftToWakePreferenceController; import com.android.settings.display.NightDisplayPreferenceController; import com.android.settings.display.NightModePreferenceController; @@ -62,6 +64,12 @@ public class DisplaySettings extends DashboardFragment { return R.xml.display_settings; } + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + use(DarkUIPreferenceController.class).setParentFragment(this); + } + @Override protected List createPreferenceControllers(Context context) { return buildPreferenceControllers(context, getSettingsLifecycle()); diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index efa14f64c7c..478db9f298f 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -244,7 +244,7 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements private SwitchPreference mToggleInversionPreference; private ColorInversionPreferenceController mInversionPreferenceController; private AccessibilityHearingAidPreferenceController mHearingAidPreferenceController; - private Preference mDarkUIModePreference; + private SwitchPreference mDarkUIModePreference; private DarkUIPreferenceController mDarkUIPreferenceController; private LiveCaptionPreferenceController mLiveCaptionPreferenceController; @@ -524,8 +524,8 @@ public class AccessibilitySettings extends SettingsPreferenceFragment implements mDarkUIModePreference = findPreference(DARK_UI_MODE_PREFERENCE); mDarkUIPreferenceController = new DarkUIPreferenceController(getContext(), DARK_UI_MODE_PREFERENCE); + mDarkUIPreferenceController.setParentFragment(this); mDarkUIPreferenceController.displayPreference(getPreferenceScreen()); - mDarkUIModePreference.setSummary(mDarkUIPreferenceController.getSummary()); } private void updateAllPreferences() { diff --git a/src/com/android/settings/display/DarkUIInfoDialogFragment.java b/src/com/android/settings/display/DarkUIInfoDialogFragment.java new file mode 100644 index 00000000000..8fca679081c --- /dev/null +++ b/src/com/android/settings/display/DarkUIInfoDialogFragment.java @@ -0,0 +1,93 @@ +/* + * 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.display; + +import static com.android.settings.display.DarkUIPreferenceController.DARK_MODE_PREFS; +import static com.android.settings.display.DarkUIPreferenceController.PREF_DARK_MODE_DIALOG_SEEN; + +import android.app.Dialog; +import android.app.UiModeManager; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; + +public class DarkUIInfoDialogFragment extends InstrumentedDialogFragment + implements DialogInterface.OnClickListener{ + + @Override + public int getMetricsCategory() { + // TODO(b/130251804): Add metrics constant in followup change to avoid merge conflict in + // beta cherrypick + return 0; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Context context = getContext(); + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + LayoutInflater inflater = LayoutInflater.from(dialog.getContext()); + View titleView = inflater.inflate(R.layout.settings_dialog_title, null); + ((ImageView) titleView.findViewById(R.id.settings_icon)) + .setImageDrawable(context.getDrawable(R.drawable.dark_theme)); + ((TextView) titleView.findViewById(R.id.settings_title)).setText(R.string.dark_ui_mode); + + dialog.setCustomTitle(titleView) + .setMessage(R.string.dark_ui_settings_dark_summary) + .setPositiveButton( + R.string.dark_ui_settings_dialog_acknowledge, + this); + return dialog.create(); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + enableDarkTheme(); + super.onDismiss(dialog); + } + + @Override + public void onClick(DialogInterface dialogInterface, int i) { + // We have to manually dismiss the dialog because changing night mode causes it to + // recreate itself. + dialogInterface.dismiss(); + enableDarkTheme(); + } + + private void enableDarkTheme() { + final Context context = getContext(); + if (context != null) { + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.DARK_MODE_DIALOG_SEEN, + DarkUIPreferenceController.DIALOG_SEEN); + context.getSystemService(UiModeManager.class) + .setNightMode(UiModeManager.MODE_NIGHT_YES); + } + } +} diff --git a/src/com/android/settings/display/DarkUIPreferenceController.java b/src/com/android/settings/display/DarkUIPreferenceController.java index 7d8fd56ae13..9df2402850e 100644 --- a/src/com/android/settings/display/DarkUIPreferenceController.java +++ b/src/com/android/settings/display/DarkUIPreferenceController.java @@ -18,37 +18,66 @@ package com.android.settings.display; import android.app.UiModeManager; import android.content.Context; +import android.provider.Settings; import androidx.annotation.VisibleForTesting; -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; +import androidx.fragment.app.Fragment; -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.TogglePreferenceController; -public class DarkUIPreferenceController extends BasePreferenceController { +public class DarkUIPreferenceController extends TogglePreferenceController { + public static final String DARK_MODE_PREFS = "dark_mode_prefs"; + public static final String PREF_DARK_MODE_DIALOG_SEEN = "dark_mode_dialog_seen"; + public static final int DIALOG_SEEN = 1; private UiModeManager mUiModeManager; + private Context mContext; + private Fragment mFragment; public DarkUIPreferenceController(Context context, String key) { super(context, key); + mContext = context; mUiModeManager = context.getSystemService(UiModeManager.class); } + @Override + public boolean isChecked() { + return mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES; + } + + @Override + public boolean setChecked(boolean isChecked) { + final boolean dialogSeen = + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.DARK_MODE_DIALOG_SEEN, 0) == DIALOG_SEEN; + if (!dialogSeen && isChecked) { + showDarkModeDialog(); + return false; + } + mUiModeManager.setNightMode(isChecked + ? UiModeManager.MODE_NIGHT_YES + : UiModeManager.MODE_NIGHT_NO); + return true; + } + + private void showDarkModeDialog() { + final DarkUIInfoDialogFragment frag = new DarkUIInfoDialogFragment(); + if (mFragment.getFragmentManager() != null) { + frag.show(mFragment.getFragmentManager(), getClass().getName()); + } + } + @VisibleForTesting void setUiModeManager(UiModeManager uiModeManager) { mUiModeManager = uiModeManager; } + public void setParentFragment(Fragment fragment) { + mFragment = fragment; + } + @Override public int getAvailabilityStatus() { return AVAILABLE; } - - @Override - public CharSequence getSummary() { - return DarkUISettingsRadioButtonsController.modeToDescription( - mContext, mUiModeManager.getNightMode()); - } } diff --git a/src/com/android/settings/display/DarkUISettings.java b/src/com/android/settings/display/DarkUISettings.java deleted file mode 100644 index 50fd3868c93..00000000000 --- a/src/com/android/settings/display/DarkUISettings.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.display; - -import android.app.UiModeManager; -import android.app.settings.SettingsEnums; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.provider.SearchIndexableResource; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; -import com.android.settings.R; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.Indexable; -import com.android.settings.widget.RadioButtonPickerFragment; -import com.android.settingslib.search.SearchIndexable; -import com.android.settingslib.widget.CandidateInfo; -import com.android.settingslib.widget.FooterPreference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * The screen for selecting the dark theme preference for this device. Automatically updates - * the associated footer view with any needed information. - */ -@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) -public class DarkUISettings extends RadioButtonPickerFragment implements Indexable { - - private DarkUISettingsRadioButtonsController mController; - private Preference mFooter; - - @Override - protected int getPreferenceScreenResId() { - return R.xml.dark_ui_settings; - } - - @Override - public void onAttach(Context context) { - super.onAttach(context); - // TODO(b/128686189): add illustration once it is ready - setIllustration(0, 0); - mFooter = new FooterPreference(context); - mFooter.setIcon(android.R.color.transparent); - mController = new DarkUISettingsRadioButtonsController(context, mFooter); - } - - @Override - protected List getCandidates() { - final Context context = getContext(); - final List candidates = new ArrayList<>(); - candidates.add(new DarkUISettingsCandidateInfo( - DarkUISettingsRadioButtonsController.modeToDescription( - context, UiModeManager.MODE_NIGHT_YES), - /* summary */ null, - DarkUISettingsRadioButtonsController.KEY_DARK, - /* enabled */ true)); - candidates.add(new DarkUISettingsCandidateInfo( - DarkUISettingsRadioButtonsController.modeToDescription( - context, UiModeManager.MODE_NIGHT_NO), - /* summary */ null, - DarkUISettingsRadioButtonsController.KEY_LIGHT, - /* enabled */ true)); - return candidates; - } - - @Override - protected void addStaticPreferences(PreferenceScreen screen) { - screen.addPreference(mFooter); - } - - @Override - protected String getDefaultKey() { - return mController.getDefaultKey(); - } - - @Override - protected boolean setDefaultKey(String key) { - return mController.setDefaultKey(key); - } - - @Override - public int getMetricsCategory() { - return SettingsEnums.DARK_UI_SETTINGS; - } - - static class DarkUISettingsCandidateInfo extends CandidateInfo { - - private final CharSequence mLabel; - private final CharSequence mSummary; - private final String mKey; - - DarkUISettingsCandidateInfo(CharSequence label, CharSequence summary, String key, - boolean enabled) { - super(enabled); - mLabel = label; - mKey = key; - mSummary = summary; - } - - @Override - public CharSequence loadLabel() { - return mLabel; - } - - @Override - public Drawable loadIcon() { - return null; - } - - @Override - public String getKey() { - return mKey; - } - - public CharSequence getSummary() { - return mSummary; - } - } - - public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getXmlResourcesToIndex( - Context context, boolean enabled) { - final SearchIndexableResource sir = new SearchIndexableResource(context); - sir.xmlResId = R.xml.dark_ui_settings; - return Arrays.asList(sir); - } - }; -} diff --git a/src/com/android/settings/display/DarkUISettingsRadioButtonsController.java b/src/com/android/settings/display/DarkUISettingsRadioButtonsController.java deleted file mode 100644 index 0fca306338c..00000000000 --- a/src/com/android/settings/display/DarkUISettingsRadioButtonsController.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.display; - -import android.app.UiModeManager; -import android.content.Context; -import androidx.preference.Preference; -import com.android.settings.R; -import androidx.annotation.VisibleForTesting; - -public class DarkUISettingsRadioButtonsController { - - public static final String KEY_DARK = "key_dark_ui_settings_dark"; - public static final String KEY_LIGHT = "key_dark_ui_settings_light"; - - @VisibleForTesting - UiModeManager mManager; - - private Preference mFooter; - - public DarkUISettingsRadioButtonsController(Context context, Preference footer) { - mManager = context.getSystemService(UiModeManager.class); - mFooter = footer; - } - - public String getDefaultKey() { - final int mode = mManager.getNightMode(); - updateFooter(); - return mode == UiModeManager.MODE_NIGHT_YES ? KEY_DARK : KEY_LIGHT; - } - - public boolean setDefaultKey(String key) { - switch(key) { - case KEY_DARK: - mManager.setNightMode(UiModeManager.MODE_NIGHT_YES); - break; - case KEY_LIGHT: - mManager.setNightMode(UiModeManager.MODE_NIGHT_NO); - break; - default: - throw new IllegalStateException( - "Not a valid key for " + this.getClass().getSimpleName() + ": " + key); - } - updateFooter(); - return true; - } - - public void updateFooter() { - final int mode = mManager.getNightMode(); - switch (mode) { - case UiModeManager.MODE_NIGHT_YES: - mFooter.setSummary(R.string.dark_ui_settings_dark_summary); - break; - case UiModeManager.MODE_NIGHT_NO: - case UiModeManager.MODE_NIGHT_AUTO: - default: - mFooter.setSummary(R.string.dark_ui_settings_light_summary); - } - } - - public static String modeToDescription(Context context, int mode) { - final String[] values = context.getResources().getStringArray(R.array.dark_ui_mode_entries); - switch (mode) { - case UiModeManager.MODE_NIGHT_YES: - return values[0]; - case UiModeManager.MODE_NIGHT_NO: - case UiModeManager.MODE_NIGHT_AUTO: - default: - return values[1]; - } - } -} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 0cc9dc43375..a07ffb9f505 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -154,19 +154,6 @@ public class AccessibilitySettingsTest { assertThat(preference.getSummary()).isEqualTo(mContext.getResources().getString(resId)); } - @Test - public void testDarkUIModePreferenceSummary_shouldUpdateSummary() { - final Preference darkUIModePreference = new Preference(mContext); - final DarkUIPreferenceController mController; - doReturn(darkUIModePreference).when(mSettings).findPreference( - DARK_UI_MODE_PREFERENCE); - mController = new DarkUIPreferenceController(mContext, DARK_UI_MODE_PREFERENCE); - final String darkUIModeDescription = modeToDescription(mUiModeManager.getNightMode()); - darkUIModePreference.setSummary(mController.getSummary()); - - assertThat(darkUIModePreference.getSummary()).isEqualTo(darkUIModeDescription); - } - private String modeToDescription(int mode) { String[] values = mContext.getResources().getStringArray(R.array.dark_ui_mode_entries); switch (mode) { diff --git a/tests/robotests/src/com/android/settings/display/DarkUIInfoDialogFragmentTest.java b/tests/robotests/src/com/android/settings/display/DarkUIInfoDialogFragmentTest.java new file mode 100644 index 00000000000..7a8bdedd63e --- /dev/null +++ b/tests/robotests/src/com/android/settings/display/DarkUIInfoDialogFragmentTest.java @@ -0,0 +1,65 @@ +/* + * 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.display; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class DarkUIInfoDialogFragmentTest { + private DarkUIInfoDialogFragment mFragment; + @Mock + private DialogInterface dialog; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mFragment = spy(new DarkUIInfoDialogFragment()); + } + + @Test + public void dialogDismissedOnConfirmation() { + doReturn(RuntimeEnvironment.application).when(mFragment).getContext(); + SharedPreferences prefs = RuntimeEnvironment.application.getSharedPreferences( + DarkUIPreferenceController.DARK_MODE_PREFS, + Context.MODE_PRIVATE); + assertThat(prefs.getBoolean(DarkUIPreferenceController.PREF_DARK_MODE_DIALOG_SEEN, false)) + .isFalse(); + mFragment.onClick(dialog, DialogInterface.BUTTON_POSITIVE); + verify(dialog, times(1)).dismiss(); + assertThat(prefs.getBoolean(DarkUIPreferenceController.PREF_DARK_MODE_DIALOG_SEEN, false)) + .isTrue(); + + } +} diff --git a/tests/robotests/src/com/android/settings/display/DarkUISettingsRadioButtonsControllerTest.java b/tests/robotests/src/com/android/settings/display/DarkUISettingsRadioButtonsControllerTest.java deleted file mode 100644 index 76142a42e60..00000000000 --- a/tests/robotests/src/com/android/settings/display/DarkUISettingsRadioButtonsControllerTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.android.settings.display; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; - -import android.app.UiModeManager; -import android.content.Context; -import androidx.preference.Preference; -import com.android.settings.R; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -@RunWith(RobolectricTestRunner.class) -public class DarkUISettingsRadioButtonsControllerTest { - - @Mock - private UiModeManager mUiModeManager; - @Mock - private Preference mFooter; - private Context mContext; - private DarkUISettingsRadioButtonsController mController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mController = new DarkUISettingsRadioButtonsController(mContext, mFooter); - mController.mManager = mUiModeManager; - } - - @Test - public void footerUpdatesCorrectly() { - doReturn(UiModeManager.MODE_NIGHT_YES).when(mUiModeManager).getNightMode(); - mController.updateFooter(); - verify(mFooter).setSummary(eq(R.string.dark_ui_settings_dark_summary)); - - doReturn(UiModeManager.MODE_NIGHT_NO).when(mUiModeManager).getNightMode(); - mController.updateFooter(); - verify(mFooter).setSummary(eq(R.string.dark_ui_settings_light_summary)); - } - - public int getCurrentMode() { - final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class); - return uiModeManager.getNightMode(); - } -} From 994929ecc7b5470c30db9255e0981b8b538d6a96 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Tue, 16 Apr 2019 16:46:57 +0800 Subject: [PATCH 18/25] Hide 'Share' button and 'Add device' preference for a Passpoint Wi-Fi network Passpoint provisioning is much different from other Wi-Fi networks and ZXing does not support a QR code format to share a passpoint Wi-Fi network. We should not show any button to share a passpoint Wi-Fi network. Bug: 130609037 Test: manual Change-Id: Ie2538ed9e030e5aa2e88452b50b6592223ca46a8 --- .../WifiDetailPreferenceController.java | 28 +++++++++++-------- .../settings/wifi/dpp/WifiDppUtils.java | 6 ++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index 5706e9be1cf..238143ea861 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -404,9 +404,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController .setButton3Enabled(true) .setButton4Text(R.string.share) .setButton4Icon(R.drawable.ic_qrcode_24dp) - .setButton4OnClickListener(view -> shareNetwork()) - .setButton4Visible( - WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint)); + .setButton4OnClickListener(view -> shareNetwork()); mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF); mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED); @@ -739,14 +737,19 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController mButtonsPref.setButton1Text( mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget); - mButtonsPref.setButton1Visible(canForgetNetwork()); - mButtonsPref.setButton2Visible(canSignIntoNetwork()); - mButtonsPref.setButton3Visible(canConnectNetwork()); - mButtonsPref.setButton4Visible(canShareNetwork()); - mButtonsPref.setVisible(canSignIntoNetwork() - || canForgetNetwork() - || canShareNetwork() - || canConnectNetwork()); + boolean canForgetNetwork = canForgetNetwork(); + boolean canSignIntoNetwork = canSignIntoNetwork(); + boolean canConnectNetwork = canConnectNetwork(); + boolean canShareNetwork = canShareNetwork(); + + mButtonsPref.setButton1Visible(canForgetNetwork); + mButtonsPref.setButton2Visible(canSignIntoNetwork); + mButtonsPref.setButton3Visible(canConnectNetwork); + mButtonsPref.setButton4Visible(canShareNetwork); + mButtonsPref.setVisible(canForgetNetwork + || canSignIntoNetwork + || canConnectNetwork + || canShareNetwork); } private boolean canConnectNetwork() { @@ -843,7 +846,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController * Returns whether the user can share the network represented by this preference with QR code. */ private boolean canShareNetwork() { - return mAccessPoint.getConfig() != null; + return mAccessPoint.getConfig() != null && + WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint); } /** diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index 98673d4911f..4644f125017 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -345,6 +345,9 @@ public class WifiDppUtils { */ public static boolean isSupportConfiguratorQrCodeScanner(Context context, AccessPoint accessPoint) { + if (accessPoint.isPasspoint()) { + return false; + } return isSupportWifiDpp(context, accessPoint.getSecurity()); } @@ -356,6 +359,9 @@ public class WifiDppUtils { */ public static boolean isSupportConfiguratorQrCodeGenerator(Context context, AccessPoint accessPoint) { + if (accessPoint.isPasspoint()) { + return false; + } return isSupportZxing(context, accessPoint.getSecurity()); } From 9a7ead8962723692181012c3c7b73476190707a9 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Mon, 15 Apr 2019 14:52:08 +0800 Subject: [PATCH 19/25] Hide filter spinner when data is loading. When the filter spinner is initialized, set GONE to its visibility. Only make it visible after network cycle data finishes loading to avoid presenting an empty filter spinner. Fixes: 130397929 Test: robotests Change-Id: I740fdad35ec82bc0be386c24e53348917bf6bfba --- .../settings/datausage/DataUsageList.java | 21 +++-- .../settings/datausage/DataUsageListTest.java | 82 ++++++++++++++++--- 2 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java index e5158ffd63a..96a1e22c6a0 100644 --- a/src/com/android/settings/datausage/DataUsageList.java +++ b/src/com/android/settings/datausage/DataUsageList.java @@ -99,22 +99,23 @@ public class DataUsageList extends DataUsageBaseFragment { } }; - private ChartDataUsagePreference mChart; - private TelephonyManager mTelephonyManager; - @VisibleForTesting NetworkTemplate mTemplate; @VisibleForTesting int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; @VisibleForTesting int mNetworkType; + @VisibleForTesting + Spinner mCycleSpinner; + @VisibleForTesting + LoadingViewController mLoadingViewController; + + private ChartDataUsagePreference mChart; + private TelephonyManager mTelephonyManager; private List mCycleData; private ArrayList mCycles; - - private LoadingViewController mLoadingViewController; private UidDetailProvider mUidDetailProvider; private CycleAdapter mCycleAdapter; - private Spinner mCycleSpinner; private Preference mUsageAmount; private PreferenceGroup mApps; private View mHeader; @@ -158,6 +159,7 @@ public class DataUsageList extends DataUsageBaseFragment { .launch(); }); mCycleSpinner = mHeader.findViewById(R.id.filter_spinner); + mCycleSpinner.setVisibility(View.GONE); mCycleAdapter = new CycleAdapter(mCycleSpinner.getContext(), new SpinnerInterface() { @Override public void setAdapter(CycleAdapter cycleAdapter) { @@ -276,7 +278,8 @@ public class DataUsageList extends DataUsageBaseFragment { * Update chart sweeps and cycle list to reflect {@link NetworkPolicy} for * current {@link #mTemplate}. */ - private void updatePolicy() { + @VisibleForTesting + void updatePolicy() { final NetworkPolicy policy = services.mPolicyEditor.getPolicy(mTemplate); final View configureButton = mHeader.findViewById(R.id.filter_settings); //SUB SELECT @@ -486,7 +489,8 @@ public class DataUsageList extends DataUsageBaseFragment { } }; - private final LoaderCallbacks> mNetworkCycleDataCallbacks = + @VisibleForTesting + final LoaderCallbacks> mNetworkCycleDataCallbacks = new LoaderCallbacks>() { @Override public Loader> onCreateLoader(int id, Bundle args) { @@ -503,6 +507,7 @@ public class DataUsageList extends DataUsageBaseFragment { mCycleData = data; // calculate policy cycles based on available data updatePolicy(); + mCycleSpinner.setVisibility(View.VISIBLE); } @Override diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java index 7d042ce3c44..9aa1c6f1497 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageListTest.java @@ -18,22 +18,32 @@ package com.android.settings.datausage; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; +import android.app.Activity; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkTemplate; import android.os.Bundle; import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; import android.widget.Spinner; +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceManager; + +import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.widget.LoadingViewController; import com.android.settingslib.AppItem; import com.android.settingslib.NetworkPolicyEditor; import com.android.settingslib.core.instrumentation.VisibilityLoggerMixin; @@ -45,15 +55,14 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ActivityController; import org.robolectric.util.ReflectionHelpers; import java.util.ArrayList; import java.util.List; -import androidx.fragment.app.FragmentActivity; -import androidx.preference.PreferenceManager; - @RunWith(RobolectricTestRunner.class) public class DataUsageListTest { @@ -61,18 +70,21 @@ public class DataUsageListTest { private CellDataPreference.DataStateListener mListener; @Mock private TemplatePreference.NetworkServices mNetworkServices; - @Mock - private Context mContext; + + private Activity mActivity; private DataUsageList mDataUsageList; @Before public void setUp() { MockitoAnnotations.initMocks(this); FakeFeatureFactory.setupForTest(); + final ActivityController mActivityController = + Robolectric.buildActivity(Activity.class); + mActivity = spy(mActivityController.get()); mNetworkServices.mPolicyEditor = mock(NetworkPolicyEditor.class); mDataUsageList = spy(DataUsageList.class); - doReturn(mContext).when(mDataUsageList).getContext(); + doReturn(mActivity).when(mDataUsageList).getContext(); ReflectionHelpers.setField(mDataUsageList, "mDataStateListener", mListener); ReflectionHelpers.setField(mDataUsageList, "services", mNetworkServices); } @@ -86,11 +98,11 @@ public class DataUsageListTest { mDataUsageList.onResume(); - verify(mListener).setListener(true, mDataUsageList.mSubId, mContext); + verify(mListener).setListener(true, mDataUsageList.mSubId, mActivity); mDataUsageList.onPause(); - verify(mListener).setListener(false, mDataUsageList.mSubId, mContext); + verify(mListener).setListener(false, mDataUsageList.mSubId, mActivity); } @Test @@ -140,7 +152,7 @@ public class DataUsageListTest { final List data = new ArrayList<>(); final NetworkCycleChartData.Builder builder = new NetworkCycleChartData.Builder(); builder.setStartTime(startTime) - .setEndTime(endTime); + .setEndTime(endTime); data.add(builder.build()); ReflectionHelpers.setField(mDataUsageList, "mCycleData", data); final Spinner spinner = mock(Spinner.class); @@ -150,14 +162,58 @@ public class DataUsageListTest { mDataUsageList.startAppDataUsage(new AppItem()); - verify(mContext).startActivity(intent.capture()); + verify(mActivity).startActivity(intent.capture()); final Bundle arguments = - intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); + intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); assertThat(arguments.getLong(AppDataUsage.ARG_SELECTED_CYCLE)).isEqualTo(endTime); final ArrayList cycles = - (ArrayList) arguments.getSerializable(AppDataUsage.ARG_NETWORK_CYCLES); + (ArrayList) arguments.getSerializable(AppDataUsage.ARG_NETWORK_CYCLES); assertThat(cycles).hasSize(2); assertThat(cycles.get(0)).isEqualTo(endTime); assertThat(cycles.get(1)).isEqualTo(startTime); } + + @Test + public void onViewCreated_shouldHideCycleSpinner() { + final View view = new View(mActivity); + final View header = getHeader(); + final Spinner spinner = getSpinner(header); + spinner.setVisibility(View.VISIBLE); + doReturn(header).when(mDataUsageList).setPinnedHeaderView(anyInt()); + doReturn(view).when(mDataUsageList).getView(); + + mDataUsageList.onViewCreated(view, null); + + assertThat(spinner.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onLoadFinished_networkCycleDataCallback_shouldShowCycleSpinner() { + final LoadingViewController loadingViewController = mock(LoadingViewController.class); + mDataUsageList.mLoadingViewController = loadingViewController; + final Spinner spinner = getSpinner(getHeader()); + spinner.setVisibility(View.INVISIBLE); + mDataUsageList.mCycleSpinner = spinner; + assertThat(spinner.getVisibility()).isEqualTo(View.INVISIBLE); + doNothing().when(mDataUsageList).updatePolicy(); + + mDataUsageList.mNetworkCycleDataCallbacks.onLoadFinished(null, null); + + assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE); + } + + private View getHeader() { + final View rootView = LayoutInflater.from(mActivity) + .inflate(R.layout.preference_list_fragment, null, false); + final FrameLayout pinnedHeader = rootView.findViewById(R.id.pinned_header); + final View header = mActivity.getLayoutInflater() + .inflate(R.layout.apps_filter_spinner, pinnedHeader, false); + + return header; + } + + private Spinner getSpinner(View header) { + final Spinner spinner = header.findViewById(R.id.filter_spinner); + return spinner; + } } From 006582b48e770a93dff9e9eb947478612b4902aa Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Mon, 15 Apr 2019 15:24:38 +0800 Subject: [PATCH 20/25] Tint battery fix icon Fixes: 129492004 Test: manual Change-Id: I161284619bc47e342d78f1507ae291f7c23ad3ff --- .../fuelgauge/batterytip/tips/BatteryTip.java | 2 +- .../slices/BatteryFixSlice.java | 12 ++- .../slices/BatteryFixSliceTest.java | 83 +++++++++++++++++-- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 0867a012602..ebc493967e4 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -160,7 +160,7 @@ public abstract class BatteryTip implements Comparable, Parcelable { } /** Returns the color resid for tinting {@link #getIconId()} or {@link View#NO_ID} if none. */ - protected @IdRes int getIconTintColorId() { + public @IdRes int getIconTintColorId() { return View.NO_ID; } diff --git a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java index a75f99a54fb..761755c60fe 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSlice.java @@ -25,6 +25,9 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.util.ArrayMap; @@ -39,6 +42,7 @@ import androidx.slice.builders.SliceAction; import com.android.internal.os.BatteryStatsHelper; import com.android.settings.R; import com.android.settings.SubSettings; +import com.android.settings.Utils; import com.android.settings.fuelgauge.BatteryStatsHelperLoader; import com.android.settings.fuelgauge.PowerUsageSummary; import com.android.settings.fuelgauge.batterytip.BatteryTipLoader; @@ -107,8 +111,12 @@ public class BatteryFixSlice implements CustomSliceable { if (batteryTip.getState() == BatteryTip.StateType.INVISIBLE) { continue; } - final IconCompat icon = IconCompat.createWithResource(mContext, - batteryTip.getIconId()); + final Drawable drawable = mContext.getDrawable(batteryTip.getIconId()); + drawable.setColorFilter(new PorterDuffColorFilter( + mContext.getResources().getColor(batteryTip.getIconTintColorId()), + PorterDuff.Mode.SRC_IN)); + + final IconCompat icon = Utils.createIconWithDrawable(drawable); final SliceAction primaryAction = SliceAction.createDeeplink(getPrimaryAction(), icon, ListBuilder.ICON_IMAGE, diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java index 1027cf1d0c5..dcfba42c787 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BatteryFixSliceTest.java @@ -18,7 +18,11 @@ package com.android.settings.homepage.contextualcards.slices; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + import android.content.Context; +import android.net.Uri; import androidx.slice.Slice; import androidx.slice.SliceMetadata; @@ -26,11 +30,13 @@ import androidx.slice.SliceProvider; import androidx.slice.widget.SliceLiveData; import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.R; import com.android.settings.fuelgauge.BatteryStatsHelperLoader; import com.android.settings.fuelgauge.batterytip.BatteryTipLoader; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.EarlyWarningTip; import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip; +import com.android.settings.slices.SliceBackgroundWorker; import org.junit.After; import org.junit.Before; @@ -48,6 +54,10 @@ import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) +@Config(shadows = { + BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class, + BatteryFixSliceTest.ShadowBatteryTipLoader.class +}) public class BatteryFixSliceTest { private Context mContext; @@ -66,13 +76,11 @@ public class BatteryFixSliceTest { @After public void tearDown() { ShadowBatteryTipLoader.reset(); + ShadowSliceBackgroundWorker.reset(); + ShadowEarlyWarningTip.reset(); } @Test - @Config(shadows = { - ShadowBatteryStatsHelperLoader.class, - ShadowBatteryTipLoader.class - }) public void updateBatteryTipAvailabilityCache_hasImportantTip_shouldReturnTrue() { final List tips = new ArrayList<>(); tips.add(new LowBatteryTip(BatteryTip.StateType.INVISIBLE, false, "")); @@ -85,10 +93,6 @@ public class BatteryFixSliceTest { } @Test - @Config(shadows = { - ShadowBatteryStatsHelperLoader.class, - ShadowBatteryTipLoader.class - }) public void getSlice_unimportantSlice_shouldSkip() { final List tips = new ArrayList<>(); tips.add(new LowBatteryTip(BatteryTip.StateType.INVISIBLE, false, "")); @@ -101,6 +105,28 @@ public class BatteryFixSliceTest { assertThat(SliceMetadata.from(mContext, slice).isErrorSlice()).isTrue(); } + @Test + @Config(shadows = { + BatteryFixSliceTest.ShadowEarlyWarningTip.class, + BatteryFixSliceTest.ShadowSliceBackgroundWorker.class + }) + public void getSlice_hasImportantTip_shouldTintIcon() { + final List tips = new ArrayList<>(); + tips.add(new EarlyWarningTip(BatteryTip.StateType.NEW, false)); + // Create fake cache data + ShadowBatteryTipLoader.setBatteryTips(tips); + BatteryFixSlice.updateBatteryTipAvailabilityCache(mContext); + // Create fake background worker data + BatteryFixSlice.BatteryTipWorker batteryTipWorker = mock( + BatteryFixSlice.BatteryTipWorker.class); + when(batteryTipWorker.getResults()).thenReturn(tips); + ShadowSliceBackgroundWorker.setBatteryTipWorkerWorker(batteryTipWorker); + + final Slice slice = mSlice.getSlice(); + + assertThat(ShadowEarlyWarningTip.isIconTintColorIdCalled()).isTrue(); + } + @Implements(BatteryStatsHelperLoader.class) public static class ShadowBatteryStatsHelperLoader { @@ -129,4 +155,45 @@ public class BatteryFixSliceTest { sBatteryTips = tips; } } + + @Implements(SliceBackgroundWorker.class) + public static class ShadowSliceBackgroundWorker { + + private static BatteryFixSlice.BatteryTipWorker sBatteryTipWorkerWorker; + + @Resetter + public static void reset() { + sBatteryTipWorkerWorker = null; + } + + @Implementation + protected static T getInstance(Uri uri) { + return (T) sBatteryTipWorkerWorker; + } + + public static void setBatteryTipWorkerWorker(BatteryFixSlice.BatteryTipWorker worker) { + sBatteryTipWorkerWorker = worker; + } + } + + @Implements(EarlyWarningTip.class) + public static class ShadowEarlyWarningTip { + + private static boolean mIsGetIconTintColorIdCalled; + + @Resetter + public static void reset() { + mIsGetIconTintColorIdCalled = false; + } + + @Implementation + protected int getIconTintColorId() { + mIsGetIconTintColorIdCalled = true; + return R.color.battery_bad_color_light; + } + + public static boolean isIconTintColorIdCalled() { + return mIsGetIconTintColorIdCalled; + } + } } From ce3359f4d4ddeec0c3fae6e951f033539b18625e Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Tue, 16 Apr 2019 23:08:40 +0800 Subject: [PATCH 21/25] Clean up NotificationChannelSlice - Remove unused variable. - Change function interface. Bug: 123065955 Test: visual, robotests Change-Id: I39c0a92539d45cdbc11bd9090a3ab97a72482632 --- .../slices/NotificationChannelSlice.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java index e5cee37abcf..07fc8996d96 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java @@ -154,16 +154,12 @@ public class NotificationChannelSlice implements CustomSliceable { // TODO(b/123065955): Review latency of NotificationChannelSlice final List multiChannelPackages = getMultiChannelPackages( getRecentlyInstalledPackages()); - final PackageInfo packageInfo = getMaxSentNotificationsPackage(multiChannelPackages); - - // Return a header with IsError flag, if package is not found. - if (packageInfo == null) { + mPackageName = getMaxSentNotificationsPackage(multiChannelPackages); + if (mPackageName == null) { + // Return a header with IsError flag, if package is not found. return listBuilder.setHeader(getNoSuggestedAppHeader()) .setIsError(true).build(); } - - // Save eligible package name and its uid, they will be used in getIntent(). - mPackageName = packageInfo.packageName; mUid = getApplicationUid(mPackageName); // Add notification channel header. @@ -177,7 +173,7 @@ public class NotificationChannelSlice implements CustomSliceable { .setPrimaryAction(getPrimarySliceAction(icon, title, getIntent()))); // Add notification channel rows. - final List rows = getNotificationChannelRows(packageInfo, icon); + final List rows = getNotificationChannelRows(icon); for (ListBuilder.RowBuilder rowBuilder : rows) { listBuilder.addRow(rowBuilder); } @@ -282,8 +278,7 @@ public class NotificationChannelSlice implements CustomSliceable { .setPrimaryAction(primarySliceActionForNoSuggestedApp); } - private List getNotificationChannelRows(PackageInfo packageInfo, - IconCompat icon) { + private List getNotificationChannelRows(IconCompat icon) { final List notificationChannelRows = new ArrayList<>(); final List displayableChannels = getDisplayableChannels(mAppRow); @@ -388,14 +383,14 @@ public class NotificationChannelSlice implements CustomSliceable { .collect(Collectors.toList()); } - private PackageInfo getMaxSentNotificationsPackage(List packageInfoList) { + private String getMaxSentNotificationsPackage(List packageInfoList) { if (packageInfoList.isEmpty()) { return null; } // Get the package which has sent at least ~10 notifications and not turn off channels. int maxSentCount = 0; - PackageInfo maxSentCountPackage = null; + String maxSentCountPackage = null; for (PackageInfo packageInfo : packageInfoList) { final NotificationBackend.AppRow appRow = mNotificationBackend.loadAppRow(mContext, mContext.getPackageManager(), packageInfo); @@ -408,7 +403,7 @@ public class NotificationChannelSlice implements CustomSliceable { final int sentCount = appRow.sentByApp.sentCount; if (sentCount >= MIN_NOTIFICATION_SENT_COUNT && sentCount > maxSentCount) { maxSentCount = sentCount; - maxSentCountPackage = packageInfo; + maxSentCountPackage = packageInfo.packageName; mAppRow = appRow; } } From 22f39444320d6033e4a8b88e66b9965831fe0e46 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 16 Apr 2019 16:23:02 +0000 Subject: [PATCH 22/25] Revert "Fix voice access issue on home page" This reverts commit 0f3049d4b74bdfe0a0d0894c4575360371b8cec5. Reason for revert: b/129456622 Fixes: 129456622 Test: rebuild Change-Id: Ic883722a9ebe6a2905067dc9cc503c9878acd2fc --- res/layout/settings_homepage_container.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/res/layout/settings_homepage_container.xml b/res/layout/settings_homepage_container.xml index c86bccfdd59..856bd8085b7 100644 --- a/res/layout/settings_homepage_container.xml +++ b/res/layout/settings_homepage_container.xml @@ -25,7 +25,6 @@ android:id="@+id/main_content_scrollable_container" android:layout_width="match_parent" android:layout_height="match_parent" - android:importantForAccessibility="no" app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior"> Date: Fri, 8 Mar 2019 08:15:00 -0800 Subject: [PATCH 23/25] Add entry animation to Settings Panels. Settings Panels as a dialog have a default animation for entering the screen, but Slices complicate the animation. While the dialog enters the screen, Slices begin to bind, thus changing the height of the dialog as it enters, causing perceived bounce / jank in the animation. This CL is cherry-picked/based on ag/6671083 but do the following modification: (See the original commit message for the whole concept) When trying to load all the Slices, there are few possible situations: 1. Slice starts loading slowly, starting at state LOADED_NONE 2. Slice is loading in progress, having state LOADED_PARTIAL 3. Slice is loaded, but there's error return from the Slice data (We don't need to show the Slice in this case) 4. Slice is loaded, progress to state LOADED_ALL 5. Slice starts from state LOADED_NONE, but never progress to the next state because it crashes at setting backend. Notice that there are two cases that the state will stay at LOADED_NONE and we can't distinguish them. Hence, we decide to do the following: If Slice is with error (case 3) we remove the slice from the list and mark it loaded. If Slice is loaded with LOADED_ALL (case 4, which is the ideal case), we mark it as loaded. In the other cases, we fire a handler to mark the slice loaded anyway after 250ms timeout. When all the slices are marked loaded (which should happen after 250ms timeout, we will animate the panel out. Although there might be slices which are still partial loaded, we can still have the slice in the panel once it is ready. The panel might bounce/jank in this case, but at least it will still showing correctly, and should show up smoothly in most cases. The solution to this problem is twofold: 1. Load all Slices first 2. Create a custom animation to draw the panel once the recyclerview has been laid out. Test: Manual/Visual inspection Test: make -j40 RunSettingsRobotests Bug: 123942159 Change-Id: I639a707aa4ba3f906bd6f9752c92727aaba28142 --- res/layout/panel_layout.xml | 88 ++++---- res/layout/settings_panel.xml | 5 +- res/values/themes.xml | 2 +- .../android/settings/panel/PanelFragment.java | 192 ++++++++++++++++-- .../settings/panel/PanelSlicesAdapter.java | 49 ++--- .../PanelSlicesLoaderCountdownLatch.java | 83 ++++++++ .../settings/panel/PanelFragmentTest.java | 11 +- .../panel/PanelSlicesAdapterTest.java | 83 ++++---- .../PanelSlicesLoaderCountdownLatchTest.java | 92 +++++++++ 9 files changed, 462 insertions(+), 143 deletions(-) create mode 100644 src/com/android/settings/panel/PanelSlicesLoaderCountdownLatch.java create mode 100644 tests/robotests/src/com/android/settings/panel/PanelSlicesLoaderCountdownLatchTest.java diff --git a/res/layout/panel_layout.xml b/res/layout/panel_layout.xml index 3a8045fc2a5..c697afc56f8 100644 --- a/res/layout/panel_layout.xml +++ b/res/layout/panel_layout.xml @@ -15,50 +15,60 @@ limitations under the License --> - - - - - - + + android:orientation="vertical"> -