aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp18
-rw-r--r--OWNERS1
-rw-r--r--build/Android.bp33
-rw-r--r--build/include/android-modules-utils/sdk_level.h48
-rw-r--r--build/src/com/android/modules/utils/build/SdkLevel.java55
-rw-r--r--os/Android.bp42
-rw-r--r--os/java/com/android/modules/utils/BaseParceledListSlice.java210
-rw-r--r--os/java/com/android/modules/utils/BasicShellCommandHandler.java341
-rw-r--r--os/java/com/android/modules/utils/ParceledListSlice.aidl19
-rw-r--r--os/java/com/android/modules/utils/ParceledListSlice.java87
-rw-r--r--os/java/com/android/modules/utils/StringParceledListSlice.aidl19
-rw-r--r--os/java/com/android/modules/utils/StringParceledListSlice.java81
-rw-r--r--os/tests/Android.bp20
-rw-r--r--os/tests/AndroidManifest.xml23
-rw-r--r--os/tests/src/com/android/modules/utils/ParceledListSliceTest.java322
15 files changed, 1319 insertions, 0 deletions
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..97f99c0
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+// Limit visibility of all modules in this repo by default.
+package {
+ default_visibility: [":__subpackages__"],
+}
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..13b792a
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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-build",
+ srcs: ["src/**/*.java"],
+ sdk_version: "module_current",
+ min_sdk_version: "29",
+ visibility: ["//visibility:public"],
+ apex_available: ["//apex_available:anyapex"],
+}
+
+cc_library_static {
+ name: "libmodules-utils-build",
+ min_sdk_version: "29",
+ export_include_dirs: ["include"],
+ static_libs: ["libbase"],
+ visibility: ["//visibility:public"],
+ apex_available: ["//apex_available:anyapex"],
+}
diff --git a/build/include/android-modules-utils/sdk_level.h b/build/include/android-modules-utils/sdk_level.h
new file mode 100644
index 0000000..a373178
--- /dev/null
+++ b/build/include/android-modules-utils/sdk_level.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace modules {
+namespace sdklevel {
+
+// Return true iff the running Android SDK is at least "R".
+static inline bool IsAtLeastR() {
+ return android::base::GetIntProperty("ro.build.version.sdk", -1) >= 30;
+}
+
+// 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).
+static inline bool 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?).
+ std::string codename =
+ android::base::GetProperty("ro.build.version.codename", "");
+ return android::base::GetIntProperty("ro.build.version.sdk", -1) == 30 &&
+ (codename == "S" || codename == "T");
+}
+
+} // namespace utils
+} // namespace modules
+} // namespace android
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..802ae89
--- /dev/null
+++ b/os/Android.bp
@@ -0,0 +1,42 @@
+//
+// 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: [
+ "java/**/*.aidl",
+ "java/**/*.java",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ visibility: ["//visibility:public"],
+ apex_available: [
+ "//apex_available:anyapex",
+ "//apex_available:platform",
+ ],
+}
+
+filegroup {
+ name: "module-utils-os-aidls",
+ srcs: [
+ "java/com/android/modules/utils/ParceledListSlice.aidl",
+ "java/com/android/modules/utils/StringParceledListSlice.aidl",
+ ],
+ path: "java",
+ visibility: [
+ "//frameworks/base/wifi",
+ "//packages/modules/Wifi/framework",
+ ],
+}
diff --git a/os/java/com/android/modules/utils/BaseParceledListSlice.java b/os/java/com/android/modules/utils/BaseParceledListSlice.java
new file mode 100644
index 0000000..86f06be
--- /dev/null
+++ b/os/java/com/android/modules/utils/BaseParceledListSlice.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 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.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * Caveat: for efficiency and security, all elements must be the same concrete type.
+ * In order to avoid writing the class name of each object, we must ensure that
+ * each object is the same type, or else unparceling then reparceling the data may yield
+ * a different result if the class name encoded in the Parcelable is a Base type.
+ * See b/17671747.
+ */
+abstract class BaseParceledListSlice<T> implements Parcelable {
+ private static String TAG = "ParceledListSlice";
+ private static boolean DEBUG = false;
+
+ /*
+ * TODO get this number from somewhere else. For now set it to a quarter of
+ * the 1MB limit.
+ */
+ private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
+
+ private final List<T> mList;
+
+ private int mInlineCountLimit = Integer.MAX_VALUE;
+
+ public BaseParceledListSlice(List<T> list) {
+ mList = list;
+ }
+
+ @SuppressWarnings("unchecked")
+ BaseParceledListSlice(Parcel p, ClassLoader loader) {
+ final int N = p.readInt();
+ mList = new ArrayList<T>(N);
+ if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+ if (N <= 0) {
+ return;
+ }
+
+ Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
+ Class<?> listElementClass = null;
+
+ int i = 0;
+ while (i < N) {
+ if (p.readInt() == 0) {
+ break;
+ }
+
+ final T parcelable = readCreator(creator, p, loader);
+ if (listElementClass == null) {
+ listElementClass = parcelable.getClass();
+ } else {
+ verifySameType(listElementClass, parcelable.getClass());
+ }
+
+ mList.add(parcelable);
+
+ if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ if (i >= N) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < N) {
+ if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+ return;
+ }
+ while (i < N && reply.readInt() != 0) {
+ final T parcelable = readCreator(creator, reply, loader);
+ verifySameType(listElementClass, parcelable.getClass());
+
+ mList.add(parcelable);
+
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
+ }
+
+ private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ Parcelable.ClassLoaderCreator<?> classLoaderCreator =
+ (Parcelable.ClassLoaderCreator<?>) creator;
+ return (T) classLoaderCreator.createFromParcel(p, loader);
+ }
+ return (T) creator.createFromParcel(p);
+ }
+
+ private static void verifySameType(final Class<?> expected, final Class<?> actual) {
+ if (!actual.equals(expected)) {
+ throw new IllegalArgumentException("Can't unparcel type "
+ + (actual == null ? null : actual.getName()) + " in list of type "
+ + (expected == null ? null : expected.getName()));
+ }
+ }
+
+ public List<T> getList() {
+ return mList;
+ }
+
+ /**
+ * Set a limit on the maximum number of entries in the array that will be included
+ * inline in the initial parcelling of this object.
+ */
+ public void setInlineCountLimit(int maxCount) {
+ mInlineCountLimit = maxCount;
+ }
+
+ /**
+ * Write this to another Parcel. Note that this discards the internal Parcel
+ * and should not be used anymore. This is so we can pass this to a Binder
+ * where we won't have a chance to call recycle on this.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ final int N = mList.size();
+ final int callFlags = flags;
+ dest.writeInt(N);
+ if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+ if (N > 0) {
+ final Class<?> listElementClass = mList.get(0).getClass();
+ writeParcelableCreator(mList.get(0), dest);
+ int i = 0;
+ while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
+ dest.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, dest, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+ while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+
+ final T parcelable = mList.get(i);
+ verifySameType(listElementClass, parcelable.getClass());
+ writeElement(parcelable, reply, callFlags);
+
+ if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+ dest.writeStrongBinder(retriever);
+ }
+ }
+ }
+
+ protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
+
+ protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
+
+ protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
+}
diff --git a/os/java/com/android/modules/utils/BasicShellCommandHandler.java b/os/java/com/android/modules/utils/BasicShellCommandHandler.java
new file mode 100644
index 0000000..32e6f79
--- /dev/null
+++ b/os/java/com/android/modules/utils/BasicShellCommandHandler.java
@@ -0,0 +1,341 @@
+/*
+ * 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.
+ */
+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();
+}
diff --git a/os/java/com/android/modules/utils/ParceledListSlice.aidl b/os/java/com/android/modules/utils/ParceledListSlice.aidl
new file mode 100644
index 0000000..bbe8e2c
--- /dev/null
+++ b/os/java/com/android/modules/utils/ParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011, 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;
+
+parcelable ParceledListSlice<T>;
diff --git a/os/java/com/android/modules/utils/ParceledListSlice.java b/os/java/com/android/modules/utils/ParceledListSlice.java
new file mode 100644
index 0000000..24a85d8
--- /dev/null
+++ b/os/java/com/android/modules/utils/ParceledListSlice.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2011 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.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * @see BaseParceledListSlice
+ */
+public class ParceledListSlice<T extends Parcelable> extends BaseParceledListSlice<T> {
+ public ParceledListSlice(List<T> list) {
+ super(list);
+ }
+
+ private ParceledListSlice(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ }
+
+ public static <T extends Parcelable> ParceledListSlice<T> emptyList() {
+ return new ParceledListSlice<T>(Collections.<T> emptyList());
+ }
+
+ @Override
+ public int describeContents() {
+ int contents = 0;
+ final List<T> list = getList();
+ for (int i=0; i<list.size(); i++) {
+ contents |= list.get(i).describeContents();
+ }
+ return contents;
+ }
+
+ @Override
+ protected void writeElement(T parcelable, Parcel dest, int callFlags) {
+ parcelable.writeToParcel(dest, callFlags);
+ }
+
+ @Override
+ protected void writeParcelableCreator(T parcelable, Parcel dest) {
+ dest.writeParcelableCreator((Parcelable) parcelable);
+ }
+
+ @Override
+ protected Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+ return from.readParcelableCreator(loader);
+ }
+
+// @SuppressWarnings("unchecked")
+ public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
+ public ParceledListSlice createFromParcel(Parcel in) {
+ return new ParceledListSlice(in, null);
+ }
+
+ @Override
+ public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new ParceledListSlice(in, loader);
+ }
+
+ @Override
+ public ParceledListSlice[] newArray(int size) {
+ return new ParceledListSlice[size];
+ }
+ };
+}
diff --git a/os/java/com/android/modules/utils/StringParceledListSlice.aidl b/os/java/com/android/modules/utils/StringParceledListSlice.aidl
new file mode 100644
index 0000000..83b9711
--- /dev/null
+++ b/os/java/com/android/modules/utils/StringParceledListSlice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.android.modules.utils;
+
+parcelable StringParceledListSlice;
diff --git a/os/java/com/android/modules/utils/StringParceledListSlice.java b/os/java/com/android/modules/utils/StringParceledListSlice.java
new file mode 100644
index 0000000..4c688a8
--- /dev/null
+++ b/os/java/com/android/modules/utils/StringParceledListSlice.java
@@ -0,0 +1,81 @@
+/*
+ * 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.android.modules.utils;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
+ *
+ * @see BaseParceledListSlice
+ */
+public class StringParceledListSlice extends BaseParceledListSlice<String> {
+ public StringParceledListSlice(List<String> list) {
+ super(list);
+ }
+
+ private StringParceledListSlice(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ }
+
+ public static StringParceledListSlice emptyList() {
+ return new StringParceledListSlice(Collections.<String> emptyList());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ protected void writeElement(String parcelable, Parcel reply, int callFlags) {
+ reply.writeString(parcelable);
+ }
+
+ @Override
+ protected void writeParcelableCreator(String parcelable, Parcel dest) {
+ return;
+ }
+
+ @Override
+ protected Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader) {
+ return Parcel.STRING_CREATOR;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static final Parcelable.ClassLoaderCreator<StringParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<StringParceledListSlice>() {
+ public StringParceledListSlice createFromParcel(Parcel in) {
+ return new StringParceledListSlice(in, null);
+ }
+
+ @Override
+ public StringParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new StringParceledListSlice(in, loader);
+ }
+
+ @Override
+ public StringParceledListSlice[] newArray(int size) {
+ return new StringParceledListSlice[size];
+ }
+ };
+}
diff --git a/os/tests/Android.bp b/os/tests/Android.bp
new file mode 100644
index 0000000..5434d0d
--- /dev/null
+++ b/os/tests/Android.bp
@@ -0,0 +1,20 @@
+android_test {
+ name: "modules-utils-os-tests",
+
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "src/**/*.java",
+ ],
+
+ static_libs: [
+ "modules-utils-os",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ ],
+
+ libs: [
+ "android.test.runner",
+ ],
+}
diff --git a/os/tests/AndroidManifest.xml b/os/tests/AndroidManifest.xml
new file mode 100644
index 0000000..8fdfba3
--- /dev/null
+++ b/os/tests/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.modules.utils.tests">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.modules.utils.tests"
+ android:label="Modules-Utils Tests" />
+</manifest>
diff --git a/os/tests/src/com/android/modules/utils/ParceledListSliceTest.java b/os/tests/src/com/android/modules/utils/ParceledListSliceTest.java
new file mode 100644
index 0000000..c1457d6
--- /dev/null
+++ b/os/tests/src/com/android/modules/utils/ParceledListSliceTest.java
@@ -0,0 +1,322 @@
+/*
+ * 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.test.filters.LargeTest;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@LargeTest
+public class ParceledListSliceTest extends TestCase {
+
+ public void testSmallList() throws Exception {
+ final int objectCount = 100;
+ List<SmallObject> list = new ArrayList<SmallObject>();
+ for (int i = 0; i < objectCount; i++) {
+ list.add(new SmallObject(i * 2, (i * 2) + 1));
+ }
+
+ ParceledListSlice<SmallObject> slice;
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(new ParceledListSlice<SmallObject>(list), 0);
+ parcel.setDataPosition(0);
+ slice = parcel.readParcelable(getClass().getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+
+ assertNotNull(slice);
+ assertNotNull(slice.getList());
+ assertEquals(objectCount, slice.getList().size());
+
+ for (int i = 0; i < objectCount; i++) {
+ assertEquals(i * 2, slice.getList().get(i).mFieldA);
+ assertEquals((i * 2) + 1, slice.getList().get(i).mFieldB);
+ }
+ }
+
+ private static int measureLargeObject() {
+ Parcel p = Parcel.obtain();
+ try {
+ new LargeObject(0, 0, 0, 0, 0).writeToParcel(p, 0);
+ return p.dataPosition();
+ } finally {
+ p.recycle();
+ }
+ }
+
+ /**
+ * Test that when the list is large, the data is successfully parceled
+ * and unparceled (the implementation will send pieces of the list in
+ * separate round-trips to avoid the IPC limit).
+ */
+ public void testLargeList() throws Exception {
+ final int thresholdBytes = 256 * 1024;
+ final int objectCount = thresholdBytes / measureLargeObject();
+
+ List<LargeObject> list = new ArrayList<LargeObject>();
+ for (int i = 0; i < objectCount; i++) {
+ list.add(new LargeObject(
+ i * 5,
+ (i * 5) + 1,
+ (i * 5) + 2,
+ (i * 5) + 3,
+ (i * 5) + 4
+ ));
+ }
+
+ ParceledListSlice<LargeObject> slice;
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(new ParceledListSlice<LargeObject>(list), 0);
+ parcel.setDataPosition(0);
+ slice = parcel.readParcelable(getClass().getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+
+ assertNotNull(slice);
+ assertNotNull(slice.getList());
+ assertEquals(objectCount, slice.getList().size());
+
+ for (int i = 0; i < objectCount; i++) {
+ assertEquals(i * 5, slice.getList().get(i).mFieldA);
+ assertEquals((i * 5) + 1, slice.getList().get(i).mFieldB);
+ assertEquals((i * 5) + 2, slice.getList().get(i).mFieldC);
+ assertEquals((i * 5) + 3, slice.getList().get(i).mFieldD);
+ assertEquals((i * 5) + 4, slice.getList().get(i).mFieldE);
+ }
+ }
+
+ private void sendParcelStringList(List<String> list) {
+ StringParceledListSlice slice;
+ Parcel parcel = Parcel.obtain();
+
+ try {
+ parcel.writeParcelable(new StringParceledListSlice(list), 0);
+ parcel.setDataPosition(0);
+ slice = parcel.readParcelable(getClass().getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+
+ assertNotNull(slice);
+ assertNotNull(slice.getList());
+ assertEquals(list, slice.getList());
+ }
+
+ public void testStringList() throws Exception {
+ final int objectCount = 400;
+ List<String> list = new ArrayList<String>();
+ for (long i = 0; i < objectCount; i++) {
+ list.add(Long.toString(i * (6 - i)));
+ }
+
+ sendParcelStringList(list);
+ }
+
+ public void testLargeStringList() throws Exception {
+ final int thresholdBytes = 256 * 1024;
+ final String value = Long.toString(Long.MAX_VALUE);
+ final int objectCount = 2 * thresholdBytes / value.length();
+ final List<String> list = Collections.nCopies(objectCount, value);
+
+ sendParcelStringList(list);
+ }
+
+
+ /**
+ * Test that only homogeneous elements may be unparceled.
+ */
+ public void testHomogeneousElements() throws Exception {
+ List<BaseObject> list = new ArrayList<BaseObject>();
+ list.add(new LargeObject(0, 1, 2, 3, 4));
+ list.add(new SmallObject(5, 6));
+ list.add(new SmallObject(7, 8));
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ writeEvilParceledListSlice(parcel, list);
+ parcel.setDataPosition(0);
+ try {
+ ParceledListSlice.CREATOR.createFromParcel(parcel, getClass().getClassLoader());
+ assertTrue("Unparceled heterogeneous ParceledListSlice", false);
+ } catch (IllegalArgumentException e) {
+ // Success, we're not allowed to process heterogeneous
+ // elements in a ParceledListSlice.
+ }
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ /**
+ * Write a ParcelableListSlice that uses the BaseObject base class as the Creator.
+ * This is dangerous, as it may affect how the data is unparceled, then later parceled
+ * by the system, leading to a self-modifying data security vulnerability.
+ */
+ private static <T extends BaseObject> void writeEvilParceledListSlice(Parcel dest,
+ List<T> list) {
+ final int listCount = list.size();
+
+ // Number of items.
+ dest.writeInt(listCount);
+
+ // The type/creator to use when unparceling. Here we use the base class
+ // to simulate an attack on ParceledListSlice.
+ dest.writeString(BaseObject.class.getName());
+
+ for (int i = 0; i < listCount; i++) {
+ // 1 means the item is present.
+ dest.writeInt(1);
+ list.get(i).writeToParcel(dest, 0);
+ }
+ }
+
+ public abstract static class BaseObject implements Parcelable {
+ protected static final int TYPE_SMALL = 0;
+ protected static final int TYPE_LARGE = 1;
+
+ protected void writeToParcel(Parcel dest, int flags, int type) {
+ dest.writeInt(type);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * This is *REALLY* bad, but we're doing it in the test to ensure that we handle
+ * the possible exploit when unparceling an object with the BaseObject written as
+ * Creator.
+ */
+ public static final Creator<BaseObject> CREATOR = new Creator<BaseObject>() {
+ @Override
+ public BaseObject createFromParcel(Parcel source) {
+ switch (source.readInt()) {
+ case TYPE_SMALL:
+ return SmallObject.createFromParcelBody(source);
+ case TYPE_LARGE:
+ return LargeObject.createFromParcelBody(source);
+ default:
+ throw new IllegalArgumentException("Unknown type");
+ }
+ }
+
+ @Override
+ public BaseObject[] newArray(int size) {
+ return new BaseObject[size];
+ }
+ };
+ }
+
+ public static class SmallObject extends BaseObject {
+ public int mFieldA;
+ public int mFieldB;
+
+ public SmallObject(int a, int b) {
+ mFieldA = a;
+ mFieldB = b;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, TYPE_SMALL);
+ dest.writeInt(mFieldA);
+ dest.writeInt(mFieldB);
+ }
+
+ public static SmallObject createFromParcelBody(Parcel source) {
+ return new SmallObject(source.readInt(), source.readInt());
+ }
+
+ public static final Creator<SmallObject> CREATOR = new Creator<SmallObject>() {
+ @Override
+ public SmallObject createFromParcel(Parcel source) {
+ // Consume the type (as it is always written out).
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public SmallObject[] newArray(int size) {
+ return new SmallObject[size];
+ }
+ };
+ }
+
+ public static class LargeObject extends BaseObject {
+ public int mFieldA;
+ public int mFieldB;
+ public int mFieldC;
+ public int mFieldD;
+ public int mFieldE;
+
+ public LargeObject(int a, int b, int c, int d, int e) {
+ mFieldA = a;
+ mFieldB = b;
+ mFieldC = c;
+ mFieldD = d;
+ mFieldE = e;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, TYPE_LARGE);
+ dest.writeInt(mFieldA);
+ dest.writeInt(mFieldB);
+ dest.writeInt(mFieldC);
+ dest.writeInt(mFieldD);
+ dest.writeInt(mFieldE);
+ }
+
+ public static LargeObject createFromParcelBody(Parcel source) {
+ return new LargeObject(
+ source.readInt(),
+ source.readInt(),
+ source.readInt(),
+ source.readInt(),
+ source.readInt()
+ );
+ }
+
+ public static final Creator<LargeObject> CREATOR = new Creator<LargeObject>() {
+ @Override
+ public LargeObject createFromParcel(Parcel source) {
+ // Consume the type (as it is always written out).
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public LargeObject[] newArray(int size) {
+ return new LargeObject[size];
+ }
+ };
+ }
+}