blob: 6b7c40126a8f86b46d9e5ab438334f7af275756d [file] [log] [blame]
/*
* Copyright (C) 2017 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.example.android.contentproviderpaging;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* ContentProvider that demonstrates how the paging support works introduced in Android O.
* This class fetches the images from the local storage but the storage could be
* other locations such as a remote server.
*/
public class ImageProvider extends ContentProvider {
private static final String TAG = "ImageDocumentsProvider";
private static final int IMAGES = 1;
private static final int IMAGE_ID = 2;
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sUriMatcher.addURI(ImageContract.AUTHORITY, "images", IMAGES);
sUriMatcher.addURI(ImageContract.AUTHORITY, "images/#", IMAGE_ID);
}
// Indicated how many same images are going to be written as placeholder images
private static final int REPEAT_COUNT_WRITE_FILES = 10;
private File mBaseDir;
@Override
public boolean onCreate() {
Log.d(TAG, "onCreate");
Context context = getContext();
if (context == null) {
return false;
}
mBaseDir = context.getFilesDir();
writeDummyFilesToStorage(context);
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s,
@Nullable String[] strings1, @Nullable String s1) {
throw new UnsupportedOperationException();
}
@Override
public Cursor query(Uri uri, String[] projection, Bundle queryArgs,
CancellationSignal cancellationSignal) {
int match = sUriMatcher.match(uri);
// We only support a query for multiple images, return null for other form of queries
// including a query for a single image.
switch (match) {
case IMAGES:
break;
default:
return null;
}
MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
File[] files = mBaseDir.listFiles();
int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE);
Log.d(TAG, "queryChildDocuments with Bundle, Uri: " +
uri + ", offset: " + offset + ", limit: " + limit);
if (offset < 0) {
throw new IllegalArgumentException("Offset must not be less than 0");
}
if (limit < 0) {
throw new IllegalArgumentException("Limit must not be less than 0");
}
if (offset >= files.length) {
return result;
}
for (int i = offset, maxIndex = Math.min(offset + limit, files.length); i < maxIndex; i++) {
includeFile(result, files[i]);
}
Bundle bundle = new Bundle();
bundle.putInt(ContentResolver.EXTRA_SIZE, files.length);
String[] honoredArgs = new String[2];
int size = 0;
if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
honoredArgs[size++] = ContentResolver.QUERY_ARG_OFFSET;
}
if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
honoredArgs[size++] = ContentResolver.QUERY_ARG_LIMIT;
}
if (size != honoredArgs.length) {
honoredArgs = Arrays.copyOf(honoredArgs, size);
}
bundle.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, honoredArgs);
result.setExtras(bundle);
return result;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = sUriMatcher.match(uri);
switch (match) {
case IMAGES:
return "vnd.android.cursor.dir/images";
case IMAGE_ID:
return "vnd.android.cursor.item/images";
default:
throw new IllegalArgumentException(String.format("Unknown URI: %s", uri));
}
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
throw new UnsupportedOperationException();
}
@Override
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
throw new UnsupportedOperationException();
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s,
@Nullable String[] strings) {
throw new UnsupportedOperationException();
}
private static String[] resolveDocumentProjection(String[] projection) {
return projection != null ? projection : ImageContract.PROJECTION_ALL;
}
/**
* Add a representation of a file to a cursor.
*
* @param result the cursor to modify
* @param file the File object representing the desired file (may be null if given docID)
*/
private void includeFile(MatrixCursor result, File file) {
MatrixCursor.RowBuilder row = result.newRow();
row.add(ImageContract.Columns.DISPLAY_NAME, file.getName());
row.add(ImageContract.Columns.SIZE, file.length());
row.add(ImageContract.Columns.ABSOLUTE_PATH, file.getAbsolutePath());
}
/**
* Preload sample files packaged in the apk into the internal storage directory. This is a
* placeholder function specific to this demo. The MyCloud mock cloud service doesn't actually
* have a backend, so it simulates by reading content from the device's internal storage.
*/
private void writeDummyFilesToStorage(Context context) {
if (mBaseDir.list().length > 0) {
return;
}
int[] imageResIds = getResourceIdArray(context, R.array.image_res_ids);
for (int i = 0; i < REPEAT_COUNT_WRITE_FILES; i++) {
for (int resId : imageResIds) {
writeFileToInternalStorage(context, resId, "-" + i + ".jpeg");
}
}
}
/**
* Write a file to internal storage. Used to set up our placeholder "cloud server".
*
* @param context the Context
* @param resId the resource ID of the file to write to internal storage
* @param extension the file extension (ex. .png, .mp3)
*/
private void writeFileToInternalStorage(Context context, int resId, String extension) {
InputStream ins = context.getResources().openRawResource(resId);
int size;
byte[] buffer = new byte[1024];
try {
String filename = context.getResources().getResourceEntryName(resId) + extension;
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
while ((size = ins.read(buffer, 0, 1024)) >= 0) {
fos.write(buffer, 0, size);
}
ins.close();
fos.write(buffer);
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private int[] getResourceIdArray(Context context, int arrayResId) {
TypedArray ar = context.getResources().obtainTypedArray(arrayResId);
int len = ar.length();
int[] resIds = new int[len];
for (int i = 0; i < len; i++) {
resIds[i] = ar.getResourceId(i, 0);
}
ar.recycle();
return resIds;
}
}