content: /*
   * 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 dummy 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
       * dummy 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 dummy "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;
      }
  }
  