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

Commit 5b5d4c6d authored by Yanting Yang's avatar Yanting Yang Committed by Android (Google) Code Review
Browse files

Merge "Add Low Storage slice to Contextual Settings Homepage"

parents d103f8eb 04eb082b
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -10333,4 +10333,7 @@
    <string name="contextual_card_dismiss_keep">Keep</string>
    <!-- String for contextual card dismissal [CHAR LIMIT=NONE] -->
    <string name="contextual_card_dismiss_confirm_message">Remove this suggestion?</string>
    <!-- Summary for low storage slice. [CHAR LIMIT=NONE] -->
    <string name="low_storage_summary">Storage is low. <xliff:g id="percentage" example="54%">%1$s</xliff:g> used - <xliff:g id="free_space" example="32GB">%2$s</xliff:g> free</string>
</resources>
 No newline at end of file
+8 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.Nullable;

import com.android.settings.homepage.contextualcards.deviceinfo.BatterySlice;
import com.android.settings.homepage.contextualcards.slices.ConnectedDeviceSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.intelligence.ContextualCardProto.ContextualCard;
import com.android.settings.intelligence.ContextualCardProto.ContextualCardList;
import com.android.settings.wifi.WifiSlice;
@@ -54,10 +55,17 @@ public class SettingsContextualCardProvider extends ContextualCardProvider {
                        .setCardName(ConnectedDeviceSlice.PATH_CONNECTED_DEVICE)
                        .setCardCategory(ContextualCard.Category.IMPORTANT)
                        .build();
        final ContextualCard lowStorageCard =
                ContextualCard.newBuilder()
                        .setSliceUri(LowStorageSlice.LOW_STORAGE_URI.toString())
                        .setCardName(LowStorageSlice.PATH_LOW_STORAGE)
                        .setCardCategory(ContextualCard.Category.IMPORTANT)
                        .build();
        final ContextualCardList cards = ContextualCardList.newBuilder()
                .addCard(wifiCard)
                .addCard(batteryInfoCard)
                .addCard(connectedDeviceCard)
                .addCard(lowStorageCard)
                .build();

        return cards;
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.storage.StorageManager;
import android.text.format.Formatter;
import android.util.Log;

import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.ListBuilder.RowBuilder;
import androidx.slice.builders.SliceAction;

import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.Utils;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SettingsSliceProvider;
import com.android.settings.slices.SliceBuilderUtils;
import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;

import java.text.NumberFormat;

public class LowStorageSlice implements CustomSliceable {

    /**
     * The path denotes the unique name of Low storage Slice.
     */
    public static final String PATH_LOW_STORAGE = "low_storage";

    /**
     * Backing Uri for Low storage Slice.
     */
    public static final Uri LOW_STORAGE_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
            .appendPath(PATH_LOW_STORAGE)
            .build();

    private static final String TAG = "LowStorageSlice";

    /**
     * If user used >= 85% storage.
     */
    private static final double LOW_STORAGE_THRESHOLD = 0.85;

    private final Context mContext;

    public LowStorageSlice(Context context) {
        mContext = context;
    }

    /**
     * Return a Low storage Slice bound to {@link #LOW_STORAGE_URI}
     */
    @Override
    public Slice getSlice() {
        // Get current storage percentage from StorageManager.
        final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(
                new StorageManagerVolumeProvider(mContext.getSystemService(StorageManager.class)));
        final double currentStoragePercentage =
                (double) (info.totalBytes - info.freeBytes) / info.totalBytes;

        // Used storage < 85%. NOT show Low storage Slice.
        if (currentStoragePercentage < LOW_STORAGE_THRESHOLD) {
            /**
             * TODO(b/114808204): Contextual Home Page - "Low Storage"
             * The behavior is under decision making, will update new behavior or remove TODO later.
             */
            Log.i(TAG, "Not show low storage slice, not match condition.");
            return null;
        }

        // Show Low storage Slice.
        final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_storage);
        final CharSequence title = mContext.getText(R.string.storage_menu_free);
        final SliceAction primarySliceAction = new SliceAction(
                PendingIntent.getActivity(mContext, 0, getIntent(), 0), icon, title);
        final String lowStorageSummary = mContext.getString(R.string.low_storage_summary,
                NumberFormat.getPercentInstance().format(currentStoragePercentage),
                Formatter.formatFileSize(mContext, info.freeBytes));

        /**
         * TODO(b/114808204): Contextual Home Page - "Low Storage"
         * Slices doesn't support "Icon on the left" in header. Now we intend to start with Icon
         * right aligned. Will update the icon to left until Slices support it.
         */
        return new ListBuilder(mContext, LOW_STORAGE_URI, ListBuilder.INFINITY)
                .setAccentColor(Utils.getColorAccentDefaultColor(mContext))
                .addRow(new RowBuilder()
                        .setTitle(title)
                        .setSubtitle(lowStorageSummary)
                        .addEndItem(icon, ListBuilder.ICON_IMAGE)
                        .setPrimaryAction(primarySliceAction))
                .build();
    }

    @Override
    public Uri getUri() {
        return LOW_STORAGE_URI;
    }

    @Override
    public void onNotifyChange(Intent intent) {

    }

    @Override
    public Intent getIntent() {
        final String screenTitle = mContext.getText(R.string.storage_label)
                .toString();
        final Uri contentUri = new Uri.Builder().appendPath(PATH_LOW_STORAGE).build();

        return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
                StorageSettings.class.getName(), PATH_LOW_STORAGE,
                screenTitle,
                MetricsProto.MetricsEvent.SLICE)
                .setClassName(mContext.getPackageName(), SubSettings.class.getName())
                .setData(contentUri);
    }
}
 No newline at end of file
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.settings.homepage.contextualcards.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.contextualcards.deviceinfo.DeviceInfoSlice;
import com.android.settings.homepage.contextualcards.deviceinfo.StorageSlice;
import com.android.settings.homepage.contextualcards.slices.ConnectedDeviceSlice;
import com.android.settings.homepage.contextualcards.slices.LowStorageSlice;
import com.android.settings.wifi.WifiSlice;

import java.util.Map;
@@ -105,5 +106,6 @@ public class CustomSliceManager {
        mUriMap.put(StorageSlice.STORAGE_CARD_URI, StorageSlice.class);
        mUriMap.put(BatterySlice.BATTERY_CARD_URI, BatterySlice.class);
        mUriMap.put(ConnectedDeviceSlice.CONNECTED_DEVICE_URI, ConnectedDeviceSlice.class);
        mUriMap.put(LowStorageSlice.LOW_STORAGE_URI, LowStorageSlice.class);
    }
}
+109 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 android.content.Context;

import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.SliceProvider;
import androidx.slice.widget.SliceLiveData;

import com.android.settings.R;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import com.android.settings.testutils.SliceTester;
import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;

import java.util.List;

@RunWith(SettingsRobolectricTestRunner.class)
public class LowStorageSliceTest {

    private Context mContext;
    private LowStorageSlice mLowStorageSlice;

    @Before
    public void setUp() {
        mContext = RuntimeEnvironment.application;

        // Set-up specs for SliceMetadata.
        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);

        mLowStorageSlice = new LowStorageSlice(mContext);
    }

    @After
    public void tearDown() {
        ShadowPrivateStorageInfo.reset();
    }

    @Test
    @Config(shadows = ShadowPrivateStorageInfo.class)
    public void getSlice_hasLowStorage_shouldBeCorrectSliceContent() {
        ShadowPrivateStorageInfo.setPrivateStorageInfo(new PrivateStorageInfo(10L, 100L));

        final Slice slice = mLowStorageSlice.getSlice();

        final List<SliceItem> sliceItems = slice.getItems();
        SliceTester.assertTitle(sliceItems, mContext.getString(R.string.storage_menu_free));
    }

    @Test
    @Config(shadows = ShadowPrivateStorageInfo.class)
    public void getSlice_hasNoLowStorage_shouldBeNull() {
        ShadowPrivateStorageInfo.setPrivateStorageInfo(new PrivateStorageInfo(100L, 100L));

        final Slice slice = mLowStorageSlice.getSlice();

        assertThat(slice).isNull();
    }

    @Implements(PrivateStorageInfo.class)
    public static class ShadowPrivateStorageInfo {

        private static PrivateStorageInfo sPrivateStorageInfo = null;

        @Resetter
        public static void reset() {
            sPrivateStorageInfo = null;
        }

        @Implementation
        public static PrivateStorageInfo getPrivateStorageInfo(
                StorageVolumeProvider storageVolumeProvider) {
            return sPrivateStorageInfo;
        }

        public static void setPrivateStorageInfo(
                PrivateStorageInfo privateStorageInfo) {
            sPrivateStorageInfo = privateStorageInfo;
        }
    }
}
 No newline at end of file