All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowParcelFileDescriptor Maven / Gradle / Ivy

The newest version!
package org.robolectric.shadows;

import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.system.Os;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;

@Implements(ParcelFileDescriptor.class)
@SuppressLint("NewApi")
public class ShadowParcelFileDescriptor {
  // TODO: consider removing this shadow in favor of shadowing file operations at the libcore.os
  // level
  private static final String PIPE_TMP_DIR = "ShadowParcelFileDescriptor";
  private static final String PIPE_FILE_NAME = "pipe";
  private static final Map filesInTransitById =
      Collections.synchronizedMap(new HashMap<>());
  private static final AtomicInteger NEXT_FILE_ID = new AtomicInteger();

  private RandomAccessFile file;
  private int fileIdPledgedOnClose; // != 0 if 'file' was written to a Parcel.
  private int lazyFileId; // != 0 if we were created from a Parcel but don't own a 'file' yet.
  private boolean closed;
  private Handler handler;
  private ParcelFileDescriptor.OnCloseListener onCloseListener;

  @RealObject private ParcelFileDescriptor realParcelFd;
  @RealObject private ParcelFileDescriptor realObject;

  @Implementation
  protected static void __staticInitializer__() {
    Shadow.directInitialize(ParcelFileDescriptor.class);
    ReflectionHelpers.setStaticField(
        ParcelFileDescriptor.class, "CREATOR", ShadowParcelFileDescriptor.CREATOR);
  }

  @Resetter
  public static void reset() {
    filesInTransitById.clear();
  }

  @Implementation
  protected void __constructor__(ParcelFileDescriptor wrapped) {
    invokeConstructor(
        ParcelFileDescriptor.class, realObject, from(ParcelFileDescriptor.class, wrapped));
    if (wrapped != null) {
      ShadowParcelFileDescriptor shadowParcelFileDescriptor = Shadow.extract(wrapped);
      this.file = shadowParcelFileDescriptor.file;
    }
  }

  static final Parcelable.Creator CREATOR =
      new Parcelable.Creator() {
        @Override
        public ParcelFileDescriptor createFromParcel(Parcel source) {
          int fileId = source.readInt();
          ParcelFileDescriptor result = newParcelFileDescriptor();
          ShadowParcelFileDescriptor shadowResult = Shadow.extract(result);
          shadowResult.lazyFileId = fileId;
          return result;
        }

        @Override
        public ParcelFileDescriptor[] newArray(int size) {
          return new ParcelFileDescriptor[size];
        }
      };

  @Implementation
  protected void writeToParcel(Parcel out, int flags) {
    if (fileIdPledgedOnClose == 0) {
      fileIdPledgedOnClose = (lazyFileId != 0) ? lazyFileId : NEXT_FILE_ID.incrementAndGet();
    }
    out.writeInt(fileIdPledgedOnClose);

    if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
      try {
        close();
      } catch (IOException e) {
        // Close "quietly", just like Android does.
      }
    }
  }

  private static ParcelFileDescriptor newParcelFileDescriptor() {
    return new ParcelFileDescriptor(new FileDescriptor());
  }

  @Implementation
  protected static ParcelFileDescriptor open(File file, int mode) throws FileNotFoundException {
    ParcelFileDescriptor pfd = newParcelFileDescriptor();
    ShadowParcelFileDescriptor shadowParcelFileDescriptor = Shadow.extract(pfd);
    shadowParcelFileDescriptor.file = new RandomAccessFile(file, getFileMode(mode));
    if ((mode & ParcelFileDescriptor.MODE_TRUNCATE) != 0) {
      try {
        shadowParcelFileDescriptor.file.setLength(0);
      } catch (IOException ioe) {
        FileNotFoundException fnfe = new FileNotFoundException("Unable to truncate");
        fnfe.initCause(ioe);
        throw fnfe;
      }
    }
    if ((mode & ParcelFileDescriptor.MODE_APPEND) != 0) {
      try {
        shadowParcelFileDescriptor.file.seek(shadowParcelFileDescriptor.file.length());
      } catch (IOException ioe) {
        FileNotFoundException fnfe = new FileNotFoundException("Unable to append");
        fnfe.initCause(ioe);
        throw fnfe;
      }
    }
    return pfd;
  }

  @Implementation
  protected static ParcelFileDescriptor open(
      File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener)
      throws IOException {
    if (handler == null) {
      throw new IllegalArgumentException("Handler must not be null");
    }
    if (listener == null) {
      throw new IllegalArgumentException("Listener must not be null");
    }
    ParcelFileDescriptor pfd = open(file, mode);
    ShadowParcelFileDescriptor shadowParcelFileDescriptor = Shadow.extract(pfd);
    shadowParcelFileDescriptor.handler = handler;
    shadowParcelFileDescriptor.onCloseListener = listener;
    return pfd;
  }

  private static String getFileMode(int mode) {
    if ((mode & ParcelFileDescriptor.MODE_CREATE) != 0) {
      return "rw";
    }
    switch (mode & ParcelFileDescriptor.MODE_READ_WRITE) {
      case ParcelFileDescriptor.MODE_READ_ONLY:
        return "r";
      case ParcelFileDescriptor.MODE_WRITE_ONLY:
      case ParcelFileDescriptor.MODE_READ_WRITE:
        return "rw";
    }
    return "rw";
  }

  @Implementation
  protected static ParcelFileDescriptor[] createPipe() throws IOException {
    File file =
        new File(
            RuntimeEnvironment.getTempDirectory().createIfNotExists(PIPE_TMP_DIR).toFile(),
            PIPE_FILE_NAME + "-" + UUID.randomUUID());
    if (!file.createNewFile()) {
      throw new IOException("Cannot create pipe file: " + file.getAbsolutePath());
    }
    ParcelFileDescriptor readSide = open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    ParcelFileDescriptor writeSide = open(file, ParcelFileDescriptor.MODE_READ_WRITE);
    file.deleteOnExit();
    return new ParcelFileDescriptor[] {readSide, writeSide};
  }

  @Implementation
  protected static ParcelFileDescriptor[] createReliablePipe() throws IOException {
    return createPipe();
  }

  private RandomAccessFile getFile() {
    if (file == null && lazyFileId != 0) {
      file = filesInTransitById.remove(lazyFileId);
      lazyFileId = 0;
      if (file == null) {
        throw new FileDescriptorFromParcelUnavailableException();
      }
    }
    return file;
  }

  @Implementation
  protected FileDescriptor getFileDescriptor() {
    try {
      RandomAccessFile file = getFile();
      if (file != null) {
        return file.getFD();
      }
      return reflector(ParcelFileDescriptorReflector.class, realParcelFd).getFileDescriptor();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Implementation
  protected long getStatSize() {
    try {
      return getFile().length();
    } catch (IOException e) {
      // This might occur when the file object has been closed.
      return -1;
    }
  }

  @Implementation
  protected int getFd() {
    if (closed) {
      throw new IllegalStateException("Already closed");
    }

    try {
      return ReflectionHelpers.getField(getFile().getFD(), "fd");
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Implementation
  protected void close() throws IOException {
    // Act this status check the same as real close operation in AOSP.
    if (closed) {
      return;
    }

    if (file != null) {
      if (fileIdPledgedOnClose != 0) {
        // Don't actually close 'file'! Instead stash it where our Parcel reader(s) can find it.
        filesInTransitById.put(fileIdPledgedOnClose, file);
        fileIdPledgedOnClose = 0;

        // Replace this.file with a dummy instance to be close()d below. This leaves instances that
        // have been written to Parcels and never-parceled ones in exactly the same state.
        File tempFile = Files.createTempFile(null, null).toFile();
        file = new RandomAccessFile(tempFile, "rw");
        tempFile.delete();
      }
      file.close();
    }

    reflector(ParcelFileDescriptorReflector.class, realParcelFd).close();
    closed = true;
    if (handler != null && onCloseListener != null) {
      handler.post(() -> onCloseListener.onClose(null));
    }
  }

  @Implementation
  protected ParcelFileDescriptor dup() throws IOException {
    return new ParcelFileDescriptor(realParcelFd);
  }

  /**
   * Support shadowing of the static method {@link ParcelFileDescriptor#dup}.
   *
   * 

The real implementation calls {@link Os#fcntlInt} in order to duplicate the FileDescriptor * in native code. This cannot be simulated on the JVM without the use of native code. */ @Implementation protected static ParcelFileDescriptor dup(FileDescriptor fileDescriptor) throws IOException { File dupFile = new File( RuntimeEnvironment.getTempDirectory().createIfNotExists(PIPE_TMP_DIR).toFile(), "dupfd-" + UUID.randomUUID()); // Duplicate the file represented by the file descriptor. Note that neither file streams should // be closed because doing so will invalidate the corresponding file descriptor. FileInputStream fileInputStream = new FileInputStream(fileDescriptor); FileOutputStream fileOutputStream = new FileOutputStream(dupFile); FileChannel sourceChannel = fileInputStream.getChannel(); long originalPosition = sourceChannel.position(); sourceChannel.position(0); ByteStreams.copy(fileInputStream, fileOutputStream); sourceChannel.position(originalPosition); RandomAccessFile randomAccessFile = new RandomAccessFile(dupFile, "rw"); return new ParcelFileDescriptor(randomAccessFile.getFD()); } static class FileDescriptorFromParcelUnavailableException extends RuntimeException { FileDescriptorFromParcelUnavailableException() { super( "ParcelFileDescriptors created from a Parcel refer to the same content as the" + " ParcelFileDescriptor that originally wrote it. Robolectric has the unfortunate" + " limitation that only one of these instances can be functional at a time. Try" + " closing the original ParcelFileDescriptor before using any duplicates created via" + " the Parcelable API."); } } @ForType(ParcelFileDescriptor.class) interface ParcelFileDescriptorReflector { @Direct void close(); @Direct FileDescriptor getFileDescriptor(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy