Commit 3ecc16b9 authored by Joy Ming's avatar Joy Ming Committed by Commit Bot

[Downloads location] Implementation of dropdown location selection.

After trying multiple methods in which to select the download location
(including a full-fledged folder selector, surfacing only the
cannonical directories like Music or Pictures), we decided on giving the
user only the option to select between internal and external
directories (ie. Downloads from internal storage vs. SD card). This CL
implements the user interface for this.

Some of the structural changes include:
- Implementing Spinners for preferences and the dialog, using styles
that are generic but match with the different context.
- Consolidating DownloadDirectoryList functionality into
DownloadDirectoryAdapter.
- Modifying DownloadDirectoryAdapter to be an ArrayAdapter and using it
as the adapter for the Spinners, keeping things uniform.

Screenshots are as follows:

Downloads preferences:
https://drive.google.com/open?id=1CNJG8e4M9Q3Seps5QyA6Sx45bOUewO0y
https://drive.google.com/open?id=1UnCCIGwdYeh5iOV1Cb2S4X9hjS-YV5s8

Download location dialog:
https://drive.google.com/open?id=1szZlaMiqGyWD6RoVU1B8fSoDqOjLl-KH
https://drive.google.com/open?id=1pKAATJMwaWoetHxGoiW4CqdqLiXj3kPx

Bug: 792775
Change-Id: Ie4aabed98f900d260298d98fe9dc172438bbb719
Reviewed-on: https://chromium-review.googlesource.com/959384Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Commit-Queue: Joy Ming <jming@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543120}
parent cd508f21
......@@ -26,7 +26,8 @@
<org.chromium.chrome.browser.widget.AlertDialogEditText
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:textAppearance="@style/BlackTitle1" />
</LinearLayout>
......@@ -42,10 +43,11 @@
android:tint="@color/black_alpha_87"
style="@style/ListItemStartIcon" />
<org.chromium.chrome.browser.widget.AlertDialogEditText
<Spinner
android:id="@+id/file_location"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
style="@android:style/Widget.EditText"/>
</LinearLayout>
......
......@@ -3,20 +3,19 @@
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"
<!-- Note: Nested layouts are used because the styling was being overwritten,
likely because of the behavior of the Android Spinner class. -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ListItemContainer" >
<include layout="@layout/modern_list_item_view" />
<org.chromium.chrome.browser.widget.TintedImageButton
android:id="@+id/selected_view"
style="@style/ListItemEndIcon"
android:src="@drawable/ic_check_googblue_24dp" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
</FrameLayout>
\ No newline at end of file
......@@ -3,14 +3,9 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="21"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM12,8h-2L10,4h2v4zM15,8h-2L13,4h2v4zM18,8h-2L16,4h2v4z"/>
</vector>
\ No newline at end of file
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/text"
android:singleLine="true"
android:textAppearance="@style/BlackTitle1" />
\ No newline at end of file
......@@ -5,10 +5,9 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<org.chromium.chrome.browser.preferences.ChromeBasePreference
android:fragment="org.chromium.chrome.browser.preferences.download.DownloadDirectoryPreference"
<org.chromium.chrome.browser.preferences.SpinnerPreference
android:key="location_change"
android:title="@string/download_location_title" />
android:title="@string/downloads_location_selector_title" />
<org.chromium.chrome.browser.preferences.ChromeSwitchPreference
android:key="location_prompt_enabled"
......
......@@ -5,18 +5,14 @@
package org.chromium.chrome.browser.download;
import android.content.Context;
import android.content.Intent;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.Spinner;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
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.preferences.download.DownloadDirectoryAdapter;
import org.chromium.chrome.browser.widget.AlertDialogEditText;
import java.io.File;
......@@ -26,11 +22,11 @@ import javax.annotation.Nullable;
/**
* Dialog that is displayed to ask user where they want to download the file.
*/
public class DownloadLocationDialog extends ModalDialogView implements View.OnFocusChangeListener {
private DownloadDirectoryList mDownloadDirectoryUtil;
public class DownloadLocationDialog extends ModalDialogView {
private DownloadDirectoryAdapter mDirectoryAdapter;
private AlertDialogEditText mFileName;
private AlertDialogEditText mFileLocation;
private Spinner mFileLocation;
private CheckBox mDontShowAgain;
/**
......@@ -57,16 +53,15 @@ public class DownloadLocationDialog extends ModalDialogView implements View.OnFo
Controller controller, Context context, File suggestedPath, Params params) {
super(controller, params);
mDownloadDirectoryUtil = new DownloadDirectoryList(context);
mDirectoryAdapter = new DownloadDirectoryAdapter(context);
mFileName = (AlertDialogEditText) params.customView.findViewById(R.id.file_name);
mFileName.setText(suggestedPath.getName());
mFileLocation = (AlertDialogEditText) params.customView.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());
mFileLocation = (Spinner) params.customView.findViewById(R.id.file_location);
mFileLocation.setAdapter(mDirectoryAdapter);
mFileLocation.setSelection(mDirectoryAdapter.getSelectedItemId());
// Automatically check "don't show again" the first time the user is seeing the dialog.
mDontShowAgain = (CheckBox) params.customView.findViewById(R.id.show_again_checkbox);
......@@ -77,16 +72,6 @@ public class DownloadLocationDialog extends ModalDialogView implements View.OnFo
// Helper methods available to DownloadLocationDialogBridge.
/**
* 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));
}
/**
* @return The text that the user inputted as the name of the file.
*/
......@@ -102,7 +87,9 @@ public class DownloadLocationDialog extends ModalDialogView implements View.OnFo
@Nullable
File getFileLocation() {
if (mFileLocation == null) return null;
return mDownloadDirectoryUtil.getFileForName(mFileLocation.getText().toString());
DownloadDirectoryAdapter.DirectoryOption selected =
(DownloadDirectoryAdapter.DirectoryOption) mFileLocation.getSelectedItem();
return selected.getLocation();
}
/**
......@@ -111,16 +98,4 @@ public class DownloadLocationDialog extends ModalDialogView implements View.OnFo
boolean getDontShowAgain() {
return mDontShowAgain != null && mDontShowAgain.isChecked();
}
// 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);
}
}
}
......@@ -13,49 +13,27 @@ 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 ModalDialogView.Controller {
// TODO(jming): Remove this when switching to a dropdown instead of going to preferences.
private static DownloadLocationDialogBridge sInstance;
private long mNativeDownloadLocationDialogBridge;
private DownloadLocationDialog mLocationDialog;
private ModalDialogManager mModalDialogManager;
@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) {
sInstance = new DownloadLocationDialogBridge(nativeDownloadLocationDialogBridge);
return sInstance;
return new DownloadLocationDialogBridge(nativeDownloadLocationDialogBridge);
}
@CalledByNative
private void destroy() {
mNativeDownloadLocationDialogBridge = 0;
if (mModalDialogManager != null) mModalDialogManager.dismissDialog(mLocationDialog);
sInstance = null;
}
@CalledByNative
......
......@@ -10,7 +10,6 @@ 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;
......
......@@ -44,7 +44,8 @@ public class SpinnerPreference extends Preference {
/**
* Provides a list of arbitrary objects to be shown in the spinner. Visually, each option will
* be presented as its toString() text.
* be presented as its toString() text. Alternative to {@link #setAdapter(ArrayAdapter, int)}.
*
* @param options The options to be shown in the spinner.
* @param selectedIndex Index of the initially selected option.
*/
......@@ -60,6 +61,20 @@ public class SpinnerPreference extends Preference {
mSelectedIndex = selectedIndex;
}
/**
* Provides an adapter containing objects to be shown in the spinner. Alternatively, a list of
* objects to be shown may be provided in {@link #setOptions(Object[], int)}. It is expected
* that only one of these methods will be called.
*
* @param arrayAdapter The array adapter to use.
* @param selectedIndex The index of the selected item.
*/
public void setAdapter(ArrayAdapter<Object> arrayAdapter, int selectedIndex) {
mAdapter = arrayAdapter;
mAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSelectedIndex = selectedIndex;
}
/**
* @return The currently selected option.
*/
......
// 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());
}
}
// 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.os.Bundle;
import android.preference.PreferenceFragment;
import android.support.annotation.Nullable;
import android.widget.ListView;
import org.chromium.chrome.R;
/**
* Preference that allows the user to select which folder they would like to make the default
* location their downloads will save to.
*/
public class DownloadDirectoryPreference extends PreferenceFragment {
private ListView mListView;
private DownloadDirectoryAdapter mDownloadDirectoryAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.downloads_location_selector_title);
mDownloadDirectoryAdapter = new DownloadDirectoryAdapter(getActivity());
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mListView = (ListView) getView().findViewById(android.R.id.list);
mListView.setAdapter(mDownloadDirectoryAdapter);
}
@Override
public void onStart() {
super.onStart();
mDownloadDirectoryAdapter.start();
}
@Override
public void onStop() {
super.onStop();
mDownloadDirectoryAdapter.stop();
}
}
......@@ -11,10 +11,10 @@ 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.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.PreferenceUtils;
import org.chromium.chrome.browser.preferences.SpinnerPreference;
/**
* Fragment to keep track of all downloads related preferences.
......@@ -24,7 +24,7 @@ public class DownloadPreferences
private static final String PREF_LOCATION_CHANGE = "location_change";
private static final String PREF_LOCATION_PROMPT_ENABLED = "location_prompt_enabled";
private ChromeBasePreference mLocationChangePref;
private SpinnerPreference mLocationChangePref;
private ChromeSwitchPreference mLocationPromptEnabledPref;
@Override
......@@ -38,7 +38,9 @@ public class DownloadPreferences
(ChromeSwitchPreference) findPreference(PREF_LOCATION_PROMPT_ENABLED);
mLocationPromptEnabledPref.setOnPreferenceChangeListener(this);
mLocationChangePref = (ChromeBasePreference) findPreference(PREF_LOCATION_CHANGE);
mLocationChangePref = (SpinnerPreference) findPreference(PREF_LOCATION_CHANGE);
DownloadDirectoryAdapter directoryAdapter = new DownloadDirectoryAdapter(getActivity());
mLocationChangePref.setAdapter(directoryAdapter, directoryAdapter.getSelectedItemId());
updateSummaries();
}
......@@ -80,6 +82,10 @@ public class DownloadPreferences
PrefServiceBridge.getInstance().setPromptForDownloadAndroid(
DownloadPromptStatus.DONT_SHOW);
}
} else if (PREF_LOCATION_CHANGE.equals(preference.getKey())) {
PrefServiceBridge.getInstance().setDownloadAndSaveFileDefaultDirectory(
(String) newValue);
updateSummaries();
}
return true;
}
......
......@@ -1189,9 +1189,6 @@ To obtain new licenses, connect to the internet and play your downloaded content
</message>
<!-- Downloads preferences -->
<message name="IDS_DOWNLOAD_LOCATION_TITLE" desc="Title for 'Downloads location' preference that changes the default device directory that stores downloaded items.">
Location
</message>
<message name="IDS_DOWNLOADS_LOCATION_SELECTOR_TITLE" desc="Title of the preference that allows the user to select which of the folders they would like to make the default location to save their downloads.">
Download location
</message>
......
......@@ -983,8 +983,6 @@ chrome_java_sources = [
"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/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",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment