blob: bde64142fdc96d9a31f2bf534b28910dc9480b37 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.download.ui;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.StatFs;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
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 org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.widget.MaterialProgressBar;
import java.io.File;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
/** A View that manages the display of space used by the downloads. */
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 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[] 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 View mView;
private View mViewContainer;
private TextView mSpaceUsedByDownloadsTextView;
private TextView mSpaceFreeAndOtherAppsTextView;
private MaterialProgressBar mSpaceBar;
private long mFreeBytes;
SpaceDisplay(final ViewGroup parent, DownloadHistoryAdapter historyAdapter) {
mHistoryAdapter = historyAdapter;
mViewContainer = LayoutInflater.from(ContextUtils.getApplicationContext())
.inflate(R.layout.download_manager_ui_space_widget, parent, false);
mView = mViewContainer.findViewById(R.id.space_widget_content);
mSpaceUsedByDownloadsTextView = (TextView) mView.findViewById(R.id.size_downloaded);
mSpaceFreeAndOtherAppsTextView =
(TextView) mView.findViewById(R.id.size_free_and_other_apps);
mSpaceBar = (MaterialProgressBar) mView.findViewById(R.id.space_bar);
mFileSystemBytesTask =
new StorageSizeTask(true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/** @return The view container of space display view. */
public View getViewContainer() {
return mViewContainer;
}
/** Returns the view. */
public View getView() {
return mView;
}
@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 free now, then update the display.
if (mFreeBytesTask == null) {
mFreeBytesTask = new StorageSizeTask(false) {
@Override
protected void onPostExecute(Long bytes) {
mFreeBytes = bytes.longValue();
mFreeBytesTask = null;
update();
}
};
try {
mFreeBytesTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} catch (RejectedExecutionException e) {
mFreeBytesTask = null;
}
}
}
@VisibleForTesting
public void addObserverForTests(Observer observer) {
mObservers.addObserver(observer);
}
private void update() {
long fileSystemBytes = 0;
try {
fileSystemBytes = mFileSystemBytesTask.get();
} catch (ExecutionException | InterruptedException e) {
// Can't do anything here.
}
// Indicate how much space has been used by everything on the device via the progress bar.
long bytesUsedTotal = Math.max(0, fileSystemBytes - mFreeBytes);
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.
Context context = mSpaceUsedByDownloadsTextView.getContext();
mSpaceUsedByDownloadsTextView.setText(
DownloadUtils.getStringForBytes(context, USED_STRINGS, bytesUsedByDownloads));
String spaceFree = DownloadUtils.getStringForAvailableBytes(context, mFreeBytes);
String spaceUsedByOtherApps =
DownloadUtils.getStringForBytes(context, OTHER_STRINGS, bytesUsedByOtherApps);
mSpaceFreeAndOtherAppsTextView.setText(
context.getResources().getString(R.string.download_manager_ui_space_free_and_other,
spaceFree, spaceUsedByOtherApps));
// Set a minimum size for the download size so that it shows up in the progress bar.
long threePercentOfSystem = fileSystemBytes == 0 ? 0 : fileSystemBytes / 100 * 3;
long fudgedBytesUsedByDownloads = Math.max(bytesUsedByDownloads, threePercentOfSystem);
long fudgedBytesUsedByOtherApps = Math.max(bytesUsedByOtherApps, threePercentOfSystem);
// 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(
fudgedBytesUsedByDownloads + fudgedBytesUsedByOtherApps, fileSystemBytes);
int percentageDownloaded = computePercentage(fudgedBytesUsedByDownloads, fileSystemBytes);
mSpaceBar.setProgress(percentageUsedTotal);
mSpaceBar.setSecondaryProgress(percentageDownloaded);
for (Observer observer : mObservers) observer.onSpaceDisplayUpdated(this);
}
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));
}
}