0

[Downloads location] Include file name/location editing on interstitial.

This is part of a set of changes to make it possible for users to choose
where they want to save their files during downloads on Android. This CL
updates the interstitial download location dialog to include the option
to edit the name and location of the download at the time of download.

This CL includes the following functionality:
- Option to change the name of the download file.
- Option to change the location of the download file through the
download directory preference.
- Updating the location of selected download file on the dialog.
- Checking the "Don't show again" box the first time the interstitial
is shown.
- Saving the file with the new name and location.

Mocks: go/downloads-location-mocks

Screenshot:
https://jming.users.x20web.corp.google.com/www/downloaddialog.png

Bug: 792775,810198
Change-Id: I997fb394e5cbbc2481a3809214b9d9aa5dc1c109
Reviewed-on: https://chromium-review.googlesource.com/896866
Commit-Queue: Joy Ming <jming@chromium.org>
Reviewed-by: David Trainor <dtrainor@chromium.org>
Reviewed-by: Theresa <twellington@chromium.org>
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#535839}
This commit is contained in:
Joy Ming
2018-02-09 21:48:03 +00:00
committed by Commit Bot
parent 984653dce0
commit 41055bd7bd
24 changed files with 547 additions and 154 deletions

@ -1775,6 +1775,12 @@ if (is_android) {
]
}
java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/download_prompt_status.h",
]
}
source_set("chrome_android_core") {
sources = [
"app/android/chrome_android_initializer.cc",

@ -284,6 +284,7 @@ android_library("chrome_java") {
"//chrome:content_settings_type_javagen",
"//chrome:credit_card_javagen",
"//chrome:data_use_ui_message_enum_javagen",
"//chrome:download_enum_javagen",
"//chrome:offline_pages_enum_javagen",
"//chrome:page_info_connection_type_javagen",
"//chrome:page_info_action_javagen",

@ -3,12 +3,58 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/AlertDialogContent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<org.chromium.chrome.browser.widget.TintedImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:srcCompat="@drawable/ic_drive_file_24dp"
android:tint="@color/black_alpha_87"
style="@style/ListItemStartIcon" />
<org.chromium.chrome.browser.widget.AlertDialogEditText
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<org.chromium.chrome.browser.widget.TintedImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_folder_blue_24dp"
android:tint="@color/black_alpha_87"
style="@style/ListItemStartIcon" />
<org.chromium.chrome.browser.widget.AlertDialogEditText
android:id="@+id/file_location"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<CheckBox
android:id="@+id/show_again_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/download_location_dialog_checkbox"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/path_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

@ -6,21 +6,34 @@ package org.chromium.chrome.browser.download;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.support.v7.app.AlertDialog;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.CheckBox;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.PreferencesLauncher;
import org.chromium.chrome.browser.preferences.download.DownloadDirectoryList;
import org.chromium.chrome.browser.preferences.download.DownloadDirectoryPreference;
import org.chromium.chrome.browser.widget.AlertDialogEditText;
import java.io.File;
/**
* Dialog that is displayed to ask user where they want to download the file.
*/
public class DownloadLocationDialog extends AlertDialog implements DialogInterface.OnClickListener {
public class DownloadLocationDialog
extends AlertDialog implements DialogInterface.OnClickListener, View.OnFocusChangeListener,
DialogInterface.OnCancelListener {
private DownloadLocationDialogListener mListener;
private File mLocationPath;
private DownloadDirectoryList mDownloadDirectoryUtil;
private AlertDialogEditText mFileName;
private AlertDialogEditText mFileLocation;
private CheckBox mDontShowAgain;
/**
* Interface for a listener to the events related to the DownloadLocationDialog.
@ -31,37 +44,116 @@ public class DownloadLocationDialog extends AlertDialog implements DialogInterfa
* @param returnedPath from the dialog.
*/
void onComplete(File returnedPath);
/**
* Notify listeners that the dialog has been canceled.
*/
void onCanceled();
}
/**
* Create a DownloadLocationDialog that is displayed before a download begins.
*
* @param context of the dialog.
* @param listener to the updates from the dialog.
* @param suggestedPath of the download location.
*/
public DownloadLocationDialog(
DownloadLocationDialog(
Context context, DownloadLocationDialogListener listener, File suggestedPath) {
super(context, 0);
mListener = listener;
mLocationPath = suggestedPath;
mDownloadDirectoryUtil = new DownloadDirectoryList(context);
setButton(BUTTON_POSITIVE, context.getText(R.string.ok), this);
setButton(BUTTON_POSITIVE,
context.getText(R.string.duplicate_download_infobar_download_button), this);
setButton(BUTTON_NEGATIVE, context.getText(R.string.cancel), this);
setIcon(0);
setTitle(context.getString(R.string.download_location_dialog_title));
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.download_location_dialog, null);
TextView pathText = (TextView) view.findViewById(R.id.path_text);
pathText.setText(suggestedPath.getAbsolutePath());
mFileName = (AlertDialogEditText) view.findViewById(R.id.file_name);
mFileName.setText(suggestedPath.getName());
mFileLocation = (AlertDialogEditText) view.findViewById(R.id.file_location);
// NOTE: This makes the EditText correctly styled but not editable.
mFileLocation.setInputType(InputType.TYPE_NULL);
mFileLocation.setOnFocusChangeListener(this);
setFileLocation(suggestedPath.getParentFile());
// Automatically check "don't show again" the first time the user is seeing the dialog.
mDontShowAgain = (CheckBox) view.findViewById(R.id.show_again_checkbox);
boolean isInitial = PrefServiceBridge.getInstance().getPromptForDownloadAndroid()
== DownloadPromptStatus.SHOW_INITIAL;
mDontShowAgain.setChecked(isInitial);
setView(view);
setCanceledOnTouchOutside(false);
setOnCancelListener(this);
}
/**
* Update the string in the file location text view.
*
* @param location The location that the download will go to.
*/
void setFileLocation(File location) {
if (mFileLocation == null) return;
mFileLocation.setText(mDownloadDirectoryUtil.getNameForFile(location));
}
// DialogInterface.OnClickListener implementation.
@Override
public void onClick(DialogInterface dialogInterface, int which) {
// TODO(jming): In future iterations, distinguish which buttons have been clicked.
mListener.onComplete(mLocationPath);
if (which == BUTTON_POSITIVE) {
// Get new file path information.
String fileName = mFileName.getText().toString();
File fileLocation =
mDownloadDirectoryUtil.getFileForName(mFileLocation.getText().toString());
if (fileLocation != null) {
mListener.onComplete(new File(fileLocation, fileName));
} else {
// If file location returned null, treat as cancellation.
mListener.onCanceled();
dismiss();
return;
}
// Update preference to show prompt based on whether checkbox is checked.
if (mDontShowAgain.isChecked()) {
PrefServiceBridge.getInstance().setPromptForDownloadAndroid(
DownloadPromptStatus.DONT_SHOW);
} else {
PrefServiceBridge.getInstance().setPromptForDownloadAndroid(
DownloadPromptStatus.SHOW_PREFERENCE);
}
} else {
mListener.onCanceled();
}
dismiss();
}
// Dialog.OnCancelListener implementation.
@Override
public void onCancel(DialogInterface dialogInterface) {
mListener.onCanceled();
dismiss();
}
// View.OnFocusChange implementation.
@Override
public void onFocusChange(View view, boolean hasFocus) {
// When the file location text view is clicked.
if (hasFocus) {
Intent intent = PreferencesLauncher.createIntentForSettingsPage(
getContext(), DownloadDirectoryPreference.class.getName());
getContext().startActivity(intent);
}
}
}

@ -11,27 +11,49 @@ import org.chromium.content_public.browser.WebContents;
import java.io.File;
import javax.annotation.Nullable;
/**
* Helper class to handle communication between download location dialog and native.
*/
public class DownloadLocationDialogBridge
implements DownloadLocationDialog.DownloadLocationDialogListener {
// TODO(jming): Remove this when switching to a dropdown instead of going to preferences.
private static DownloadLocationDialogBridge sInstance;
private long mNativeDownloadLocationDialogBridge;
private DownloadLocationDialog mLocationDialog;
@Nullable
public static DownloadLocationDialogBridge getInstance() {
return sInstance;
}
private DownloadLocationDialogBridge(long nativeDownloadLocationDialogBridge) {
mNativeDownloadLocationDialogBridge = nativeDownloadLocationDialogBridge;
}
/**
* Update the file location that is displayed on the alert dialog.
*
* @param newLocation Where the user wants to download the file.
*/
public void updateFileLocation(File newLocation) {
if (mLocationDialog == null) return;
mLocationDialog.setFileLocation(newLocation);
}
@CalledByNative
public static DownloadLocationDialogBridge create(long nativeDownloadLocationDialogBridge) {
return new DownloadLocationDialogBridge(nativeDownloadLocationDialogBridge);
sInstance = new DownloadLocationDialogBridge(nativeDownloadLocationDialogBridge);
return sInstance;
}
@CalledByNative
private void destroy() {
mNativeDownloadLocationDialogBridge = 0;
if (mLocationDialog != null) mLocationDialog.dismiss();
sInstance = null;
}
@CalledByNative
@ -42,6 +64,7 @@ public class DownloadLocationDialogBridge
if (mLocationDialog != null) return;
mLocationDialog =
new DownloadLocationDialog(windowAndroidActivity, this, new File(suggestedPath));
// TODO(jming): Use ModalDialogManager.
mLocationDialog.show();
}
@ -53,6 +76,15 @@ public class DownloadLocationDialogBridge
mLocationDialog = null;
}
@Override
public void onCanceled() {
if (mNativeDownloadLocationDialogBridge == 0) return;
nativeOnCanceled(mNativeDownloadLocationDialogBridge);
mLocationDialog = null;
}
public native void nativeOnComplete(
long nativeDownloadLocationDialogBridge, String returnedPath);
public native void nativeOnCanceled(long nativeDownloadLocationDialogBridge);
}

@ -10,6 +10,7 @@ import android.text.TextUtils;
import org.chromium.base.Log;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.UrlConstants;
import org.chromium.chrome.browser.preferences.download.DownloadDirectoryList;
import java.util.Locale;
@ -43,7 +44,7 @@ public class DownloadFilter {
*
* Changing the ordering of these items requires changing the FILTER_* values in
* {@link DownloadHistoryAdapter} and the values in mCanonicalDirectoryPairs in
* {@link org.chromium.chrome.browser.preferences.download.DownloadDirectoryAdapter}
* {@link DownloadDirectoryList}.
*/
static final int[][] FILTER_LIST = new int[][] {
{R.drawable.ic_file_download_24dp, R.string.download_manager_ui_all_downloads},

@ -13,6 +13,7 @@ import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.ContentSettingsType;
import org.chromium.chrome.browser.download.DownloadPromptStatus;
import org.chromium.chrome.browser.preferences.languages.LanguageItem;
import org.chromium.chrome.browser.preferences.website.ContentSetting;
import org.chromium.chrome.browser.preferences.website.ContentSettingException;
@ -1065,6 +1066,21 @@ public final class PrefServiceBridge {
nativeSetDownloadAndSaveFileDefaultDirectory(directory);
}
/**
* @return The status of prompt for download pref, defined by {@link DownloadPromptStatus}.
*/
@DownloadPromptStatus
public int getPromptForDownloadAndroid() {
return nativeGetPromptForDownloadAndroid();
}
/**
* @param status New status to update the prompt for download preference.
*/
public void setPromptForDownloadAndroid(@DownloadPromptStatus int status) {
nativeSetPromptForDownloadAndroid(status);
}
private native boolean nativeGetBoolean(int preference);
private native void nativeSetBoolean(int preference, boolean value);
private native boolean nativeGetAcceptCookiesEnabled();
@ -1180,4 +1196,6 @@ public final class PrefServiceBridge {
private native void nativeSetLanguageBlockedState(String language, boolean blocked);
private native String nativeGetDownloadDefaultDirectory();
private native void nativeSetDownloadAndSaveFileDefaultDirectory(String directory);
private native int nativeGetPromptForDownloadAndroid();
private native void nativeSetPromptForDownloadAndroid(int status);
}

@ -6,12 +6,6 @@ package org.chromium.chrome.browser.preferences.download;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Environment;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.content.ContextCompat;
import android.text.format.Formatter;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -20,7 +14,7 @@ import android.widget.ListView;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.ui.DownloadFilter;
import org.chromium.chrome.browser.download.DownloadLocationDialogBridge;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.widget.TintedImageButton;
@ -28,55 +22,22 @@ import org.chromium.chrome.browser.widget.TintedImageView;
import org.chromium.chrome.browser.widget.selection.SelectableItemView;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Custom adapter that populates the list of which directories the user can choose as their default
* download location.
*/
public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClickListener {
private static class DownloadDirectoryOption {
DownloadDirectoryOption(String directoryName, Drawable directoryIcon,
File directoryLocation, String availableSpace) {
this.mName = directoryName;
this.mIcon = directoryIcon;
this.mLocation = directoryLocation;
this.mAvailableSpace = availableSpace;
}
String mName;
Drawable mIcon;
File mLocation;
String mAvailableSpace;
}
private Context mContext;
private LayoutInflater mLayoutInflater;
private List<Pair<String, Integer>> mCanonicalDirectoryPairs = new ArrayList<>();
private List<DownloadDirectoryOption> mCanonicalDirectoryOptions = new ArrayList<>();
private List<DownloadDirectoryOption> mAdditionalDirectoryOptions = new ArrayList<>();
private DownloadDirectoryList mDownloadDirectoryUtil;
private int mSelectedView;
public DownloadDirectoryAdapter(Context context) {
DownloadDirectoryAdapter(Context context) {
mContext = context;
mLayoutInflater = LayoutInflater.from(mContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mCanonicalDirectoryPairs.add(
Pair.create(Environment.DIRECTORY_DOCUMENTS, DownloadFilter.FILTER_DOCUMENT));
}
mCanonicalDirectoryPairs.add(
Pair.create(Environment.DIRECTORY_PICTURES, DownloadFilter.FILTER_IMAGE));
mCanonicalDirectoryPairs.add(
Pair.create(Environment.DIRECTORY_MUSIC, DownloadFilter.FILTER_AUDIO));
mCanonicalDirectoryPairs.add(
Pair.create(Environment.DIRECTORY_MOVIES, DownloadFilter.FILTER_VIDEO));
mCanonicalDirectoryPairs.add(
Pair.create(Environment.DIRECTORY_DOWNLOADS, DownloadFilter.FILTER_ALL));
mDownloadDirectoryUtil = new DownloadDirectoryList(context);
}
/**
@ -90,87 +51,21 @@ public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClic
* Stop the adapter and reset lists of directories.
*/
public void stop() {
mCanonicalDirectoryOptions.clear();
mAdditionalDirectoryOptions.clear();
}
private void setCanonicalDirectoryOptions() {
if (mCanonicalDirectoryOptions.size() == mCanonicalDirectoryPairs.size()) return;
mCanonicalDirectoryOptions.clear();
for (Pair<String, Integer> nameAndIndex : mCanonicalDirectoryPairs) {
String directoryName =
mContext.getString(DownloadFilter.getStringIdForFilter(nameAndIndex.second));
Drawable directoryIcon = ContextCompat.getDrawable(
mContext, DownloadFilter.getDrawableForFilter(nameAndIndex.second));
File directoryLocation =
Environment.getExternalStoragePublicDirectory(nameAndIndex.first);
String availableBytes = getAvailableBytesString(directoryLocation);
mCanonicalDirectoryOptions.add(new DownloadDirectoryOption(
directoryName, directoryIcon, directoryLocation, availableBytes));
}
}
private void setAdditionalDirectoryOptions() {
mAdditionalDirectoryOptions.clear();
// TODO(jming): Is there any way to do this for API < 19????
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
File[] externalDirs = mContext.getExternalFilesDirs(Environment.DIRECTORY_DOWNLOADS);
int numAdditionalDirectories = externalDirs.length - 1;
// If there are no more additional directories, it is only the primary storage available.
if (numAdditionalDirectories == 0) return;
for (File dir : externalDirs) {
if (dir == null) continue;
// Skip the directory that is in primary storage.
if (dir.getAbsolutePath().contains(
Environment.getExternalStorageDirectory().getAbsolutePath())) {
continue;
}
int numOtherAdditionalDirectories = mAdditionalDirectoryOptions.size();
// Add index (ie. SD Card 2) if there is more than one secondary storage option.
String directoryName = (numOtherAdditionalDirectories > 0)
? mContext.getString(R.string.downloads_location_sd_card_number,
numOtherAdditionalDirectories + 1)
: mContext.getString(R.string.downloads_location_sd_card);
Drawable directoryIcon = VectorDrawableCompat.create(
mContext.getResources(), R.drawable.ic_sd_storage, mContext.getTheme());
String availableBytes = getAvailableBytesString(dir);
mAdditionalDirectoryOptions.add(
new DownloadDirectoryOption(directoryName, directoryIcon, dir, availableBytes));
}
}
private String getAvailableBytesString(File file) {
return Formatter.formatFileSize(mContext, file.getUsableSpace());
mDownloadDirectoryUtil.clearData();
}
private void refreshData() {
setCanonicalDirectoryOptions();
setAdditionalDirectoryOptions();
mDownloadDirectoryUtil.refreshData();
}
@Override
public int getCount() {
return mCanonicalDirectoryOptions.size() + mAdditionalDirectoryOptions.size();
return mDownloadDirectoryUtil.getCount();
}
@Override
public Object getItem(int position) {
int canonicalDirectoriesEndPosition = mCanonicalDirectoryOptions.size() - 1;
if (position <= canonicalDirectoriesEndPosition) {
return mCanonicalDirectoryOptions.get(position);
} else {
return mAdditionalDirectoryOptions.get(position - canonicalDirectoriesEndPosition - 1);
}
return mDownloadDirectoryUtil.getDirectoryOption(position);
}
@Override
@ -189,7 +84,8 @@ public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClic
view.setOnClickListener(this);
view.setTag(position);
DownloadDirectoryOption directoryOption = (DownloadDirectoryOption) getItem(position);
DownloadDirectoryList.Option directoryOption =
(DownloadDirectoryList.Option) getItem(position);
TextView directoryName = (TextView) view.findViewById(R.id.title);
directoryName.setText(directoryOption.mName);
@ -212,7 +108,7 @@ public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClic
TintedImageView startIcon = view.findViewById(R.id.icon_view);
TintedImageButton endIcon = view.findViewById(R.id.selected_view);
Drawable defaultIcon = ((DownloadDirectoryOption) getItem((int) view.getTag())).mIcon;
Drawable defaultIcon = ((DownloadDirectoryList.Option) getItem((int) view.getTag())).mIcon;
if (FeatureUtilities.isChromeModernDesignEnabled()) {
// In Modern Design, the default icon is replaced with a check mark if selected.
SelectableItemView.applyModernIconStyle(startIcon, defaultIcon, isSelected);
@ -227,11 +123,12 @@ public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClic
@Override
public void onClick(View view) {
int clickedViewPosition = (int) view.getTag();
DownloadDirectoryOption directoryOption =
(DownloadDirectoryOption) getItem(clickedViewPosition);
DownloadDirectoryList.Option directoryOption =
(DownloadDirectoryList.Option) getItem(clickedViewPosition);
PrefServiceBridge.getInstance().setDownloadAndSaveFileDefaultDirectory(
directoryOption.mLocation.getAbsolutePath());
updateSelectedView(view);
updateAlertDialog(directoryOption.mLocation);
}
private void updateSelectedView(View newSelectedView) {
@ -242,4 +139,10 @@ public class DownloadDirectoryAdapter extends BaseAdapter implements View.OnClic
styleViewSelectionAndIcons(newSelectedView, true);
mSelectedView = (int) newSelectedView.getTag();
}
private void updateAlertDialog(File location) {
DownloadLocationDialogBridge bridge = DownloadLocationDialogBridge.getInstance();
if (bridge == null) return;
bridge.updateFileLocation(location);
}
}

@ -0,0 +1,207 @@
// Copyright 2018 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.preferences.download;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Environment;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.text.format.Formatter;
import android.util.Pair;
import org.chromium.chrome.browser.download.ui.DownloadFilter;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
/**
* A utility class that helps maintain the available directories for downloading.
*/
public class DownloadDirectoryList {
class Option {
Option(String directoryName, Drawable directoryIcon, File directoryLocation,
String availableSpace) {
this.mName = directoryName;
this.mIcon = directoryIcon;
this.mLocation = directoryLocation;
this.mAvailableSpace = availableSpace;
}
String mName;
Drawable mIcon;
File mLocation;
String mAvailableSpace;
}
private final Context mContext;
private List<Pair<String, Integer>> mCanonicalPairs = new ArrayList<>();
private List<Option> mCanonicalOptions = new ArrayList<>();
private List<Option> mAdditionalOptions = new ArrayList<>();
private List<List<Option>> mAllOptions = Arrays.asList(mCanonicalOptions, mAdditionalOptions);
/**
* Create a DownloadDirectoryList based on a given context.
*
* @param context The context in which the DownloadDirectoryList exists.
*/
public DownloadDirectoryList(Context context) {
mContext = context;
// Build canonical directory pairs.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mCanonicalPairs.add(
Pair.create(Environment.DIRECTORY_DOCUMENTS, DownloadFilter.FILTER_DOCUMENT));
}
mCanonicalPairs.add(
Pair.create(Environment.DIRECTORY_PICTURES, DownloadFilter.FILTER_IMAGE));
mCanonicalPairs.add(Pair.create(Environment.DIRECTORY_MUSIC, DownloadFilter.FILTER_AUDIO));
mCanonicalPairs.add(Pair.create(Environment.DIRECTORY_MOVIES, DownloadFilter.FILTER_VIDEO));
mCanonicalPairs.add(
Pair.create(Environment.DIRECTORY_DOWNLOADS, DownloadFilter.FILTER_ALL));
refreshData();
}
/**
* Refresh the information that is available in the DownloadDirectoryList.
*
*/
void refreshData() {
setCanonicalDirectoryOptions();
setAdditionalDirectoryOptions();
}
/**
* Clear the information that is kept by the DownloadDirectoryList.
*/
void clearData() {
mCanonicalOptions.clear();
mAdditionalOptions.clear();
}
/**
* @return The number of directory options there are, including canonical and additional.
*/
int getCount() {
return mCanonicalOptions.size() + mAdditionalOptions.size();
}
/**
* Get a specific directory option for a position.
*
* @param position The index of the directory option that is to be returned.
* @return The directory option for that given position.
*/
Option getDirectoryOption(int position) {
int canonicalDirectoriesEndPosition = mCanonicalOptions.size() - 1;
if (position <= canonicalDirectoriesEndPosition) {
return mCanonicalOptions.get(position);
} else {
return mAdditionalOptions.get(position - canonicalDirectoriesEndPosition - 1);
}
}
/**
* Get a file location given the display name of the file.
*
* @param name The display name of the file.
* @return The actual location of the file with that given display name.
*/
@Nullable
public File getFileForName(String name) {
for (List<Option> optionList : mAllOptions) {
for (Option option : optionList) {
if (option.mName.equals(name)) {
return option.mLocation;
}
}
}
return null;
}
/**
* Get a display name for a given file location.
*
* @param file The file location.
* @return The display name associated with that file location.
*/
@Nullable
public String getNameForFile(File file) {
for (List<Option> optionList : mAllOptions) {
for (Option option : optionList) {
if (option.mLocation.equals(file)) {
return option.mName;
}
}
}
return null;
}
private void setCanonicalDirectoryOptions() {
if (mCanonicalOptions.size() == mCanonicalPairs.size()) return;
mCanonicalOptions.clear();
for (Pair<String, Integer> nameAndIndex : mCanonicalPairs) {
String directoryName =
mContext.getString(DownloadFilter.getStringIdForFilter(nameAndIndex.second));
Drawable directoryIcon = VectorDrawableCompat.create(mContext.getResources(),
DownloadFilter.getDrawableForFilter(nameAndIndex.second), mContext.getTheme());
File directoryLocation =
Environment.getExternalStoragePublicDirectory(nameAndIndex.first);
String availableBytes = getAvailableBytesString(directoryLocation);
mCanonicalOptions.add(
new Option(directoryName, directoryIcon, directoryLocation, availableBytes));
}
}
private void setAdditionalDirectoryOptions() {
mAdditionalOptions.clear();
// TODO(jming): Is there any way to do this for API < 19????
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
File[] externalDirs = mContext.getExternalFilesDirs(Environment.DIRECTORY_DOWNLOADS);
int numAdditionalDirectories = externalDirs.length - 1;
// If there are no more additional directories, it is only the primary storage available.
if (numAdditionalDirectories == 0) return;
for (File dir : externalDirs) {
if (dir == null) continue;
// Skip the directory that is in primary storage.
if (dir.getAbsolutePath().contains(
Environment.getExternalStorageDirectory().getAbsolutePath())) {
continue;
}
int numOtherAdditionalDirectories = mAdditionalOptions.size();
// Add index (ie. SD Card 2) if there is more than one secondary storage option.
String directoryName = (numOtherAdditionalDirectories > 0)
? mContext.getString(
org.chromium.chrome.R.string.downloads_location_sd_card_number,
numOtherAdditionalDirectories + 1)
: mContext.getString(org.chromium.chrome.R.string.downloads_location_sd_card);
Drawable directoryIcon = VectorDrawableCompat.create(mContext.getResources(),
org.chromium.chrome.R.drawable.ic_sd_storage, mContext.getTheme());
String availableBytes = getAvailableBytesString(dir);
mAdditionalOptions.add(new Option(directoryName, directoryIcon, dir, availableBytes));
}
}
private String getAvailableBytesString(File file) {
return Formatter.formatFileSize(mContext, file.getUsableSpace());
}
}

@ -10,9 +10,9 @@ import android.preference.PreferenceFragment;
import android.support.annotation.Nullable;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.download.DownloadPromptStatus;
import org.chromium.chrome.browser.preferences.ChromeBasePreference;
import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.PreferenceUtils;
@ -56,8 +56,10 @@ public class DownloadPreferences
}
if (mLocationPromptEnabledPref != null) {
// Location prompt is marked enabled if the prompt status is not don't show.
boolean isLocationPromptEnabled =
PrefServiceBridge.getInstance().getBoolean(Pref.PROMPT_FOR_DOWNLOAD_ANDROID);
PrefServiceBridge.getInstance().getPromptForDownloadAndroid()
!= DownloadPromptStatus.DONT_SHOW;
mLocationPromptEnabledPref.setChecked(isLocationPromptEnabled);
}
}
@ -67,8 +69,17 @@ public class DownloadPreferences
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (PREF_LOCATION_PROMPT_ENABLED.equals(preference.getKey())) {
PrefServiceBridge.getInstance().setBoolean(
Pref.PROMPT_FOR_DOWNLOAD_ANDROID, (boolean) newValue);
if ((boolean) newValue) {
// Only update if the interstitial has been shown before.
if (PrefServiceBridge.getInstance().getPromptForDownloadAndroid()
!= DownloadPromptStatus.SHOW_INITIAL) {
PrefServiceBridge.getInstance().setPromptForDownloadAndroid(
DownloadPromptStatus.SHOW_PREFERENCE);
}
} else {
PrefServiceBridge.getInstance().setPromptForDownloadAndroid(
DownloadPromptStatus.DONT_SHOW);
}
}
return true;
}

@ -1201,6 +1201,12 @@ To obtain new licenses, connect to the internet and play your downloaded content
<message name="IDS_DOWNLOAD_LOCATION_PROMPT_ENABLED_TITLE" desc="Title for preference that allows the user to indicate whether they want to show a change downloads location prompt every time they download.">
Ask where to save each file before downloading
</message>
<message name="IDS_DOWNLOAD_LOCATION_DIALOG_TITLE" desc="Title for the dialog that asks where the user wants to save the download file before the download begins.">
Download file
</message>
<message name="IDS_DOWNLOAD_LOCATION_DIALOG_CHECKBOX" desc="Label for the checkbox that allows the user to indicate if they do not want the download location selection dialog to appear every time they initiate a download.">
Dont show again
</message>
<!-- About Chrome preferences -->
<message name="IDS_PREFS_ABOUT_CHROME" desc="Title for the About Chrome page. [CHAR-LIMIT=32]">
@ -2190,9 +2196,6 @@ To obtain new licenses, connect to the internet and play your downloaded content
<message name="IDS_ACCESSIBILITY_COLLAPSE_OFFLINE_PAGES" desc="Content description for the suggested section header collapse button.">
Tap to collapse
</message>
<message name="IDS_DOWNLOAD_LOCATION_DIALOG_TITLE" desc="Title for the dialog that asks where the user wants to save the download file before the download begins.">
Download file
</message>
<!-- Browsing History UI -->
<message name="IDS_HISTORY_MANAGER_EMPTY" desc="Indicates that there are no browsing history items.">

@ -977,9 +977,10 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionProxyUma.java",
"java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionStatsPreference.java",
"java/src/org/chromium/chrome/browser/preferences/datareduction/DataReductionDataUseItem.java",
"java/src/org/chromium/chrome/browser/preferences/download/DownloadPreferences.java",
"java/src/org/chromium/chrome/browser/preferences/download/DownloadDirectoryAdapter.java",
"java/src/org/chromium/chrome/browser/preferences/download/DownloadDirectoryList.java",
"java/src/org/chromium/chrome/browser/preferences/download/DownloadDirectoryPreference.java",
"java/src/org/chromium/chrome/browser/preferences/download/DownloadPreferences.java",
"java/src/org/chromium/chrome/browser/preferences/languages/AddLanguageFragment.java",
"java/src/org/chromium/chrome/browser/preferences/languages/LanguagesManager.java",
"java/src/org/chromium/chrome/browser/preferences/languages/LanguagesPreferences.java",

@ -416,6 +416,7 @@ split_static_library("browser") {
"download/download_permission_request.h",
"download/download_prefs.cc",
"download/download_prefs.h",
"download/download_prompt_status.h",
"download/download_query.cc",
"download/download_query.h",
"download/download_request_limiter.cc",

@ -56,6 +56,7 @@ class DownloadController : public DownloadControllerBase {
CANCEL_REASON_NO_EXTERNAL_STORAGE,
CANCEL_REASON_CANNOT_DETERMINE_DOWNLOAD_TARGET,
CANCEL_REASON_OTHER_NATIVE_RESONS,
CANCEL_REASON_USER_CANCELED,
CANCEL_REASON_MAX
};
static void RecordDownloadCancelReason(DownloadCancelReason reason);

@ -6,6 +6,8 @@
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "chrome/browser/android/download/download_controller.h"
#include "chrome/browser/android/download/download_manager_service.h"
#include "content/public/browser/web_contents.h"
#include "jni/DownloadLocationDialogBridge_jni.h"
@ -27,6 +29,9 @@ void DownloadLocationDialogBridge::ShowDialog(
content::WebContents* web_contents,
const base::FilePath& suggested_path,
const DownloadTargetDeterminerDelegate::ConfirmationCallback& callback) {
if (!web_contents)
return;
// If dialog is showing, run the callback to continue without confirmation.
if (is_dialog_showing_) {
if (!callback.is_null()) {
@ -61,3 +66,16 @@ void DownloadLocationDialogBridge::OnComplete(
is_dialog_showing_ = false;
}
void DownloadLocationDialogBridge::OnCanceled(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
if (!dialog_complete_callback_.is_null()) {
DownloadController::RecordDownloadCancelReason(
DownloadController::CANCEL_REASON_USER_CANCELED);
base::ResetAndReturn(&dialog_complete_callback_)
.Run(DownloadConfirmationResult::CANCELED, base::FilePath());
}
is_dialog_showing_ = false;
}

@ -30,6 +30,8 @@ class DownloadLocationDialogBridge {
const base::android::JavaParamRef<jobject>& obj,
const base::android::JavaParamRef<jstring>& returned_path);
void OnCanceled(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
private:
jboolean is_dialog_showing_;
base::android::ScopedJavaGlobalRef<jobject> java_obj_;

@ -1296,6 +1296,19 @@ static void JNI_PrefServiceBridge_SetDownloadAndSaveFileDefaultDirectory(
base::FilePath(FILE_PATH_LITERAL(path)));
}
static jint JNI_PrefServiceBridge_GetPromptForDownloadAndroid(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
return GetPrefService()->GetInteger(prefs::kPromptForDownloadAndroid);
}
static void JNI_PrefServiceBridge_SetPromptForDownloadAndroid(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const jint status) {
GetPrefService()->SetInteger(prefs::kPromptForDownloadAndroid, status);
}
const char* PrefServiceBridge::GetPrefNameExposedToJava(int pref_index) {
DCHECK_GE(pref_index, 0);
DCHECK_LT(pref_index, Pref::PREF_NUM_PREFS);

@ -28,7 +28,6 @@ enum Pref {
const char* const kPrefsExposedToJava[] = {
prefs::kAllowDeletingBrowserHistory, prefs::kIncognitoModeAvailability,
dom_distiller::prefs::kReaderForAccessibility,
prefs::kPromptForDownloadAndroid,
};
prefs::kPromptForDownloadAndroid};
#endif // CHROME_BROWSER_ANDROID_PREFERENCES_PREFS_H_

@ -11,6 +11,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
@ -24,10 +25,12 @@
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_core_service_impl.h"
#include "chrome/browser/download/download_prompt_status.h"
#include "chrome/browser/download/download_target_determiner.h"
#include "chrome/browser/download/trusted_sources_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/safe_browsing/file_type_policies.h"
@ -228,7 +231,12 @@ void DownloadPrefs::RegisterProfilePrefs(
registry->RegisterBooleanPref(prefs::kOpenPdfDownloadInSystemReader, false);
#endif
#if defined(OS_ANDROID)
registry->RegisterBooleanPref(prefs::kPromptForDownloadAndroid, false);
DownloadPromptStatus download_prompt_status =
(base::FeatureList::IsEnabled(features::kDownloadsLocationChange))
? DownloadPromptStatus::SHOW_INITIAL
: DownloadPromptStatus::DONT_SHOW;
registry->RegisterIntegerPref(prefs::kPromptForDownloadAndroid,
static_cast<int>(download_prompt_status));
#endif
}
@ -310,7 +318,10 @@ bool DownloadPrefs::PromptForDownload() const {
// Return the Android prompt for download only.
#if defined(OS_ANDROID)
return *prompt_for_download_android_;
// As long as they haven't indicated in preferences they do not want the
// dialog shown, show the dialog.
return *prompt_for_download_android_ !=
static_cast<int>(DownloadPromptStatus::DONT_SHOW);
#endif
return *prompt_for_download_;

@ -118,7 +118,7 @@ class DownloadPrefs {
BooleanPrefMember prompt_for_download_;
#if defined(OS_ANDROID)
BooleanPrefMember prompt_for_download_android_;
IntegerPrefMember prompt_for_download_android_;
#endif
FilePathPrefMember download_path_;

@ -0,0 +1,17 @@
// Copyright 2018 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.
#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_PROMPT_STATUS_H_
#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_PROMPT_STATUS_H_
// The status of the download prompt.
// A Java counterpart will be generated for this enum.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.download
enum class DownloadPromptStatus {
SHOW_INITIAL, // Show the prompt because it hasn't been shown before.
SHOW_PREFERENCE, // Show the prompt because user indicated preference.
DONT_SHOW // Don't show the prompt because user indicated preference.
};
#endif // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_PROMPT_STATUS_H_

@ -25,6 +25,7 @@
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_confirmation_result.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_prompt_status.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/download/download_target_determiner.h"
#include "chrome/browser/download/download_target_info.h"
@ -324,6 +325,11 @@ void DownloadTargetDeterminerTest::SetUp() {
download_prefs_->SetDownloadPath(test_download_dir());
delegate_.SetupDefaults();
SetUpFileTypePolicies();
#if defined(OS_ANDROID)
profile()->GetTestingPrefService()->SetInteger(
prefs::kPromptForDownloadAndroid,
static_cast<int>(DownloadPromptStatus::DONT_SHOW));
#endif
}
void DownloadTargetDeterminerTest::TearDown() {
@ -404,8 +410,12 @@ void DownloadTargetDeterminerTest::SetPromptForDownload(bool prompt) {
profile()->GetTestingPrefService()->
SetBoolean(prefs::kPromptForDownload, prompt);
#if defined(OS_ANDROID)
profile()->GetTestingPrefService()->SetBoolean(
prefs::kPromptForDownloadAndroid, prompt);
DownloadPromptStatus download_prompt_status =
prompt ? DownloadPromptStatus::SHOW_PREFERENCE
: DownloadPromptStatus::DONT_SHOW;
profile()->GetTestingPrefService()->SetInteger(
prefs::kPromptForDownloadAndroid,
static_cast<int>(download_prompt_status));
#endif
}

@ -1390,9 +1390,8 @@ const char kOpenPdfDownloadInSystemReader[] =
#endif
#if defined(OS_ANDROID)
// Boolean which specifies whether we should ask the user if we should download
// a file (true) or just download it automatically, specifically for Android.
// This is set to true as the prompt is intially shown to all Android users.
// Int (as defined by DownloadPromptStatus) which specifies whether we should
// ask the user where they want to download the file (only for Android).
const char kPromptForDownloadAndroid[] = "download.prompt_for_download_android";
#endif

@ -28871,6 +28871,7 @@ Called by update_use_counter_css.py.-->
<int value="6" label="No external storage"/>
<int value="7" label="Cannot determine download target"/>
<int value="8" label="Cancelled by native for other reasons"/>
<int value="9" label="Cancelled by user action"/>
</enum>
<enum name="MobileDownloadDuplicateInfobar">