Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit beffb797 authored by android-build-team Robot's avatar android-build-team Robot
Browse files

Snap for 6018910 from 713143df to qt-qpr2-release

Change-Id: I1e10a22c28307436ceb1a136ad8cd5e3eb6b3292
parents ed904949 713143df
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -10132,6 +10132,12 @@
    <!-- [CHAR_LIMIT=40] Positive button text in dark theme notification -->
    <!-- [CHAR_LIMIT=40] Positive button text in dark theme notification -->
    <string name="dark_ui_settings_dialog_acknowledge">Got it</string>
    <string name="dark_ui_settings_dialog_acknowledge">Got it</string>
    <!-- [CHAR_LIMIT=50] Title string in the dark theme slice(suggestion) -->
    <string name="dark_theme_slice_title">Try Dark theme</string>
    <!-- [CHAR_LIMIT=50] Subtitle string in the dark theme slice(suggestion) -->
    <string name="dark_theme_slice_subtitle">Helps extend battery life</string>
    <!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
    <!-- [CHAR LIMIT=60] Name of dev option to enable extra quick settings tiles -->
    <string name="quick_settings_developer_tiles">Quick settings developer tiles</string>
    <string name="quick_settings_developer_tiles">Quick settings developer tiles</string>
+7 −0
Original line number Original line Diff line number Diff line
@@ -78,6 +78,12 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
                        .setCardName(CustomSliceRegistry.FACE_ENROLL_SLICE_URI.toString())
                        .setCardName(CustomSliceRegistry.FACE_ENROLL_SLICE_URI.toString())
                        .setCardCategory(ContextualCard.Category.DEFAULT)
                        .setCardCategory(ContextualCard.Category.DEFAULT)
                        .build();
                        .build();
        final ContextualCard darkThemeCard =
                ContextualCard.newBuilder()
                        .setSliceUri(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
                        .setCardName(CustomSliceRegistry.DARK_THEME_SLICE_URI.toString())
                        .setCardCategory(ContextualCard.Category.IMPORTANT)
                        .build();
        final ContextualCardList cards = ContextualCardList.newBuilder()
        final ContextualCardList cards = ContextualCardList.newBuilder()
                .addCard(wifiCard)
                .addCard(wifiCard)
                .addCard(connectedDeviceCard)
                .addCard(connectedDeviceCard)
@@ -86,6 +92,7 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
                .addCard(notificationChannelCard)
                .addCard(notificationChannelCard)
                .addCard(contextualAdaptiveSleepCard)
                .addCard(contextualAdaptiveSleepCard)
                .addCard(contextualFaceSettingsCard)
                .addCard(contextualFaceSettingsCard)
                .addCard(darkThemeCard)
                .build();
                .build();


        return cards;
        return cards;
+127 −0
Original line number Original line Diff line number Diff line
/*
 * 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.homepage.contextualcards.slices;

import static androidx.slice.builders.ListBuilder.ICON_IMAGE;

import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.app.UiModeManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
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.overlay.FeatureFactory;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable;

public class DarkThemeSlice implements CustomSliceable {
    private static final String TAG = "DarkThemeSlice";
    private static final int BATTERY_LEVEL_THRESHOLD = 50;
    private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200;

    // Keep the slice even Dark theme mode changed when it is on HomePage
    @VisibleForTesting
    static boolean sKeepSliceShow;
    @VisibleForTesting
    static long sActiveUiSession = -1000;

    private final Context mContext;
    private final UiModeManager mUiModeManager;

    public DarkThemeSlice(Context context) {
        mContext = context;
        mUiModeManager = context.getSystemService(UiModeManager.class);
    }

    @Override
    public Slice getSlice() {
        final long currentUiSession = FeatureFactory.getFactory(mContext)
                .getSlicesFeatureProvider().getUiSessionToken();
        if (currentUiSession != sActiveUiSession) {
            sActiveUiSession = currentUiSession;
            sKeepSliceShow = false;
        }
        if (!sKeepSliceShow && !isAvailable(mContext)) {
            return null;
        }
        sKeepSliceShow = true;
        final PendingIntent toggleAction = getBroadcastIntent(mContext);
        @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
        final IconCompat icon =
                IconCompat.createWithResource(mContext, R.drawable.dark_theme);
        final boolean isChecked = mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES;
        return new ListBuilder(mContext, CustomSliceRegistry.DARK_THEME_SLICE_URI,
                ListBuilder.INFINITY)
                .setAccentColor(color)
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle(mContext.getText(R.string.dark_theme_slice_title))
                        .setTitleItem(icon, ICON_IMAGE)
                        .setSubtitle(mContext.getText(R.string.dark_theme_slice_subtitle))
                        .setPrimaryAction(
                                SliceAction.createToggle(toggleAction, null /* actionTitle */,
                                        isChecked)))
                .build();
    }

    @Override
    public Uri getUri() {
        return CustomSliceRegistry.DARK_THEME_SLICE_URI;
    }

    @Override
    public void onNotifyChange(Intent intent) {
        final boolean isChecked = intent.getBooleanExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE,
                false);
        // make toggle transition more smooth before dark theme takes effect
        new Handler(Looper.getMainLooper()).postDelayed(() -> {
            mUiModeManager.setNightMode(
                isChecked ? UiModeManager.MODE_NIGHT_YES : UiModeManager.MODE_NIGHT_NO);
        }, DELAY_TIME_EXECUTING_DARK_THEME);
    }

    @Override
    public Intent getIntent() {
        return null;
    }

    @VisibleForTesting
    boolean isAvailable(Context context) {
        // checking dark theme mode.
        if (mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_YES) {
            return false;
        }

        // checking the current battery level
        final BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
        final int level = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
        Log.d(TAG, "battery level=" + level);

        return level <= BATTERY_LEVEL_THRESHOLD;
    }
}
+12 −0
Original line number Original line Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.settings.homepage.contextualcards.slices.BatteryFixSlice;
import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice;
import com.android.settings.homepage.contextualcards.slices.BluetoothDevicesSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualAdaptiveSleepSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
import com.android.settings.homepage.contextualcards.slices.ContextualNotificationChannelSlice;
import com.android.settings.homepage.contextualcards.slices.DarkThemeSlice;
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
import com.android.settings.homepage.contextualcards.slices.NotificationChannelSlice;
@@ -342,6 +343,16 @@ public class CustomSliceRegistry {
            .appendPath("media_output_indicator")
            .appendPath("media_output_indicator")
            .build();
            .build();


    /**
     * Backing Uri for the Dark theme Slice.
     */
    public static final Uri DARK_THEME_SLICE_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
            .appendPath("dark_theme")
            .build();

    @VisibleForTesting
    @VisibleForTesting
    static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;
    static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;


@@ -367,6 +378,7 @@ public class CustomSliceRegistry {
        sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class);
        sUriToSlice.put(NOTIFICATION_CHANNEL_SLICE_URI, NotificationChannelSlice.class);
        sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
        sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class);
        sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
        sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
        sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class);
    }
    }


    public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {
    public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {
+157 −0
Original line number Original line Diff line number Diff line
/*
 * 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.homepage.contextualcards.slices;

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.when;

import android.app.UiModeManager;
import android.content.Context;
import android.net.Uri;
import android.os.BatteryManager;

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.slices.CustomSliceRegistry;
import com.android.settings.slices.SlicesFeatureProviderImpl;
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;

@RunWith(RobolectricTestRunner.class)
public class DarkThemeSliceTest {
    @Mock
    private UiModeManager mUiModeManager;
    @Mock
    private BatteryManager mBatteryManager;

    private Context mContext;
    private DarkThemeSlice mDarkThemeSlice;
    private FakeFeatureFactory mFeatureFactory;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = spy(RuntimeEnvironment.application);
        mFeatureFactory = FakeFeatureFactory.setupForTest();
        mFeatureFactory.slicesFeatureProvider = new SlicesFeatureProviderImpl();
        mFeatureFactory.slicesFeatureProvider.newUiSession();
        doReturn(mUiModeManager).when(mContext).getSystemService(UiModeManager.class);

        // Set-up specs for SliceMetadata.
        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
        mDarkThemeSlice = new DarkThemeSlice(mContext);
        mDarkThemeSlice.sKeepSliceShow = false;
    }

    @Test
    public void getUri_shouldBeDarkThemeSliceUri() {
        final Uri uri = mDarkThemeSlice.getUri();

        assertThat(uri).isEqualTo(CustomSliceRegistry.DARK_THEME_SLICE_URI);
    }

    @Test
    public void isAvailable_inDarkThemeMode_returnFalse() {
        when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);

        assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
    }

    @Test
    public void isAvailable_nonDarkThemeBatteryCapacityEq100_returnFalse() {
        setBatteryCapacityLevel(100);

        assertThat(mDarkThemeSlice.isAvailable(mContext)).isFalse();
    }

    @Test
    public void isAvailable_nonDarkThemeBatteryCapacityLt50_returnTrue() {
        setBatteryCapacityLevel(40);

        assertThat(mDarkThemeSlice.isAvailable(mContext)).isTrue();
    }

    @Test
    public void getSlice_notAvailable_returnNull() {
        when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);

        assertThat(mDarkThemeSlice.getSlice()).isNull();
    }

    @Test
    public void getSlice_newSession_notAvailable_returnNull() {
        // previous displayed: yes
        mDarkThemeSlice.sKeepSliceShow = true;
        // Session: use original value + 1 to become a new session
        mDarkThemeSlice.sActiveUiSession =
                mFeatureFactory.slicesFeatureProvider.getUiSessionToken() + 1;

        when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_YES);

        assertThat(mDarkThemeSlice.getSlice()).isNull();
    }

    @Test
    public void getSlice_previouslyDisplayed_isAvailable_returnSlice() {
        mDarkThemeSlice.sActiveUiSession =
                mFeatureFactory.slicesFeatureProvider.getUiSessionToken();
        mDarkThemeSlice.sKeepSliceShow = true;
        setBatteryCapacityLevel(40);

        assertThat(mDarkThemeSlice.getSlice()).isNotNull();
    }

    @Test
    public void getSlice_isAvailable_returnSlice() {
        setBatteryCapacityLevel(40);

        assertThat(mDarkThemeSlice.getSlice()).isNotNull();
    }

    @Test
    public void getSlice_isAvailable_showTitleSubtitle() {
        setBatteryCapacityLevel(40);

        final Slice slice = mDarkThemeSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertThat(metadata.getTitle()).isEqualTo(
                mContext.getString(R.string.dark_theme_slice_title));
        assertThat(metadata.getSubtitle()).isEqualTo(
                mContext.getString(R.string.dark_theme_slice_subtitle));
    }

    private void setBatteryCapacityLevel(int power_level) {
        when(mUiModeManager.getNightMode()).thenReturn(UiModeManager.MODE_NIGHT_NO);
        doReturn(mBatteryManager).when(mContext).getSystemService(BatteryManager.class);
        when(mBatteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY))
                .thenReturn(power_level);
    }
}