Add @CorePlatformApi for customization of Libcore.os by frameworks.

Provide a @CorePlatformApi to replace the default implementation of
libcore.io.Os, held in the static (internal) field libcore.io.Libcore.os,
which should not otherwise be updated.

This allows callers to safely update the Os implementation even when
there may be concurrent callers:

while (true) {
    Os os = Os.getDefault();
    Os wrapper = new ForwardingOs(os) {
        // ...override/customize some methods...
    };

    if (Os.compareAndSetDefault(os, wrapper)) {
        break;
    }
}

Initially, ForwardingOs is the only Os implementation that can be
used from outside libcore, and open() is the only method that can be
overridden/customized; the plan is that customization of additional
methods will be supported in future.

Existing code that directly reads the field Libcore.os continues to
be safe / correct. Writing directly to that field continues to be
not be supported.

Bug: 115503977
Test: CtsLibcoreTestCases
Test: OsTest
Test: Checked that locally adding code similar to the above to
      frameworks' ActivityThread.main() compiles.
Change-Id: I49d9171fb869664b0e72c4cbc055d168c101c669
diff --git a/luni/src/main/java/libcore/io/ForwardingOs.java b/luni/src/main/java/libcore/io/ForwardingOs.java
index f64755e..f8c8b45 100644
--- a/luni/src/main/java/libcore/io/ForwardingOs.java
+++ b/luni/src/main/java/libcore/io/ForwardingOs.java
@@ -48,9 +48,11 @@
 /**
  * Subclass this if you want to override some {@link Os} methods but otherwise delegate.
  */
+@libcore.api.CorePlatformApi
 public class ForwardingOs implements Os {
     private final Os os;
 
+    @libcore.api.CorePlatformApi
     protected ForwardingOs(Os os) {
         this.os = Objects.requireNonNull(os);
     }
@@ -145,6 +147,7 @@
     public void msync(long address, long byteCount, int flags) throws ErrnoException { os.msync(address, byteCount, flags); }
     public void munlock(long address, long byteCount) throws ErrnoException { os.munlock(address, byteCount); }
     public void munmap(long address, long byteCount) throws ErrnoException { os.munmap(address, byteCount); }
+    @libcore.api.CorePlatformApi
     public FileDescriptor open(String path, int flags, int mode) throws ErrnoException { return os.open(path, flags, mode); }
     public FileDescriptor[] pipe2(int flags) throws ErrnoException { return os.pipe2(flags); }
     public int poll(StructPollfd[] fds, int timeoutMs) throws ErrnoException { return os.poll(fds, timeoutMs); }
diff --git a/luni/src/main/java/libcore/io/Libcore.java b/luni/src/main/java/libcore/io/Libcore.java
index b111608..b8af244 100644
--- a/luni/src/main/java/libcore/io/Libcore.java
+++ b/luni/src/main/java/libcore/io/Libcore.java
@@ -16,6 +16,8 @@
 
 package libcore.io;
 
+import java.util.Objects;
+
 /** @hide */
 @libcore.api.CorePlatformApi
 @libcore.api.IntraCoreApi
@@ -31,7 +33,31 @@
 
     /**
      * Access to syscalls with helpful checks/guards.
+     * For read access only; the only supported way to update this field is via
+     * {@link #compareAndSetOs}.
      */
     @libcore.api.IntraCoreApi
-    public static Os os = new BlockGuardOs(rawOs);
+    public static volatile Os os = new BlockGuardOs(rawOs);
+
+    public static Os getOs() {
+        return os;
+    }
+
+    /**
+     * Updates {@link #os} if {@code os == expect}. The update is atomic with
+     * respect to other invocations of this method.
+     */
+    public static boolean compareAndSetOs(Os expect, Os update) {
+        Objects.requireNonNull(update);
+        if (os != expect) {
+            return false;
+        }
+        synchronized (Libcore.class) {
+            boolean result = (os == expect);
+            if (result) {
+                os = update;
+            }
+            return result;
+        }
+    }
 }
diff --git a/luni/src/main/java/libcore/io/Os.java b/luni/src/main/java/libcore/io/Os.java
index bdebfce..a55f165 100644
--- a/luni/src/main/java/libcore/io/Os.java
+++ b/luni/src/main/java/libcore/io/Os.java
@@ -45,6 +45,7 @@
 import java.nio.ByteBuffer;
 
 /** @hide */
+@libcore.api.CorePlatformApi
 @libcore.api.IntraCoreApi
 public interface Os {
     public FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException;
@@ -190,4 +191,25 @@
     public int write(FileDescriptor fd, ByteBuffer buffer) throws ErrnoException, InterruptedIOException;
     public int write(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount) throws ErrnoException, InterruptedIOException;
     public int writev(FileDescriptor fd, Object[] buffers, int[] offsets, int[] byteCounts) throws ErrnoException, InterruptedIOException;
+
+    /**
+     * Atomically sets the system's default {@link Os} implementation to be
+     * {@code update} if the current value {@code == expect}.
+     *
+     * @param expect the expected value.
+     * @param update the new value to set; must not be null.
+     * @return whether the update was successful.
+     */
+    @libcore.api.CorePlatformApi
+    public static boolean compareAndSetDefault(Os expect, Os update) {
+        return Libcore.compareAndSetOs(expect, update);
+    }
+
+    /**
+     * @return the system's default {@link Os} implementation currently in use.
+     */
+    @libcore.api.CorePlatformApi
+    public static Os getDefault() {
+        return Libcore.getOs();
+    }
 }
diff --git a/luni/src/test/java/libcore/libcore/io/BlockGuardOsTest.java b/luni/src/test/java/libcore/libcore/io/BlockGuardOsTest.java
index aea709f..91d7c3a 100644
--- a/luni/src/test/java/libcore/libcore/io/BlockGuardOsTest.java
+++ b/luni/src/test/java/libcore/libcore/io/BlockGuardOsTest.java
@@ -28,6 +28,7 @@
 import android.system.StructAddrinfo;
 
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.net.InetAddress;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -215,7 +216,11 @@
 
         // Verify that all the methods in libcore.io.Os should either be overridden in BlockGuardOs
         // or else they should be in the "methodsNotRequiredBlockGuardCheckSet".
+        // We don't care about static methods because they can't be overridden.
         for (Method method : Os.class.getDeclaredMethods()) {
+            if (Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
             String methodSignature = method.toString();
             String methodNameAndParameters = getMethodNameAndParameters(methodSignature);
             if (!methodsNotRequiredBlockGuardCheckSet.contains(methodNameAndParameters) &&
diff --git a/luni/src/test/java/libcore/libcore/io/OsTest.java b/luni/src/test/java/libcore/libcore/io/OsTest.java
index a251be8..e1dafe1 100644
--- a/luni/src/test/java/libcore/libcore/io/OsTest.java
+++ b/luni/src/test/java/libcore/libcore/io/OsTest.java
@@ -51,9 +51,14 @@
 import java.util.concurrent.atomic.AtomicReference;
 import junit.framework.TestCase;
 
+import libcore.io.BlockGuardOs;
+import libcore.io.ForwardingOs;
 import libcore.io.IoBridge;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
+import libcore.io.Os;
+
+import org.mockito.Mockito;
 
 import static android.system.OsConstants.*;
 import static libcore.libcore.io.OsTest.SendFileImpl.ANDROID_SYSTEM_OS_INT64_REF;
@@ -1045,4 +1050,52 @@
     } catch (NullPointerException expected) {
     }
   }
+
+  public void testGetDefault_instanceofBlockguardOs() {
+    Os os = Os.getDefault();
+    assertTrue(os.getClass().toString(), os instanceof BlockGuardOs);
+  }
+
+  public void testCompareAndSetDefault_success() throws Exception {
+    Os defaultOs = Os.getDefault();
+    Os mockOs = Mockito.mock(Os.class);
+    try {
+      // There shouldn't be any concurrent threads replacing the default Os.
+      assertTrue(Os.compareAndSetDefault(defaultOs, mockOs));
+      assertSame(mockOs, Os.getDefault());
+
+      // Calls to android.system.Os should now reach our custom Os instance.
+      android.system.Os.rename("/old/path", "/new/path");
+      Mockito.verify(mockOs).rename("/old/path", "/new/path");
+    } finally {
+      assertTrue(Os.compareAndSetDefault(mockOs, defaultOs));
+      assertSame(defaultOs, Os.getDefault());
+    }
+  }
+
+  public void testCompareandSetDefault_null() {
+    Os defaultOs = Os.getDefault();
+    // update == null is not allowed
+    try {
+      Os.compareAndSetDefault(defaultOs, null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+    // value hasn't changed
+    assertSame(defaultOs, Os.getDefault());
+
+  }
+
+  public void testCompareAndSetDefault_comparisonFailure() throws Exception {
+    Os defaultOs = Os.getDefault();
+    Os otherOs = new ForwardingOs(defaultOs) { };
+
+    // current default is non-null, but expect is null
+    assertFalse(Os.compareAndSetDefault(null, otherOs));
+    assertSame(defaultOs, Os.getDefault());
+
+    // current default != expect (both non-null)
+    assertFalse(Os.compareAndSetDefault(otherOs, otherOs));
+    assertSame(defaultOs, Os.getDefault());
+  }
 }
diff --git a/mmodules/intracoreapi/api/intra/current-api.txt b/mmodules/intracoreapi/api/intra/current-api.txt
index ee4aa88..d31f02b 100644
--- a/mmodules/intracoreapi/api/intra/current-api.txt
+++ b/mmodules/intracoreapi/api/intra/current-api.txt
@@ -70,7 +70,7 @@
   }
 
   public final class Libcore {
-    field public static libcore.io.Os os;
+    field public static volatile libcore.io.Os os;
   }
 
   public abstract interface Os {