Home | History | Annotate | Download | only in com.example.android.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.*
     20 import android.database.Cursor
     21 import android.database.MatrixCursor
     22 import android.net.Uri
     23 import android.os.Bundle
     24 import android.os.CancellationSignal
     25 import android.util.Log
     26 import java.io.File
     27 import java.io.IOException
     28 
     29 /**
     30  * ContentProvider that demonstrates how the paging support works introduced in Android O.
     31  * This class fetches the images from the local storage but the storage could be
     32  * other locations such as a remote server.
     33  */
     34 class ImageProvider : ContentProvider() {
     35 
     36     private var mBaseDir: File? = null
     37 
     38     override fun onCreate(): Boolean {
     39         Log.d(TAG, "onCreate")
     40 
     41         val context = context ?: return false
     42         mBaseDir = context.filesDir
     43         writeDummyFilesToStorage(context)
     44 
     45         return true
     46     }
     47 
     48     override fun query(uri: Uri, strings: Array<String>?, s: String?,
     49                        strings1: Array<String>?, s1: String?): Cursor? {
     50         throw UnsupportedOperationException()
     51     }
     52 
     53     override fun query(uri: Uri, projection: Array<String>?, queryArgs: Bundle,
     54                        cancellationSignal: CancellationSignal?): Cursor? {
     55         val match = sUriMatcher.match(uri)
     56         // We only support a query for multiple images, return null for other form of queries
     57         // including a query for a single image.
     58         when (match) {
     59             IMAGES -> {
     60             }
     61             else -> return null
     62         }
     63         val result = MatrixCursor(resolveDocumentProjection(projection))
     64 
     65         val files = mBaseDir!!.listFiles()
     66         val offset = queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0)
     67         val limit = queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE)
     68         Log.d(TAG, "queryChildDocuments with Bundle, Uri: " +
     69                 uri + ", offset: " + offset + ", limit: " + limit)
     70         if (offset < 0) {
     71             throw IllegalArgumentException("Offset must not be less than 0")
     72         }
     73         if (limit < 0) {
     74             throw IllegalArgumentException("Limit must not be less than 0")
     75         }
     76 
     77         if (offset >= files.size) {
     78             return result
     79         }
     80 
     81         val maxIndex = Math.min(offset + limit, files.size)
     82         for (i in offset..maxIndex) {
     83             includeFile(result, files[i])
     84         }
     85 
     86         val bundle = constructExtras(queryArgs, files)
     87         result.extras = bundle
     88         return result
     89     }
     90 
     91     private fun constructExtras(queryArgs: Bundle, files: Array<File>): Bundle {
     92         val bundle = Bundle()
     93         bundle.putInt(ContentResolver.EXTRA_TOTAL_SIZE, files.size)
     94         var size = 0
     95         if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
     96             size++
     97         }
     98         if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
     99             size++
    100         }
    101         if (size > 0) {
    102             val honoredArgs = arrayOfNulls<String>(size)
    103             var index = 0
    104             if (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET)) {
    105                 honoredArgs[index++] = ContentResolver.QUERY_ARG_OFFSET
    106             }
    107             if (queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)) {
    108                 honoredArgs[index++] = ContentResolver.QUERY_ARG_LIMIT
    109             }
    110             bundle.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, honoredArgs)
    111         }
    112         return bundle
    113     }
    114 
    115     override fun getType(uri: Uri): String? {
    116         val match = sUriMatcher.match(uri)
    117         when (match) {
    118             IMAGES -> return "vnd.android.cursor.dir/images"
    119             IMAGE_ID -> return "vnd.android.cursor.item/images"
    120             else -> throw IllegalArgumentException(String.format("Unknown URI: %s", uri))
    121         }
    122     }
    123 
    124     override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
    125         throw UnsupportedOperationException()
    126     }
    127 
    128     override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
    129         throw UnsupportedOperationException()
    130     }
    131 
    132     override fun update(uri: Uri, contentValues: ContentValues?, s: String?,
    133                         strings: Array<String>?): Int {
    134         throw UnsupportedOperationException()
    135     }
    136 
    137     /**
    138      * Add a representation of a file to a cursor.
    139 
    140      * @param result the cursor to modify
    141      * *
    142      * @param file   the File object representing the desired file (may be null if given docID)
    143      */
    144     private fun includeFile(result: MatrixCursor, file: File) {
    145         val row = result.newRow()
    146         row.add(ImageContract.Columns.DISPLAY_NAME, file.name)
    147         row.add(ImageContract.Columns.SIZE, file.length())
    148         row.add(ImageContract.Columns.ABSOLUTE_PATH, file.absolutePath)
    149     }
    150 
    151     /**
    152      * Preload sample files packaged in the apk into the internal storage directory.  This is a
    153      * dummy function specific to this demo.  The MyCloud mock cloud service doesn't actually
    154      * have a backend, so it simulates by reading content from the device's internal storage.
    155      */
    156     private fun writeDummyFilesToStorage(context: Context) {
    157         if (mBaseDir!!.list().isNotEmpty()) {
    158             return
    159         }
    160 
    161         val imageResIds = getResourceIdArray(context, R.array.image_res_ids)
    162         for (i in 0..REPEAT_COUNT_WRITE_FILES - 1) {
    163             for (resId in imageResIds) {
    164                 writeFileToInternalStorage(context, resId, "-$i.jpeg")
    165             }
    166         }
    167     }
    168 
    169     /**
    170      * Write a file to internal storage.  Used to set up our dummy "cloud server".
    171 
    172      * @param context   the Context
    173      * *
    174      * @param resId     the resource ID of the file to write to internal storage
    175      * *
    176      * @param extension the file extension (ex. .png, .mp3)
    177      */
    178     private fun writeFileToInternalStorage(context: Context, resId: Int, extension: String) {
    179         val ins = context.resources.openRawResource(resId)
    180         val buffer = ByteArray(1024)
    181         try {
    182             val filename = context.resources.getResourceEntryName(resId) + extension
    183             val fos = context.openFileOutput(filename, Context.MODE_PRIVATE)
    184             while (true) {
    185                 val size = ins.read(buffer, 0, 1024)
    186                 if (size < 0) break
    187                 fos.write(buffer, 0, size)
    188             }
    189             ins.close()
    190             fos.write(buffer)
    191             fos.close()
    192         } catch (e: IOException) {
    193             throw RuntimeException(e)
    194         }
    195 
    196     }
    197 
    198     private fun getResourceIdArray(context: Context, arrayResId: Int): IntArray {
    199         val ar = context.resources.obtainTypedArray(arrayResId)
    200         val len = ar.length()
    201         val resIds = IntArray(len)
    202         for (i in 0..len - 1) {
    203             resIds[i] = ar.getResourceId(i, 0)
    204         }
    205         ar.recycle()
    206         return resIds
    207     }
    208 
    209     companion object {
    210 
    211         private val TAG = "ImageDocumentsProvider"
    212 
    213         private val IMAGES = 1
    214 
    215         private val IMAGE_ID = 2
    216 
    217         private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH)
    218 
    219         init {
    220             sUriMatcher.addURI(ImageContract.AUTHORITY, "images", IMAGES)
    221             sUriMatcher.addURI(ImageContract.AUTHORITY, "images/#", IMAGE_ID)
    222         }
    223 
    224         // Indicated how many same images are going to be written as dummy images
    225         private val REPEAT_COUNT_WRITE_FILES = 10
    226 
    227         private fun resolveDocumentProjection(projection: Array<String>?): Array<String> {
    228             return projection ?: ImageContract.PROJECTION_ALL
    229         }
    230     }
    231 }
    232