package com.datdo.mobilib.imageinput; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; import android.content.Context; import android.database.Cursor; import android.media.MediaScannerConnection; import android.net.Uri; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Log; import com.datdo.mobilib.util.MblUtils; // reference: https://github.com/jerickson314/sdscanner // work in 4.4 kitkat class MblImagePickingScanEngine { private static final String TAG = MblUtils.getTag(MblImagePickingScanEngine.class); private static final String THUMBNAIL_FOLDER = ".thumbnails"; private static Pattern sImagePattern; static { StringBuilder imageRegExBuilder = new StringBuilder(); imageRegExBuilder.append("^.*\\.(?i)("); String[] extensions = new String[MblImageInput.sExtensionsOfPickedImages.length]; for (int i = 0; i < MblImageInput.sExtensionsOfPickedImages.length; i++) { extensions[i] = Pattern.quote(MblImageInput.sExtensionsOfPickedImages[i]); } imageRegExBuilder.append(TextUtils.join("|", extensions)); imageRegExBuilder.append(")$"); sImagePattern = Pattern.compile(imageRegExBuilder.toString()); } public static String buildMediaQuerySelection(String[] imageFolders) { StringBuilder ret = new StringBuilder(); // filter folder ret.append(MediaStore.Images.Media.DATA + " NOT LIKE ?"); ret.append(" AND ("); for (int i = 0; i < imageFolders.length; i++) { if (i > 0) ret.append(" OR "); ret.append(MediaStore.Images.Media.DATA + " LIKE ?"); } ret.append(")"); // filter extentions ret.append(" AND ("); for (int i = 0; i < MblImageInput.sExtensionsOfPickedImages.length; i++) { if (i > 0) ret.append(" OR "); ret.append("(" + MediaStore.Images.Media.DATA + " LIKE ? OR " + MediaStore.Images.Media.DATA + " LIKE ?)"); } ret.append(")"); return ret.toString(); } public static String[] buildMediaQuerySelectionArgs(String[] imageFolders) { List<String> ret = new ArrayList<String>(); // arguments for folder filter ret.add("%/" + THUMBNAIL_FOLDER + "/%"); for (String folder : imageFolders) { ret.add(folder + "%"); } // arguments for extension filter for (String ext : MblImageInput.sExtensionsOfPickedImages) { ret.add("%." + ext.toLowerCase(Locale.US)); ret.add("%." + ext.toUpperCase(Locale.US)); } return ret.toArray(new String[ret.size()]); } public static interface CmScanCallback { public void onFinish(int nUpdatedFiles); public void onFailure(); } public static void scan(final String[] imageFolders, final CmScanCallback callback) { final Context context = MblUtils.getCurrentContext(); MblUtils.executeOnAsyncThread(new Runnable() { private Set<File> mFilesToProcess = new TreeSet<File>(); private int mNumberOfScannedFiles; private void recursiveAddFiles(File file) throws IOException { if (file.isDirectory()) { // do not care ".thumbnails" folder if (file.getPath().endsWith(THUMBNAIL_FOLDER)) return; // only recurse downward if not blocked by nomedia. boolean nomedia = new File(file, ".nomedia").exists(); if (!nomedia) { for (File nextFile : file.listFiles()) { recursiveAddFiles(nextFile); } } } else { if (sImagePattern.matcher(file.getName()).matches()) { mFilesToProcess.add(file.getCanonicalFile()); } } } @Override public void run() { try { try { for (String f : imageFolders) { recursiveAddFiles(new File(f)); } } catch (IOException ignored) {} Cursor cursor = context.getContentResolver().query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_MODIFIED }, buildMediaQuerySelection(imageFolders), buildMediaQuerySelectionArgs(imageFolders), null); int dataColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA); int modifiedColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED); try { while (cursor.moveToNext()) { File file = new File(cursor.getString(dataColumn)).getCanonicalFile(); if ( !file.exists() || file.lastModified() > 1000L * cursor.getLong(modifiedColumn) ) { // media scanner handles these cases. mFilesToProcess.add(file); } else { // don't want to waste time scanning an up-to-date file mFilesToProcess.remove(file); } } } catch (IOException ignored) {} // don't need the cursor any more. cursor.close(); // scan files if (!mFilesToProcess.isEmpty()) { final int n = mFilesToProcess.size(); final String[] paths = new String[n]; int i = 0; for (File file : mFilesToProcess) { paths[i++] = file.getPath(); } mNumberOfScannedFiles = 0; MediaScannerConnection.scanFile( context.getApplicationContext(), paths, null, new MediaScannerConnection.OnScanCompletedListener() { public void onScanCompleted(String path, Uri uri) { mNumberOfScannedFiles++; if (mNumberOfScannedFiles >= n) { if (callback != null) { MblUtils.executeOnMainThread(new Runnable() { @Override public void run() { callback.onFinish(n); } }); } } } }); } else { if (callback != null) { MblUtils.executeOnMainThread(new Runnable() { @Override public void run() { callback.onFinish(0); } }); } } } catch (Exception e) { Log.e(TAG, "Can not scan media files", e); if (callback != null) { MblUtils.executeOnMainThread(new Runnable() { @Override public void run() { callback.onFailure(); } }); } } } }); } }