Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ff200957 authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Query specific collections for properties.

We're now enforcing the public API schema, so clients like MTP need
to query specific collections when asking for properties that are
specific to those media types.

Also refactor MediaScanner calls to go through MediaProvider, so
they can be handled by ModernMediaScanner.  We no longer directly
try inserting items, and instead rely completely on the scanner.

Bug: 127625964, 122263824
Test: atest MediaProviderTests
Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore*
Change-Id: Ic86982766606a619afafe725f9c60013a5671840
parent 1c3ef21a
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -136,6 +136,7 @@ public final class MediaStore {
     * removing nomedia files
     * @hide
     */
    @Deprecated
    public static final String UNHIDE_CALL = "unhide";

    /**
@@ -3536,4 +3537,17 @@ public final class MediaStore {
            throw new IOException("User " + user + " must be unlocked and running");
        }
    }

    /** @hide */
    public static Uri scanFile(Context context, File file) {
        final ContentResolver resolver = context.getContentResolver();
        try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
            final Bundle in = new Bundle();
            in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file));
            final Bundle out = client.call(SCAN_FILE_CALL, null, in);
            return out.getParcelable(Intent.EXTRA_STREAM);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }
}
+28 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.android.internal.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Denotes that the class, method or field has its visibility relaxed so
 * that native code can access it.
 */
@Retention(RetentionPolicy.CLASS)
public @interface VisibleForNative {
}
+59 −124
Original line number Diff line number Diff line
@@ -25,16 +25,13 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.media.MediaScanner;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.storage.StorageVolume;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.MediaColumns;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -42,6 +39,8 @@ import android.util.Log;
import android.view.Display;
import android.view.WindowManager;

import com.android.internal.annotations.VisibleForNative;

import dalvik.system.CloseGuard;

import com.google.android.collect.Sets;
@@ -72,7 +71,6 @@ public class MtpDatabase implements AutoCloseable {
    private final ContentProviderClient mMediaProvider;
    private final String mVolumeName;
    private final Uri mObjectsUri;
    private final MediaScanner mMediaScanner;

    private final AtomicBoolean mClosed = new AtomicBoolean();
    private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -159,7 +157,6 @@ public class MtpDatabase implements AutoCloseable {
            MtpConstants.PROPERTY_TRACK,
            MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE,
            MtpConstants.PROPERTY_DURATION,
            MtpConstants.PROPERTY_GENRE,
            MtpConstants.PROPERTY_COMPOSER,
            MtpConstants.PROPERTY_AUDIO_WAVE_CODEC,
            MtpConstants.PROPERTY_BITRATE_TYPE,
@@ -187,6 +184,7 @@ public class MtpDatabase implements AutoCloseable {
            MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE,
    };

    @VisibleForNative
    private int[] getSupportedObjectProperties(int format) {
        switch (format) {
            case MtpConstants.FORMAT_MP3:
@@ -214,14 +212,41 @@ public class MtpDatabase implements AutoCloseable {
        }
    }

    public static Uri getObjectPropertiesUri(int format, String volumeName) {
        switch (format) {
            case MtpConstants.FORMAT_MP3:
            case MtpConstants.FORMAT_WAV:
            case MtpConstants.FORMAT_WMA:
            case MtpConstants.FORMAT_OGG:
            case MtpConstants.FORMAT_AAC:
                return MediaStore.Audio.Media.getContentUri(volumeName);
            case MtpConstants.FORMAT_MPEG:
            case MtpConstants.FORMAT_3GP_CONTAINER:
            case MtpConstants.FORMAT_WMV:
                return MediaStore.Video.Media.getContentUri(volumeName);
            case MtpConstants.FORMAT_EXIF_JPEG:
            case MtpConstants.FORMAT_GIF:
            case MtpConstants.FORMAT_PNG:
            case MtpConstants.FORMAT_BMP:
            case MtpConstants.FORMAT_DNG:
            case MtpConstants.FORMAT_HEIF:
                return MediaStore.Images.Media.getContentUri(volumeName);
            default:
                return MediaStore.Files.getContentUri(volumeName);
        }
    }

    @VisibleForNative
    private int[] getSupportedDeviceProperties() {
        return DEVICE_PROPERTIES;
    }

    @VisibleForNative
    private int[] getSupportedPlaybackFormats() {
        return PLAYBACK_FORMATS;
    }

    @VisibleForNative
    private int[] getSupportedCaptureFormats() {
        // no capture formats yet
        return null;
@@ -254,7 +279,6 @@ public class MtpDatabase implements AutoCloseable {
                .acquireContentProviderClient(MediaStore.AUTHORITY);
        mVolumeName = volumeName;
        mObjectsUri = Files.getMtpObjectsUri(volumeName);
        mMediaScanner = new MediaScanner(context, mVolumeName);
        mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() {
            @Override
            public void sendObjectAdded(int id) {
@@ -304,7 +328,6 @@ public class MtpDatabase implements AutoCloseable {
        mManager.close();
        mCloseGuard.close();
        if (mClosed.compareAndSet(false, true)) {
            mMediaScanner.close();
            if (mMediaProvider != null) {
                mMediaProvider.close();
            }
@@ -380,6 +403,7 @@ public class MtpDatabase implements AutoCloseable {
        }
    }

    @VisibleForNative
    private int beginSendObject(String path, int format, int parent, int storageId) {
        MtpStorageManager.MtpObject parentObj =
                parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
@@ -391,6 +415,7 @@ public class MtpDatabase implements AutoCloseable {
        return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format);
    }

    @VisibleForNative
    private void endSendObject(int handle, boolean succeeded) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null || !mManager.endSendObject(obj, succeeded)) {
@@ -399,69 +424,16 @@ public class MtpDatabase implements AutoCloseable {
        }
        // Add the new file to MediaProvider
        if (succeeded) {
            String path = obj.getPath().toString();
            int format = obj.getFormat();
            // Get parent info from MediaProvider, since the id is different from MTP's
            ContentValues values = new ContentValues();
            values.put(Files.FileColumns.DATA, path);
            values.put(Files.FileColumns.FORMAT, format);
            values.put(Files.FileColumns.SIZE, obj.getSize());
            values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
            try {
                if (obj.getParent().isRoot()) {
                    values.put(Files.FileColumns.PARENT, 0);
                } else {
                    int parentId = findInMedia(obj.getParent().getPath());
                    if (parentId != -1) {
                        values.put(Files.FileColumns.PARENT, parentId);
                    } else {
                        // The parent isn't in MediaProvider. Don't add the new file.
                        return;
                    }
                }

                Uri uri = mMediaProvider.insert(mObjectsUri, values);
                if (uri != null) {
                    rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException in beginSendObject", e);
            }
            MediaStore.scanFile(mContext, obj.getPath().toFile());
        }
    }

    @VisibleForNative
    private void rescanFile(String path, int handle, int format) {
        // handle abstract playlists separately
        // they do not exist in the file system so don't use the media scanner here
        if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) {
            // extract name from path
            String name = path;
            int lastSlash = name.lastIndexOf('/');
            if (lastSlash >= 0) {
                name = name.substring(lastSlash + 1);
            }
            // strip trailing ".pla" from the name
            if (name.endsWith(".pla")) {
                name = name.substring(0, name.length() - 4);
            }

            ContentValues values = new ContentValues(1);
            values.put(Audio.Playlists.DATA, path);
            values.put(Audio.Playlists.NAME, name);
            values.put(Files.FileColumns.FORMAT, format);
            values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000);
            values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle);
            try {
                mMediaProvider.insert(
                        Audio.Playlists.EXTERNAL_CONTENT_URI, values);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException in endSendObject", e);
            }
        } else {
            mMediaScanner.scanMtpFile(path, handle, format);
        }
        MediaStore.scanFile(mContext, new File(path));
    }

    @VisibleForNative
    private int[] getObjectList(int storageID, int format, int parent) {
        List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
                format, storageID);
@@ -475,6 +447,7 @@ public class MtpDatabase implements AutoCloseable {
        return ret;
    }

    @VisibleForNative
    private int getNumObjects(int storageID, int format, int parent) {
        List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
                format, storageID);
@@ -484,6 +457,7 @@ public class MtpDatabase implements AutoCloseable {
        return objs.size();
    }

    @VisibleForNative
    private MtpPropertyList getObjectPropertyList(int handle, int format, int property,
            int groupCode, int depth) {
        // FIXME - implement group support
@@ -551,16 +525,16 @@ public class MtpDatabase implements AutoCloseable {
                // format should be the same between get & put
                propertyGroup = mPropertyGroupsByFormat.get(format);
                if (propertyGroup == null) {
                    int[] propertyList = getSupportedObjectProperties(format);
                    final int[] propertyList = getSupportedObjectProperties(format);
                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
                            propertyList);
                    mPropertyGroupsByFormat.put(format, propertyGroup);
                }
            } else {
                // Get this property value
                final int[] propertyList = new int[]{property};
                propertyGroup = mPropertyGroupsByProperty.get(property);
                if (propertyGroup == null) {
                    final int[] propertyList = new int[]{property};
                    propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName,
                            propertyList);
                    mPropertyGroupsByProperty.put(property, propertyGroup);
@@ -616,28 +590,19 @@ public class MtpDatabase implements AutoCloseable {
        if (obj.isDir()) {
            // for directories, check if renamed from something hidden to something non-hidden
            if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) {
                // directory was unhidden
                try {
                    mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null);
                } catch (RemoteException e) {
                    Log.e(TAG, "failed to unhide/rescan for " + newPath);
                }
                MediaStore.scanFile(mContext, newPath.toFile());
            }
        } else {
            // for files, check if renamed from .nomedia to something else
            if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)
                    && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) {
                try {
                    mMediaProvider.call(MediaStore.UNHIDE_CALL,
                            oldPath.getParent().toString(), null);
                } catch (RemoteException e) {
                    Log.e(TAG, "failed to unhide/rescan for " + newPath);
                }
                MediaStore.scanFile(mContext, newPath.getParent().toFile());
            }
        }
        return MtpConstants.RESPONSE_OK;
    }

    @VisibleForNative
    private int beginMoveObject(int handle, int newParent, int newStorage) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        MtpStorageManager.MtpObject parent = newParent == 0 ?
@@ -649,6 +614,7 @@ public class MtpDatabase implements AutoCloseable {
        return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR;
    }

    @VisibleForNative
    private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage,
            int objId, boolean success) {
        MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ?
@@ -698,20 +664,14 @@ public class MtpDatabase implements AutoCloseable {
                mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs);
            } else {
                // Old parent doesn't exist - add the object
                values.put(Files.FileColumns.FORMAT, obj.getFormat());
                values.put(Files.FileColumns.SIZE, obj.getSize());
                values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
                Uri uri = mMediaProvider.insert(mObjectsUri, values);
                if (uri != null) {
                    rescanFile(path.toString(),
                            Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat());
                }
                MediaStore.scanFile(mContext, path.toFile());
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
        }
    }

    @VisibleForNative
    private int beginCopyObject(int handle, int newParent, int newStorage) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        MtpStorageManager.MtpObject parent = newParent == 0 ?
@@ -721,6 +681,7 @@ public class MtpDatabase implements AutoCloseable {
        return mManager.beginCopyObject(obj, parent);
    }

    @VisibleForNative
    private void endCopyObject(int handle, boolean success) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null || !mManager.endCopyObject(obj, success)) {
@@ -730,39 +691,10 @@ public class MtpDatabase implements AutoCloseable {
        if (!success) {
            return;
        }
        String path = obj.getPath().toString();
        int format = obj.getFormat();
        // Get parent info from MediaProvider, since the id is different from MTP's
        ContentValues values = new ContentValues();
        values.put(Files.FileColumns.DATA, path);
        values.put(Files.FileColumns.FORMAT, format);
        values.put(Files.FileColumns.SIZE, obj.getSize());
        values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime());
        try {
            if (obj.getParent().isRoot()) {
                values.put(Files.FileColumns.PARENT, 0);
            } else {
                int parentId = findInMedia(obj.getParent().getPath());
                if (parentId != -1) {
                    values.put(Files.FileColumns.PARENT, parentId);
                } else {
                    // The parent isn't in MediaProvider. Don't add the new file.
                    return;
                }
            }
            if (obj.isDir()) {
                mMediaScanner.scanDirectories(new String[]{path});
            } else {
                Uri uri = mMediaProvider.insert(mObjectsUri, values);
                if (uri != null) {
                    rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format);
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in beginSendObject", e);
        }
        MediaStore.scanFile(mContext, obj.getPath().toFile());
    }

    @VisibleForNative
    private int setObjectProperty(int handle, int property,
            long intValue, String stringValue) {
        switch (property) {
@@ -774,6 +706,7 @@ public class MtpDatabase implements AutoCloseable {
        }
    }

    @VisibleForNative
    private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) {
        switch (property) {
            case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
@@ -809,6 +742,7 @@ public class MtpDatabase implements AutoCloseable {
        }
    }

    @VisibleForNative
    private int setDeviceProperty(int property, long intValue, String stringValue) {
        switch (property) {
            case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER:
@@ -823,6 +757,7 @@ public class MtpDatabase implements AutoCloseable {
        return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
    }

    @VisibleForNative
    private boolean getObjectInfo(int handle, int[] outStorageFormatParent,
            char[] outName, long[] outCreatedModified) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
@@ -842,6 +777,7 @@ public class MtpDatabase implements AutoCloseable {
        return true;
    }

    @VisibleForNative
    private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null) {
@@ -866,6 +802,7 @@ public class MtpDatabase implements AutoCloseable {
        return obj.getFormat();
    }

    @VisibleForNative
    private int beginDeleteObject(int handle) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null) {
@@ -877,6 +814,7 @@ public class MtpDatabase implements AutoCloseable {
        return MtpConstants.RESPONSE_OK;
    }

    @VisibleForNative
    private void endDeleteObject(int handle, boolean success) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null) {
@@ -922,12 +860,7 @@ public class MtpDatabase implements AutoCloseable {
            String[] whereArgs = new String[]{path.toString()};
            if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) {
                if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) {
                    try {
                        String parentPath = path.getParent().toString();
                        mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null);
                    } catch (RemoteException e) {
                        Log.e(TAG, "failed to unhide/rescan for " + path);
                    }
                    MediaStore.scanFile(mContext, path.getParent().toFile());
                }
            } else {
                Log.i(TAG, "Mediaprovider didn't delete " + path);
@@ -937,6 +870,7 @@ public class MtpDatabase implements AutoCloseable {
        }
    }

    @VisibleForNative
    private int[] getObjectReferences(int handle) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null)
@@ -972,6 +906,7 @@ public class MtpDatabase implements AutoCloseable {
        return null;
    }

    @VisibleForNative
    private int setObjectReferences(int handle, int[] references) {
        MtpStorageManager.MtpObject obj = mManager.getObject(handle);
        if (obj == null)
@@ -1004,7 +939,7 @@ public class MtpDatabase implements AutoCloseable {
        return MtpConstants.RESPONSE_GENERAL_ERROR;
    }

    // used by the JNI code
    @VisibleForNative
    private long mNativeContext;

    private native final void native_setup();
+5 −61
Original line number Diff line number Diff line
@@ -48,7 +48,6 @@ class MtpPropertyGroup {

    private final ContentProviderClient mProvider;
    private final String mVolumeName;
    private final Uri mUri;

    // list of all properties in this group
    private final Property[] mProperties;
@@ -62,7 +61,6 @@ class MtpPropertyGroup {
    public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
        mProvider = provider;
        mVolumeName = volumeName;
        mUri = Files.getMtpObjectsUri(volumeName);

        int count = properties.length;
        ArrayList<String> columns = new ArrayList<>(count);
@@ -139,10 +137,6 @@ class MtpPropertyGroup {
                column = Audio.AudioColumns.ALBUM_ARTIST;
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_GENRE:
                // genre requires a special query
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_COMPOSER:
                column = Audio.AudioColumns.COMPOSER;
                type = MtpConstants.TYPE_STR;
@@ -176,46 +170,6 @@ class MtpPropertyGroup {
        }
    }

    private String queryAudio(String path, String column) {
        Cursor c = null;
        try {
            c = mProvider.query(Audio.Media.getContentUri(mVolumeName),
                            new String [] { column },
                            PATH_WHERE, new String[] {path}, null, null);
            if (c != null && c.moveToNext()) {
                return c.getString(0);
            } else {
                return "";
            }
        } catch (Exception e) {
            return "";
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    private String queryGenre(String path) {
        Cursor c = null;
        try {
            c = mProvider.query(Audio.Genres.getContentUri(mVolumeName),
                            new String [] { Audio.GenresColumns.NAME },
                            PATH_WHERE, new String[] {path}, null, null);
            if (c != null && c.moveToNext()) {
                return c.getString(0);
            } else {
                return "";
            }
        } catch (Exception e) {
            return "";
        } finally {
            if (c != null) {
                c.close();
            }
        }
    }

    /**
     * Gets the values of the properties represented by this property group for the given
     * object and adds them to the given property list.
@@ -229,12 +183,16 @@ class MtpPropertyGroup {
            if (property.column != -1 && c == null) {
                try {
                    // Look up the entry in MediaProvider only if one of those properties is needed.
                    c = mProvider.query(mUri, mColumns,
                    final Uri uri = MtpDatabase.getObjectPropertiesUri(object.getFormat(),
                            mVolumeName);
                    c = mProvider.query(uri, mColumns,
                            PATH_WHERE, new String[] {path}, null, null);
                    if (c != null && !c.moveToNext()) {
                        c.close();
                        c = null;
                    }
                } catch (IllegalArgumentException e) {
                    return MtpConstants.RESPONSE_INVALID_OBJECT_PROP_CODE;
                } catch (RemoteException e) {
                    Log.e(TAG, "Mediaprovider lookup failed");
                }
@@ -290,20 +248,6 @@ class MtpPropertyGroup {
                    list.append(id, property.code, MtpConstants.TYPE_UINT16,
                            track % 1000);
                    break;
                case MtpConstants.PROPERTY_ARTIST:
                    list.append(id, property.code,
                            queryAudio(path, Audio.AudioColumns.ARTIST));
                    break;
                case MtpConstants.PROPERTY_ALBUM_NAME:
                    list.append(id, property.code,
                            queryAudio(path, Audio.AudioColumns.ALBUM));
                    break;
                case MtpConstants.PROPERTY_GENRE:
                    String genre = queryGenre(path);
                    if (genre != null) {
                        list.append(id, property.code, genre);
                    }
                    break;
                case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
                case MtpConstants.PROPERTY_AUDIO_BITRATE:
                case MtpConstants.PROPERTY_SAMPLE_RATE: