More storage UI updates.

Storage volumes now have headers with larger fonts and progress bars
to show used versus free space.  Updated Memory to use new formatting
template, and Data Usage to use consistent display logic.

Allocate a unique color for each private volume, and yell when a
volume is running low on space.  Update private volume details to
launch into MediaStore-backed storage backends in a management mode,
and only show detailed items when hosting emulated storage.  Show
details dialog about "Other" and user storage items.

Shortcut into single private volume when it's the only device.  Add
real eject icon.

Bug: 21756698, 20275574, 21326612
Change-Id: If3ecd1d912d3e709c09d3e4da24f368e04dd3f9d
This commit is contained in:
Jeff Sharkey
2015-06-15 21:08:56 -07:00
parent edb7b0d9a9
commit 2597625fd9
17 changed files with 576 additions and 280 deletions

View File

@@ -0,0 +1,24 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M5 17h14v2H5zm7,-12L5.33 15h13.34z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M5 17h14v2H5zm7,-12L5.33 15h13.34z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M2 42h44L24 4 2 42zm24,-6h-4v-4h4v4zm0,-8h-4v-8h4v8z"/>
</vector>

View File

@@ -0,0 +1,24 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
android:fillColor="#FF000000"
android:pathData="M2 42h44L24 4 2 42zm24,-6h-4v-4h4v4zm0,-8h-4v-8h4v8z"/>
</vector>

View File

@@ -20,7 +20,7 @@
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
<ImageView
android:id="@+id/unmount"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
@@ -29,19 +29,7 @@
android:contentDescription="@string/storage_menu_unmount"
android:layout_gravity="center"
android:gravity="center"
android:textSize="30sp"
android:src="@drawable/ic_eject_24dp"
android:background="?android:attr/selectableItemBackground" />
<!--
<ImageView
android:id="@+id/eject"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:src="@drawable/ic_sync_green_holo"
android:contentDescription="@string/storage_menu_eject"
android:layout_gravity="center"
android:background="?android:attr/selectableItemBackground" />
-->
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="16dip"
android:paddingBottom="16dip"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Subhead"
android:textColor="#ff607d8b"
android:textSize="36sp"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textAppearance="@android:style/TextAppearance.Material.Body1"
android:textColor="#8a000000"
android:maxLines="10" />
<ProgressBar
android:id="@android:id/progress"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:visibility="gone"
android:max="100"
style="?android:attr/progressBarStyleHorizontal" />
</LinearLayout>

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/activatedBackgroundIndicator"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="-4dp"
android:minWidth="60dp"
android:gravity="start|center_vertical"
android:orientation="horizontal"
android:paddingEnd="12dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<com.android.internal.widget.PreferenceImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="48dp"
android:maxHeight="48dp" />
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
<TextView android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10" />
<ProgressBar
android:id="@android:id/progress"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="8dp"
android:layout_below="@android:id/summary"
android:layout_alignStart="@android:id/summary"
android:max="100"
style="?android:attr/progressBarStyleHorizontal" />
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="end|center_vertical"
android:paddingStart="16dp"
android:orientation="vertical" />
</LinearLayout>

View File

@@ -2363,7 +2363,7 @@
<!-- Summary of a single storage volume, constrasting available and total storage space. [CHAR LIMIT=48]-->
<string name="storage_volume_summary"><xliff:g id="used" example="1.2GB">%1$s</xliff:g> used of <xliff:g id="total" example="32GB">%2$s</xliff:g></string>
<!-- Summary of a single storage volume used space. [CHAR LIMIT=24] -->
<string name="storage_size_large"><xliff:g id="number" example="128">^1</xliff:g> <small><small><xliff:g id="unit" example="KB">^2</xliff:g></small></small></string>
<string name="storage_size_large"><xliff:g id="number" example="128">^1</xliff:g><small><small> <xliff:g id="unit" example="KB">^2</xliff:g></small></small></string>
<!-- Summary of a single storage volume total space. [CHAR LIMIT=48]-->
<string name="storage_volume_used">Used of <xliff:g id="total" example="32GB">%1$s</xliff:g></string>
<!-- Summary of a single storage volume total space. [CHAR LIMIT=48]-->

View File

@@ -19,11 +19,12 @@ import android.content.Context;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.InstrumentedFragment;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.ProcStatsData.MemInfo;
@@ -76,7 +77,8 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
double usedRam = memInfo.realUsedRam;
double totalRam = memInfo.realTotalRam;
double freeRam = memInfo.realFreeRam;
String usedString = Formatter.formatShortFileSize(context, (long) usedRam);
BytesResult usedResult = Formatter.formatBytes(context.getResources(), (long) usedRam,
Formatter.FLAG_SHORTER);
String totalString = Formatter.formatShortFileSize(context, (long) totalRam);
String freeString = Formatter.formatShortFileSize(context, (long) freeRam);
CharSequence memString;
@@ -87,7 +89,8 @@ public class ProcessStatsSummary extends ProcessStatsBase implements OnPreferenc
} else {
memString = memStatesStr[memStatesStr.length - 1];
}
mMemStatus.setText(usedString);
mMemStatus.setText(TextUtils.expandTemplate(getText(R.string.storage_size_large),
usedResult.value, usedResult.units));
float usedRatio = (float)(usedRam / (freeRam + usedRam));
mColors.setRatios(usedRatio, 0, 1 - usedRatio);

View File

@@ -21,7 +21,7 @@ import static com.android.settings.deviceinfo.StorageSettings.TAG;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.DownloadManager;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
@@ -34,15 +34,16 @@ import android.os.Bundle;
import android.os.Environment;
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.preference.Preference;
import android.preference.PreferenceScreen;
import android.provider.MediaStore;
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;
@@ -79,8 +80,12 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
// TODO: warn when mounted read-only
private static final String TAG_RENAME = "rename";
private static final String TAG_OTHER_INFO = "otherInfo";
private static final String TAG_USER_INFO = "userInfo";
private static final String TAG_CONFIRM_CLEAR_CACHE = "confirmClearCache";
private static final String AUTHORITY_MEDIA = "com.android.providers.media.documents";
private StorageManager mStorageManager;
private UserManager mUserManager;
@@ -94,19 +99,16 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
private int mNextOrder = 0;
private UsageBarPreference mGraph;
private StorageItemPreference mTotal;
private StorageItemPreference mAvailable;
private StorageSummaryPreference mSummary;
private StorageItemPreference mApps;
private StorageItemPreference mDcim;
private StorageItemPreference mMusic;
private StorageItemPreference mDownloads;
private StorageItemPreference mImages;
private StorageItemPreference mVideos;
private StorageItemPreference mAudio;
private StorageItemPreference mOther;
private StorageItemPreference mCache;
private StorageItemPreference mMisc;
private List<StorageItemPreference> mUsers = Lists.newArrayList();
private long mTotalSize;
private long mAvailSize;
private Preference mExplore;
@Override
protected int getMetricsCategory() {
@@ -136,28 +138,26 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
mMeasure = new StorageMeasurement(context, mVolume, mSharedVolume);
mMeasure.setReceiver(mReceiver);
mGraph = buildGraph();
mTotal = buildItem(R.string.memory_size, 0);
mAvailable = buildItem(R.string.memory_available, R.color.memory_avail);
mSummary = new StorageSummaryPreference(context);
mApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage);
mDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim);
mMusic = buildItem(R.string.memory_music_usage, R.color.memory_music);
mDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads);
mCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache);
mMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc);
mApps = buildItem(R.string.storage_detail_apps);
mImages = buildItem(R.string.storage_detail_images);
mVideos = buildItem(R.string.storage_detail_videos);
mAudio = buildItem(R.string.storage_detail_audio);
mOther = buildItem(R.string.storage_detail_other);
mCache = buildItem(R.string.storage_detail_cached);
mCurrentUser = mUserManager.getUserInfo(UserHandle.myUserId());
final List<UserInfo> otherUsers = getUsersExcluding(mCurrentUser);
for (int i = 0; i < otherUsers.size(); i++) {
final UserInfo user = otherUsers.get(i);
final int colorRes = i % 2 == 0 ? R.color.memory_user_light
: R.color.memory_user_dark;
final StorageItemPreference userPref = new StorageItemPreference(
context, user.name, colorRes, user.id);
context, user.name, user.id);
mUsers.add(userPref);
}
mExplore = buildAction(R.string.storage_menu_explore);
setHasOptionsMenu(true);
}
@@ -178,22 +178,25 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
return;
}
screen.addPreference(mGraph);
screen.addPreference(mTotal);
screen.addPreference(mAvailable);
screen.addPreference(mSummary);
final boolean showUsers = !mUsers.isEmpty();
final boolean showShared = (mSharedVolume != null) && mSharedVolume.isMountedReadable();
if (showUsers) {
screen.addPreference(new PreferenceHeader(context, mCurrentUser.name));
}
screen.addPreference(mApps);
screen.addPreference(mDcim);
screen.addPreference(mMusic);
screen.addPreference(mDownloads);
if (showShared) {
screen.addPreference(mImages);
screen.addPreference(mVideos);
screen.addPreference(mAudio);
screen.addPreference(mOther);
}
screen.addPreference(mCache);
screen.addPreference(mMisc);
if (showShared) {
screen.addPreference(mExplore);
}
if (showUsers) {
screen.addPreference(new PreferenceHeader(context, R.string.storage_other_users));
for (Preference pref : mUsers) {
@@ -209,29 +212,29 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
}
final File file = mVolume.getPath();
mTotalSize = file.getTotalSpace();
mAvailSize = file.getFreeSpace();
final long totalBytes = file.getTotalSpace();
final long freeBytes = file.getFreeSpace();
final long usedBytes = totalBytes - freeBytes;
mTotal.setSummary(Formatter.formatFileSize(context, mTotalSize));
mAvailable.setSummary(Formatter.formatFileSize(context, mAvailSize));
mGraph.clear();
mGraph.addEntry(0, (mTotalSize - mAvailSize) / (float) mTotalSize,
android.graphics.Color.GRAY);
mGraph.commit();
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, totalBytes)));
mSummary.setPercent((int) ((usedBytes * 100) / totalBytes));
mMeasure.forceMeasure();
}
private UsageBarPreference buildGraph() {
final UsageBarPreference pref = new UsageBarPreference(getActivity());
private StorageItemPreference buildItem(int titleRes) {
final StorageItemPreference pref = new StorageItemPreference(getActivity(), titleRes);
pref.setOrder(mNextOrder++);
return pref;
}
private StorageItemPreference buildItem(int titleRes, int colorRes) {
final StorageItemPreference pref = new StorageItemPreference(getActivity(), titleRes,
colorRes);
private Preference buildAction(int titleRes) {
final Preference pref = new Preference(getActivity());
pref.setTitle(titleRes);
pref.setOrder(mNextOrder++);
return pref;
}
@@ -341,27 +344,40 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
intent = Utils.onBuildStartFragmentIntent(getActivity(),
ManageApplications.class.getName(), args, null, R.string.apps_storage, null,
false);
} else if (pref == mDownloads) {
intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
} else if (pref == mMusic) {
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("audio/mp3");
} else if (pref == mImages) {
intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "images_root"));
intent.addCategory(Intent.CATEGORY_DEFAULT);
} else if (pref == mDcim) {
intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
} else if (pref == mVideos) {
intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "videos_root"));
intent.addCategory(Intent.CATEGORY_DEFAULT);
} else if (pref == mAudio) {
intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
intent.setData(DocumentsContract.buildRootUri(AUTHORITY_MEDIA, "audio_root"));
intent.addCategory(Intent.CATEGORY_DEFAULT);
} else if (pref == mOther) {
OtherInfoFragment.show(this, mStorageManager.getBestVolumeDescription(mVolume),
mSharedVolume);
return true;
} else if (pref == mCache) {
ConfirmClearCacheFragment.show(this);
return true;
} else if (pref == mMisc) {
} else if (pref == mExplore) {
intent = mSharedVolume.buildBrowseIntent();
}
if (mUsers.contains(pref)) {
UserInfoFragment.show(this, pref.getTitle(), pref.getSummary());
return true;
}
if (intent != null) {
try {
startActivity(intent);
@@ -381,39 +397,31 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
};
private void updateDetails(MeasurementDetails details) {
mGraph.clear();
updatePreference(mApps, details.appsSize);
final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
final long imagesSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
updatePreference(mDcim, dcimSize);
updatePreference(mImages, imagesSize);
final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
final long videosSize = totalValues(details.mediaSize, Environment.DIRECTORY_MOVIES);
updatePreference(mVideos, videosSize);
final long audioSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
updatePreference(mMusic, musicSize);
final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
updatePreference(mDownloads, downloadsSize);
updatePreference(mAudio, audioSize);
updatePreference(mCache, details.cacheSize);
updatePreference(mMisc, details.miscSize);
updatePreference(mOther, details.miscSize);
for (StorageItemPreference userPref : mUsers) {
final long userSize = details.usersSize.get(userPref.userHandle);
updatePreference(userPref, userSize);
}
mGraph.commit();
}
private void updatePreference(StorageItemPreference pref, long size) {
pref.setSummary(Formatter.formatFileSize(getActivity(), size));
if (size > 0) {
final int order = pref.getOrder();
mGraph.addEntry(order, size / (float) mTotalSize, pref.color);
}
}
/**
@@ -507,11 +515,78 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment {
}
}
public static class OtherInfoFragment extends DialogFragment {
public static void show(Fragment parent, String title, VolumeInfo sharedVol) {
if (!parent.isAdded()) return;
final OtherInfoFragment dialog = new OtherInfoFragment();
dialog.setTargetFragment(parent, 0);
final Bundle args = new Bundle();
args.putString(Intent.EXTRA_TITLE, title);
args.putParcelable(Intent.EXTRA_INTENT, sharedVol.buildBrowseIntent());
dialog.setArguments(args);
dialog.show(parent.getFragmentManager(), TAG_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) {
startActivity(intent);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
}
public static class UserInfoFragment extends DialogFragment {
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 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 DialogFragment {
public static void show(PrivateVolumeSettings parent) {
public static void show(Fragment parent) {
if (!parent.isAdded()) return;
final ConfirmClearCacheFragment dialog = new ConfirmClearCacheFragment();

View File

@@ -30,7 +30,9 @@ import android.os.storage.VolumeRecord;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
@@ -58,18 +60,13 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
private int mNextOrder = 0;
private UsageBarPreference mGraph;
private StorageItemPreference mTotal;
private StorageItemPreference mAvailable;
private StorageSummaryPreference mSummary;
private Preference mMount;
private Preference mUnmount;
private Preference mFormatPublic;
private Preference mFormatPrivate;
private long mTotalSize;
private long mAvailSize;
@Override
protected int getMetricsCategory() {
return MetricsLogger.DEVICEINFO_STORAGE;
@@ -103,9 +100,7 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
addPreferencesFromResource(R.xml.device_info_storage_volume);
mGraph = buildGraph();
mTotal = buildItem(R.string.memory_size, 0);
mAvailable = buildItem(R.string.memory_available, R.color.memory_avail);
mSummary = new StorageSummaryPreference(context);
mMount = buildAction(R.string.storage_menu_mount);
mUnmount = buildAction(R.string.storage_menu_unmount);
@@ -128,21 +123,19 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
}
if (mVolume.isMountedReadable()) {
screen.addPreference(mGraph);
screen.addPreference(mTotal);
screen.addPreference(mAvailable);
screen.addPreference(mSummary);
final File file = mVolume.getPath();
mTotalSize = file.getTotalSpace();
mAvailSize = file.getFreeSpace();
final long totalBytes = file.getTotalSpace();
final long freeBytes = file.getFreeSpace();
final long usedBytes = totalBytes - freeBytes;
mTotal.setSummary(Formatter.formatFileSize(context, mTotalSize));
mAvailable.setSummary(Formatter.formatFileSize(context, mAvailSize));
mGraph.clear();
mGraph.addEntry(0, (mTotalSize - mAvailSize) / (float) mTotalSize,
android.graphics.Color.GRAY);
mGraph.commit();
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, totalBytes)));
mSummary.setPercent((int) ((usedBytes * 100) / totalBytes));
}
if (mVolume.getState() == VolumeInfo.STATE_UNMOUNTED) {
@@ -157,19 +150,6 @@ public class PublicVolumeSettings extends SettingsPreferenceFragment {
}
}
private UsageBarPreference buildGraph() {
final UsageBarPreference pref = new UsageBarPreference(getActivity());
pref.setOrder(mNextOrder++);
return pref;
}
private StorageItemPreference buildItem(int titleRes, int colorRes) {
final StorageItemPreference pref = new StorageItemPreference(getActivity(), titleRes,
colorRes);
pref.setOrder(mNextOrder++);
return pref;
}
private Preference buildAction(int titleRes) {
final Preference pref = new Preference(getActivity());
pref.setTitle(titleRes);

View File

@@ -17,52 +17,27 @@
package com.android.settings.deviceinfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.os.UserHandle;
import android.preference.Preference;
import com.android.settings.R;
public class StorageItemPreference extends Preference {
public final int color;
public final int userHandle;
public StorageItemPreference(Context context, int titleRes, int colorRes) {
this(context, context.getText(titleRes), colorRes, UserHandle.USER_NULL);
public StorageItemPreference(Context context, int titleRes) {
this(context, context.getText(titleRes), UserHandle.USER_NULL);
}
public StorageItemPreference(
Context context, CharSequence title, int colorRes, int userHandle) {
public StorageItemPreference(Context context, CharSequence title, int userHandle) {
super(context);
if (colorRes != 0) {
this.color = context.getColor(colorRes);
final Resources res = context.getResources();
final int width = res.getDimensionPixelSize(R.dimen.device_memory_usage_button_width);
final int height = res.getDimensionPixelSize(R.dimen.device_memory_usage_button_height);
setIcon(createRectShape(width, height, this.color));
} else {
this.color = Color.MAGENTA;
}
setTitle(title);
setSummary(R.string.memory_calculating_size);
this.userHandle = userHandle;
}
private static ShapeDrawable createRectShape(int width, int height, int color) {
ShapeDrawable shape = new ShapeDrawable(new RectShape());
shape.setIntrinsicHeight(height);
shape.setIntrinsicWidth(width);
shape.getPaint().setColor(color);
return shape;
}
public void setLoading() {
setSummary(R.string.memory_calculating_size);
}

View File

@@ -23,9 +23,9 @@ import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.UserManager;
import android.os.storage.DiskInfo;
import android.os.storage.StorageEventListener;
import android.os.storage.StorageManager;
@@ -35,6 +35,8 @@ import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.util.Log;
import android.widget.Toast;
@@ -45,6 +47,7 @@ import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -59,14 +62,24 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
private static final String TAG_VOLUME_UNMOUNTED = "volume_unmounted";
private static final String TAG_DISK_INIT = "disk_init";
// TODO: badging to indicate devices running low on storage
static final int COLOR_PUBLIC = Color.parseColor("#ff9e9e9e");
static final int COLOR_WARNING = Color.parseColor("#fff4511e");
static final int[] COLOR_PRIVATE = new int[] {
Color.parseColor("#ff26a69a"),
Color.parseColor("#ffab47bc"),
Color.parseColor("#fff2a600"),
Color.parseColor("#ffec407a"),
Color.parseColor("#ffc0ca33"),
};
private UserManager mUserManager;
private StorageManager mStorageManager;
private PreferenceCategory mInternalCategory;
private PreferenceCategory mExternalCategory;
private StorageSummaryPreference mInternalSummary;
@Override
protected int getMetricsCategory() {
return MetricsLogger.DEVICEINFO_STORAGE;
@@ -83,8 +96,6 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
final Context context = getActivity();
mUserManager = context.getSystemService(UserManager.class);
mStorageManager = context.getSystemService(StorageManager.class);
mStorageManager.registerListener(mStorageListener);
@@ -93,7 +104,7 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
mInternalCategory = (PreferenceCategory) findPreference("storage_internal");
mExternalCategory = (PreferenceCategory) findPreference("storage_external");
// TODO: if only one volume visible, shortcut into it
mInternalSummary = new StorageSummaryPreference(context);
setHasOptionsMenu(true);
}
@@ -124,14 +135,28 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
mInternalCategory.removeAll();
mExternalCategory.removeAll();
mInternalCategory.addPreference(mInternalSummary);
int privateCount = 0;
long privateUsedBytes = 0;
long privateTotalBytes = 0;
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
Collections.sort(volumes, VolumeInfo.getDescriptionComparator());
for (VolumeInfo vol : volumes) {
if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
mInternalCategory.addPreference(new StorageVolumePreference(context, vol));
final int color = COLOR_PRIVATE[privateCount++ % COLOR_PRIVATE.length];
mInternalCategory.addPreference(
new StorageVolumePreference(context, vol, color));
if (vol.isMountedReadable()) {
final File path = vol.getPath();
privateUsedBytes += path.getTotalSpace() - path.getFreeSpace();
privateTotalBytes += path.getTotalSpace();
}
} else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
mExternalCategory.addPreference(new StorageVolumePreference(context, vol));
mExternalCategory.addPreference(
new StorageVolumePreference(context, vol, COLOR_PUBLIC));
}
}
@@ -162,12 +187,28 @@ public class StorageSettings extends SettingsPreferenceFragment implements Index
}
}
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
final Bundle args = new Bundle();
args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
startFragment(this, PrivateVolumeSettings.class.getCanonicalName(),
-1, 0, args);
finish();
}
}
@Override

View File

@@ -0,0 +1,52 @@
/*
* 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.content.Context;
import android.preference.Preference;
import android.view.View;
import android.widget.ProgressBar;
import com.android.settings.R;
public class StorageSummaryPreference extends Preference {
private int mPercent = -1;
public StorageSummaryPreference(Context context) {
super(context);
setLayoutResource(R.layout.storage_summary);
setEnabled(false);
}
public void setPercent(int percent) {
mPercent = percent;
}
@Override
protected void onBindView(View view) {
final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
if (mPercent != -1) {
progress.setVisibility(View.VISIBLE);
progress.setProgress(mPercent);
} else {
progress.setVisibility(View.GONE);
}
super.onBindView(view);
}
}

View File

@@ -17,12 +17,17 @@
package com.android.settings.deviceinfo;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.preference.Preference;
import android.text.format.Formatter;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.settings.R;
@@ -38,32 +43,53 @@ public class StorageVolumePreference extends Preference {
private final StorageManager mStorageManager;
private final VolumeInfo mVolume;
public StorageVolumePreference(Context context, VolumeInfo volume) {
private int mColor;
private int mUsedPercent = -1;
public StorageVolumePreference(Context context, VolumeInfo volume, int color) {
super(context);
mStorageManager = context.getSystemService(StorageManager.class);
mVolume = volume;
mColor = color;
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_settings_storage);
} else {
icon = context.getDrawable(R.drawable.ic_sim_sd);
}
if (volume.isMountedReadable()) {
// TODO: move statfs() to background thread
final File path = volume.getPath();
final long usedBytes = path.getTotalSpace() - path.getFreeSpace();
final long freeBytes = path.getFreeSpace();
final long totalBytes = path.getTotalSpace();
final long usedBytes = totalBytes - freeBytes;
final String used = Formatter.formatFileSize(context, usedBytes);
final String total = Formatter.formatFileSize(context, path.getTotalSpace());
final String total = Formatter.formatFileSize(context, totalBytes);
setSummary(context.getString(R.string.storage_volume_summary, used, total));
mUsedPercent = (int) ((usedBytes * 100) / totalBytes);
if (freeBytes < mStorageManager.getStorageLowBytes(path)) {
mColor = StorageSettings.COLOR_WARNING;
icon = context.getDrawable(R.drawable.ic_warning_24dp);
}
} else {
setSummary(volume.getStateDescription());
mUsedPercent = -1;
}
// TODO: better icons
if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(volume.getId())) {
setIcon(context.getDrawable(R.drawable.ic_settings_storage));
} else {
setIcon(context.getDrawable(R.drawable.ic_sim_sd));
}
icon.mutate();
icon.setTint(mColor);
setIcon(icon);
if (volume.getType() == VolumeInfo.TYPE_PUBLIC
&& volume.isMountedReadable()) {
@@ -73,12 +99,21 @@ public class StorageVolumePreference extends Preference {
@Override
protected void onBindView(View view) {
final TextView unmount = (TextView) view.findViewById(R.id.unmount);
final ImageView unmount = (ImageView) view.findViewById(R.id.unmount);
if (unmount != null) {
unmount.setText("\u23CF");
unmount.getDrawable().setTint(Color.parseColor("#8a000000"));
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(ColorStateList.valueOf(mColor));
} else {
progress.setVisibility(View.GONE);
}
super.onBindView(view);
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright (C) 2010 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.content.Context;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import com.android.settings.R;
import com.google.android.collect.Lists;
import java.util.Collections;
import java.util.List;
/**
* Creates a percentage bar chart inside a preference.
*/
public class UsageBarPreference extends Preference {
private PercentageBarChart mChart = null;
private final List<PercentageBarChart.Entry> mEntries = Lists.newArrayList();
public UsageBarPreference(Context context) {
this(context, null);
}
public UsageBarPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
public UsageBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_memoryusage);
}
public void addEntry(int order, float percentage, int color) {
mEntries.add(PercentageBarChart.createEntry(order, percentage, color));
Collections.sort(mEntries);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
mChart = (PercentageBarChart) view.findViewById(R.id.percentage_bar_chart);
mChart.setEntries(mEntries);
}
public void commit() {
if (mChart != null) {
mChart.invalidate();
}
}
public void clear() {
mEntries.clear();
}
}

View File

@@ -16,7 +16,6 @@
package com.android.settings.widget;
import static android.net.TrafficStats.GB_IN_BYTES;
import static android.net.TrafficStats.MB_IN_BYTES;
import android.content.Context;
@@ -29,8 +28,11 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Formatter;
import android.text.format.Formatter.BytesResult;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
@@ -533,33 +535,11 @@ public class ChartDataUsageView extends ChartView {
@Override
public long buildLabel(Resources res, SpannableStringBuilder builder, long value) {
final CharSequence unit;
final long unitFactor;
if (value < 1000 * MB_IN_BYTES) {
unit = res.getText(com.android.internal.R.string.megabyteShort);
unitFactor = MB_IN_BYTES;
} else {
unit = res.getText(com.android.internal.R.string.gigabyteShort);
unitFactor = GB_IN_BYTES;
}
final double result = (double) value / unitFactor;
final double resultRounded;
final CharSequence size;
if (result < 10) {
size = String.format("%.1f", result);
resultRounded = (unitFactor * Math.round(result * 10)) / 10;
} else {
size = String.format("%.0f", result);
resultRounded = unitFactor * Math.round(result);
}
setText(builder, sSpanSize, size, "^1");
setText(builder, sSpanUnit, unit, "^2");
return (long) resultRounded;
final BytesResult result = Formatter.formatBytes(res, value,
Formatter.FLAG_SHORTER | Formatter.FLAG_CALCULATE_ROUNDED);
setText(builder, sSpanSize, result.value, "^1");
setText(builder, sSpanUnit, result.units, "^2");
return result.roundedBytes;
}
@Override