Merge "Cleanup Storage Settings" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-04-15 02:28:35 +00:00
committed by Android (Google) Code Review
40 changed files with 143 additions and 2997 deletions

View File

@@ -79,7 +79,6 @@ public class Settings extends SettingsActivity {
public static class DataSaverSummaryActivity extends SettingsActivity{ /* empty */ }
public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }
public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiSettings2Activity extends SettingsActivity { /* empty */ }

View File

@@ -78,10 +78,8 @@ import com.android.settings.datetime.DateTimeSettings;
import com.android.settings.deletionhelper.AutomaticStorageManagerSettings;
import com.android.settings.development.DevelopmentSettingsDashboardFragment;
import com.android.settings.deviceinfo.PrivateVolumeForget;
import com.android.settings.deviceinfo.PrivateVolumeSettings;
import com.android.settings.deviceinfo.PublicVolumeSettings;
import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings;
import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
@@ -215,9 +213,7 @@ public class SettingsGateway {
ToggleDaltonizerPreferenceFragment.class.getName(),
ToggleReduceBrightColorsPreferenceFragment.class.getName(),
TextToSpeechSettings.class.getName(),
StorageSettings.class.getName(),
PrivateVolumeForget.class.getName(),
PrivateVolumeSettings.class.getName(),
PublicVolumeSettings.class.getName(),
DevelopmentSettingsDashboardFragment.class.getName(),
AndroidBeam.class.getName(),

View File

@@ -16,8 +16,6 @@
package com.android.settings.deviceinfo;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.app.usage.ExternalStorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -37,6 +35,8 @@ import java.io.IOException;
import java.util.UUID;
public abstract class MigrateEstimateTask extends AsyncTask<Void, Void, Long> {
private static final String TAG = "MigrateEstimateTask";
private static final String EXTRA_SIZE_BYTES = "size_bytes";
/**

View File

@@ -1,898 +0,0 @@
/*
* Copyright (C) 2015 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;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.Settings.StorageUseActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.deviceinfo.StorageSettings.MountTask;
import com.android.settingslib.deviceinfo.StorageMeasurement;
import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementDetails;
import com.android.settingslib.deviceinfo.StorageMeasurement.MeasurementReceiver;
import com.google.android.collect.Lists;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* Panel showing summary and actions for a {@link VolumeInfo#TYPE_PRIVATE}
* storage volume.
*/
public class PrivateVolumeSettings extends SettingsPreferenceFragment {
// TODO: disable unmount when providing over MTP/PTP
// TODO: warn when mounted read-only
private static final String TAG = "PrivateVolumeSettings";
private static final boolean LOGV = false;
private static final String TAG_RENAME = "rename";
private static final String TAG_OTHER_INFO = "otherInfo";
private static final String TAG_SYSTEM_INFO = "systemInfo";
private static final String TAG_USER_INFO = "userInfo";
private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
private static final String EXTRA_VOLUME_SIZE = "volume_size";
private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
private static final int[] ITEMS_NO_SHOW_SHARED = new int[] {
R.string.storage_detail_apps,
R.string.storage_detail_system,
};
private static final int[] ITEMS_SHOW_SHARED = new int[] {
R.string.storage_detail_apps,
R.string.storage_detail_images,
R.string.storage_detail_videos,
R.string.storage_detail_audio,
R.string.storage_detail_system,
R.string.storage_detail_other,
};
private StorageManager mStorageManager;
private UserManager mUserManager;
private String mVolumeId;
private VolumeInfo mVolume;
private VolumeInfo mSharedVolume;
private long mTotalSize;
private long mSystemSize;
private StorageMeasurement mMeasure;
private UserInfo mCurrentUser;
private StorageSummaryPreference mSummary;
private List<StorageItemPreference> mItemPreferencePool = Lists.newArrayList();
private List<PreferenceCategory> mHeaderPreferencePool = Lists.newArrayList();
private int mHeaderPoolIndex;
private int mItemPoolIndex;
private Preference mExplore;
private boolean mNeedsUpdate;
private boolean isVolumeValid() {
return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
&& mVolume.isMountedReadable();
}
public PrivateVolumeSettings() {
setRetainInstance(true);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DEVICEINFO_STORAGE;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Context context = getActivity();
mUserManager = context.getSystemService(UserManager.class);
mStorageManager = context.getSystemService(StorageManager.class);
mVolumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
mVolume = mStorageManager.findVolumeById(mVolumeId);
final long sharedDataSize = mVolume.getPath().getTotalSpace();
mTotalSize = getArguments().getLong(EXTRA_VOLUME_SIZE, 0);
mSystemSize = mTotalSize - sharedDataSize;
if (LOGV) Log.v(TAG,
"onCreate() mTotalSize: " + mTotalSize + " sharedDataSize: " + sharedDataSize);
if (mTotalSize <= 0) {
mTotalSize = sharedDataSize;
mSystemSize = 0;
}
// Find the emulated shared storage layered above this private volume
mSharedVolume = mStorageManager.findEmulatedForPrivate(mVolume);
mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume);
mMeasure.setReceiver(mReceiver);
if (!isVolumeValid()) {
getActivity().finish();
return;
}
addPreferencesFromResource(R.xml.device_info_storage_volume);
getPreferenceScreen().setOrderingAsAdded(true);
mSummary = new StorageSummaryPreference(getPrefContext());
mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId());
mExplore = buildAction(R.string.storage_menu_explore);
mNeedsUpdate = true;
setHasOptionsMenu(true);
}
private void setTitle() {
getActivity().setTitle(mStorageManager.getBestVolumeDescription(mVolume));
}
private void update() {
if (!isVolumeValid()) {
getActivity().finish();
return;
}
setTitle();
// Valid options may have changed
getActivity().invalidateOptionsMenu();
final Context context = getActivity();
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
addPreference(screen, mSummary);
List<UserInfo> allUsers = mUserManager.getUsers();
final int userCount = allUsers.size();
final boolean showHeaders = userCount > 1;
final boolean showShared = (mSharedVolume != null) && mSharedVolume.isMountedReadable();
mItemPoolIndex = 0;
mHeaderPoolIndex = 0;
int addedUserCount = 0;
// Add current user and its profiles first
for (int userIndex = 0; userIndex < userCount; ++userIndex) {
final UserInfo userInfo = allUsers.get(userIndex);
if (Utils.isProfileOf(mCurrentUser, userInfo)) {
final PreferenceGroup details = showHeaders ?
addCategory(screen, userInfo.name) : screen;
addDetailItems(details, showShared, userInfo.id);
++addedUserCount;
}
}
// Add rest of users
if (userCount - addedUserCount > 0) {
PreferenceGroup otherUsers = addCategory(screen,
getText(R.string.storage_other_users));
for (int userIndex = 0; userIndex < userCount; ++userIndex) {
final UserInfo userInfo = allUsers.get(userIndex);
if (!Utils.isProfileOf(mCurrentUser, userInfo)) {
addItem(otherUsers, /* titleRes */ 0, userInfo.name, userInfo.id);
}
}
}
addItem(screen, R.string.storage_detail_cached, null, UserHandle.USER_NULL);
if (showShared) {
addPreference(screen, mExplore);
}
final long freeBytes = mVolume.getPath().getFreeSpace();
final long usedBytes = mTotalSize - freeBytes;
if (LOGV) Log.v(TAG, "update() freeBytes: " + freeBytes + " usedBytes: " + usedBytes);
final BytesResult result = Formatter.formatBytes(getResources(), usedBytes, 0);
mSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
result.value, result.units));
mSummary.setSummary(getString(R.string.storage_volume_used,
Formatter.formatFileSize(context, mTotalSize)));
mSummary.setPercent(usedBytes, mTotalSize);
mMeasure.forceMeasure();
mNeedsUpdate = false;
}
private void addPreference(PreferenceGroup group, Preference pref) {
pref.setOrder(Preference.DEFAULT_ORDER);
group.addPreference(pref);
}
private PreferenceCategory addCategory(PreferenceGroup group, CharSequence title) {
PreferenceCategory category;
if (mHeaderPoolIndex < mHeaderPreferencePool.size()) {
category = mHeaderPreferencePool.get(mHeaderPoolIndex);
} else {
category = new PreferenceCategory(getPrefContext());
mHeaderPreferencePool.add(category);
}
category.setTitle(title);
category.removeAll();
addPreference(group, category);
++mHeaderPoolIndex;
return category;
}
private void addDetailItems(PreferenceGroup category, boolean showShared, int userId) {
final int[] itemsToAdd = (showShared ? ITEMS_SHOW_SHARED : ITEMS_NO_SHOW_SHARED);
for (int i = 0; i < itemsToAdd.length; ++i) {
addItem(category, itemsToAdd[i], null, userId);
}
}
private void addItem(PreferenceGroup group, int titleRes, CharSequence title, int userId) {
if (titleRes == R.string.storage_detail_system) {
if (mSystemSize <= 0) {
Log.w(TAG, "Skipping System storage because its size is " + mSystemSize);
return;
}
if (userId != UserHandle.myUserId()) {
// Only display system on current user.
return;
}
}
StorageItemPreference item;
if (mItemPoolIndex < mItemPreferencePool.size()) {
item = mItemPreferencePool.get(mItemPoolIndex);
} else {
item = buildItem();
mItemPreferencePool.add(item);
}
if (title != null) {
item.setTitle(title);
item.setKey(title.toString());
} else {
item.setTitle(titleRes);
item.setKey(Integer.toString(titleRes));
}
item.setSummary(R.string.memory_calculating_size);
item.userHandle = userId;
addPreference(group, item);
++mItemPoolIndex;
}
private StorageItemPreference buildItem() {
final StorageItemPreference item = new StorageItemPreference(getPrefContext());
item.setIcon(R.drawable.empty_icon);
return item;
}
private Preference buildAction(int titleRes) {
final Preference pref = new Preference(getPrefContext());
pref.setTitle(titleRes);
pref.setKey(Integer.toString(titleRes));
return pref;
}
static void setVolumeSize(Bundle args, long size) {
args.putLong(EXTRA_VOLUME_SIZE, size);
}
@Override
public void onResume() {
super.onResume();
// Refresh to verify that we haven't been formatted away
mVolume = mStorageManager.findVolumeById(mVolumeId);
if (!isVolumeValid()) {
getActivity().finish();
return;
}
mStorageManager.registerListener(mStorageListener);
if (mNeedsUpdate) {
update();
} else {
setTitle();
}
}
@Override
public void onPause() {
super.onPause();
mStorageManager.unregisterListener(mStorageListener);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mMeasure != null) {
mMeasure.onDestroy();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.storage_volume, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
if (!isVolumeValid()) return;
final MenuItem rename = menu.findItem(R.id.storage_rename);
final MenuItem mount = menu.findItem(R.id.storage_mount);
final MenuItem unmount = menu.findItem(R.id.storage_unmount);
final MenuItem format = menu.findItem(R.id.storage_format);
final MenuItem migrate = menu.findItem(R.id.storage_migrate);
final MenuItem manage = menu.findItem(R.id.storage_free);
// Actions live in menu for non-internal private volumes; they're shown
// as preference items for public volumes.
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolume.getId())) {
rename.setVisible(false);
mount.setVisible(false);
unmount.setVisible(false);
format.setVisible(false);
manage.setVisible(getResources().getBoolean(
R.bool.config_storage_manager_settings_enabled));
} else {
rename.setVisible(mVolume.getType() == VolumeInfo.TYPE_PRIVATE);
mount.setVisible(mVolume.getState() == VolumeInfo.STATE_UNMOUNTED);
unmount.setVisible(mVolume.isMountedReadable());
format.setVisible(true);
manage.setVisible(false);
}
format.setTitle(R.string.storage_menu_format_public);
// Only offer to migrate when not current storage
final VolumeInfo privateVol = getActivity().getPackageManager()
.getPrimaryStorageCurrentVolume();
migrate.setVisible((privateVol != null)
&& (privateVol.getType() == VolumeInfo.TYPE_PRIVATE)
&& !Objects.equals(mVolume, privateVol)
&& privateVol.isMountedWritable());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final Context context = getActivity();
final Bundle args = new Bundle();
int i = item.getItemId();
if (i == R.id.storage_rename) {
RenameFragment.show(this, mVolume);
return true;
} else if (i == R.id.storage_mount) {
new MountTask(context, mVolume).execute();
return true;
} else if (i == R.id.storage_unmount) {
args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
new SubSettingLauncher(context)
.setDestination(PrivateVolumeUnmount.class.getCanonicalName())
.setTitleRes(R.string.storage_menu_unmount)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args)
.launch();
return true;
} else if (i == R.id.storage_format) {
args.putString(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
new SubSettingLauncher(context)
.setDestination(PrivateVolumeFormat.class.getCanonicalName())
.setTitleRes(R.string.storage_menu_format)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args)
.launch();
return true;
} else if (i == R.id.storage_migrate) {
final Intent intent = new Intent(context, StorageWizardMigrateConfirm.class);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, mVolume.getId());
startActivity(intent);
return true;
} else if (i == R.id.storage_free) {
final Intent deletion_helper_intent =
new Intent(StorageManager.ACTION_MANAGE_STORAGE);
startActivity(deletion_helper_intent);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPreferenceTreeClick(Preference pref) {
// TODO: launch better intents for specific volume
final int userId = (pref instanceof StorageItemPreference ?
((StorageItemPreference) pref).userHandle : -1);
int itemTitleId;
try {
itemTitleId = Integer.parseInt(pref.getKey());
} catch (NumberFormatException e) {
itemTitleId = 0;
}
Intent intent = null;
if (itemTitleId == R.string.storage_detail_apps) {
Bundle args = new Bundle();
args.putString(ManageApplications.EXTRA_CLASSNAME,
StorageUseActivity.class.getName());
args.putString(ManageApplications.EXTRA_VOLUME_UUID, mVolume.getFsUuid());
args.putString(ManageApplications.EXTRA_VOLUME_NAME, mVolume.getDescription());
args.putInt(
ManageApplications.EXTRA_STORAGE_TYPE,
ManageApplications.STORAGE_TYPE_LEGACY);
intent = new SubSettingLauncher(getActivity())
.setDestination(ManageApplications.class.getName())
.setArguments(args)
.setTitleRes(R.string.apps_storage)
.setSourceMetricsCategory(getMetricsCategory())
.toIntent();
} else if (itemTitleId == R.string.storage_detail_images) {
intent = getIntentForStorage(AUTHORITY_MEDIA, "images_root");
} else if (itemTitleId == R.string.storage_detail_videos) {
intent = getIntentForStorage(AUTHORITY_MEDIA, "videos_root");
} else if (itemTitleId == R.string.storage_detail_audio) {
intent = getIntentForStorage(AUTHORITY_MEDIA, "audio_root");
} else if (itemTitleId == R.string.storage_detail_system) {
SystemInfoFragment.show(this);
return true;
} else if (itemTitleId == R.string.storage_detail_other) {
OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
mSharedVolume, userId);
return true;
} else if (itemTitleId == R.string.storage_detail_cached) {
ConfirmClearCacheFragment.show(this);
return true;
} else if (itemTitleId == R.string.storage_menu_explore) {
intent = mSharedVolume.buildBrowseIntent();
} else if (itemTitleId == 0) {
UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
return true;
}
if (intent != null) {
intent.putExtra(Intent.EXTRA_USER_ID, userId);
Utils.launchIntent(this, intent);
return true;
}
return super.onPreferenceTreeClick(pref);
}
private Intent getIntentForStorage(String authority, String root) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(
DocumentsContract.buildRootUri(authority, root),
DocumentsContract.Root.MIME_TYPE_ITEM);
intent.addCategory(Intent.CATEGORY_DEFAULT);
return intent;
}
private final MeasurementReceiver mReceiver = new MeasurementReceiver() {
@Override
public void onDetailsChanged(MeasurementDetails details) {
updateDetails(details);
}
};
private void updateDetails(MeasurementDetails details) {
StorageItemPreference otherItem = null;
long accountedSize = 0;
long totalMiscSize = 0;
long totalDownloadsSize = 0;
for (int i = 0; i < mItemPoolIndex; ++i) {
StorageItemPreference item = mItemPreferencePool.get(i);
final int userId = item.userHandle;
int itemTitleId;
try {
itemTitleId = Integer.parseInt(item.getKey());
} catch (NumberFormatException e) {
itemTitleId = 0;
}
// Cannot display 'Other' until all known items are accounted for.
if (itemTitleId == R.string.storage_detail_system) {
updatePreference(item, mSystemSize);
accountedSize += mSystemSize;
if (LOGV) Log.v(TAG, "mSystemSize: " + mSystemSize
+ " accountedSize: " + accountedSize);
} else if (itemTitleId == R.string.storage_detail_apps) {
updatePreference(item, details.appsSize.get(userId));
accountedSize += details.appsSize.get(userId);
if (LOGV) Log.v(TAG, "appsSize: " + details.appsSize.get(userId)
+ " accountedSize: " + accountedSize);
} else if (itemTitleId == R.string.storage_detail_images) {
final long imagesSize = totalValues(details, userId,
Environment.DIRECTORY_DCIM, Environment.DIRECTORY_PICTURES);
updatePreference(item, imagesSize);
accountedSize += imagesSize;
if (LOGV) Log.v(TAG, "imagesSize: " + imagesSize
+ " accountedSize: " + accountedSize);
} else if (itemTitleId == R.string.storage_detail_videos) {
final long videosSize = totalValues(details, userId,
Environment.DIRECTORY_MOVIES);
updatePreference(item, videosSize);
accountedSize += videosSize;
if (LOGV) Log.v(TAG, "videosSize: " + videosSize
+ " accountedSize: " + accountedSize);
} else if (itemTitleId == R.string.storage_detail_audio) {
final long audioSize = totalValues(details, userId,
Environment.DIRECTORY_MUSIC,
Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
updatePreference(item, audioSize);
accountedSize += audioSize;
if (LOGV) Log.v(TAG, "audioSize: " + audioSize
+ " accountedSize: " + accountedSize);
} else if (itemTitleId == R.string.storage_detail_other) {
final long downloadsSize = totalValues(details, userId,
Environment.DIRECTORY_DOWNLOADS);
final long miscSize = details.miscSize.get(userId);
totalDownloadsSize += downloadsSize;
totalMiscSize += miscSize;
accountedSize += miscSize + downloadsSize;
if (LOGV)
Log.v(TAG, "miscSize for " + userId + ": " + miscSize + "(total: "
+ totalMiscSize + ") \ndownloadsSize: " + downloadsSize + "(total: "
+ totalDownloadsSize + ") accountedSize: " + accountedSize);
otherItem = item;
} else if (itemTitleId == R.string.storage_detail_cached) {
updatePreference(item, details.cacheSize);
accountedSize += details.cacheSize;
if (LOGV)
Log.v(TAG, "cacheSize: " + details.cacheSize + " accountedSize: "
+ accountedSize);
} else if (itemTitleId == 0) {
final long userSize = details.usersSize.get(userId);
updatePreference(item, userSize);
accountedSize += userSize;
if (LOGV) Log.v(TAG, "userSize: " + userSize
+ " accountedSize: " + accountedSize);
}
}
if (otherItem != null) {
final long usedSize = mTotalSize - details.availSize;
final long unaccountedSize = usedSize - accountedSize;
final long otherSize = totalMiscSize + totalDownloadsSize + unaccountedSize;
Log.v(TAG, "Other items: \n\tmTotalSize: " + mTotalSize + " availSize: "
+ details.availSize + " usedSize: " + usedSize + "\n\taccountedSize: "
+ accountedSize + " unaccountedSize size: " + unaccountedSize
+ "\n\ttotalMiscSize: " + totalMiscSize + " totalDownloadsSize: "
+ totalDownloadsSize + "\n\tdetails: " + details);
updatePreference(otherItem, otherSize);
}
}
private void updatePreference(StorageItemPreference pref, long size) {
pref.setStorageSize(size, mTotalSize);
}
private static long totalValues(MeasurementDetails details, int userId, String... keys) {
long total = 0;
HashMap<String, Long> map = details.mediaSize.get(userId);
if (map != null) {
for (String key : keys) {
if (map.containsKey(key)) {
total += map.get(key);
}
}
} else {
Log.w(TAG, "MeasurementDetails mediaSize array does not have key for user " + userId);
}
return total;
}
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (Objects.equals(mVolume.getId(), vol.getId())) {
mVolume = vol;
update();
}
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
if (Objects.equals(mVolume.getFsUuid(), rec.getFsUuid())) {
mVolume = mStorageManager.findVolumeById(mVolumeId);
update();
}
}
};
/**
* Dialog that allows editing of volume nickname.
*/
public static class RenameFragment extends InstrumentedDialogFragment {
public static void show(PrivateVolumeSettings parent, VolumeInfo vol) {
if (!parent.isAdded()) return;
final RenameFragment dialog = new RenameFragment();
dialog.setTargetFragment(parent, 0);
final Bundle args = new Bundle();
args.putString(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid());
dialog.setArguments(args);
dialog.show(parent.getFragmentManager(), TAG_RENAME);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_VOLUME_RENAME;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final StorageManager storageManager = context.getSystemService(StorageManager.class);
final String fsUuid = getArguments().getString(VolumeRecord.EXTRA_FS_UUID);
final VolumeInfo vol = storageManager.findVolumeByUuid(fsUuid);
final VolumeRecord rec = storageManager.findRecordByUuid(fsUuid);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
final View view = dialogInflater.inflate(R.layout.dialog_edittext, null, false);
final EditText nickname = (EditText) view.findViewById(R.id.edittext);
nickname.setText(rec.getNickname());
builder.setTitle(R.string.storage_rename_title);
builder.setView(view);
builder.setPositiveButton(R.string.save,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO: move to background thread
storageManager.setVolumeNickname(fsUuid,
nickname.getText().toString());
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
}
}
public static class SystemInfoFragment extends InstrumentedDialogFragment {
public static void show(Fragment parent) {
if (!parent.isAdded()) return;
final SystemInfoFragment dialog = new SystemInfoFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_SYSTEM_INFO);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_STORAGE_SYSTEM_INFO;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(getContext().getString(R.string.storage_detail_dialog_system,
Build.VERSION.RELEASE_OR_CODENAME))
.setPositiveButton(android.R.string.ok, null)
.create();
}
}
public static class OtherInfoFragment extends InstrumentedDialogFragment {
public static void show(Fragment parent, String title, VolumeInfo sharedVol, int userId) {
if (!parent.isAdded()) return;
final OtherInfoFragment dialog = new OtherInfoFragment();
dialog.setTargetFragment(parent, 0);
final Bundle args = new Bundle();
args.putString(Intent.EXTRA_TITLE, title);
final Intent intent = sharedVol.buildBrowseIntent();
intent.putExtra(Intent.EXTRA_USER_ID, userId);
args.putParcelable(Intent.EXTRA_INTENT, intent);
dialog.setArguments(args);
dialog.show(parent.getFragmentManager(), TAG_OTHER_INFO);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_STORAGE_OTHER_INFO;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final String title = getArguments().getString(Intent.EXTRA_TITLE);
final Intent intent = getArguments().getParcelable(Intent.EXTRA_INTENT);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(
TextUtils.expandTemplate(getText(R.string.storage_detail_dialog_other), title));
builder.setPositiveButton(R.string.storage_menu_explore,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Utils.launchIntent(OtherInfoFragment.this, intent);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
public static class UserInfoFragment extends InstrumentedDialogFragment {
public static void show(Fragment parent, CharSequence userLabel, CharSequence userSize) {
if (!parent.isAdded()) return;
final UserInfoFragment dialog = new UserInfoFragment();
dialog.setTargetFragment(parent, 0);
final Bundle args = new Bundle();
args.putCharSequence(Intent.EXTRA_TITLE, userLabel);
args.putCharSequence(Intent.EXTRA_SUBJECT, userSize);
dialog.setArguments(args);
dialog.show(parent.getFragmentManager(), TAG_USER_INFO);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_STORAGE_USER_INFO;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final CharSequence userLabel = getArguments().getCharSequence(Intent.EXTRA_TITLE);
final CharSequence userSize = getArguments().getCharSequence(Intent.EXTRA_SUBJECT);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_detail_dialog_user), userLabel, userSize));
builder.setPositiveButton(android.R.string.ok, null);
return builder.create();
}
}
/**
* Dialog to request user confirmation before clearing all cache data.
*/
public static class ConfirmClearCacheFragment extends InstrumentedDialogFragment {
public static void show(Fragment parent) {
if (!parent.isAdded()) return;
final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_CONFIRM_CLEAR_CACHE);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_STORAGE_CLEAR_CACHE;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.memory_clear_cache_title);
builder.setMessage(getString(R.string.memory_clear_cache_message));
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final PrivateVolumeSettings target = (PrivateVolumeSettings) getTargetFragment();
final PackageManager pm = context.getPackageManager();
final UserManager um = context.getSystemService(UserManager.class);
for (int userId : um.getProfileIdsWithDisabled(context.getUserId())) {
final List<PackageInfo> infos = pm.getInstalledPackagesAsUser(0, userId);
final ClearCacheObserver observer = new ClearCacheObserver(
target, infos.size());
for (PackageInfo info : infos) {
pm.deleteApplicationCacheFilesAsUser(info.packageName, userId,
observer);
}
}
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
private static class ClearCacheObserver extends IPackageDataObserver.Stub {
private final PrivateVolumeSettings mTarget;
private int mRemaining;
public ClearCacheObserver(PrivateVolumeSettings target, int remaining) {
mTarget = target;
mRemaining = remaining;
}
@Override
public void onRemoveCompleted(final String packageName, final boolean succeeded) {
synchronized (this) {
if (--mRemaining == 0) {
mTarget.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mTarget.update();
}
});
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.deviceinfo.StorageSettings.UnmountTask;
import com.android.settings.deviceinfo.storage.StorageUtils.UnmountTask;
import com.android.settings.search.actionbar.SearchMenuController;
public class PrivateVolumeUnmount extends InstrumentedFragment {

View File

@@ -40,8 +40,8 @@ import androidx.preference.PreferenceScreen;
import com.android.internal.util.Preconditions;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.deviceinfo.StorageSettings.MountTask;
import com.android.settings.deviceinfo.StorageSettings.UnmountTask;
import com.android.settings.deviceinfo.storage.StorageUtils.MountTask;
import com.android.settings.deviceinfo.storage.StorageUtils.UnmountTask;
import java.io.File;
import java.util.Objects;

View File

@@ -1,692 +0,0 @@
/*
* Copyright (C) 2015 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;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Panel showing both internal storage (both built-in storage and private
* volumes) and removable storage (public volumes).
*/
@SearchIndexable
public class StorageSettings extends SettingsPreferenceFragment implements Indexable {
static final String TAG = "StorageSettings";
private static final String KEY_STORAGE_SETTINGS = "storage_settings";
private static final String KEY_INTERNAL_STORAGE = "storage_settings_internal_storage";
private static final String KEY_STORAGE_SETTINGS_VOLUME = "storage_settings_volume_";
private static final String KEY_STORAGE_SETTINGS_MEMORY_SIZE = "storage_settings_memory_size";
private static final String KEY_STORAGE_SETTINGS_MEMORY = "storage_settings_memory_available";
private static final String KEY_STORAGE_SETTINGS_DCIM = "storage_settings_dcim_space";
private static final String KEY_STORAGE_SETTINGS_MUSIC = "storage_settings_music_space";
private static final String KEY_STORAGE_SETTINGS_MISC = "storage_settings_misc_space";
private static final String KEY_STORAGE_SETTINGS_FREE_SPACE = "storage_settings_free_space";
private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted";
private static final String TAG_DISK_INIT = "disk_init";
private static final int METRICS_CATEGORY = SettingsEnums.DEVICEINFO_STORAGE;
private StorageManager mStorageManager;
private PreferenceCategory mInternalCategory;
private PreferenceCategory mExternalCategory;
private StorageSummaryPreference mInternalSummary;
private static long sTotalInternalStorage;
private boolean mHasLaunchedPrivateVolumeSettings = false;
@Override
public int getMetricsCategory() {
return METRICS_CATEGORY;
}
@Override
public int getHelpResource() {
return R.string.help_uri_storage;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Context context = getActivity();
mStorageManager = context.getSystemService(StorageManager.class);
if (sTotalInternalStorage <= 0) {
sTotalInternalStorage = mStorageManager.getPrimaryStorageSize();
}
addPreferencesFromResource(R.xml.device_info_storage);
mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
mExternalCategory = (PreferenceCategory) findPreference("storage_external");
mInternalSummary = new StorageSummaryPreference(getPrefContext());
setHasOptionsMenu(true);
}
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (isInteresting(vol)) {
refresh();
}
}
@Override
public void onDiskDestroyed(DiskInfo disk) {
refresh();
}
};
private static boolean isInteresting(VolumeInfo vol) {
switch (vol.getType()) {
case VolumeInfo.TYPE_PRIVATE:
case VolumeInfo.TYPE_PUBLIC:
case VolumeInfo.TYPE_STUB:
return true;
default:
return false;
}
}
private synchronized void refresh() {
final Context context = getPrefContext();
getPreferenceScreen().removeAll();
mInternalCategory.removeAll();
mExternalCategory.removeAll();
mInternalCategory.addPreference(mInternalSummary);
final StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(mStorageManager);
final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(smvp);
final long privateTotalBytes = info.totalBytes;
final long privateUsedBytes = info.totalBytes - info.freeBytes;
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
for (VolumeInfo vol : volumes) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
mInternalCategory.addPreference(
new StorageVolumePreference(context, vol, 0));
} else {
final long volumeTotalBytes = PrivateStorageInfo.getTotalSize(vol,
sTotalInternalStorage);
mInternalCategory.addPreference(
new StorageVolumePreference(context, vol, volumeTotalBytes));
}
} else if (vol.getType() == VolumeInfo.TYPE_PUBLIC
|| vol.getType() == VolumeInfo.TYPE_STUB) {
mExternalCategory.addPreference(
new StorageVolumePreference(context, vol, 0));
}
}
// Show missing private volumes
final List<VolumeRecord> recs = mStorageManager.getVolumeRecords();
for (VolumeRecord rec : recs) {
if (rec.getType() == VolumeInfo.TYPE_PRIVATE
&& mStorageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
// TODO: add actual storage type to record
final Preference pref = new Preference(context);
pref.setKey(rec.getFsUuid());
pref.setTitle(rec.getNickname());
pref.setSummary(com.android.internal.R.string.ext_media_status_missing);
pref.setIcon(R.drawable.ic_sim_sd);
mInternalCategory.addPreference(pref);
}
}
// Show unsupported disks to give a chance to init
final List<DiskInfo> disks = mStorageManager.getDisks();
for (DiskInfo disk : disks) {
if (disk.volumeCount == 0 && disk.size > 0) {
final Preference pref = new Preference(context);
pref.setKey(disk.getId());
pref.setTitle(disk.getDescription());
pref.setSummary(com.android.internal.R.string.ext_media_status_unsupported);
pref.setIcon(R.drawable.ic_sim_sd);
mExternalCategory.addPreference(pref);
}
}
final BytesResult result = Formatter.formatBytes(getResources(), privateUsedBytes, 0);
mInternalSummary.setTitle(TextUtils.expandTemplate(getText(R.string.storage_size_large),
result.value, result.units));
mInternalSummary.setSummary(getString(R.string.storage_volume_used_total,
Formatter.formatFileSize(context, privateTotalBytes)));
if (mInternalCategory.getPreferenceCount() > 0) {
getPreferenceScreen().addPreference(mInternalCategory);
}
if (mExternalCategory.getPreferenceCount() > 0) {
getPreferenceScreen().addPreference(mExternalCategory);
}
if (mInternalCategory.getPreferenceCount() == 2
&& mExternalCategory.getPreferenceCount() == 0) {
// Only showing primary internal storage, so just shortcut
if (!mHasLaunchedPrivateVolumeSettings) {
mHasLaunchedPrivateVolumeSettings = true;
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
new SubSettingLauncher(getActivity())
.setDestination(StorageDashboardFragment.class.getName())
.setArguments(args)
.setTitleRes(R.string.storage_settings)
.setSourceMetricsCategory(getMetricsCategory())
.launch();
finish();
}
}
}
@Override
public void onResume() {
super.onResume();
mStorageManager.registerListener(mStorageListener);
refresh();
}
@Override
public void onPause() {
super.onPause();
mStorageManager.unregisterListener(mStorageListener);
}
@Override
public boolean onPreferenceTreeClick(Preference pref) {
final String key = pref.getKey();
if (pref instanceof StorageVolumePreference) {
// Picked a normal volume
final VolumeInfo vol = mStorageManager.findVolumeById(key);
if (vol == null) {
return false;
}
if (vol.getState() == VolumeInfo.STATE_UNMOUNTED) {
VolumeUnmountedFragment.show(this, vol.getId());
return true;
} else if (vol.getState() == VolumeInfo.STATE_UNMOUNTABLE) {
DiskInitFragment.show(this, R.string.storage_dialog_unmountable, vol.getDiskId());
return true;
}
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
new SubSettingLauncher(getContext())
.setDestination(StorageDashboardFragment.class.getCanonicalName())
.setTitleRes(R.string.storage_settings)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args)
.launch();
} else {
// TODO: Go to the StorageDashboardFragment once it fully handles all of the
// SD card cases and other private internal storage cases.
PrivateVolumeSettings.setVolumeSize(args, PrivateStorageInfo.getTotalSize(vol,
sTotalInternalStorage));
new SubSettingLauncher(getContext())
.setDestination(PrivateVolumeSettings.class.getCanonicalName())
.setTitleRes(-1)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args)
.launch();
}
return true;
} else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
return handlePublicVolumeClick(getContext(), vol);
} else if (vol.getType() == VolumeInfo.TYPE_STUB) {
return handleStubVolumeClick(getContext(), vol);
}
} else if (key.startsWith("disk:")) {
// Picked an unsupported disk
DiskInitFragment.show(this, R.string.storage_dialog_unsupported, key);
return true;
} else {
// Picked a missing private volume
final Bundle args = new Bundle();
args.putString(VolumeRecord.EXTRA_FS_UUID, key);
new SubSettingLauncher(getContext())
.setDestination(PrivateVolumeForget.class.getCanonicalName())
.setTitleRes(R.string.storage_menu_forget)
.setSourceMetricsCategory(getMetricsCategory())
.setArguments(args)
.launch();
return true;
}
return false;
}
@VisibleForTesting
static boolean handleStubVolumeClick(Context context, VolumeInfo vol) {
final Intent intent = vol.buildBrowseIntent();
if (vol.isMountedReadable() && intent != null) {
context.startActivity(intent);
return true;
}
return false;
}
@VisibleForTesting
static boolean handlePublicVolumeClick(Context context, VolumeInfo vol) {
final Intent intent = vol.buildBrowseIntent();
if (vol.isMountedReadable() && intent != null) {
context.startActivity(intent);
return true;
} else {
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, vol.getId());
new SubSettingLauncher(context)
.setDestination(PublicVolumeSettings.class.getCanonicalName())
.setTitleRes(-1)
.setSourceMetricsCategory(METRICS_CATEGORY)
.setArguments(args)
.launch();
return true;
}
}
public static class MountTask extends AsyncTask<Void, Void, Exception> {
private final Context mContext;
private final StorageManager mStorageManager;
private final String mVolumeId;
private final String mDescription;
public MountTask(Context context, VolumeInfo volume) {
mContext = context.getApplicationContext();
mStorageManager = mContext.getSystemService(StorageManager.class);
mVolumeId = volume.getId();
mDescription = mStorageManager.getBestVolumeDescription(volume);
}
@Override
protected Exception doInBackground(Void... params) {
try {
mStorageManager.mount(mVolumeId);
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onPostExecute(Exception e) {
if (e == null) {
Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
mDescription), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Failed to mount " + mVolumeId, e);
Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
mDescription), Toast.LENGTH_SHORT).show();
}
}
}
public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
private final Context mContext;
private final StorageManager mStorageManager;
private final String mVolumeId;
private final String mDescription;
public UnmountTask(Context context, VolumeInfo volume) {
mContext = context.getApplicationContext();
mStorageManager = mContext.getSystemService(StorageManager.class);
mVolumeId = volume.getId();
mDescription = mStorageManager.getBestVolumeDescription(volume);
}
@Override
protected Exception doInBackground(Void... params) {
try {
mStorageManager.unmount(mVolumeId);
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onPostExecute(Exception e) {
if (e == null) {
Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
mDescription), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Failed to unmount " + mVolumeId, e);
Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
mDescription), Toast.LENGTH_SHORT).show();
}
}
}
public static class VolumeUnmountedFragment extends InstrumentedDialogFragment {
public static void show(Fragment parent, String volumeId) {
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeId);
final VolumeUnmountedFragment dialog = new VolumeUnmountedFragment();
dialog.setArguments(args);
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_VOLUME_UNMOUNTED);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_VOLUME_UNMOUNT;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final StorageManager sm = context.getSystemService(StorageManager.class);
final String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
final VolumeInfo vol = sm.findVolumeById(volumeId);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(TextUtils.expandTemplate(
getText(R.string.storage_dialog_unmounted), vol.getDisk().getDescription()));
builder.setPositiveButton(R.string.storage_menu_mount,
new DialogInterface.OnClickListener() {
/**
* Check if an {@link
* RestrictedLockUtils#sendShowAdminSupportDetailsIntent admin
* details intent} should be shown for the restriction and show it.
*
* @param restriction The restriction to check
* @return {@code true} iff a intent was shown.
*/
private boolean wasAdminSupportIntentShown(@NonNull String restriction) {
EnforcedAdmin admin = RestrictedLockUtilsInternal
.checkIfRestrictionEnforced(getActivity(), restriction,
UserHandle.myUserId());
boolean hasBaseUserRestriction =
RestrictedLockUtilsInternal.hasBaseUserRestriction(
getActivity(), restriction, UserHandle.myUserId());
if (admin != null && !hasBaseUserRestriction) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
admin);
return true;
}
return false;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (wasAdminSupportIntentShown(
UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
return;
}
if (vol.disk != null && vol.disk.isUsb() &&
wasAdminSupportIntentShown(
UserManager.DISALLOW_USB_FILE_TRANSFER)) {
return;
}
new MountTask(context, vol).execute();
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
}
}
public static class DiskInitFragment extends InstrumentedDialogFragment {
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_VOLUME_INIT;
}
public static void show(Fragment parent, int resId, String diskId) {
final Bundle args = new Bundle();
args.putInt(Intent.EXTRA_TEXT, resId);
args.putString(DiskInfo.EXTRA_DISK_ID, diskId);
final DiskInitFragment dialog = new DiskInitFragment();
dialog.setArguments(args);
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), TAG_DISK_INIT);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
final StorageManager sm = context.getSystemService(StorageManager.class);
final int resId = getArguments().getInt(Intent.EXTRA_TEXT);
final String diskId = getArguments().getString(DiskInfo.EXTRA_DISK_ID);
final DiskInfo disk = sm.findDiskById(diskId);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(TextUtils.expandTemplate(getText(resId), disk.getDescription()));
builder.setPositiveButton(R.string.storage_menu_set_up,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final Intent intent = new Intent(context, StorageWizardInit.class);
intent.putExtra(DiskInfo.EXTRA_DISK_ID, diskId);
startActivity(intent);
}
});
builder.setNegativeButton(R.string.cancel, null);
return builder.create();
}
}
/** Enable indexing of searchable data */
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableRaw> getRawDataToIndex(
Context context, boolean enabled) {
final List<SearchIndexableRaw> result = new ArrayList<>();
SearchIndexableRaw data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.storage_settings);
data.key = KEY_STORAGE_SETTINGS;
data.screenTitle = context.getString(R.string.storage_settings);
data.keywords = context.getString(R.string.keywords_storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.internal_storage);
data.key = KEY_INTERNAL_STORAGE;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
final StorageManager storage = context.getSystemService(StorageManager.class);
final List<VolumeInfo> vols = storage.getVolumes();
for (VolumeInfo vol : vols) {
if (isInteresting(vol)) {
data.title = storage.getBestVolumeDescription(vol);
data.key = KEY_STORAGE_SETTINGS_VOLUME + vol.id;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
}
}
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.memory_size);
data.key = KEY_STORAGE_SETTINGS_MEMORY_SIZE;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.memory_available);
data.key = KEY_STORAGE_SETTINGS_MEMORY;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.memory_dcim_usage);
data.key = KEY_STORAGE_SETTINGS_DCIM;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.memory_music_usage);
data.key = KEY_STORAGE_SETTINGS_MUSIC;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.memory_media_misc_usage);
data.key = KEY_STORAGE_SETTINGS_MISC;
data.screenTitle = context.getString(R.string.storage_settings);
result.add(data);
data = new SearchIndexableRaw(context);
data.title = context.getString(R.string.storage_menu_free);
data.key = KEY_STORAGE_SETTINGS_FREE_SPACE;
data.screenTitle = context.getString(R.string.storage_menu_free);
data.intentAction = StorageManager.ACTION_MANAGE_STORAGE;
data.keywords = context.getString(R.string.keywords_storage_menu_free);
result.add(data);
return result;
}
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> niks = super.getNonIndexableKeys(context);
if (isExternalExist(context)) {
niks.add(KEY_STORAGE_SETTINGS);
niks.add(KEY_INTERNAL_STORAGE);
niks.add(KEY_STORAGE_SETTINGS_MEMORY_SIZE);
niks.add(KEY_STORAGE_SETTINGS_MEMORY);
niks.add(KEY_STORAGE_SETTINGS_DCIM);
niks.add(KEY_STORAGE_SETTINGS_MUSIC);
niks.add(KEY_STORAGE_SETTINGS_MISC);
niks.add(KEY_STORAGE_SETTINGS_FREE_SPACE);
final StorageManager storage = context.getSystemService(
StorageManager.class);
final List<VolumeInfo> vols = storage.getVolumes();
for (VolumeInfo vol : vols) {
if (isInteresting(vol)) {
niks.add(KEY_STORAGE_SETTINGS_VOLUME + vol.id);
}
}
}
return niks;
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return !isExternalExist(context);
}
private boolean isExternalExist(Context context) {
int internalCount = 0;
StorageManager storageManager = context.getSystemService(StorageManager.class);
final List<VolumeInfo> volumes = storageManager.getVolumes();
for (VolumeInfo vol : volumes) {
//External storage
if (vol.getType() == VolumeInfo.TYPE_PUBLIC
|| vol.getType() == VolumeInfo.TYPE_STUB) {
return true;
} else if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
internalCount++;
}
}
// Unsupported disks
final List<DiskInfo> disks = storageManager.getDisks();
for (DiskInfo disk : disks) {
if (disk.volumeCount == 0 && disk.size > 0) {
return true;
}
}
// Missing private volumes
final List<VolumeRecord> recs = storageManager.getVolumeRecords();
for (VolumeRecord rec : recs) {
if (rec.getType() == VolumeInfo.TYPE_PRIVATE
&& storageManager.findVolumeByUuid(rec.getFsUuid()) == null) {
internalCount++;
}
}
return (internalCount != 1);
}
};
}

View File

@@ -16,8 +16,6 @@
package com.android.settings.deviceinfo;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -25,9 +23,11 @@ import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.Log;
import com.android.settings.deviceinfo.StorageSettings.UnmountTask;
import com.android.settings.deviceinfo.storage.StorageUtils.UnmountTask;
public class StorageUnmountReceiver extends BroadcastReceiver {
private static final String TAG = "StorageUnmountReceiver";
@Override
public void onReceive(Context context, Intent intent) {
final StorageManager storage = context.getSystemService(StorageManager.class);

View File

@@ -1,153 +0,0 @@
/*
* Copyright (C) 2015 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;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.ProgressBar;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.deviceinfo.StorageSettings.UnmountTask;
import com.android.settingslib.Utils;
import java.io.File;
import java.io.IOException;
/**
* Preference line representing a single {@link VolumeInfo}, possibly including
* quick actions like unmounting.
*/
public class StorageVolumePreference extends Preference {
private static final String TAG = StorageVolumePreference.class.getSimpleName();
private final StorageManager mStorageManager;
private final VolumeInfo mVolume;
private int mUsedPercent = -1;
private ColorStateList mColorTintList;
// TODO: ideally, VolumeInfo should have a total physical size.
public StorageVolumePreference(Context context, VolumeInfo volume, long totalBytes) {
super(context);
mStorageManager = context.getSystemService(StorageManager.class);
mVolume = volume;
mColorTintList = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
setLayoutResource(R.layout.storage_volume);
setKey(volume.getId());
setTitle(mStorageManager.getBestVolumeDescription(volume));
Drawable icon;
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(volume.getId())) {
icon = context.getDrawable(R.drawable.ic_storage);
} else {
icon = context.getDrawable(R.drawable.ic_sim_sd);
}
if (volume.isMountedReadable()) {
// TODO: move statfs() to background thread
final File path = volume.getPath();
long freeBytes = 0;
long usedBytes = 0;
if (volume.getType() == VolumeInfo.TYPE_PRIVATE) {
final StorageStatsManager stats =
context.getSystemService(StorageStatsManager.class);
try {
totalBytes = stats.getTotalBytes(volume.getFsUuid());
freeBytes = stats.getFreeBytes(volume.getFsUuid());
usedBytes = totalBytes - freeBytes;
} catch (IOException e) {
Log.w(TAG, e);
}
} else {
// StorageStatsManager can only query private volumes.
// Default to previous storage calculation for public volumes.
if (totalBytes <= 0) {
totalBytes = path.getTotalSpace();
}
freeBytes = path.getFreeSpace();
usedBytes = totalBytes - freeBytes;
}
final String used = Formatter.formatFileSize(context, usedBytes);
final String total = Formatter.formatFileSize(context, totalBytes);
setSummary(context.getString(R.string.storage_volume_summary, used, total));
if (totalBytes > 0) {
mUsedPercent = (int) ((usedBytes * 100) / totalBytes);
}
if (freeBytes < mStorageManager.getStorageLowBytes(path)) {
mColorTintList = Utils.getColorAttr(context, android.R.attr.colorError);
icon = context.getDrawable(R.drawable.ic_warning_24dp);
icon.mutate();
icon.setTintList(mColorTintList);
}
} else {
setSummary(volume.getStateDescription());
mUsedPercent = -1;
}
setIcon(icon);
if (volume.getType() == VolumeInfo.TYPE_PUBLIC
&& volume.isMountedReadable()) {
setWidgetLayoutResource(R.layout.preference_storage_action);
}
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
final ImageView unmount = (ImageView) view.findViewById(R.id.unmount);
if (unmount != null) {
unmount.setOnClickListener(mUnmountListener);
}
final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
if (mVolume.getType() == VolumeInfo.TYPE_PRIVATE && mUsedPercent != -1) {
progress.setVisibility(View.VISIBLE);
progress.setProgress(mUsedPercent);
progress.setProgressTintList(mColorTintList);
} else {
progress.setVisibility(View.GONE);
}
super.onBindViewHolder(view);
}
private final View.OnClickListener mUnmountListener = new OnClickListener() {
@Override
public void onClick(View v) {
new UnmountTask(getContext(), mVolume).execute();
}
};
}

View File

@@ -19,8 +19,6 @@ package com.android.settings.deviceinfo;
import static android.os.storage.DiskInfo.EXTRA_DISK_ID;
import static android.os.storage.VolumeInfo.EXTRA_VOLUME_ID;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
import android.content.Intent;
@@ -54,6 +52,8 @@ import java.util.List;
import java.util.Objects;
public abstract class StorageWizardBase extends FragmentActivity {
private static final String TAG = "StorageWizardBase";
protected static final String EXTRA_FORMAT_FORGET_UUID = "format_forget_uuid";
protected static final String EXTRA_FORMAT_PRIVATE = "format_private";
protected static final String EXTRA_FORMAT_SLOW = "format_slow";

View File

@@ -18,8 +18,6 @@ package com.android.settings.deviceinfo;
import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.Intent;
import android.content.pm.IPackageMoveObserver;
import android.os.AsyncTask;
@@ -40,6 +38,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class StorageWizardFormatProgress extends StorageWizardBase {
private static final String TAG = "StorageWizardFormatProgress";
private static final String PROP_DEBUG_STORAGE_SLOW = "sys.debug.storage_slow";
private boolean mFormatPrivate;

View File

@@ -16,8 +16,6 @@
package com.android.settings.deviceinfo;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -39,6 +37,8 @@ import com.android.settings.password.ChooseLockSettingsHelper;
import java.util.Objects;
public class StorageWizardMigrateConfirm extends StorageWizardBase {
private static final String TAG = "StorageWizardMigrateConfirm";
private static final int REQUEST_CREDENTIAL = 100;
private MigrateEstimateTask mEstimate;

View File

@@ -18,8 +18,6 @@ package com.android.settings.deviceinfo;
import static android.content.pm.PackageManager.EXTRA_MOVE_ID;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -34,6 +32,8 @@ import android.widget.Toast;
import com.android.settings.R;
public class StorageWizardMigrateProgress extends StorageWizardBase {
private static final String TAG = "StorageWizardMigrateProgress";
private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
private int mMoveId;

View File

@@ -21,8 +21,6 @@ import static android.content.Intent.EXTRA_TITLE;
import static android.content.pm.PackageManager.EXTRA_MOVE_ID;
import static android.os.storage.VolumeInfo.EXTRA_VOLUME_ID;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -39,6 +37,8 @@ import com.android.settings.R;
import com.android.settings.password.ChooseLockSettingsHelper;
public class StorageWizardMoveConfirm extends StorageWizardBase {
private static final String TAG = "StorageWizardMoveConfirm";
private static final int REQUEST_CREDENTIAL = 100;
private String mPackageName;

View File

@@ -19,8 +19,6 @@ package com.android.settings.deviceinfo;
import static android.content.Intent.EXTRA_TITLE;
import static android.content.pm.PackageManager.EXTRA_MOVE_ID;
import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.MoveCallback;
import android.os.Bundle;
@@ -32,6 +30,8 @@ import android.widget.Toast;
import com.android.settings.R;
public class StorageWizardMoveProgress extends StorageWizardBase {
private static final String TAG = "StorageWizardMoveProgress";
private int mMoveId;
@Override

View File

@@ -35,11 +35,11 @@ import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.deviceinfo.StorageSettings.MountTask;
import com.android.settings.deviceinfo.StorageSettings.UnmountTask;
import com.android.settings.deviceinfo.storage.StorageEntry;
import com.android.settings.deviceinfo.storage.StorageRenameFragment;
import com.android.settings.deviceinfo.storage.StorageUtils;
import com.android.settings.deviceinfo.storage.StorageUtils.MountTask;
import com.android.settings.deviceinfo.storage.StorageUtils.UnmountTask;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnCreateOptionsMenu;
import com.android.settingslib.core.lifecycle.events.OnOptionsItemSelected;

View File

@@ -45,8 +45,8 @@ import com.android.settings.Utils;
import com.android.settings.applications.manageapplications.ManageApplications;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.deviceinfo.PrivateVolumeSettings.SystemInfoFragment;
import com.android.settings.deviceinfo.StorageItemPreference;
import com.android.settings.deviceinfo.storage.StorageUtils.SystemInfoFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;

View File

@@ -1,91 +0,0 @@
/*
* Copyright (C) 2016 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 android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.storage.StorageManager;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.DonutView;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* StorageSummaryDonutPreference is a preference which summarizes the used and remaining storage left
* on a given storage volume. It is visualized with a donut graphing the % used.
*/
public class StorageSummaryDonutPreference extends Preference implements View.OnClickListener {
private double mPercent = -1;
public StorageSummaryDonutPreference(Context context) {
this(context, null);
}
public StorageSummaryDonutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.storage_summary_donut);
setEnabled(false);
}
public void setPercent(long usedBytes, long totalBytes) {
if (totalBytes == 0) {
return;
}
mPercent = usedBytes / (double) totalBytes;
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
view.itemView.setClickable(false);
final DonutView donut = (DonutView) view.findViewById(R.id.donut);
if (donut != null) {
donut.setPercentage(mPercent);
}
final Button deletionHelperButton = (Button) view.findViewById(R.id.deletion_helper_button);
if (deletionHelperButton != null) {
deletionHelperButton.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
if (v != null && R.id.deletion_helper_button == v.getId()) {
final Context context = getContext();
final MetricsFeatureProvider metricsFeatureProvider =
FeatureFactory.getFactory(context).getMetricsFeatureProvider();
metricsFeatureProvider.logClickedPreference(this,
getExtras().getInt(DashboardFragment.CATEGORY));
metricsFeatureProvider.action(context, SettingsEnums.STORAGE_FREE_UP_SPACE_NOW);
final Intent intent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
context.startActivity(intent);
}
}
}

View File

@@ -1,136 +0,0 @@
/*
* Copyright (C) 2017 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 android.content.Context;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.text.TextUtils;
import android.text.format.Formatter;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;
import com.android.settingslib.utils.ThreadUtils;
import java.text.NumberFormat;
/**
* SummaryPreferenceController updates the donut storage summary preference to have the
* correct sizes showing.
*/
public class StorageSummaryDonutPreferenceController extends BasePreferenceController {
private long mUsedBytes;
private long mTotalBytes;
private StorageSummaryDonutPreference mSummary;
private final StorageManager mStorageManager;
private final StorageManagerVolumeProvider mStorageManagerVolumeProvider;
public StorageSummaryDonutPreferenceController(Context context, String key) {
super(context, key);
mStorageManager = mContext.getSystemService(StorageManager.class);
mStorageManagerVolumeProvider = new StorageManagerVolumeProvider(mStorageManager);
}
/**
* Converts a used storage amount to a formatted text.
*
* @param usedBytes used bytes of storage
* @return a formatted text.
*/
public static CharSequence convertUsedBytesToFormattedText(Context context, long usedBytes) {
final Formatter.BytesResult result = Formatter.formatBytes(context.getResources(),
usedBytes, 0);
return TextUtils.expandTemplate(context.getText(R.string.storage_size_large_alternate),
result.value, result.units);
}
@Override
public void displayPreference(PreferenceScreen screen) {
mSummary = screen.findPreference(getPreferenceKey());
mSummary.setEnabled(true);
ThreadUtils.postOnBackgroundThread(() -> {
final NumberFormat percentageFormat = NumberFormat.getPercentInstance();
final PrivateStorageInfo info = PrivateStorageInfo.getPrivateStorageInfo(
mStorageManagerVolumeProvider);
final double privateUsedBytes = info.totalBytes - info.freeBytes;
mTotalBytes = info.totalBytes;
mUsedBytes = info.totalBytes - info.freeBytes;
ThreadUtils.postOnMainThread(() -> {
updateState(mSummary);
});
});
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
mSummary.setTitle(convertUsedBytesToFormattedText(mContext, mUsedBytes));
mSummary.setSummary(mContext.getString(R.string.storage_volume_total,
Formatter.formatShortFileSize(mContext, mTotalBytes)));
mSummary.setPercent(mUsedBytes, mTotalBytes);
mSummary.setEnabled(true);
}
/** Invalidates the data on the view and re-renders. */
public void invalidateData() {
if (mSummary != null) {
updateState(mSummary);
}
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
/**
* Updates the state of the donut preference for the next update.
*
* @param used Total number of used bytes on the summarized volume.
* @param total Total number of bytes on the summarized volume.
*/
public void updateBytes(long used, long total) {
mUsedBytes = used;
mTotalBytes = total;
invalidateData();
}
/**
* Updates the state of the donut preference for the next update using volume to summarize.
*
* @param volume VolumeInfo to use to populate the informayion.
*/
public void updateSizes(StorageVolumeProvider svp, VolumeInfo volume) {
final long sharedDataSize = volume.getPath().getTotalSpace();
long totalSize = svp.getPrimaryStorageSize();
if (totalSize <= 0) {
totalSize = sharedDataSize;
}
final long usedBytes = totalSize - volume.getPath().getFreeSpace();
updateBytes(usedBytes, totalSize);
}
}

View File

@@ -16,18 +16,31 @@
package com.android.settings.deviceinfo.storage;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.deviceinfo.PrivateVolumeForget;
/** Storage utilities */
public class StorageUtils {
private static final String TAG = "StorageUtils";
/** Launches the fragment to forget a specified missing volume record. */
public static void launchForgetMissingVolumeRecordFragment(Context context,
StorageEntry storageEntry) {
@@ -44,4 +57,104 @@ public class StorageUtils {
.setArguments(args)
.launch();
}
/** An AsyncTask to unmount a specified volume. */
public static class UnmountTask extends AsyncTask<Void, Void, Exception> {
private final Context mContext;
private final StorageManager mStorageManager;
private final String mVolumeId;
private final String mDescription;
public UnmountTask(Context context, VolumeInfo volume) {
mContext = context.getApplicationContext();
mStorageManager = mContext.getSystemService(StorageManager.class);
mVolumeId = volume.getId();
mDescription = mStorageManager.getBestVolumeDescription(volume);
}
@Override
protected Exception doInBackground(Void... params) {
try {
mStorageManager.unmount(mVolumeId);
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onPostExecute(Exception e) {
if (e == null) {
Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_success,
mDescription), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Failed to unmount " + mVolumeId, e);
Toast.makeText(mContext, mContext.getString(R.string.storage_unmount_failure,
mDescription), Toast.LENGTH_SHORT).show();
}
}
}
/** An AsyncTask to mount a specified volume. */
public static class MountTask extends AsyncTask<Void, Void, Exception> {
private final Context mContext;
private final StorageManager mStorageManager;
private final String mVolumeId;
private final String mDescription;
public MountTask(Context context, VolumeInfo volume) {
mContext = context.getApplicationContext();
mStorageManager = mContext.getSystemService(StorageManager.class);
mVolumeId = volume.getId();
mDescription = mStorageManager.getBestVolumeDescription(volume);
}
@Override
protected Exception doInBackground(Void... params) {
try {
mStorageManager.mount(mVolumeId);
return null;
} catch (Exception e) {
return e;
}
}
@Override
protected void onPostExecute(Exception e) {
if (e == null) {
Toast.makeText(mContext, mContext.getString(R.string.storage_mount_success,
mDescription), Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Failed to mount " + mVolumeId, e);
Toast.makeText(mContext, mContext.getString(R.string.storage_mount_failure,
mDescription), Toast.LENGTH_SHORT).show();
}
}
}
/* Shows information about system storage. */
public static class SystemInfoFragment extends InstrumentedDialogFragment {
/** Shows the fragment. */
public static void show(Fragment parent) {
if (!parent.isAdded()) return;
final SystemInfoFragment dialog = new SystemInfoFragment();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), "systemInfo");
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_STORAGE_SYSTEM_INFO;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(getContext().getString(R.string.storage_detail_dialog_system,
Build.VERSION.RELEASE_OR_CODENAME))
.setPositiveButton(android.R.string.ok, null)
.create();
}
}
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright (C) 2017 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.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.icu.text.DecimalFormatSymbols;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.ColorRes;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import java.util.Locale;
/**
* DonutView represents a donut graph. It visualizes a certain percentage of fullness with a
* corresponding label with the fullness on the inside (i.e. "50%" inside of the donut).
*/
public class DonutView extends View {
private static final int TOP = -90;
// From manual testing, this is the longest we can go without visual errors.
private static final int LINE_CHARACTER_LIMIT = 10;
private float mStrokeWidth;
private double mPercent;
private Paint mBackgroundCircle;
private Paint mFilledArc;
private TextPaint mTextPaint;
private TextPaint mBigNumberPaint;
private String mPercentString;
private String mFullString;
private boolean mShowPercentString = true;
private int mMeterBackgroundColor;
private int mMeterConsumedColor;
public DonutView(Context context) {
super(context);
}
public DonutView(Context context, AttributeSet attrs) {
super(context, attrs);
mMeterBackgroundColor = context.getColor(R.color.meter_background_color);
mMeterConsumedColor = Utils.getColorStateListDefaultColor(mContext,
R.color.meter_consumed_color);
boolean applyColorAccent = true;
Resources resources = context.getResources();
mStrokeWidth = resources.getDimension(R.dimen.storage_donut_thickness);
if (attrs != null) {
TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.DonutView);
mMeterBackgroundColor = styledAttrs.getColor(R.styleable.DonutView_meterBackgroundColor,
mMeterBackgroundColor);
mMeterConsumedColor = styledAttrs.getColor(R.styleable.DonutView_meterConsumedColor,
mMeterConsumedColor);
applyColorAccent = styledAttrs.getBoolean(R.styleable.DonutView_applyColorAccent,
true);
mShowPercentString = styledAttrs.getBoolean(R.styleable.DonutView_showPercentString,
true);
mStrokeWidth = styledAttrs.getDimensionPixelSize(R.styleable.DonutView_thickness,
(int) mStrokeWidth);
styledAttrs.recycle();
}
mBackgroundCircle = new Paint();
mBackgroundCircle.setAntiAlias(true);
mBackgroundCircle.setStrokeCap(Paint.Cap.BUTT);
mBackgroundCircle.setStyle(Paint.Style.STROKE);
mBackgroundCircle.setStrokeWidth(mStrokeWidth);
mBackgroundCircle.setColor(mMeterBackgroundColor);
mFilledArc = new Paint();
mFilledArc.setAntiAlias(true);
mFilledArc.setStrokeCap(Paint.Cap.BUTT);
mFilledArc.setStyle(Paint.Style.STROKE);
mFilledArc.setStrokeWidth(mStrokeWidth);
mFilledArc.setColor(mMeterConsumedColor);
if (applyColorAccent) {
final ColorFilter mAccentColorFilter =
new PorterDuffColorFilter(
Utils.getColorAttrDefaultColor(context, android.R.attr.colorAccent),
PorterDuff.Mode.SRC_IN);
mBackgroundCircle.setColorFilter(mAccentColorFilter);
mFilledArc.setColorFilter(mAccentColorFilter);
}
final Locale locale = resources.getConfiguration().locale;
final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
final int bidiFlags = (layoutDirection == LAYOUT_DIRECTION_LTR)
? Paint.BIDI_LTR
: Paint.BIDI_RTL;
mTextPaint = new TextPaint();
mTextPaint.setColor(Utils.getColorAccentDefaultColor(getContext()));
mTextPaint.setAntiAlias(true);
mTextPaint.setTextSize(
resources.getDimension(R.dimen.storage_donut_view_label_text_size));
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setBidiFlags(bidiFlags);
mBigNumberPaint = new TextPaint();
mBigNumberPaint.setColor(Utils.getColorAccentDefaultColor(getContext()));
mBigNumberPaint.setAntiAlias(true);
mBigNumberPaint.setTextSize(
resources.getDimension(R.dimen.storage_donut_view_percent_text_size));
mBigNumberPaint.setTypeface(Typeface.create(
context.getString(com.android.internal.R.string.config_headlineFontFamily),
Typeface.NORMAL));
mBigNumberPaint.setBidiFlags(bidiFlags);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawDonut(canvas);
if (mShowPercentString) {
drawInnerText(canvas);
}
}
private void drawDonut(Canvas canvas) {
canvas.drawArc(
0 + mStrokeWidth,
0 + mStrokeWidth,
getWidth() - mStrokeWidth,
getHeight() - mStrokeWidth,
TOP,
360,
false,
mBackgroundCircle);
canvas.drawArc(
0 + mStrokeWidth,
0 + mStrokeWidth,
getWidth() - mStrokeWidth,
getHeight() - mStrokeWidth,
TOP,
(360 * (float) mPercent),
false,
mFilledArc);
}
private void drawInnerText(Canvas canvas) {
final float centerX = getWidth() / 2;
final float centerY = getHeight() / 2;
final float totalHeight = getTextHeight(mTextPaint) + getTextHeight(mBigNumberPaint);
final float startY = centerY + totalHeight / 2;
// Support from Android P
final String localizedPercentSign = new DecimalFormatSymbols().getPercentString();
// The first line y-coordinates start at (total height - all TextPaint height) / 2
canvas.save();
final Spannable percentStringSpan =
getPercentageStringSpannable(getResources(), mPercentString, localizedPercentSign);
final StaticLayout percentStringLayout = new StaticLayout(percentStringSpan,
mBigNumberPaint, getWidth(), Layout.Alignment.ALIGN_CENTER, 1, 0, false);
canvas.translate(0, (getHeight() - totalHeight) / 2);
percentStringLayout.draw(canvas);
canvas.restore();
// The second line starts at the bottom + room for the descender.
canvas.drawText(mFullString, centerX, startY - mTextPaint.descent(), mTextPaint);
}
/**
* Set a percentage full to have the donut graph.
*/
public void setPercentage(double percent) {
mPercent = percent;
mPercentString = Utils.formatPercentage(mPercent);
mFullString = getContext().getString(R.string.storage_percent_full);
if (mFullString.length() > LINE_CHARACTER_LIMIT) {
mTextPaint.setTextSize(
getContext()
.getResources()
.getDimension(
R.dimen.storage_donut_view_shrunken_label_text_size));
}
setContentDescription(getContext().getString(
R.string.join_two_unrelated_items, mPercentString, mFullString));
invalidate();
}
@ColorRes
public int getMeterBackgroundColor() {
return mMeterBackgroundColor;
}
public void setMeterBackgroundColor(@ColorRes int meterBackgroundColor) {
mMeterBackgroundColor = meterBackgroundColor;
mBackgroundCircle.setColor(meterBackgroundColor);
invalidate();
}
@ColorRes
public int getMeterConsumedColor() {
return mMeterConsumedColor;
}
public void setMeterConsumedColor(@ColorRes int meterConsumedColor) {
mMeterConsumedColor = meterConsumedColor;
mFilledArc.setColor(meterConsumedColor);
invalidate();
}
@VisibleForTesting
static Spannable getPercentageStringSpannable(
Resources resources, String percentString, String percentageSignString) {
final float fontProportion =
resources.getDimension(R.dimen.storage_donut_view_percent_sign_size)
/ resources.getDimension(R.dimen.storage_donut_view_percent_text_size);
final Spannable percentStringSpan = new SpannableString(percentString);
int startIndex = percentString.indexOf(percentageSignString);
int endIndex = startIndex + percentageSignString.length();
// Fallback to no small string if we can't find the percentage sign.
if (startIndex < 0) {
startIndex = 0;
endIndex = percentString.length();
}
percentStringSpan.setSpan(
new RelativeSizeSpan(fontProportion),
startIndex,
endIndex,
Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
return percentStringSpan;
}
private float getTextHeight(TextPaint paint) {
// Technically, this should be the cap height, but I can live with the descent - ascent.
return paint.descent() - paint.ascent();
}
}