Revamp Storage Settings header part

1. Add new object StorageEntry to encapsulate VolumeInfo and
   unsupported DiskInfo and missing VolumeRecord.
2. Replaces StorageSummaryDonutPreference with UsageProgressBarPreference.
3. Add storage select spinner.
4. Add a "Free up storage" preference to replace "Manage storage" button.

Bug: 174964885
Test: atest com.android.settings.deviceinfo.storage
      atest com.android.settings.deviceinfo
      manual
      Insert an USB drive, select the drive in StorageDashboardFragment
      and observe UI.
Change-Id: I83877f76869414de4fb2788b6b18fe507aa5cfcf
Merged-In: I83877f76869414de4fb2788b6b18fe507aa5cfcf
This commit is contained in:
Arc Wang
2021-02-25 15:01:08 +08:00
parent fea98ca447
commit d496a737ce
14 changed files with 1443 additions and 33 deletions

View File

@@ -0,0 +1,301 @@
/*
* Copyright (C) 2021 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.deviceinfo.storage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.storage.DiskInfo;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.Objects;
@RunWith(AndroidJUnit4.class)
public class StorageEntryTest {
private static final String VOLUME_INFO_ID = "volume_info_id";
private static final String DISK_INFO_ID = "disk_info_id";
private static final String VOLUME_RECORD_UUID = "volume_record_id";
@Mock
private VolumeInfo mVolumeInfo;
@Mock
private DiskInfo mDiskInfo;
@Mock
private VolumeRecord mVolumeRecord;
private Context mContext;
@Mock
private StorageManager mStorageManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
}
@Test
public void equals_volumesOfSameId_shouldBeTheSame() {
final StorageEntry volumeStorage1 = new StorageEntry(mContext,
new VolumeInfo(VOLUME_INFO_ID, 0 /* type */, null /* disk */, null /* partGuid */));
final StorageEntry volumeStorage2 = new StorageEntry(mContext,
new VolumeInfo(VOLUME_INFO_ID, 0 /* type */, null /* disk */, null /* partGuid */));
final StorageEntry diskStorage1 =
new StorageEntry(new DiskInfo(DISK_INFO_ID, 0 /* flags */));
final StorageEntry diskStorage2 =
new StorageEntry(new DiskInfo(DISK_INFO_ID, 0 /* flags */));
final StorageEntry volumeRecordStorage1 = new StorageEntry(new VolumeRecord(0 /* flags */,
VOLUME_RECORD_UUID));
final StorageEntry volumeRecordStorage2 = new StorageEntry(new VolumeRecord(0 /* flags */,
VOLUME_RECORD_UUID));
assertThat(Objects.equals(volumeStorage1, volumeStorage2)).isTrue();
assertThat(Objects.equals(diskStorage1, diskStorage2)).isTrue();
assertThat(Objects.equals(volumeRecordStorage1, volumeRecordStorage2)).isTrue();
}
@Test
public void equals_volumesOfDifferentId_shouldBeDifferent() {
final StorageEntry volumeStorage1 = new StorageEntry(mContext,
new VolumeInfo(VOLUME_INFO_ID, 0 /* type */, null /* disk */, null /* partGuid */));
final StorageEntry volumeStorage2 = new StorageEntry(mContext,
new VolumeInfo("id2", 0 /* type */, null /* disk */, null /* partGuid */));
final StorageEntry diskStorage1 =
new StorageEntry(new DiskInfo(DISK_INFO_ID, 0 /* flags */));
final StorageEntry diskStorage2 =
new StorageEntry(new DiskInfo("id2", 0 /* flags */));
final StorageEntry volumeRecordStorage1 = new StorageEntry(new VolumeRecord(0 /* flags */,
VOLUME_RECORD_UUID));
final StorageEntry volumeRecordStorage2 = new StorageEntry(new VolumeRecord(0 /* flags */,
"id2"));
assertThat(Objects.equals(volumeStorage1, volumeStorage2)).isFalse();
assertThat(Objects.equals(diskStorage1, diskStorage2)).isFalse();
assertThat(Objects.equals(volumeRecordStorage1, volumeRecordStorage2)).isFalse();
}
@Test
public void compareTo_defaultInternalStorage_shouldBeAtTopMost() {
final StorageEntry storage1 = mock(StorageEntry.class);
when(storage1.isDefaultInternalStorage()).thenReturn(true);
final StorageEntry storage2 = mock(StorageEntry.class);
when(storage2.isDefaultInternalStorage()).thenReturn(false);
assertThat(storage1.compareTo(storage2) > 0).isTrue();
}
@Test
public void getDefaultInternalStorageEntry_shouldReturnVolumeInfoStorageOfIdPrivateInternal() {
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
when(mStorageManager.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL)).thenReturn(volumeInfo);
assertThat(StorageEntry.getDefaultInternalStorageEntry(mContext))
.isEqualTo(new StorageEntry(mContext, volumeInfo));
}
@Test
public void isVolumeInfo_shouldReturnTrueForVolumeInfo() {
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
final StorageEntry storage = new StorageEntry(mContext, volumeInfo);
assertThat(storage.isVolumeInfo()).isTrue();
assertThat(storage.isUnsupportedDiskInfo()).isFalse();
assertThat(storage.isMissingVolumeRecord()).isFalse();
}
@Test
public void isUnsupportedDiskInfo_shouldReturnTrueForDiskInfo() {
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry storage = new StorageEntry(diskInfo);
assertThat(storage.isVolumeInfo()).isFalse();
assertThat(storage.isUnsupportedDiskInfo()).isTrue();
assertThat(storage.isMissingVolumeRecord()).isFalse();
}
@Test
public void isMissingVolumeRecord_shouldReturnTrueForVolumeRecord() {
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry storage = new StorageEntry(volumeRecord);
assertThat(storage.isVolumeInfo()).isFalse();
assertThat(storage.isUnsupportedDiskInfo()).isFalse();
assertThat(storage.isMissingVolumeRecord()).isTrue();
}
@Test
public void isMounted_mountedOrMountedReadOnly_shouldReturnTrue() {
final VolumeInfo mountedVolumeInfo1 = mock(VolumeInfo.class);
final StorageEntry mountedStorage1 = new StorageEntry(mContext, mountedVolumeInfo1);
when(mountedVolumeInfo1.getState()).thenReturn(VolumeInfo.STATE_MOUNTED);
final VolumeInfo mountedVolumeInfo2 = mock(VolumeInfo.class);
when(mountedVolumeInfo2.getState()).thenReturn(VolumeInfo.STATE_MOUNTED_READ_ONLY);
final StorageEntry mountedStorage2 = new StorageEntry(mContext, mountedVolumeInfo2);
assertThat(mountedStorage1.isMounted()).isTrue();
assertThat(mountedStorage2.isMounted()).isTrue();
}
@Test
public void isMounted_nonVolumeInfo_shouldReturnFalse() {
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage2 = new StorageEntry(volumeRecord);
assertThat(diskStorage.isMounted()).isFalse();
assertThat(recordStorage2.isMounted()).isFalse();
}
@Test
public void isUnmountable_unmountableVolume_shouldReturnTrue() {
final VolumeInfo unmountableVolumeInfo = mock(VolumeInfo.class);
final StorageEntry mountedStorage = new StorageEntry(mContext, unmountableVolumeInfo);
when(unmountableVolumeInfo.getState()).thenReturn(VolumeInfo.STATE_UNMOUNTABLE);
assertThat(mountedStorage.isUnmountable()).isTrue();
}
@Test
public void isUnmountable_nonVolumeInfo_shouldReturnFalse() {
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage2 = new StorageEntry(volumeRecord);
assertThat(diskStorage.isUnmountable()).isFalse();
assertThat(recordStorage2.isUnmountable()).isFalse();
}
@Test
public void isPrivate_privateVolume_shouldReturnTrue() {
final VolumeInfo privateVolumeInfo = mock(VolumeInfo.class);
final StorageEntry privateStorage = new StorageEntry(mContext, privateVolumeInfo);
when(privateVolumeInfo.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE);
assertThat(privateStorage.isPrivate()).isTrue();
}
@Test
public void isPrivate_nonVolumeInfo_shouldReturnFalse() {
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage2 = new StorageEntry(volumeRecord);
assertThat(diskStorage.isPrivate()).isFalse();
assertThat(recordStorage2.isPrivate()).isFalse();
}
@Test
public void getDescription_shouldReturnDescription() {
final String description = "description";
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
when(mStorageManager.getBestVolumeDescription(volumeInfo)).thenReturn(description);
final StorageEntry volumeStorage = new StorageEntry(mContext, volumeInfo);
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
when(diskInfo.getDescription()).thenReturn(description);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage = new StorageEntry(volumeRecord);
when(volumeRecord.getNickname()).thenReturn(description);
assertThat(volumeStorage.getDescription()).isEqualTo(description);
assertThat(diskStorage.getDescription()).isEqualTo(description);
assertThat(recordStorage.getDescription()).isEqualTo(description);
}
@Test
public void getDiskId_shouldReturnDiskId() {
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
final StorageEntry volumeStorage = new StorageEntry(mContext, volumeInfo);
when(volumeInfo.getDiskId()).thenReturn(VOLUME_INFO_ID);
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
when(diskInfo.getId()).thenReturn(DISK_INFO_ID);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage = new StorageEntry(volumeRecord);
assertThat(volumeStorage.getDiskId()).isEqualTo(VOLUME_INFO_ID);
assertThat(diskStorage.getDiskId()).isEqualTo(DISK_INFO_ID);
assertThat(recordStorage.getDiskId()).isEqualTo(null);
}
@Test
public void getFsUuid_shouldReturnFsUuid() {
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
final StorageEntry volumeStorage = new StorageEntry(mContext, volumeInfo);
when(volumeInfo.getFsUuid()).thenReturn(VOLUME_INFO_ID);
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage = new StorageEntry(volumeRecord);
when(volumeRecord.getFsUuid()).thenReturn(VOLUME_RECORD_UUID);
assertThat(volumeStorage.getFsUuid()).isEqualTo(VOLUME_INFO_ID);
assertThat(diskStorage.getFsUuid()).isEqualTo(null);
assertThat(recordStorage.getFsUuid()).isEqualTo(VOLUME_RECORD_UUID);
}
@Test
public void getPath_shouldReturnPath() {
final File file = new File("fakePath");
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
final StorageEntry volumeStorage = new StorageEntry(mContext, volumeInfo);
when(volumeInfo.getPath()).thenReturn(file);
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage = new StorageEntry(volumeRecord);
assertThat(volumeStorage.getPath()).isEqualTo(file);
assertThat(diskStorage.getPath()).isEqualTo(null);
assertThat(recordStorage.getPath()).isEqualTo(null);
}
@Test
public void getVolumeInfo_shouldVolumeInfo() {
final VolumeInfo volumeInfo = mock(VolumeInfo.class);
final StorageEntry volumeStorage = new StorageEntry(mContext, volumeInfo);
final DiskInfo diskInfo = mock(DiskInfo.class);
final StorageEntry diskStorage = new StorageEntry(diskInfo);
final VolumeRecord volumeRecord = mock(VolumeRecord.class);
final StorageEntry recordStorage = new StorageEntry(volumeRecord);
assertThat(volumeStorage.getVolumeInfo()).isEqualTo(volumeInfo);
assertThat(diskStorage.getVolumeInfo()).isEqualTo(null);
assertThat(recordStorage.getVolumeInfo()).isEqualTo(null);
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2021 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.deviceinfo.storage;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Looper;
import android.os.storage.StorageManager;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.widget.SettingsSpinnerPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
public class StorageSelectionPreferenceControllerTest {
private static final String PREFERENCE_KEY = "preference_key";
private Context mContext;
private StorageManager mStorageManager;
private StorageSelectionPreferenceController mController;
@Before
public void setUp() throws Exception {
mContext = ApplicationProvider.getApplicationContext();
mStorageManager = mContext.getSystemService(StorageManager.class);
mController = new StorageSelectionPreferenceController(mContext, PREFERENCE_KEY);
}
@Test
public void setStorageEntries_fromStorageManager_correctAdapterItems() {
final List<StorageEntry> storageEntries = mStorageManager.getVolumes().stream()
.map(volumeInfo -> new StorageEntry(mContext, volumeInfo))
.collect(Collectors.toList());
mController.setStorageEntries(storageEntries);
final int adapterItemCount = mController.mStorageAdapter.getCount();
assertThat(adapterItemCount).isEqualTo(storageEntries.size());
for (int i = 0; i < adapterItemCount; i++) {
assertThat(storageEntries.get(i).getDescription())
.isEqualTo(mController.mStorageAdapter.getItem(i).getDescription());
}
}
@Test
public void setSelectedStorageEntry_primaryStorage_correctSelectedAdapterItem() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
final PreferenceManager preferenceManager = new PreferenceManager(mContext);
final PreferenceScreen preferenceScreen =
preferenceManager.createPreferenceScreen(mContext);
final SettingsSpinnerPreference spinnerPreference = new SettingsSpinnerPreference(mContext);
spinnerPreference.setKey(PREFERENCE_KEY);
preferenceScreen.addPreference(spinnerPreference);
mController.displayPreference(preferenceScreen);
final StorageEntry primaryStorageEntry =
StorageEntry.getDefaultInternalStorageEntry(mContext);
mController.setStorageEntries(mStorageManager.getVolumes().stream()
.map(volumeInfo -> new StorageEntry(mContext, volumeInfo))
.collect(Collectors.toList()));
mController.setSelectedStorageEntry(primaryStorageEntry);
assertThat((StorageEntry) mController.mSpinnerPreference.getSelectedItem())
.isEqualTo(primaryStorageEntry);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2021 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.deviceinfo.storage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.os.Looper;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.settingslib.widget.UsageProgressBarPreference;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
public class StorageUsageProgressBarPreferenceControllerTest {
private static final String FAKE_UUID = "95D9-B3A4";
private static final long WAIT_TIMEOUT = 10_000L;
private static final long FREE_BYTES = 123L;
private static final long TOTAL_BYTES = 456L;
private static final long USAGE_BYTES = TOTAL_BYTES - FREE_BYTES;
private Context mContext;
private FakeStorageUsageProgressBarPreferenceController mController;
private PreferenceScreen mPreferenceScreen;
@Mock
private StorageStatsManager mStorageStatsManager;
@Before
public void setUp() throws Exception {
if (Looper.myLooper() == null) {
Looper.prepare();
}
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(StorageStatsManager.class)).thenReturn(mStorageStatsManager);
mController = new FakeStorageUsageProgressBarPreferenceController(mContext, "key");
final PreferenceManager preferenceManager = new PreferenceManager(mContext);
mPreferenceScreen = preferenceManager.createPreferenceScreen(mContext);
final UsageProgressBarPreference usageProgressBarPreference =
new UsageProgressBarPreference(mContext);
usageProgressBarPreference.setKey(mController.getPreferenceKey());
mPreferenceScreen.addPreference(usageProgressBarPreference);
}
@Test
public void setSelectedStorageEntry_primaryStorage_getPrimaryStorageBytes() throws IOException {
final StorageEntry defaultInternalStorageEntry =
StorageEntry.getDefaultInternalStorageEntry(mContext);
when(mStorageStatsManager.getTotalBytes(defaultInternalStorageEntry.getFsUuid()))
.thenReturn(TOTAL_BYTES);
when(mStorageStatsManager.getFreeBytes(defaultInternalStorageEntry.getFsUuid()))
.thenReturn(FREE_BYTES);
mController.displayPreference(mPreferenceScreen);
synchronized (mController.mLock) {
mController.setSelectedStorageEntry(defaultInternalStorageEntry);
mController.waitUpdateState(WAIT_TIMEOUT);
}
assertThat(mController.mUsedBytes).isEqualTo(USAGE_BYTES);
assertThat(mController.mTotalBytes).isEqualTo(TOTAL_BYTES);
}
private class FakeStorageUsageProgressBarPreferenceController
extends StorageUsageProgressBarPreferenceController {
private final Object mLock = new Object();
FakeStorageUsageProgressBarPreferenceController(Context context, String key) {
super(context, key);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
try {
mLock.notifyAll();
} catch (IllegalMonitorStateException e) {
// Catch it for displayPreference to prevent exception by object not locked by
// thread before notify. Do nothing.
}
}
public void waitUpdateState(long timeout) {
try {
mLock.wait(timeout);
} catch (InterruptedException e) {
// Do nothing.
}
}
}
}

View File

@@ -34,8 +34,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class VolumeSizesLoaderTest {
@Test
public void getVolumeSize_getsValidSizes() throws Exception {
public void getVolumeSize_privateMountedVolume_getsValidSizes() throws Exception {
VolumeInfo info = mock(VolumeInfo.class);
when(info.getType()).thenReturn(VolumeInfo.TYPE_PRIVATE);
when(info.getState()).thenReturn(VolumeInfo.STATE_MOUNTED);
StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
@@ -46,4 +48,19 @@ public class VolumeSizesLoaderTest {
assertThat(storageInfo.freeBytes).isEqualTo(1000L);
assertThat(storageInfo.totalBytes).isEqualTo(10000L);
}
@Test
public void getVolumeSize_unmountedVolume_getsValidSizes() throws Exception {
VolumeInfo info = mock(VolumeInfo.class);
when(info.getState()).thenReturn(VolumeInfo.STATE_UNMOUNTED);
StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
PrivateStorageInfo storageInfo =
VolumeSizesLoader.getVolumeSize(storageVolumeProvider, null, info);
assertThat(storageInfo.freeBytes).isEqualTo(0L);
assertThat(storageInfo.totalBytes).isEqualTo(0L);
}
}