Skip to content

Commit

Permalink
Photo Picker Dialog: Recursively traverse the photo directories.
Browse files Browse the repository at this point in the history
(Only visible change, since the picker is not decoding yet, is that
it will show as many gray squares as there are photos on disk).

BUG=656015

Review-Url: https://codereview.chromium.org/2810773002
Cr-Commit-Position: refs/heads/master@{#464641}
  • Loading branch information
finnur authored and Commit bot committed Apr 14, 2017
1 parent 5be07ac commit a106f0a
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2017 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.photo_picker;

import android.os.AsyncTask;
import android.os.Environment;

import org.chromium.base.ThreadUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* A worker task to enumerate image files on disk.
*/
class FileEnumWorkerTask extends AsyncTask<Void, Void, List<PickerBitmap>> {
/**
* An interface to use to communicate back the results to the client.
*/
public interface FilesEnumeratedCallback {
/**
* A callback to define to receive the list of all images on disk.
* @param files The list of images.
*/
void filesEnumeratedCallback(List<PickerBitmap> files);
}

// The callback to use to communicate the results.
private FilesEnumeratedCallback mCallback;

// The filter to apply to the list.
private MimeTypeFileFilter mFilter;

// The camera directory undir DCIM.
private static final String SAMPLE_DCIM_SOURCE_SUB_DIRECTORY = "Camera";

/**
* A FileEnumWorkerTask constructor.
* @param callback The callback to use to communicate back the results.
* @param filter The file filter to apply to the list.
*/
public FileEnumWorkerTask(FilesEnumeratedCallback callback, MimeTypeFileFilter filter) {
mCallback = callback;
mFilter = filter;
}

/**
* Retrieves the DCIM/camera directory.
*/
private File getCameraDirectory() {
return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
SAMPLE_DCIM_SOURCE_SUB_DIRECTORY);
}

/**
* Recursively enumerate files in a directory (and subdirectories) and add them to a list.
* @param directory The parent directory to recursively traverse.
* @param pickerBitmaps The list to add the results to.
* @return True if traversing can continue, false if traversing was aborted and should stop.
*/
private boolean traverseDir(File directory, List<PickerBitmap> pickerBitmaps) {
File[] files = directory.listFiles(mFilter);
if (files == null) return true;

for (File file : files) {
if (isCancelled()) return false;

if (file.isDirectory()) {
if (!traverseDir(file, pickerBitmaps)) return false;
} else {
pickerBitmaps.add(new PickerBitmap(
file.getPath(), file.lastModified(), PickerBitmap.PICTURE));
}
}

return true;
}

/**
* Enumerates (in the background) the image files on disk. Called on a non-UI thread
* @param params Ignored, do not use.
* @return A sorted list of images (by last-modified first).
*/
@Override
protected List<PickerBitmap> doInBackground(Void... params) {
assert !ThreadUtils.runningOnUiThread();

if (isCancelled()) return null;

List<PickerBitmap> pickerBitmaps = new ArrayList<>();

// TODO(finnur): Figure out which directories to scan and stop hard coding "Camera" above.
File[] sourceDirs = new File[] {
getCameraDirectory(),
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
};

for (File directory : sourceDirs) {
if (!traverseDir(directory, pickerBitmaps)) return null;
}

Collections.sort(pickerBitmaps);

pickerBitmaps.add(0, new PickerBitmap("", 0, PickerBitmap.GALLERY));
pickerBitmaps.add(0, new PickerBitmap("", 0, PickerBitmap.CAMERA));

return pickerBitmaps;
}

/**
* Communicates the results back to the client. Called on the UI thread.
* @param files The resulting list of files on disk.
*/
@Override
protected void onPostExecute(List<PickerBitmap> files) {
if (isCancelled()) {
return;
}

mCallback.filesEnumeratedCallback(files);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2017 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.photo_picker;

import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;

import java.io.File;
import java.io.FileFilter;
import java.util.HashSet;
import java.util.Locale;

/**
* A file filter for handling extensions mapping to MIME types (such as images/jpeg and images/*).
*/
class MimeTypeFileFilter implements FileFilter {
private HashSet<String> mExtensions = new HashSet<>();
private HashSet<String> mMimeTypes = new HashSet<>();
private HashSet<String> mMimeSupertypes = new HashSet<>();
private MimeTypeMap mMimeTypeMap;

/**
* Contructs a MimeTypeFileFilter object.
* @param acceptAttr A comma seperated list of MIME types this filter accepts.
* For example: images/gif, video/*.
*/
// TODO(finnur): Convert param to List.
public MimeTypeFileFilter(@NonNull String acceptAttr) {
for (String field : acceptAttr.toLowerCase(Locale.US).split(",")) {
field = field.trim();
if (field.startsWith(".")) {
mExtensions.add(field.substring(1));
} else if (field.endsWith("/*")) {
mMimeSupertypes.add(field.substring(0, field.length() - 2));
} else if (field.contains("/")) {
mMimeTypes.add(field);
} else {
// Throw exception?
}
}

mMimeTypeMap = MimeTypeMap.getSingleton();
}

@Override
public boolean accept(@NonNull File file) {
if (file.isDirectory()) {
return true;
}

String uri = file.toURI().toString();
String ext = MimeTypeMap.getFileExtensionFromUrl(uri).toLowerCase(Locale.US);
if (mExtensions.contains(ext)) {
return true;
}

String mimeType = getMimeTypeFromExtension(ext);
if (mimeType != null) {
if (mMimeTypes.contains(mimeType)
|| mMimeSupertypes.contains(getMimeSupertype(mimeType))) {
return true;
}
}

return false;
}

private HashSet<String> getAcceptedSupertypes() {
HashSet<String> supertypes = new HashSet<>();
supertypes.addAll(mMimeSupertypes);
for (String mimeType : mMimeTypes) {
supertypes.add(getMimeSupertype(mimeType));
}
for (String ext : mExtensions) {
String mimeType = getMimeTypeFromExtension(ext);
if (mimeType != null) {
supertypes.add(getMimeSupertype(mimeType));
}
}
return supertypes;
}

private String getMimeTypeFromExtension(@NonNull String ext) {
String mimeType = mMimeTypeMap.getMimeTypeFromExtension(ext);
return (mimeType != null) ? mimeType.toLowerCase(Locale.US) : null;
}

@NonNull
private String getMimeSupertype(@NonNull String mimeType) {
return mimeType.split("/", 2)[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,10 @@ protected void onCreate(Bundle savedInstanceState) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}

@Override
public void dismiss() {
super.dismiss();
mCategoryView.onDialogDismissed();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
Expand All @@ -21,14 +20,14 @@
import org.chromium.chrome.browser.widget.selection.SelectionDelegate;
import org.chromium.ui.PhotoPickerListener;

import java.util.ArrayList;
import java.util.List;

/**
* A class for keeping track of common data associated with showing photos in
* the photo picker, for example the RecyclerView and the bitmap caches.
*/
public class PickerCategoryView extends RelativeLayout implements OnMenuItemClickListener {
public class PickerCategoryView extends RelativeLayout
implements FileEnumWorkerTask.FilesEnumeratedCallback, OnMenuItemClickListener {
// The dialog that owns us.
private PhotoPickerDialog mDialog;

Expand Down Expand Up @@ -69,21 +68,14 @@ public class PickerCategoryView extends RelativeLayout implements OnMenuItemClic
// The size of the bitmaps (equal length for width and height).
private int mImageSize;

// A worker task for asynchronously enumerating files off the main thread.
private FileEnumWorkerTask mWorkerTask;

public PickerCategoryView(Context context) {
super(context);
postConstruction(context);
}

public PickerCategoryView(Context context, AttributeSet attrs) {
super(context, attrs);
postConstruction(context);
}

public PickerCategoryView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
postConstruction(context);
}

/**
* A helper function for initializing the PickerCategoryView.
* @param context The context to use.
Expand Down Expand Up @@ -119,6 +111,16 @@ private void postConstruction(Context context) {
prepareBitmaps();
}

/**
* Cancels any outstanding requests.
*/
public void onDialogDismissed() {
if (mWorkerTask != null) {
mWorkerTask.cancel(true);
mWorkerTask = null;
}
}

/**
* Initializes the PickerCategoryView object.
* @param dialog The dialog showing us.
Expand All @@ -134,6 +136,16 @@ public void initialize(
mListener = listener;
}

// FileEnumWorkerTask.FilesEnumeratedCallback:

@Override
public void filesEnumeratedCallback(List<PickerBitmap> files) {
mPickerBitmaps = files;
if (files != null && files.size() > 0) {
mPickerAdapter.notifyDataSetChanged();
}
}

// OnMenuItemClickListener:

@Override
Expand Down Expand Up @@ -203,15 +215,12 @@ private void calculateGridMetrics(int width) {
* Prepares bitmaps for loading.
*/
private void prepareBitmaps() {
// TODO(finnur): Use worker thread to fetch bitmaps instead of hard-coding.
mPickerBitmaps = new ArrayList<>();
mPickerBitmaps.add(0, new PickerBitmap("", 0, PickerBitmap.GALLERY));
mPickerBitmaps.add(0, new PickerBitmap("", 0, PickerBitmap.CAMERA));
mPickerBitmaps.add(new PickerBitmap("foo/bar1.jpg", 1, PickerBitmap.PICTURE));
mPickerBitmaps.add(new PickerBitmap("foo/bar2.jpg", 2, PickerBitmap.PICTURE));
mPickerBitmaps.add(new PickerBitmap("foo/bar3.jpg", 3, PickerBitmap.PICTURE));
mPickerBitmaps.add(new PickerBitmap("foo/bar4.jpg", 4, PickerBitmap.PICTURE));
mPickerAdapter.notifyDataSetChanged();
if (mWorkerTask != null) {
mWorkerTask.cancel(true);
}

mWorkerTask = new FileEnumWorkerTask(this, new MimeTypeFileFilter("image/*"));
mWorkerTask.execute();
}

/**
Expand Down
2 changes: 2 additions & 0 deletions chrome/android/java_sources.gni
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java",
"java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java",
"java/src/org/chromium/chrome/browser/physicalweb/BitmapHttpRequest.java",
"java/src/org/chromium/chrome/browser/photo_picker/FileEnumWorkerTask.java",
"java/src/org/chromium/chrome/browser/photo_picker/MimeTypeFileFilter.java",
"java/src/org/chromium/chrome/browser/photo_picker/PhotoPickerDialog.java",
"java/src/org/chromium/chrome/browser/photo_picker/PhotoPickerToolbar.java",
"java/src/org/chromium/chrome/browser/photo_picker/PickerAdapter.java",
Expand Down

0 comments on commit a106f0a

Please sign in to comment.