| Index: chrome/android/java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java
|
| index 6d9449f9c3517117682b1b5f0839286a60f2b308..dec67a4a29dd538856166b4f0bb90aabf5f750ec 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/SpaceDisplay.java
|
| @@ -15,112 +15,195 @@ import android.widget.TextView;
|
|
|
| import org.chromium.base.ApiCompatibilityUtils;
|
| import org.chromium.base.Log;
|
| +import org.chromium.base.ObserverList;
|
| +import org.chromium.base.VisibleForTesting;
|
| import org.chromium.base.metrics.RecordHistogram;
|
| import org.chromium.chrome.R;
|
|
|
| import java.io.File;
|
| +import java.util.concurrent.ExecutionException;
|
|
|
| /** A View that manages the display of space used by the downloads. */
|
| -class SpaceDisplay extends RecyclerView.AdapterDataObserver {
|
| +public class SpaceDisplay extends RecyclerView.AdapterDataObserver {
|
| + /** Observes changes to the SpaceDisplay. */
|
| + public static interface Observer {
|
| + /** Called when the display has had its values updated. */
|
| + void onSpaceDisplayUpdated(SpaceDisplay spaceDisplay);
|
| + }
|
| +
|
| private static final String TAG = "download_ui";
|
| private static final long BYTES_PER_KILOBYTE = 1024;
|
| private static final long BYTES_PER_MEGABYTE = 1024 * 1024;
|
| private static final long BYTES_PER_GIGABYTE = 1024 * 1024 * 1024;
|
|
|
| + private static final int[] USED_STRINGS = {
|
| + R.string.download_manager_ui_space_used_kb,
|
| + R.string.download_manager_ui_space_used_mb,
|
| + R.string.download_manager_ui_space_used_gb
|
| + };
|
| + private static final int[] FREE_STRINGS = {
|
| + R.string.download_manager_ui_space_free_kb,
|
| + R.string.download_manager_ui_space_free_mb,
|
| + R.string.download_manager_ui_space_free_gb
|
| + };
|
| + private static final int[] OTHER_STRINGS = {
|
| + R.string.download_manager_ui_space_other_kb,
|
| + R.string.download_manager_ui_space_other_mb,
|
| + R.string.download_manager_ui_space_other_gb
|
| + };
|
| +
|
| + private static class StorageSizeTask extends AsyncTask<Void, Void, Long> {
|
| + /**
|
| + * If true, the task gets the total size of storage. If false, it fetches how much
|
| + * space is free.
|
| + */
|
| + private boolean mFetchTotalSize;
|
| +
|
| + StorageSizeTask(boolean fetchTotalSize) {
|
| + mFetchTotalSize = fetchTotalSize;
|
| + }
|
| +
|
| + @Override
|
| + protected Long doInBackground(Void... params) {
|
| + File downloadDirectory = Environment.getExternalStoragePublicDirectory(
|
| + Environment.DIRECTORY_DOWNLOADS);
|
| +
|
| + // Create the downloads directory, if necessary.
|
| + if (!downloadDirectory.exists()) {
|
| + try {
|
| + // mkdirs() can fail, so we still need to check if the directory exists
|
| + // later.
|
| + downloadDirectory.mkdirs();
|
| + } catch (SecurityException e) {
|
| + Log.e(TAG, "SecurityException when creating download directory.", e);
|
| + }
|
| + }
|
| +
|
| + // Determine how much space is available on the storage device where downloads
|
| + // reside. If the downloads directory doesn't exist, it is likely that the user
|
| + // doesn't have an SD card installed.
|
| + long blocks = 0;
|
| + if (downloadDirectory.exists()) {
|
| + StatFs statFs = new StatFs(downloadDirectory.getPath());
|
| + if (mFetchTotalSize) {
|
| + blocks = ApiCompatibilityUtils.getBlockCount(statFs);
|
| + } else {
|
| + blocks = ApiCompatibilityUtils.getAvailableBlocks(statFs);
|
| + }
|
| +
|
| + return blocks * ApiCompatibilityUtils.getBlockSize(statFs);
|
| + } else {
|
| + Log.e(TAG, "Download directory doesn't exist.");
|
| + return 0L;
|
| + }
|
| + }
|
| + };
|
| +
|
| + private final ObserverList<Observer> mObservers = new ObserverList<>();
|
| private final AsyncTask<Void, Void, Long> mFileSystemBytesTask;
|
| + private AsyncTask<Void, Void, Long> mFreeBytesTask;
|
|
|
| private DownloadHistoryAdapter mHistoryAdapter;
|
| - private TextView mSpaceUsedTextView;
|
| - private TextView mSpaceTotalTextView;
|
| + private TextView mSpaceUsedByDownloadsTextView;
|
| + private TextView mSpaceUsedByOtherAppsTextView;
|
| + private TextView mSpaceFreeTextView;
|
| private ProgressBar mSpaceBar;
|
| - private long mFileSystemBytes = Long.MAX_VALUE;
|
|
|
| SpaceDisplay(final ViewGroup parent, DownloadHistoryAdapter historyAdapter) {
|
| mHistoryAdapter = historyAdapter;
|
| - mSpaceUsedTextView = (TextView) parent.findViewById(R.id.space_used_display);
|
| - mSpaceTotalTextView = (TextView) parent.findViewById(R.id.space_total_display);
|
| + mSpaceUsedByDownloadsTextView = (TextView) parent.findViewById(R.id.size_downloaded);
|
| + mSpaceUsedByOtherAppsTextView = (TextView) parent.findViewById(R.id.size_other_apps);
|
| + mSpaceFreeTextView = (TextView) parent.findViewById(R.id.size_free);
|
| mSpaceBar = (ProgressBar) parent.findViewById(R.id.space_bar);
|
| mFileSystemBytesTask =
|
| - createStorageSizeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
| + new StorageSizeTask(true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
| }
|
|
|
| - private AsyncTask<Void, Void, Long> createStorageSizeTask() {
|
| - return new AsyncTask<Void, Void, Long>() {
|
| - @Override
|
| - protected Long doInBackground(Void... params) {
|
| - File downloadDirectory = Environment.getExternalStoragePublicDirectory(
|
| - Environment.DIRECTORY_DOWNLOADS);
|
| -
|
| - // Create the downloads directory, if necessary.
|
| - if (!downloadDirectory.exists()) {
|
| - try {
|
| - // mkdirs() can fail, so we still need to check if the directory exists
|
| - // later.
|
| - downloadDirectory.mkdirs();
|
| - } catch (SecurityException e) {
|
| - Log.e(TAG, "SecurityException when creating download directory.", e);
|
| - }
|
| - }
|
| + @Override
|
| + public void onChanged() {
|
| + // Record how much the user has downloaded relative to the size of their storage.
|
| + try {
|
| + long bytesUsedByDownloads = Math.max(0, mHistoryAdapter.getTotalDownloadSize());
|
| + RecordHistogram.recordPercentageHistogram("Android.DownloadManager.SpaceUsed",
|
| + computePercentage(bytesUsedByDownloads, mFileSystemBytesTask.get()));
|
| + } catch (ExecutionException | InterruptedException e) {
|
| + // Can't record what we don't have.
|
| + }
|
|
|
| - // Determine how much space is available on the storage device where downloads
|
| - // reside. If the downloads directory doesn't exist, it is likely that the user
|
| - // doesn't have an SD card installed.
|
| - long fileSystemBytes = 0;
|
| - if (downloadDirectory.exists()) {
|
| - StatFs statFs = new StatFs(downloadDirectory.getPath());
|
| - long totalBlocks = ApiCompatibilityUtils.getBlockCount(statFs);
|
| - long blockSize = ApiCompatibilityUtils.getBlockSize(statFs);
|
| - fileSystemBytes = totalBlocks * blockSize;
|
| - } else {
|
| - Log.e(TAG, "Download directory doesn't exist.");
|
| - }
|
| - return fileSystemBytes;
|
| + // Determine how much space is free now, then update the display.
|
| + mFreeBytesTask = new StorageSizeTask(false) {
|
| + @Override
|
| + protected void onPostExecute(Long bytes) {
|
| + update();
|
| }
|
| };
|
| + mFreeBytesTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
| }
|
|
|
| - @Override
|
| - public void onChanged() {
|
| - if (mFileSystemBytes == Long.MAX_VALUE) {
|
| - try {
|
| - mFileSystemBytes = mFileSystemBytesTask.get();
|
| - } catch (Exception e) {
|
| - Log.e(TAG, "Failed to get file system size.");
|
| - }
|
| + @VisibleForTesting
|
| + public void addObserverForTests(Observer observer) {
|
| + mObservers.addObserver(observer);
|
| + }
|
|
|
| - // Display how big the storage is.
|
| - mSpaceTotalTextView.setText(getStringForBytes(false, mFileSystemBytes));
|
| - }
|
| + private void update() {
|
| + long fileSystemBytes = 0;
|
| + long freeBytes = 0;
|
|
|
| - // Indicate how much space has been used by downloads.
|
| - long usedBytes = mHistoryAdapter.getTotalDownloadSize();
|
| - int percentage =
|
| - mFileSystemBytes == 0 ? 0 : (int) (100.0 * usedBytes / mFileSystemBytes);
|
| - mSpaceBar.setProgress(percentage);
|
| - mSpaceUsedTextView.setText(getStringForBytes(true, usedBytes));
|
| + try {
|
| + fileSystemBytes = mFileSystemBytesTask.get();
|
| + freeBytes = mFreeBytesTask.get();
|
| + } catch (ExecutionException | InterruptedException e) {
|
| + // Can't do anything here.
|
| + }
|
|
|
| - RecordHistogram.recordPercentageHistogram("Android.DownloadManager.SpaceUsed", percentage);
|
| + // Indicate how much space has been used by everything on the device via the progress bar.
|
| + long bytesUsedTotal = Math.max(0, fileSystemBytes - freeBytes);
|
| + long bytesUsedByDownloads = Math.max(0, mHistoryAdapter.getTotalDownloadSize());
|
| + long bytesUsedByOtherApps = Math.max(0, bytesUsedTotal - bytesUsedByDownloads);
|
| +
|
| + // Describe how much space has been used by downloads in text.
|
| + mSpaceUsedByDownloadsTextView.setText(
|
| + getStringForBytes(USED_STRINGS, bytesUsedByDownloads));
|
| + mSpaceUsedByOtherAppsTextView.setText(
|
| + getStringForBytes(OTHER_STRINGS, bytesUsedByOtherApps));
|
| + mSpaceFreeTextView.setText(getStringForBytes(FREE_STRINGS, freeBytes));
|
| +
|
| + // Set a minimum size for the download size so that it shows up in the progress bar.
|
| + long onePercentOfSystem = fileSystemBytes == 0 ? 0 : fileSystemBytes / 100;
|
| + long fudgedBytesUsedByDownloads = Math.max(bytesUsedByDownloads, onePercentOfSystem);
|
| + long fudgedbytesUsedByOtherApps = Math.max(0, bytesUsedTotal - fudgedBytesUsedByDownloads);
|
| +
|
| + // Indicate how much space has been used as a progress bar. The percentage used by
|
| + // downloads is shown by the non-overlapped area of the primary and secondary progressbar.
|
| + int percentageUsedTotal = computePercentage(bytesUsedTotal, fileSystemBytes);
|
| + int percentageOtherApps = computePercentage(fudgedbytesUsedByOtherApps, fileSystemBytes);
|
| + mSpaceBar.setProgress(percentageUsedTotal);
|
| + mSpaceBar.setSecondaryProgress(percentageOtherApps);
|
| +
|
| + for (Observer observer : mObservers) observer.onSpaceDisplayUpdated(this);
|
| }
|
|
|
| - private String getStringForBytes(boolean isUsedString, long bytes) {
|
| + private String getStringForBytes(int[] stringSet, long bytes) {
|
| int resourceId;
|
| float bytesInCorrectUnits;
|
|
|
| if (bytes < BYTES_PER_MEGABYTE) {
|
| - resourceId = isUsedString ? R.string.download_manager_ui_space_used_kb
|
| - : R.string.download_manager_ui_space_available_kb;
|
| + resourceId = stringSet[0];
|
| bytesInCorrectUnits = bytes / (float) BYTES_PER_KILOBYTE;
|
| } else if (bytes < BYTES_PER_GIGABYTE) {
|
| - resourceId = isUsedString ? R.string.download_manager_ui_space_used_mb
|
| - : R.string.download_manager_ui_space_available_mb;
|
| + resourceId = stringSet[1];
|
| bytesInCorrectUnits = bytes / (float) BYTES_PER_MEGABYTE;
|
| } else {
|
| - resourceId = isUsedString ? R.string.download_manager_ui_space_used_gb
|
| - : R.string.download_manager_ui_space_available_gb;
|
| + resourceId = stringSet[2];
|
| bytesInCorrectUnits = bytes / (float) BYTES_PER_GIGABYTE;
|
| }
|
|
|
| - Context context = mSpaceUsedTextView.getContext();
|
| + Context context = mSpaceUsedByDownloadsTextView.getContext();
|
| return context.getResources().getString(resourceId, bytesInCorrectUnits);
|
| }
|
| +
|
| + private int computePercentage(long numerator, long denominator) {
|
| + if (denominator == 0) return 0;
|
| + return (int) Math.min(100.0f, Math.max(0.0f, 100.0f * numerator / denominator));
|
| + }
|
| }
|
|
|