diff --git a/src/com/android/settings/display/AlwaysOnDisplaySlice.java b/src/com/android/settings/display/AlwaysOnDisplaySlice.java new file mode 100644 index 00000000000..27374ef26e8 --- /dev/null +++ b/src/com/android/settings/display/AlwaysOnDisplaySlice.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 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 android.provider.Settings.Secure.DOZE_ALWAYS_ON; +import static android.provider.Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE; + +import android.annotation.ColorInt; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.display.AmbientDisplayConfiguration; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.aware.AwareFeatureProvider; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.slices.CustomSliceRegistry; +import com.android.settings.slices.CustomSliceable; + +/** + * Custom {@link Slice} for Always on Display. + *

+ * We make a custom slice instead of using {@link AmbientDisplayAlwaysOnPreferenceController} + * because the controller will be unavailable if devices support aware sensor, and thus + * can not convert to slice. + *

+ * + */ +public class AlwaysOnDisplaySlice implements CustomSliceable { + private static final int MY_USER = UserHandle.myUserId(); + + private final Context mContext; + private final AmbientDisplayConfiguration mConfig; + private final AwareFeatureProvider mFeatureProvider; + + public AlwaysOnDisplaySlice(Context context) { + mContext = context; + mConfig = new AmbientDisplayConfiguration(mContext); + mFeatureProvider = FeatureFactory.getFactory(context).getAwareFeatureProvider(); + } + + @Override + public Slice getSlice() { + if (!mConfig.alwaysOnAvailableForUser(MY_USER)) { + return null; + } + + final PendingIntent toggleAction = getBroadcastIntent(mContext); + @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); + final boolean isChecked = mConfig.alwaysOnEnabled(MY_USER); + + return new ListBuilder(mContext, CustomSliceRegistry.ALWAYS_ON_SLICE_URI, + ListBuilder.INFINITY) + .setAccentColor(color) + .addRow(new ListBuilder.RowBuilder() + .setTitle(mContext.getText(R.string.doze_always_on_title)) + .setSubtitle(mContext.getText(R.string.doze_always_on_summary)) + .setPrimaryAction( + SliceAction.createToggle(toggleAction, null /* actionTitle */, + isChecked))) + .build(); + } + + @Override + public Uri getUri() { + return CustomSliceRegistry.ALWAYS_ON_SLICE_URI; + } + + @Override + public void onNotifyChange(Intent intent) { + final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, + false); + final ContentResolver resolver = mContext.getContentResolver(); + final boolean isAwareSupported = mFeatureProvider.isSupported(mContext); + final boolean isAwareEnabled = mFeatureProvider.isEnabled(mContext); + + Settings.Secure.putInt(resolver, DOZE_ALWAYS_ON, isChecked ? 1 : 0); + Settings.Secure.putInt(resolver, DOZE_WAKE_DISPLAY_GESTURE, + (isAwareEnabled && isAwareSupported && isChecked) ? 1 : 0); + } + + @Override + public Intent getIntent() { + return null; + } +} diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index 78a2943b1ef..6538879db05 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import androidx.annotation.VisibleForTesting; import com.android.settings.display.AdaptiveSleepPreferenceController; +import com.android.settings.display.AlwaysOnDisplaySlice; import com.android.settings.flashlight.FlashlightSlice; import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice; @@ -324,6 +325,16 @@ public class CustomSliceRegistry { .appendPath(MediaOutputSliceConstants.KEY_REMOTE_MEDIA) .build(); + /** + * Backing Uri for the Always On Slice. + */ + public static final Uri ALWAYS_ON_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("always_on_display") + .build(); + @VisibleForTesting static final Map> sUriToSlice; @@ -349,6 +360,7 @@ public class CustomSliceRegistry { sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class); sUriToSlice.put(MEDIA_OUTPUT_GROUP_SLICE_URI, MediaOutputGroupSlice.class); + sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class); } public static Class getSliceClassByUri(Uri uri) { diff --git a/tests/robotests/src/com/android/settings/display/AlwaysOnDisplaySliceTest.java b/tests/robotests/src/com/android/settings/display/AlwaysOnDisplaySliceTest.java new file mode 100644 index 00000000000..217f92122a9 --- /dev/null +++ b/tests/robotests/src/com/android/settings/display/AlwaysOnDisplaySliceTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2020 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 android.provider.Settings.Secure.DOZE_ALWAYS_ON; +import static android.provider.Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.hardware.display.AmbientDisplayConfiguration; +import android.net.Uri; +import android.provider.Settings; + +import androidx.slice.Slice; +import androidx.slice.SliceMetadata; +import androidx.slice.SliceProvider; +import androidx.slice.widget.SliceLiveData; + +import com.android.settings.R; +import com.android.settings.aware.AwareFeatureProvider; +import com.android.settings.slices.CustomSliceRegistry; +import com.android.settings.testutils.FakeFeatureFactory; + +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; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +public class AlwaysOnDisplaySliceTest { + + private Context mContext; + private AlwaysOnDisplaySlice mSlice; + private FakeFeatureFactory mFeatureFactory; + private AwareFeatureProvider mFeatureProvider; + + @Mock + private AmbientDisplayConfiguration mConfig; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mFeatureProvider = mFeatureFactory.getAwareFeatureProvider(); + + // Set-up specs for SliceMetadata. + SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mSlice = new AlwaysOnDisplaySlice(mContext); + ReflectionHelpers.setField(mSlice, "mConfig", mConfig); + } + + @Test + public void getUri_shouldReturnCorrectSliceUri() { + final Uri uri = mSlice.getUri(); + + assertThat(uri).isEqualTo(CustomSliceRegistry.ALWAYS_ON_SLICE_URI); + } + + @Test + public void getSlice_alwaysOnNotSupported_returnNull() { + when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(false); + + final Slice slice = mSlice.getSlice(); + + assertThat(slice).isNull(); + } + + @Test + public void getSlice_alwaysOnSupported_showTitleSubtitle() { + when(mConfig.alwaysOnAvailableForUser(anyInt())).thenReturn(true); + + final Slice slice = mSlice.getSlice(); + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + + assertThat(metadata.getTitle()).isEqualTo( + mContext.getString(R.string.doze_always_on_title)); + assertThat(metadata.getSubtitle()).isEqualTo( + mContext.getString(R.string.doze_always_on_summary)); + } + + @Test + public void onNotifyChange_toggleOff_disableAoD() { + final Intent intent = new Intent(); + intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, false); + + mSlice.onNotifyChange(intent); + + final ContentResolver resolver = mContext.getContentResolver(); + assertThat(Settings.Secure.getInt(resolver, DOZE_ALWAYS_ON, 0)).isEqualTo(0); + assertThat(Settings.Secure.getInt(resolver, DOZE_WAKE_DISPLAY_GESTURE, 0)).isEqualTo(0); + } + + @Test + public void onNotifyChange_toggleOn_awareNotSupported_enableAoD() { + final Intent intent = new Intent(); + intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, true); + when(mFeatureProvider.isEnabled(mContext)).thenReturn(false); + when(mFeatureProvider.isSupported(mContext)).thenReturn(false); + + mSlice.onNotifyChange(intent); + + final ContentResolver resolver = mContext.getContentResolver(); + assertThat(Settings.Secure.getInt(resolver, DOZE_ALWAYS_ON, 0)).isEqualTo(1); + assertThat(Settings.Secure.getInt(resolver, DOZE_WAKE_DISPLAY_GESTURE, 0)).isEqualTo(0); + } + + @Test + public void onNotifyChange_toggleOn_awareDisabled_enableAoD() { + final Intent intent = new Intent(); + intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, true); + when(mFeatureProvider.isEnabled(mContext)).thenReturn(false); + when(mFeatureProvider.isSupported(mContext)).thenReturn(true); + + mSlice.onNotifyChange(intent); + + final ContentResolver resolver = mContext.getContentResolver(); + assertThat(Settings.Secure.getInt(resolver, DOZE_ALWAYS_ON, 0)).isEqualTo(1); + assertThat(Settings.Secure.getInt(resolver, DOZE_WAKE_DISPLAY_GESTURE, 0)).isEqualTo(0); + } + + @Test + public void onNotifyChange_toggleOn_awareSupported_enableAoD() { + final Intent intent = new Intent(); + intent.putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, true); + when(mFeatureProvider.isEnabled(mContext)).thenReturn(true); + when(mFeatureProvider.isSupported(mContext)).thenReturn(true); + + mSlice.onNotifyChange(intent); + + final ContentResolver resolver = mContext.getContentResolver(); + assertThat(Settings.Secure.getInt(resolver, DOZE_ALWAYS_ON, 0)).isEqualTo(1); + assertThat(Settings.Secure.getInt(resolver, DOZE_WAKE_DISPLAY_GESTURE, 0)).isEqualTo(1); + } +}