Mark ab/6881855 as merged

Bug: 172690556
Change-Id: Ie3693e3d815995ab72063ebc1dff471a8a740186
diff --git a/OWNERS b/OWNERS
index d3d3f16..db56bd5 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,5 @@
 dariofreni@google.com
 hackbod@google.com
+hansson@google.com
 narayan@google.com
 omakoto@google.com
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..f9190f7
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2020 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.
+//
+
+// For use by modules.
+java_library {
+    name: "modules-utils-build",
+    srcs: [
+        "src/**/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "29",
+    apex_available: ["//apex_available:anyapex"],
+}
diff --git a/build/src/com/android/modules/utils/build/SdkLevel.java b/build/src/com/android/modules/utils/build/SdkLevel.java
new file mode 100644
index 0000000..1f871dd
--- /dev/null
+++ b/build/src/com/android/modules/utils/build/SdkLevel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.android.modules.utils.build;
+
+import android.os.Build;
+
+/**
+ * Utility class to check SDK level.
+ *
+ * @hide
+ */
+public class SdkLevel {
+
+    private SdkLevel() {}
+
+    /** Return true iff the running Android SDK is at least "R". */
+    public static boolean isAtLeastR() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+    }
+
+    /**
+     * Returns true iff the running Android SDK is pre-release "S" or "T", built based on "R" SDK.
+     *
+     * If new SDK versions are added > R, then this method needs to be updated to recognise them
+     * (e.g. if we add SDK version for R-QPR,  the current implementation will not recognise
+     * pre-release "S" versions built on that).
+     */
+    public static boolean isAtLeastS() {
+        // TODO(b/170831689) This should check SDK_INT >= S once S sdk finalised. Note that removing the
+        // current conditions may lead to issues in mainlinefood (and possibly public beta?).
+
+        // While in development, builds will have R SDK_INT and "S" or "T" codename.
+        // We don't accept SDK_INT > R for now, since R and S may have non-consecutive values.
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R
+            && ("S".equals(Build.VERSION.CODENAME) || "T".equals(Build.VERSION.CODENAME))) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/os/Android.bp b/os/Android.bp
new file mode 100644
index 0000000..eb94960
--- /dev/null
+++ b/os/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2020 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.
+
+java_library {
+    name: "modules-utils-os",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+    sdk_version: "module_current",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
+}
\ No newline at end of file
diff --git a/os/src/com/android/modules/utils/BasicShellCommandHandler.java b/os/src/com/android/modules/utils/BasicShellCommandHandler.java
new file mode 100644
index 0000000..2efe391
--- /dev/null
+++ b/os/src/com/android/modules/utils/BasicShellCommandHandler.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2019 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.android.modules.utils;
+
+import android.util.Log;
+import android.os.Binder;
+
+import java.io.BufferedInputStream;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+/**
+ * Helper for implementing {@link Binder#onShellCommand Binder.onShellCommand}. This is meant to
+ * be copied into mainline modules, so this class must not use any hidden APIs.
+ *
+ * @hide
+ */
+public abstract class BasicShellCommandHandler {
+    protected static final String TAG = "ShellCommand";
+    protected static final boolean DEBUG = false;
+
+    private Binder mTarget;
+    private FileDescriptor mIn;
+    private FileDescriptor mOut;
+    private FileDescriptor mErr;
+    private String[] mArgs;
+
+    private String mCmd;
+    private int mArgPos;
+    private String mCurArgData;
+
+    private FileInputStream mFileIn;
+    private FileOutputStream mFileOut;
+    private FileOutputStream mFileErr;
+
+    private PrintWriter mOutPrintWriter;
+    private PrintWriter mErrPrintWriter;
+    private InputStream mInputStream;
+
+    public void init(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args, int firstArgPos) {
+        mTarget = target;
+        mIn = in;
+        mOut = out;
+        mErr = err;
+        mArgs = args;
+        mCmd = null;
+        mArgPos = firstArgPos;
+        mCurArgData = null;
+        mFileIn = null;
+        mFileOut = null;
+        mFileErr = null;
+        mOutPrintWriter = null;
+        mErrPrintWriter = null;
+        mInputStream = null;
+    }
+
+    public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
+            String[] args) {
+        String cmd;
+        int start;
+        if (args != null && args.length > 0) {
+            cmd = args[0];
+            start = 1;
+        } else {
+            cmd = null;
+            start = 0;
+        }
+        init(target, in, out, err, args, start);
+        mCmd = cmd;
+
+        if (DEBUG) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+            Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
+                    + " pid=" + Binder.getCallingPid());
+        }
+        int res = -1;
+        try {
+            res = onCommand(mCmd);
+            if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);
+        } catch (Throwable e) {
+            // Unlike usual calls, in this case if an exception gets thrown
+            // back to us we want to print it back in to the dump data, since
+            // that is where the caller expects all interesting information to
+            // go.
+            PrintWriter eout = getErrPrintWriter();
+            eout.println();
+            eout.println("Exception occurred while executing '" + mCmd + "':");
+            e.printStackTrace(eout);
+        } finally {
+            if (DEBUG) Log.d(TAG, "Flushing output streams on " + mTarget);
+            if (mOutPrintWriter != null) {
+                mOutPrintWriter.flush();
+            }
+            if (mErrPrintWriter != null) {
+                mErrPrintWriter.flush();
+            }
+            if (DEBUG) Log.d(TAG, "Sending command result on " + mTarget);
+        }
+        if (DEBUG) Log.d(TAG, "Finished command " + mCmd + " on " + mTarget);
+        return res;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the output stream.
+     */
+    public FileDescriptor getOutFileDescriptor() {
+        return mOut;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's output data stream.
+     */
+    public OutputStream getRawOutputStream() {
+        if (mFileOut == null) {
+            mFileOut = new FileOutputStream(mOut);
+        }
+        return mFileOut;
+    }
+
+    /**
+     * Return a PrintWriter for formatting output to {@link #getRawOutputStream()}.
+     */
+    public PrintWriter getOutPrintWriter() {
+        if (mOutPrintWriter == null) {
+            mOutPrintWriter = new PrintWriter(getRawOutputStream());
+        }
+        return mOutPrintWriter;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the error stream.
+     */
+    public FileDescriptor getErrFileDescriptor() {
+        return mErr;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's error output data stream.
+     */
+    public OutputStream getRawErrorStream() {
+        if (mFileErr == null) {
+            mFileErr = new FileOutputStream(mErr);
+        }
+        return mFileErr;
+    }
+
+    /**
+     * Return a PrintWriter for formatting output to {@link #getRawErrorStream()}.
+     */
+    public PrintWriter getErrPrintWriter() {
+        if (mErr == null) {
+            return getOutPrintWriter();
+        }
+        if (mErrPrintWriter == null) {
+            mErrPrintWriter = new PrintWriter(getRawErrorStream());
+        }
+        return mErrPrintWriter;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the input stream.
+     */
+    public FileDescriptor getInFileDescriptor() {
+        return mIn;
+    }
+
+    /**
+     * Return direct raw access (not buffered) to the command's input data stream.
+     */
+    public InputStream getRawInputStream() {
+        if (mFileIn == null) {
+            mFileIn = new FileInputStream(mIn);
+        }
+        return mFileIn;
+    }
+
+    /**
+     * Return buffered access to the command's {@link #getRawInputStream()}.
+     */
+    public InputStream getBufferedInputStream() {
+        if (mInputStream == null) {
+            mInputStream = new BufferedInputStream(getRawInputStream());
+        }
+        return mInputStream;
+    }
+
+    /**
+     * Return the next option on the command line -- that is an argument that
+     * starts with '-'.  If the next argument is not an option, null is returned.
+     */
+    public String getNextOption() {
+        if (mCurArgData != null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
+        }
+        if (mArgPos >= mArgs.length) {
+            return null;
+        }
+        String arg = mArgs[mArgPos];
+        if (!arg.startsWith("-")) {
+            return null;
+        }
+        mArgPos++;
+        if (arg.equals("--")) {
+            return null;
+        }
+        if (arg.length() > 1 && arg.charAt(1) != '-') {
+            if (arg.length() > 2) {
+                mCurArgData = arg.substring(2);
+                return arg.substring(0, 2);
+            } else {
+                mCurArgData = null;
+                return arg;
+            }
+        }
+        mCurArgData = null;
+        return arg;
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, return null.
+     */
+    public String getNextArg() {
+        if (mCurArgData != null) {
+            String arg = mCurArgData;
+            mCurArgData = null;
+            return arg;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos++];
+        } else {
+            return null;
+        }
+    }
+
+    public String peekNextArg() {
+        if (mCurArgData != null) {
+            return mCurArgData;
+        } else if (mArgPos < mArgs.length) {
+            return mArgs[mArgPos];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return all the remaining arguments in the command without moving the current position.
+     */
+    public String[] peekRemainingArgs() {
+        int remaining = getRemainingArgsCount();
+        String[] args = new String[remaining];
+        for (int pos = mArgPos; pos < mArgs.length; pos++) {
+            args[pos - mArgPos] = mArgs[pos];
+        }
+        return args;
+    }
+
+    /**
+     * Returns number of arguments that haven't been processed yet.
+     */
+    public int getRemainingArgsCount() {
+        if (mArgPos >= mArgs.length) {
+            return 0;
+        }
+        return mArgs.length - mArgPos;
+    }
+
+    /**
+     * Return the next argument on the command line, whatever it is; if there are
+     * no arguments left, throws an IllegalArgumentException to report this to the user.
+     */
+    public String getNextArgRequired() {
+        String arg = getNextArg();
+        if (arg == null) {
+            String prev = mArgs[mArgPos - 1];
+            throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
+        }
+        return arg;
+    }
+
+    public int handleDefaultCommands(String cmd) {
+        if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
+            onHelp();
+        } else {
+            getOutPrintWriter().println("Unknown command: " + cmd);
+        }
+        return -1;
+    }
+
+    public Binder getTarget() {
+        return mTarget;
+    }
+
+    public String[] getAllArgs() {
+        return mArgs;
+    }
+
+    /**
+     * Implement parsing and execution of a command.  If it isn't a command you understand,
+     * call {@link #handleDefaultCommands(String)} and return its result as a last resort.
+     * Use {@link #getNextOption()}, {@link #getNextArg()}, and {@link #getNextArgRequired()}
+     * to process additional command line arguments.  Command output can be written to
+     * {@link #getOutPrintWriter()} and errors to {@link #getErrPrintWriter()}.
+     *
+     * <p class="caution">Note that no permission checking has been done before entering this
+     * function, so you need to be sure to do your own security verification for any commands you
+     * are executing.  The easiest way to do this is to have the ShellCommand contain
+     * only a reference to your service's aidl interface, and do all of your command
+     * implementations on top of that -- that way you can rely entirely on your executing security
+     * code behind that interface.</p>
+     *
+     * @param cmd The first command line argument representing the name of the command to execute.
+     * @return Return the command result; generally 0 or positive indicates success and
+     * negative values indicate error.
+     */
+    public abstract int onCommand(String cmd);
+
+    /**
+     * Implement this to print help text about your command to {@link #getOutPrintWriter()}.
+     */
+    public abstract void onHelp();
+}