/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.memory;

import android.annotation.TargetApi;
import android.os.SharedMemory;
import android.system.ErrnoException;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.facebook.common.internal.Preconditions;
import java.io.Closeable;
import java.nio.ByteBuffer;
import javax.annotation.Nullable;

/** Wrapper around chunk of ashmem memory. */
@TargetApi(27)
public class AshmemMemoryChunk implements MemoryChunk, Closeable {
  private static final String TAG = "AshmemMemoryChunk";

  private @Nullable SharedMemory mSharedMemory;
  private @Nullable ByteBuffer mByteBuffer;

  /** Unique identifier of the chunk */
  private final long mId;

  public AshmemMemoryChunk(final int size) {
    Preconditions.checkArgument(size > 0);
    try {
      mSharedMemory = SharedMemory.create(TAG, size);
      mByteBuffer = mSharedMemory.mapReadWrite();
    } catch (ErrnoException e) {
      throw new RuntimeException("Fail to create AshmemMemory", e);
    }
    mId = System.identityHashCode(this);
  }

  @VisibleForTesting
  public AshmemMemoryChunk() {
    mSharedMemory = null;
    mByteBuffer = null;
    mId = System.identityHashCode(this);
  }

  @Override
  public synchronized void close() {
    if (!isClosed()) {
      mSharedMemory.unmap(mByteBuffer);
      mSharedMemory.close();
      mByteBuffer = null;
      mSharedMemory = null;
    }
  }

  @Override
  public synchronized boolean isClosed() {
    return mByteBuffer == null || mSharedMemory == null;
  }

  @Override
  public int getSize() {
    Preconditions.checkState(!isClosed());
    return mSharedMemory.getSize();
  }

  @Override
  public synchronized int write(
      final int memoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) {
    Preconditions.checkNotNull(byteArray);
    Preconditions.checkState(!isClosed());
    final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, getSize());
    MemoryChunkUtil.checkBounds(
        memoryOffset, byteArray.length, byteArrayOffset, actualCount, getSize());
    mByteBuffer.position(memoryOffset);
    mByteBuffer.put(byteArray, byteArrayOffset, actualCount);
    return actualCount;
  }

  @Override
  public synchronized int read(
      final int memoryOffset, final byte[] byteArray, final int byteArrayOffset, final int count) {
    Preconditions.checkNotNull(byteArray);
    Preconditions.checkState(!isClosed());
    final int actualCount = MemoryChunkUtil.adjustByteCount(memoryOffset, count, getSize());
    MemoryChunkUtil.checkBounds(
        memoryOffset, byteArray.length, byteArrayOffset, actualCount, getSize());
    mByteBuffer.position(memoryOffset);
    mByteBuffer.get(byteArray, byteArrayOffset, actualCount);
    return actualCount;
  }

  @Override
  public synchronized byte read(final int offset) {
    Preconditions.checkState(!isClosed());
    Preconditions.checkArgument(offset >= 0);
    Preconditions.checkArgument(offset < getSize());
    return mByteBuffer.get(offset);
  }

  @Override
  public long getNativePtr() {
    throw new UnsupportedOperationException("Cannot get the pointer of an  AshmemMemoryChunk");
  }

  @Override
  @Nullable
  public ByteBuffer getByteBuffer() {
    return mByteBuffer;
  }

  @Override
  public long getUniqueId() {
    return mId;
  }

  @Override
  public void copy(
      final int offset, final MemoryChunk other, final int otherOffset, final int count) {
    Preconditions.checkNotNull(other);
    // This implementation acquires locks on this and other objects and then delegates to
    // doCopy which does actual copy. In order to avoid deadlocks we have to establish some linear
    // order on all AshmemMemoryChunks and acquire locks according to this order. In order
    // to do that, we use unique ids.
    // So we have to address 3 cases:

    // Case 1: other buffer equals this buffer, id comparison
    if (other.getUniqueId() == getUniqueId()) {
      // we do not allow copying to the same address
      // lets log warning and not copy
      Log.w(
          TAG,
          "Copying from AshmemMemoryChunk "
              + Long.toHexString(getUniqueId())
              + " to AshmemMemoryChunk "
              + Long.toHexString(other.getUniqueId())
              + " which are the same ");
      Preconditions.checkArgument(false);
    }

    // Case 2: Other memory chunk id < this memory chunk id
    if (other.getUniqueId() < getUniqueId()) {
      synchronized (other) {
        synchronized (this) {
          doCopy(offset, other, otherOffset, count);
        }
      }
      return;
    }

    // Case 3: Other memory chunk id > this memory chunk id
    synchronized (this) {
      synchronized (other) {
        doCopy(offset, other, otherOffset, count);
      }
    }
  }

  /**
   * This does actual copy. It should be called only when we hold locks on both this and other
   * objects
   */
  private void doCopy(
      final int offset, final MemoryChunk other, final int otherOffset, final int count) {
    if (!(other instanceof AshmemMemoryChunk)) {
      throw new IllegalArgumentException("Cannot copy two incompatible MemoryChunks");
    }
    Preconditions.checkState(!isClosed());
    Preconditions.checkState(!other.isClosed());
    MemoryChunkUtil.checkBounds(offset, other.getSize(), otherOffset, count, getSize());
    mByteBuffer.position(offset);
    // ByteBuffer can't be null at this point
    other.getByteBuffer().position(otherOffset);
    // Recover the necessary part to be copied as a byte array.
    // This requires a copy, for now there is not a more efficient alternative.
    byte[] b = new byte[count];
    mByteBuffer.get(b, 0, count);
    other.getByteBuffer().put(b, 0, count);
  }
}

@@ -455,16 +455,63 @@
 itions;%0A
+import com.facebook.infer.annotation.Nullsafe;%0A
 import j
@@ -528,16 +528,16 @@
 seable;%0A
-
 import j
@@ -652,16 +652,47 @@
 Api(27)%0A
+@Nullsafe(Nullsafe.Mode.LOCAL)%0A
 public c
@@ -1541,32 +1541,36 @@
 osed()) %7B%0A      
+if (
 mSharedMemory.un
@@ -1570,14 +1570,68 @@
 mory
-.unmap
+ != null) %7B%0A        mSharedMemory.close();%0A      %7D%0A      if 
 (mBy
@@ -1638,26 +1638,36 @@
 teBuffer
-);
+ != null) %7B
 %0A      
-m
+  
 SharedMe
@@ -1667,32 +1667,51 @@
 haredMemory.
-close();
+unmap(mByteBuffer);%0A      %7D
 %0A      mByte
@@ -1941,33 +1941,37 @@
 ns.check
-State(!isClosed()
+NotNull(mSharedMemory
 );%0A    r
@@ -2217,33 +2217,35 @@
 ns.check
-State(!isClosed()
+NotNull(mByteBuffer
 );%0A    f
@@ -2788,33 +2788,35 @@
 ns.check
-State(!isClosed()
+NotNull(mByteBuffer
 );%0A    f
@@ -3351,24 +3351,69 @@
 getSize());%0A
+    Preconditions.checkNotNull(mByteBuffer);%0A
     return m
@@ -5619,32 +5619,32 @@
 e(!isClosed());%0A
-
     Precondition
@@ -5672,24 +5672,124 @@
 sClosed());%0A
+    Preconditions.checkNotNull(mByteBuffer);%0A    Preconditions.checkNotNull(other.getByteBuffer());%0A
     MemoryCh

Mark AshmemMemoryChunk