/* * Copyright (C) 2013 The CyanogenMod Project * Copyright (C) 2014 Yaroslav Mytkalyk * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cyanogenmod.filemanager.util; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.provider.BaseColumns; import android.provider.MediaStore; import android.provider.MediaStore.MediaColumns; import android.text.TextUtils; import org.apache.commons.io.IOUtils; import android.support.annotation.NonNull; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; /** * A helper class with useful methods to extract media data. */ public final class MediaHelper { private static final String EMULATED_STORAGE_SOURCE = System.getenv("EMULATED_STORAGE_SOURCE"); private static final String EMULATED_STORAGE_TARGET = System.getenv("EMULATED_STORAGE_TARGET"); private static final String EXTERNAL_STORAGE = System.getenv("EXTERNAL_STORAGE"); private static final String INTERNAL_VOLUME = "internal"; private static final String EXTERNAL_VOLUME = "external"; /** * URIs that are relevant for determining album art; * useful for content observer registration */ public static final Uri[] RELEVANT_URIS = new Uri[] { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI }; /** * Method that returns an array with all the unique albums paths and ids. * * @param cr The ContentResolver * @return The albums map */ public static Map<String, Long> getAllAlbums(ContentResolver cr) { final Map<String, Long> albums = new HashMap<>(); final String[] projection = { "distinct " + MediaStore.Audio.Media.ALBUM_ID, "substr(" + MediaStore.Audio.Media.DATA + ", 0, length(" + MediaStore.Audio.Media.DATA + ") - length(" + MediaStore.Audio.Media.DISPLAY_NAME + "))" }; final String where = MediaStore.Audio.Media.IS_MUSIC + " = ?"; Cursor c = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, where, new String[]{"1"}, null); if (c != null) { try { for (c.moveToFirst(); !c.isAfterLast(); c.moveToNext()) { long albumId = c.getLong(0); String albumPath = c.getString(1); albums.put(albumPath, albumId); } } finally { IOUtils.closeQuietly(c); } } return albums; } /** * Method that returns the album thumbnail path by its identifier. * * @param cr The ContentResolver * @param albumId The album identifier to search * @return String The album thumbnail path */ public static String getAlbumThumbnailPath(@NonNull final ContentResolver cr, final long albumId) { final String[] projection = {MediaStore.Audio.Albums.ALBUM_ART}; final String where = BaseColumns._ID + "=?"; Cursor c = cr.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, projection, where, new String[]{String.valueOf(albumId)}, null); try { if (c != null && c.moveToFirst()) { return c.getString(0); } } finally { IOUtils.closeQuietly(c); } return null; } /** * Method that converts a file reference to a content uri reference * * @param cr A content resolver * @param file The file reference * @return Uri The content uri or null if file not exists in the media database */ public static Uri fileToContentUri(ContentResolver cr, File file) { // Normalize the path to ensure media search final String normalizedPath = normalizeMediaPath(file.getAbsolutePath()); // Check in external and internal storages Uri uri = fileToContentUri(cr, normalizedPath, EXTERNAL_VOLUME); if (uri != null) { return uri; } uri = fileToContentUri(cr, normalizedPath, INTERNAL_VOLUME); if (uri != null) { return uri; } return null; } /** * Method that converts a file reference to a content uri reference * * @param cr A content resolver * @param path The path to search * @param volume The volume * @return Uri The content uri or null if file not exists in the media database */ private static Uri fileToContentUri(ContentResolver cr, String path, String volume) { final String[] projection = {BaseColumns._ID, MediaStore.Files.FileColumns.MEDIA_TYPE}; final String where = MediaColumns.DATA + " = ?"; Uri baseUri = MediaStore.Files.getContentUri(volume); Cursor c = cr.query(baseUri, projection, where, new String[]{path}, null); try { if (c != null && c.moveToNext()) { int type = c.getInt(c.getColumnIndexOrThrow( MediaStore.Files.FileColumns.MEDIA_TYPE)); if (type != 0) { // Do not force to use content uri for no media files long id = c.getLong(c.getColumnIndexOrThrow(BaseColumns._ID)); return Uri.withAppendedPath(baseUri, String.valueOf(id)); } } } finally { IOUtils.closeQuietly(c); } return null; } /** * Method that converts a content uri to a file system path * * @param cr The content resolver * @param uri The content uri * @return File The file reference */ public static File contentUriToFile(ContentResolver cr, Uri uri) { // Sanity checks if (uri == null || uri.getScheme() == null || uri.getScheme().compareTo("content") != 0) { return null; } // Retrieve the request id long id; try { id = Long.parseLong(new File(uri.getPath()).getName()); } catch (NumberFormatException nfex) { return null; } // Check in external and internal storages File file = mediaIdToFile(cr, id, EXTERNAL_VOLUME); if (file != null) { return file; } file = mediaIdToFile(cr, id, INTERNAL_VOLUME); if (file != null) { return file; } return null; } /** * Method that converts a content uri to a file system path * * @param cr The content resolver * @param id The media database id * @param volume The volume * @return File The file reference */ private static File mediaIdToFile(ContentResolver cr, long id, String volume) { final String[] projection = {MediaColumns.DATA}; final String where = MediaColumns._ID + " = ?"; Uri baseUri = MediaStore.Files.getContentUri(volume); Cursor c = cr.query(baseUri, projection, where, new String[]{String.valueOf(id)}, null); try { if (c != null && c.moveToNext()) { return new File(c.getString(c.getColumnIndexOrThrow(MediaColumns.DATA))); } } finally { if (c != null) { c.close(); } } return null; } /** * Method that converts a not standard media mount path to a standard media path * * @param path The path to normalize * @return String The normalized media path */ public static String normalizeMediaPath(String path) { // Retrieve all the paths and check that we have this environment vars if (TextUtils.isEmpty(EMULATED_STORAGE_SOURCE) || TextUtils.isEmpty(EMULATED_STORAGE_TARGET) || TextUtils.isEmpty(EXTERNAL_STORAGE)) { return path; } // We need to convert EMULATED_STORAGE_SOURCE -> EMULATED_STORAGE_TARGET if (path.startsWith(EMULATED_STORAGE_SOURCE)) { path = path.replace(EMULATED_STORAGE_SOURCE, EMULATED_STORAGE_TARGET); } // We need to convert EXTERNAL_STORAGE -> EMULATED_STORAGE_TARGET / userId if (path.startsWith(EXTERNAL_STORAGE)) { final String userId = String.valueOf(myUserId()); final String target = new File(EMULATED_STORAGE_TARGET, userId).getAbsolutePath(); path = path.replace(EXTERNAL_STORAGE, target); } return path; } private static int myUserId() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { return UserHandleJellyBeanMR1.myUserId(); } return 0; } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private static final class UserHandleJellyBeanMR1 { @SuppressLint("NewApi") static int myUserId() { try { final Method myUserIdMethod = android.os.UserHandle.class.getMethod("myUserId"); myUserIdMethod.setAccessible(true); return (Integer) myUserIdMethod.invoke(null); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return 0; } } }