Skip to content

Commit

Permalink
Android q (zhihu#665)
Browse files Browse the repository at this point in the history
* Support Android Q.

1) "COUNT(*) AS " + COLUMN_COUNT is not working in Android Q anymore (Not sure why)
2) There are no official document specific how GROUP BY should work in CursorLoader. The unofficial workaround seem no longer work in Android Q - https://stackoverflow.com/a/33367564/72437

Due to the above 2 limitations, we need to perform manual calculation on "count per directory".

* Eliminate deprecated MediaStore.MediaColumns.DATA. We replace it with self generated Uri. However, I'm not sure whether there might be conversion failed (Uri.toString and Uri.parse) in certain edge cases.
  • Loading branch information
yccheok authored and REBOOTERS committed Oct 18, 2019
1 parent 21e0614 commit 4856c98
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 47 deletions.
6 changes: 3 additions & 3 deletions matisse/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ apply plugin: 'com.novoda.bintray-release'
apply plugin: 'checkstyle'

android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 29
buildToolsVersion '29.0.2'

defaultConfig {
minSdkVersion 14
targetSdkVersion 28
targetSdkVersion 29
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
Expand Down
19 changes: 10 additions & 9 deletions matisse/src/main/java/com/zhihu/matisse/internal/entity/Album.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
Expand All @@ -43,20 +44,20 @@ public Album[] newArray(int size) {
public static final String ALBUM_NAME_ALL = "All";

private final String mId;
private final String mCoverPath;
private final Uri mCoverUri;
private final String mDisplayName;
private long mCount;

Album(String id, String coverPath, String albumName, long count) {
public Album(String id, Uri coverUri, String albumName, long count) {
mId = id;
mCoverPath = coverPath;
mCoverUri = coverUri;
mDisplayName = albumName;
mCount = count;
}

Album(Parcel source) {
private Album(Parcel source) {
mId = source.readString();
mCoverPath = source.readString();
mCoverUri = source.readParcelable(Uri.class.getClassLoader());
mDisplayName = source.readString();
mCount = source.readLong();
}
Expand All @@ -68,7 +69,7 @@ public Album[] newArray(int size) {
public static Album valueOf(Cursor cursor) {
return new Album(
cursor.getString(cursor.getColumnIndex("bucket_id")),
cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA)),
Uri.parse(cursor.getString(cursor.getColumnIndex(AlbumLoader.COLUMN_URI))),
cursor.getString(cursor.getColumnIndex("bucket_display_name")),
cursor.getLong(cursor.getColumnIndex(AlbumLoader.COLUMN_COUNT)));
}
Expand All @@ -81,7 +82,7 @@ public int describeContents() {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
dest.writeString(mCoverPath);
dest.writeParcelable(mCoverUri, 0);
dest.writeString(mDisplayName);
dest.writeLong(mCount);
}
Expand All @@ -90,8 +91,8 @@ public String getId() {
return mId;
}

public String getCoverPath() {
return mCoverPath;
public Uri getCoverUri() {
return mCoverUri;
}

public long getCount() {
Expand Down
179 changes: 148 additions & 31 deletions matisse/src/main/java/com/zhihu/matisse/internal/loader/AlbumLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,67 @@
*/
package com.zhihu.matisse.internal.loader;

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;

import androidx.loader.content.CursorLoader;

import com.zhihu.matisse.MimeType;
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Load all albums (grouped by bucket_id) into a single cursor.
*/
public class AlbumLoader extends CursorLoader {
private static final String COLUMN_BUCKET_ID = "bucket_id";
private static final String COLUMN_BUCKET_DISPLAY_NAME = "bucket_display_name";
public static final String COLUMN_URI = "uri";
public static final String COLUMN_COUNT = "count";
private static final Uri QUERY_URI = MediaStore.Files.getContentUri("external");
private static final String[] COLUMNS = {
MediaStore.Files.FileColumns._ID,
"bucket_id",
"bucket_display_name",
MediaStore.MediaColumns.DATA,
COLUMN_BUCKET_ID,
COLUMN_BUCKET_DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE,
COLUMN_URI,
COLUMN_COUNT};
private static final String[] PROJECTION = {
MediaStore.Files.FileColumns._ID,
"bucket_id",
"bucket_display_name",
MediaStore.MediaColumns.DATA,
COLUMN_BUCKET_ID,
COLUMN_BUCKET_DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE,
"COUNT(*) AS " + COLUMN_COUNT};

private static final String[] PROJECTION_29 = {
MediaStore.Files.FileColumns._ID,
COLUMN_BUCKET_ID,
COLUMN_BUCKET_DISPLAY_NAME,
MediaStore.MediaColumns.MIME_TYPE};

// === params for showSingleMediaType: false ===
private static final String SELECTION =
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ ") GROUP BY (bucket_id";
private static final String SELECTION_29 =
"(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";
private static final String[] SELECTION_ARGS = {
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO),
Expand All @@ -67,6 +88,9 @@ public class AlbumLoader extends CursorLoader {
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ ") GROUP BY (bucket_id";
private static final String SELECTION_FOR_SINGLE_MEDIA_TYPE_29 =
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0";

private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
return new String[]{String.valueOf(mediaType)};
Expand All @@ -79,6 +103,10 @@ private static String[] getSelectionArgsForSingleMediaType(int mediaType) {
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?"
+ ") GROUP BY (bucket_id";
private static final String SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29 =
MediaStore.Files.FileColumns.MEDIA_TYPE + "=?"
+ " AND " + MediaStore.MediaColumns.SIZE + ">0"
+ " AND " + MediaStore.MediaColumns.MIME_TYPE + "=?";

private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) {
return new String[]{String.valueOf(mediaType), "image/gif"};
Expand All @@ -88,26 +116,30 @@ private static String[] getSelectionArgsForSingleMediaGifType(int mediaType) {
private static final String BUCKET_ORDER_BY = "datetaken DESC";

private AlbumLoader(Context context, String selection, String[] selectionArgs) {
super(context, QUERY_URI, PROJECTION, selection, selectionArgs, BUCKET_ORDER_BY);
super(
context,
QUERY_URI,
android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? PROJECTION : PROJECTION_29,
selection,
selectionArgs,
BUCKET_ORDER_BY
);
}

public static CursorLoader newInstance(Context context) {
String selection;
String[] selectionArgs;
if (SelectionSpec.getInstance().onlyShowGif()) {
selection = SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE;
selectionArgs = getSelectionArgsForSingleMediaGifType(
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
} else if (SelectionSpec.getInstance().onlyShowImages()) {
selection = SELECTION_FOR_SINGLE_MEDIA_TYPE;
selectionArgs = getSelectionArgsForSingleMediaType(
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
selection = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE : SELECTION_FOR_SINGLE_MEDIA_GIF_TYPE_29;
selectionArgs = getSelectionArgsForSingleMediaGifType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
}else if (SelectionSpec.getInstance().onlyShowImages()) {
selection = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE);
} else if (SelectionSpec.getInstance().onlyShowVideos()) {
selection = SELECTION_FOR_SINGLE_MEDIA_TYPE;
selectionArgs = getSelectionArgsForSingleMediaType(
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
selection = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? SELECTION_FOR_SINGLE_MEDIA_TYPE : SELECTION_FOR_SINGLE_MEDIA_TYPE_29;
selectionArgs = getSelectionArgsForSingleMediaType(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO);
} else {
selection = SELECTION;
selection = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? SELECTION : SELECTION_29;
selectionArgs = SELECTION_ARGS;
}
return new AlbumLoader(context, selection, selectionArgs);
Expand All @@ -116,23 +148,108 @@ public static CursorLoader newInstance(Context context) {
@Override
public Cursor loadInBackground() {
Cursor albums = super.loadInBackground();
MatrixCursor allAlbum = new MatrixCursor(COLUMNS);
int totalCount = 0;
String allAlbumCoverPath = "";
if (albums != null) {
while (albums.moveToNext()) {
totalCount += albums.getInt(albums.getColumnIndex(COLUMN_COUNT));
MatrixCursor allAlbum = new MatrixCursor(COLUMNS);

if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
int totalCount = 0;
Uri allAlbumCoverUri = null;
MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
if (albums != null) {
while (albums.moveToNext()) {
long fileId = albums.getLong(albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));
String bucketDisplayName = albums.getString(albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
String mimeType = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
Uri uri = getUri(albums);
int count = albums.getInt(albums.getColumnIndex(COLUMN_COUNT));

otherAlbums.addRow(new String[]{Long.toString(fileId), Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(),
String.valueOf(count)});
totalCount += count;
}
if (albums.moveToFirst()) {
allAlbumCoverUri = getUri(albums);
}
}

allAlbum.addRow(new String[]{Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
String.valueOf(totalCount)});

return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
} else {
int totalCount = 0;
Uri allAlbumCoverUri = null;

// Pseudo GROUP BY
Map<Long, Long> countMap = new HashMap<>();
if (albums != null) {
while (albums.moveToNext()) {
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));

Long count = countMap.get(bucketId);
if (count == null) {
count = 1L;
} else {
count++;
}
countMap.put(bucketId, count);
}
}
if (albums.moveToFirst()) {
allAlbumCoverPath = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.DATA));

MatrixCursor otherAlbums = new MatrixCursor(COLUMNS);
if (albums != null) {
if (albums.moveToFirst()) {
allAlbumCoverUri = getUri(albums);

Set<Long> done = new HashSet<>();

do {
long bucketId = albums.getLong(albums.getColumnIndex(COLUMN_BUCKET_ID));

if (done.contains(bucketId)) {
continue;
}

long fileId = albums.getLong(albums.getColumnIndex(MediaStore.Files.FileColumns._ID));
String bucketDisplayName = albums.getString(albums.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME));
String mimeType = albums.getString(albums.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
Uri uri = getUri(albums);
long count = countMap.get(bucketId);

otherAlbums.addRow(new String[]{Long.toString(fileId), Long.toString(bucketId), bucketDisplayName, mimeType, uri.toString(),
String.valueOf(count)});
done.add(bucketId);

totalCount += count;
} while (albums.moveToNext());
}
}

allAlbum.addRow(new String[]{Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL, null,
allAlbumCoverUri == null ? null : allAlbumCoverUri.toString(),
String.valueOf(totalCount)});

return new MergeCursor(new Cursor[]{allAlbum, otherAlbums});
}
}

private static Uri getUri(Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID));
String mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE));
Uri contentUri;

if (MimeType.isImage(mimeType)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if (MimeType.isVideo(mimeType)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else {
// ?
contentUri = MediaStore.Files.getContentUri("external");
}
allAlbum.addRow(new String[]{
Album.ALBUM_ID_ALL, Album.ALBUM_ID_ALL, Album.ALBUM_NAME_ALL,
allAlbumCoverPath, "", String.valueOf(totalCount)
});

return new MergeCursor(new Cursor[]{allAlbum, albums});
Uri uri = ContentUris.withAppendedId(contentUri, id);
return uri;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
Expand All @@ -31,8 +30,6 @@
import com.zhihu.matisse.internal.entity.Album;
import com.zhihu.matisse.internal.entity.SelectionSpec;

import java.io.File;

public class AlbumsAdapter extends CursorAdapter {

private final Drawable mPlaceholder;
Expand Down Expand Up @@ -69,6 +66,6 @@ public void bindView(View view, Context context, Cursor cursor) {
// do not need to load animated Gif
SelectionSpec.getInstance().imageEngine.loadThumbnail(context, context.getResources().getDimensionPixelSize(R
.dimen.media_grid_size), mPlaceholder,
(ImageView) view.findViewById(R.id.album_cover), Uri.fromFile(new File(album.getCoverPath())));
(ImageView) view.findViewById(R.id.album_cover), album.getCoverUri());
}
}

0 comments on commit 4856c98

Please sign in to comment.