Home | History | Annotate | Download | only in contentproviderpaging
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.contentproviderpaging;
     18 
     19 import android.content.ContentProvider;
     20 import android.content.ContentResolver;
     21 import android.content.ContentValues;
     22 import android.content.Context;
     23 import android.content.UriMatcher;
     24 import android.content.res.TypedArray;
     25 import android.database.Cursor;
     26 import android.database.MatrixCursor;
     27 import android.net.Uri;
     28 import android.os.Bundle;
     29 import android.os.CancellationSignal;
     30 import android.support.annotation.NonNull;
     31 import android.support.annotation.Nullable;
     32 import android.util.Log;
     33 
     34 import java.io.File;
     35 import java.io.FileOutputStream;
     36 import java.io.IOException;
     37 import java.io.InputStream;
     38 import java.util.Arrays;
     39 
     40 /**
     41  * ContentProvider that demonstrates how the paging support works introduced in Android O.
     42  * This class fetches the images from the local storage but the storage could be
     43  * other locations such as a remote server.
     44  */
     45 public class ImageProvider extends ContentProvider {
     46 
     47     private static final String TAG = "ImageDocumentsProvider";
     48 
     49     private static final int IMAGES = 1;
     50 
     51     private static final int IMAGE_ID = 2;
     52 
     53     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     54 
     55     static {
     56         sUriMatcher.addURI(ImageContract.AUTHORITY, "images", IMAGES);
     57         sUriMatcher.addURI(ImageContract.AUTHORITY, "images/#", IMAGE_ID);
     58     }
     59 
     60     // Indicated how many same images are going to be written as dummy images
     61     private static final int REPEAT_COUNT_WRITE_FILES = 10;
     62 
     63     private File mBaseDir;
     64 
     65     @Override
     66     public boolean onCreate() {
     67         Log.d(TAG, "onCreate");
     68 
     69         Context context = getContext();
     70         if (context == null) {
     71             return false;
     72         }
     73         mBaseDir = context.getFilesDir();
     74         writeDummyFilesToStorage(context);
     75 
     76         return true;
     77     }
     78 
     79     @Nullable
     80     @Override
     81     public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s,
     82             @Nullable String[] strings1, @Nullable String s1) {
     83         throw new UnsupportedOperationException();
     84     }
     85 
     86     @Override
     87     public Cursor query(Uri uri, String[] projection, Bundle queryArgs,
     88             CancellationSignal cancellationSignal) {
     89         int match = sUriMatcher.match(uri);
     90         // We only support a query for multiple images, return null for other form of queries
     91         // including a query for a single image.
     92         switch (match) {
     93             case IMAGES:
     94                 break;
     95             default:
     96                 return null;
     97         }
     98         MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
     99 
    100         File[] files = mBaseDir.listFiles();
    101         int offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0);
    102         int limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE);
    103         Log.d(TAG, "queryChildDocuments with Bundle, Uri: " +
    104                 uri + ", offset: " + offset + ", limit: " + limit);
    105         if (offset < 0) {
    106             throw new IllegalArgumentException("Offset must not be less than 0");
    107         }
    108         if (limit < 0) {
    109             throw new IllegalArgumentException("Limit must not be less than 0");
    110         }
    111 
    112         if (offset >= files.length) {
    113             return result;
    114         }
    115 
    116         for (int i = offset, maxIndex = Math.min(offset + limit, files.length); i < maxIndex; i++) {
    117             includeFile(result, files[i]);
    118         }
    119 
    120         Bundle bundle = new Bundle();
    121         bundle.putInt(ContentResolver.EXTRA_TOTAL_SIZE, files.length);
    122         String[] honoredArgs = new String[2];
    123         int size = 0;
    124         if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
    125             honoredArgs[size++] = ContentResolver.QUERY_ARG_OFFSET;
    126         }
    127         if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
    128             honoredArgs[size++] = ContentResolver.QUERY_ARG_LIMIT;
    129         }
    130         if (size != honoredArgs.length) {
    131             honoredArgs = Arrays.copyOf(honoredArgs, size);
    132         }
    133         bundle.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, honoredArgs);
    134         result.setExtras(bundle);
    135         return result;
    136     }
    137 
    138     @Nullable
    139     @Override
    140     public String getType(@NonNull Uri uri) {
    141         int match = sUriMatcher.match(uri);
    142         switch (match) {
    143             case IMAGES:
    144                 return "vnd.android.cursor.dir/images";
    145             case IMAGE_ID:
    146                 return "vnd.android.cursor.item/images";
    147             default:
    148                 throw new IllegalArgumentException(String.format("Unknown URI: %s", uri));
    149         }
    150     }
    151 
    152     @Nullable
    153     @Override
    154     public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
    155         throw new UnsupportedOperationException();
    156     }
    157 
    158     @Override
    159     public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
    160         throw new UnsupportedOperationException();
    161     }
    162 
    163     @Override
    164     public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s,
    165             @Nullable String[] strings) {
    166         throw new UnsupportedOperationException();
    167     }
    168 
    169     private static String[] resolveDocumentProjection(String[] projection) {
    170         return projection != null ? projection : ImageContract.PROJECTION_ALL;
    171     }
    172 
    173     /**
    174      * Add a representation of a file to a cursor.
    175      *
    176      * @param result the cursor to modify
    177      * @param file   the File object representing the desired file (may be null if given docID)
    178      */
    179     private void includeFile(MatrixCursor result, File file) {
    180         MatrixCursor.RowBuilder row = result.newRow();
    181         row.add(ImageContract.Columns.DISPLAY_NAME, file.getName());
    182         row.add(ImageContract.Columns.SIZE, file.length());
    183         row.add(ImageContract.Columns.ABSOLUTE_PATH, file.getAbsolutePath());
    184     }
    185 
    186     /**
    187      * Preload sample files packaged in the apk into the internal storage directory.  This is a
    188      * dummy function specific to this demo.  The MyCloud mock cloud service doesn't actually
    189      * have a backend, so it simulates by reading content from the device's internal storage.
    190      */
    191     private void writeDummyFilesToStorage(Context context) {
    192         if (mBaseDir.list().length > 0) {
    193             return;
    194         }
    195 
    196         int[] imageResIds = getResourceIdArray(context, R.array.image_res_ids);
    197         for (int i = 0; i < REPEAT_COUNT_WRITE_FILES; i++) {
    198             for (int resId : imageResIds) {
    199                 writeFileToInternalStorage(context, resId, "-" + i + ".jpeg");
    200             }
    201         }
    202     }
    203 
    204     /**
    205      * Write a file to internal storage.  Used to set up our dummy "cloud server".
    206      *
    207      * @param context   the Context
    208      * @param resId     the resource ID of the file to write to internal storage
    209      * @param extension the file extension (ex. .png, .mp3)
    210      */
    211     private void writeFileToInternalStorage(Context context, int resId, String extension) {
    212         InputStream ins = context.getResources().openRawResource(resId);
    213         int size;
    214         byte[] buffer = new byte[1024];
    215         try {
    216             String filename = context.getResources().getResourceEntryName(resId) + extension;
    217             FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
    218             while ((size = ins.read(buffer, 0, 1024)) >= 0) {
    219                 fos.write(buffer, 0, size);
    220             }
    221             ins.close();
    222             fos.write(buffer);
    223             fos.close();
    224 
    225         } catch (IOException e) {
    226             throw new RuntimeException(e);
    227         }
    228     }
    229 
    230     private int[] getResourceIdArray(Context context, int arrayResId) {
    231         TypedArray ar = context.getResources().obtainTypedArray(arrayResId);
    232         int len = ar.length();
    233         int[] resIds = new int[len];
    234         for (int i = 0; i < len; i++) {
    235             resIds[i] = ar.getResourceId(i, 0);
    236         }
    237         ar.recycle();
    238         return resIds;
    239     }
    240 }
    241