Merge "rename try_make_readable() to try_make_writable()"
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index b28c006..0d2b620 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -473,6 +475,9 @@
@NonNull
@SystemApi(client = MODULE_LIBRARIES)
public Builder setLocalRoutesExcludedForVpn(boolean excludeLocalRoutes) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException("Method is not supported");
+ }
mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
return this;
}
diff --git a/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java b/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java
deleted file mode 100644
index 0cf9dcd..0000000
--- a/service-t/src/com/android/server/INativeDaemonConnectorCallbacks.java
+++ /dev/null
@@ -1,25 +0,0 @@
-
-/*
- * Copyright (C) 2007 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.server;
-
-interface INativeDaemonConnectorCallbacks {
-
- void onDaemonConnected();
- boolean onCheckHoldWakeLock(int code);
- boolean onEvent(int code, String raw, String[] cooked);
-}
diff --git a/service-t/src/com/android/server/NativeDaemonConnector.java b/service-t/src/com/android/server/NativeDaemonConnector.java
deleted file mode 100644
index ec8d779..0000000
--- a/service-t/src/com/android/server/NativeDaemonConnector.java
+++ /dev/null
@@ -1,704 +0,0 @@
-/*
- * Copyright (C) 2007 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.server;
-
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.Objects;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Generic connector class for interfacing with a native daemon which uses the
- * {@code libsysutils} FrameworkListener protocol.
- */
-final class NativeDaemonConnector implements Runnable, Handler.Callback {
- private final static boolean VDBG = false;
-
- private final String TAG;
-
- private String mSocket;
- private OutputStream mOutputStream;
- private LocalLog mLocalLog;
-
- private volatile boolean mDebug = false;
- private volatile Object mWarnIfHeld;
-
- private final ResponseQueue mResponseQueue;
-
- private final PowerManager.WakeLock mWakeLock;
-
- private final Looper mLooper;
-
- private INativeDaemonConnectorCallbacks mCallbacks;
- private Handler mCallbackHandler;
-
- private AtomicInteger mSequenceNumber;
-
- private static final long DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
- private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
-
- /** Lock held whenever communicating with native daemon. */
- private final Object mDaemonLock = new Object();
-
- private final int BUFFER_SIZE = 4096;
-
- NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
- int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
- mCallbacks = callbacks;
- mSocket = socket;
- mResponseQueue = new ResponseQueue(responseQueueSize);
- mWakeLock = wl;
- if (mWakeLock != null) {
- mWakeLock.setReferenceCounted(true);
- }
- mSequenceNumber = new AtomicInteger(0);
- TAG = logTag != null ? logTag : "NativeDaemonConnector";
- mLocalLog = new LocalLog(maxLogSize);
- final HandlerThread thread = new HandlerThread(TAG);
- thread.start();
- mLooper = thread.getLooper();
- }
-
- /**
- * Enable Set debugging mode, which causes messages to also be written to both
- * {@link Log} in addition to internal log.
- */
- public void setDebug(boolean debug) {
- mDebug = debug;
- }
-
- /**
- * Like SystemClock.uptimeMillis, except truncated to an int so it will fit in a message arg.
- * Inaccurate across 49.7 days of uptime, but only used for debugging.
- */
- private int uptimeMillisInt() {
- return (int) SystemClock.uptimeMillis() & Integer.MAX_VALUE;
- }
-
- /**
- * Yell loudly if someone tries making future {@link #execute(Command)}
- * calls while holding a lock on the given object.
- */
- public void setWarnIfHeld(Object warnIfHeld) {
- if (mWarnIfHeld != null) {
- throw new IllegalStateException("warnIfHeld is already set.");
- }
- mWarnIfHeld = Objects.requireNonNull(warnIfHeld);
- }
-
- @Override
- public void run() {
- mCallbackHandler = new Handler(mLooper, this);
-
- while (true) {
- try {
- listenToSocket();
- } catch (Exception e) {
- loge("Error in NativeDaemonConnector: " + e);
- SystemClock.sleep(5000);
- }
- }
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- final String event = (String) msg.obj;
- final int start = uptimeMillisInt();
- final int sent = msg.arg1;
- try {
- if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
- log(String.format("Unhandled event '%s'", event));
- }
- } catch (Exception e) {
- loge("Error handling '" + event + "': " + e);
- } finally {
- if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
- mWakeLock.release();
- }
- final int end = uptimeMillisInt();
- if (start > sent && start - sent > WARN_EXECUTE_DELAY_MS) {
- loge(String.format("NDC event {%s} processed too late: %dms", event, start - sent));
- }
- if (end > start && end - start > WARN_EXECUTE_DELAY_MS) {
- loge(String.format("NDC event {%s} took too long: %dms", event, end - start));
- }
- }
- return true;
- }
-
- private LocalSocketAddress determineSocketAddress() {
- // If we're testing, set up a socket in a namespace that's accessible to test code.
- // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
- // production devices, even if said native daemons ill-advisedly pick a socket name that
- // starts with __test__, only allow this on debug builds.
- if (mSocket.startsWith("__test__") && Build.isDebuggable()) {
- return new LocalSocketAddress(mSocket);
- } else {
- return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
- }
- }
-
- private void listenToSocket() throws IOException {
- LocalSocket socket = null;
-
- try {
- socket = new LocalSocket();
- LocalSocketAddress address = determineSocketAddress();
-
- socket.connect(address);
-
- InputStream inputStream = socket.getInputStream();
- synchronized (mDaemonLock) {
- mOutputStream = socket.getOutputStream();
- }
-
- mCallbacks.onDaemonConnected();
-
- FileDescriptor[] fdList = null;
- byte[] buffer = new byte[BUFFER_SIZE];
- int start = 0;
-
- while (true) {
- int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
- if (count < 0) {
- loge("got " + count + " reading with start = " + start);
- break;
- }
- fdList = socket.getAncillaryFileDescriptors();
-
- // Add our starting point to the count and reset the start.
- count += start;
- start = 0;
-
- for (int i = 0; i < count; i++) {
- if (buffer[i] == 0) {
- // Note - do not log this raw message since it may contain
- // sensitive data
- final String rawEvent = new String(
- buffer, start, i - start, StandardCharsets.UTF_8);
-
- boolean releaseWl = false;
- try {
- final NativeDaemonEvent event =
- NativeDaemonEvent.parseRawEvent(rawEvent, fdList);
-
- log("RCV <- {" + event + "}");
-
- if (event.isClassUnsolicited()) {
- // TODO: migrate to sending NativeDaemonEvent instances
- if (mCallbacks.onCheckHoldWakeLock(event.getCode())
- && mWakeLock != null) {
- mWakeLock.acquire();
- releaseWl = true;
- }
- Message msg = mCallbackHandler.obtainMessage(
- event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
- if (mCallbackHandler.sendMessage(msg)) {
- releaseWl = false;
- }
- } else {
- mResponseQueue.add(event.getCmdNumber(), event);
- }
- } catch (IllegalArgumentException e) {
- log("Problem parsing message " + e);
- } finally {
- if (releaseWl) {
- mWakeLock.release();
- }
- }
-
- start = i + 1;
- }
- }
-
- if (start == 0) {
- log("RCV incomplete");
- }
-
- // We should end at the amount we read. If not, compact then
- // buffer and read again.
- if (start != count) {
- final int remaining = BUFFER_SIZE - start;
- System.arraycopy(buffer, start, buffer, 0, remaining);
- start = remaining;
- } else {
- start = 0;
- }
- }
- } catch (IOException ex) {
- loge("Communications error: " + ex);
- throw ex;
- } finally {
- synchronized (mDaemonLock) {
- if (mOutputStream != null) {
- try {
- loge("closing stream for " + mSocket);
- mOutputStream.close();
- } catch (IOException e) {
- loge("Failed closing output stream: " + e);
- }
- mOutputStream = null;
- }
- }
-
- try {
- if (socket != null) {
- socket.close();
- }
- } catch (IOException ex) {
- loge("Failed closing socket: " + ex);
- }
- }
- }
-
- /**
- * Wrapper around argument that indicates it's sensitive and shouldn't be
- * logged.
- */
- public static class SensitiveArg {
- private final Object mArg;
-
- public SensitiveArg(Object arg) {
- mArg = arg;
- }
-
- @Override
- public String toString() {
- return String.valueOf(mArg);
- }
- }
-
- /**
- * Make command for daemon, escaping arguments as needed.
- */
- @VisibleForTesting
- static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
- String cmd, Object... args) {
- if (cmd.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("Unexpected command: " + cmd);
- }
- if (cmd.indexOf(' ') >= 0) {
- throw new IllegalArgumentException("Arguments must be separate from command");
- }
-
- rawBuilder.append(sequenceNumber).append(' ').append(cmd);
- logBuilder.append(sequenceNumber).append(' ').append(cmd);
- for (Object arg : args) {
- final String argString = String.valueOf(arg);
- if (argString.indexOf('\0') >= 0) {
- throw new IllegalArgumentException("Unexpected argument: " + arg);
- }
-
- rawBuilder.append(' ');
- logBuilder.append(' ');
-
- appendEscaped(rawBuilder, argString);
- if (arg instanceof SensitiveArg) {
- logBuilder.append("[scrubbed]");
- } else {
- appendEscaped(logBuilder, argString);
- }
- }
-
- rawBuilder.append('\0');
- }
-
- /**
- * Method that waits until all asychronous notifications sent by the native daemon have
- * been processed. This method must not be called on the notification thread or an
- * exception will be thrown.
- */
- public void waitForCallbacks() {
- if (Thread.currentThread() == mLooper.getThread()) {
- throw new IllegalStateException("Must not call this method on callback thread");
- }
-
- final CountDownLatch latch = new CountDownLatch(1);
- mCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- latch.countDown();
- }
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- Log.wtf(TAG, "Interrupted while waiting for unsolicited response handling", e);
- }
- }
-
- /**
- * Issue the given command to the native daemon and return a single expected
- * response.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
- return execute(cmd.mCmd, cmd.mArguments.toArray());
- }
-
- /**
- * Issue the given command to the native daemon and return a single expected
- * response. Any arguments must be separated from base command so they can
- * be properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent execute(String cmd, Object... args)
- throws NativeDaemonConnectorException {
- return execute(DEFAULT_TIMEOUT, cmd, args);
- }
-
- public NativeDaemonEvent execute(long timeoutMs, String cmd, Object... args)
- throws NativeDaemonConnectorException {
- final NativeDaemonEvent[] events = executeForList(timeoutMs, cmd, args);
- if (events.length != 1) {
- throw new NativeDaemonConnectorException(
- "Expected exactly one response, but received " + events.length);
- }
- return events[0];
- }
-
- /**
- * Issue the given command to the native daemon and return any
- * {@link NativeDaemonEvent#isClassContinue()} responses, including the
- * final terminal response.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
- return executeForList(cmd.mCmd, cmd.mArguments.toArray());
- }
-
- /**
- * Issue the given command to the native daemon and return any
- * {@link NativeDaemonEvent#isClassContinue()} responses, including the
- * final terminal response. Any arguments must be separated from base
- * command so they can be properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(String cmd, Object... args)
- throws NativeDaemonConnectorException {
- return executeForList(DEFAULT_TIMEOUT, cmd, args);
- }
-
- /**
- * Issue the given command to the native daemon and return any {@linke
- * NativeDaemonEvent@isClassContinue()} responses, including the final
- * terminal response. Note that the timeout does not count time in deep
- * sleep. Any arguments must be separated from base command so they can be
- * properly escaped.
- *
- * @throws NativeDaemonConnectorException when problem communicating with
- * native daemon, or if the response matches
- * {@link NativeDaemonEvent#isClassClientError()} or
- * {@link NativeDaemonEvent#isClassServerError()}.
- */
- public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
- throws NativeDaemonConnectorException {
- if (mWarnIfHeld != null && Thread.holdsLock(mWarnIfHeld)) {
- Log.wtf(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding 0x"
- + Integer.toHexString(System.identityHashCode(mWarnIfHeld)), new Throwable());
- }
-
- final long startTime = SystemClock.elapsedRealtime();
-
- final ArrayList<NativeDaemonEvent> events = new ArrayList<>();
-
- final StringBuilder rawBuilder = new StringBuilder();
- final StringBuilder logBuilder = new StringBuilder();
- final int sequenceNumber = mSequenceNumber.incrementAndGet();
-
- makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
-
- final String rawCmd = rawBuilder.toString();
- final String logCmd = logBuilder.toString();
-
- log("SND -> {" + logCmd + "}");
-
- synchronized (mDaemonLock) {
- if (mOutputStream == null) {
- throw new NativeDaemonConnectorException("missing output stream");
- } else {
- try {
- mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- throw new NativeDaemonConnectorException("problem sending command", e);
- }
- }
- }
-
- NativeDaemonEvent event = null;
- do {
- event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
- if (event == null) {
- loge("timed-out waiting for response to " + logCmd);
- throw new NativeDaemonTimeoutException(logCmd, event);
- }
- if (VDBG) log("RMV <- {" + event + "}");
- events.add(event);
- } while (event.isClassContinue());
-
- final long endTime = SystemClock.elapsedRealtime();
- if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
- loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
- }
-
- if (event.isClassClientError()) {
- throw new NativeDaemonArgumentException(logCmd, event);
- }
- if (event.isClassServerError()) {
- throw new NativeDaemonFailureException(logCmd, event);
- }
-
- return events.toArray(new NativeDaemonEvent[events.size()]);
- }
-
- /**
- * Append the given argument to {@link StringBuilder}, escaping as needed,
- * and surrounding with quotes when it contains spaces.
- */
- @VisibleForTesting
- static void appendEscaped(StringBuilder builder, String arg) {
- final boolean hasSpaces = arg.indexOf(' ') >= 0;
- if (hasSpaces) {
- builder.append('"');
- }
-
- final int length = arg.length();
- for (int i = 0; i < length; i++) {
- final char c = arg.charAt(i);
-
- if (c == '"') {
- builder.append("\\\"");
- } else if (c == '\\') {
- builder.append("\\\\");
- } else {
- builder.append(c);
- }
- }
-
- if (hasSpaces) {
- builder.append('"');
- }
- }
-
- private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
- public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
-
- @Override
- public IllegalArgumentException rethrowAsParcelableException() {
- throw new IllegalArgumentException(getMessage(), this);
- }
- }
-
- private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
- public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
- }
-
- /**
- * Command builder that handles argument list building. Any arguments must
- * be separated from base command so they can be properly escaped.
- */
- public static class Command {
- private String mCmd;
- private ArrayList<Object> mArguments = new ArrayList<>();
-
- public Command(String cmd, Object... args) {
- mCmd = cmd;
- for (Object arg : args) {
- appendArg(arg);
- }
- }
-
- public Command appendArg(Object arg) {
- mArguments.add(arg);
- return this;
- }
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mLocalLog.dump(fd, pw, args);
- pw.println();
- mResponseQueue.dump(fd, pw, args);
- }
-
- private void log(String logstring) {
- if (mDebug) Log.d(TAG, logstring);
- mLocalLog.log(logstring);
- }
-
- private void loge(String logstring) {
- Log.e(TAG, logstring);
- mLocalLog.log(logstring);
- }
-
- private static class ResponseQueue {
-
- private static class PendingCmd {
- public final int cmdNum;
- public final String logCmd;
-
- public BlockingQueue<NativeDaemonEvent> responses =
- new ArrayBlockingQueue<NativeDaemonEvent>(10);
-
- // The availableResponseCount member is used to track when we can remove this
- // instance from the ResponseQueue.
- // This is used under the protection of a sync of the mPendingCmds object.
- // A positive value means we've had more writers retreive this object while
- // a negative value means we've had more readers. When we've had an equal number
- // (it goes to zero) we can remove this object from the mPendingCmds list.
- // Note that we may have more responses for this command (and more readers
- // coming), but that would result in a new PendingCmd instance being created
- // and added with the same cmdNum.
- // Also note that when this goes to zero it just means a parity of readers and
- // writers have retrieved this object - not that they are done using it. The
- // responses queue may well have more responses yet to be read or may get more
- // responses added to it. But all those readers/writers have retreived and
- // hold references to this instance already so it can be removed from
- // mPendingCmds queue.
- public int availableResponseCount;
-
- public PendingCmd(int cmdNum, String logCmd) {
- this.cmdNum = cmdNum;
- this.logCmd = logCmd;
- }
- }
-
- private final LinkedList<PendingCmd> mPendingCmds;
- private int mMaxCount;
-
- ResponseQueue(int maxCount) {
- mPendingCmds = new LinkedList<PendingCmd>();
- mMaxCount = maxCount;
- }
-
- public void add(int cmdNum, NativeDaemonEvent response) {
- PendingCmd found = null;
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- if (pendingCmd.cmdNum == cmdNum) {
- found = pendingCmd;
- break;
- }
- }
- if (found == null) {
- // didn't find it - make sure our queue isn't too big before adding
- while (mPendingCmds.size() >= mMaxCount) {
- Log.e("NativeDaemonConnector.ResponseQueue",
- "more buffered than allowed: " + mPendingCmds.size() +
- " >= " + mMaxCount);
- // let any waiter timeout waiting for this
- PendingCmd pendingCmd = mPendingCmds.remove();
- Log.e("NativeDaemonConnector.ResponseQueue",
- "Removing request: " + pendingCmd.logCmd + " (" +
- pendingCmd.cmdNum + ")");
- }
- found = new PendingCmd(cmdNum, null);
- mPendingCmds.add(found);
- }
- found.availableResponseCount++;
- // if a matching remove call has already retrieved this we can remove this
- // instance from our list
- if (found.availableResponseCount == 0) mPendingCmds.remove(found);
- }
- try {
- found.responses.put(response);
- } catch (InterruptedException e) { }
- }
-
- // note that the timeout does not count time in deep sleep. If you don't want
- // the device to sleep, hold a wakelock
- public NativeDaemonEvent remove(int cmdNum, long timeoutMs, String logCmd) {
- PendingCmd found = null;
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- if (pendingCmd.cmdNum == cmdNum) {
- found = pendingCmd;
- break;
- }
- }
- if (found == null) {
- found = new PendingCmd(cmdNum, logCmd);
- mPendingCmds.add(found);
- }
- found.availableResponseCount--;
- // if a matching add call has already retrieved this we can remove this
- // instance from our list
- if (found.availableResponseCount == 0) mPendingCmds.remove(found);
- }
- NativeDaemonEvent result = null;
- try {
- result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {}
- if (result == null) {
- Log.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
- }
- return result;
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Pending requests:");
- synchronized (mPendingCmds) {
- for (PendingCmd pendingCmd : mPendingCmds) {
- pw.println(" Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
- }
- }
- }
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonConnectorException.java b/service-t/src/com/android/server/NativeDaemonConnectorException.java
deleted file mode 100644
index 4d8881c..0000000
--- a/service-t/src/com/android/server/NativeDaemonConnectorException.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2006 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.server;
-
-import android.os.Parcel;
-
-/**
- * An exception that indicates there was an error with a
- * {@link NativeDaemonConnector} operation.
- */
-public class NativeDaemonConnectorException extends Exception {
- private String mCmd;
- private NativeDaemonEvent mEvent;
-
- public NativeDaemonConnectorException(String detailMessage) {
- super(detailMessage);
- }
-
- public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
- super(detailMessage, throwable);
- }
-
- public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
- super("command '" + cmd + "' failed with '" + event + "'");
- mCmd = cmd;
- mEvent = event;
- }
-
- public int getCode() {
- return mEvent != null ? mEvent.getCode() : -1;
- }
-
- public String getCmd() {
- return mCmd;
- }
-
- /**
- * Rethrow as a {@link RuntimeException} subclass that is handled by
- * {@link Parcel#writeException(Exception)}.
- */
- public IllegalArgumentException rethrowAsParcelableException() {
- throw new IllegalStateException(getMessage(), this);
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonEvent.java b/service-t/src/com/android/server/NativeDaemonEvent.java
deleted file mode 100644
index 5683694..0000000
--- a/service-t/src/com/android/server/NativeDaemonEvent.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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.server;
-
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.util.ArrayList;
-
-/**
- * Parsed event from native side of {@link NativeDaemonConnector}.
- */
-public class NativeDaemonEvent {
-
- // TODO: keep class ranges in sync with ResponseCode.h
- // TODO: swap client and server error ranges to roughly mirror HTTP spec
-
- private final int mCmdNumber;
- private final int mCode;
- private final String mMessage;
- private final String mRawEvent;
- private final String mLogMessage;
- private String[] mParsed;
- private FileDescriptor[] mFdList;
-
- private NativeDaemonEvent(int cmdNumber, int code, String message,
- String rawEvent, String logMessage, FileDescriptor[] fdList) {
- mCmdNumber = cmdNumber;
- mCode = code;
- mMessage = message;
- mRawEvent = rawEvent;
- mLogMessage = logMessage;
- mParsed = null;
- mFdList = fdList;
- }
-
- static public final String SENSITIVE_MARKER = "{{sensitive}}";
-
- public int getCmdNumber() {
- return mCmdNumber;
- }
-
- public int getCode() {
- return mCode;
- }
-
- public String getMessage() {
- return mMessage;
- }
-
- public FileDescriptor[] getFileDescriptors() {
- return mFdList;
- }
-
- @Deprecated
- public String getRawEvent() {
- return mRawEvent;
- }
-
- @Override
- public String toString() {
- return mLogMessage;
- }
-
- /**
- * Test if event represents a partial response which is continued in
- * additional subsequent events.
- */
- public boolean isClassContinue() {
- return mCode >= 100 && mCode < 200;
- }
-
- /**
- * Test if event represents a command success.
- */
- public boolean isClassOk() {
- return mCode >= 200 && mCode < 300;
- }
-
- /**
- * Test if event represents a remote native daemon error.
- */
- public boolean isClassServerError() {
- return mCode >= 400 && mCode < 500;
- }
-
- /**
- * Test if event represents a command syntax or argument error.
- */
- public boolean isClassClientError() {
- return mCode >= 500 && mCode < 600;
- }
-
- /**
- * Test if event represents an unsolicited event from native daemon.
- */
- public boolean isClassUnsolicited() {
- return isClassUnsolicited(mCode);
- }
-
- private static boolean isClassUnsolicited(int code) {
- return code >= 600 && code < 700;
- }
-
- /**
- * Verify this event matches the given code.
- *
- * @throws IllegalStateException if {@link #getCode()} doesn't match.
- */
- public void checkCode(int code) {
- if (mCode != code) {
- throw new IllegalStateException("Expected " + code + " but was: " + this);
- }
- }
-
- /**
- * Parse the given raw event into {@link NativeDaemonEvent} instance.
- *
- * @throws IllegalArgumentException when line doesn't match format expected
- * from native side.
- */
- public static NativeDaemonEvent parseRawEvent(String rawEvent, FileDescriptor[] fdList) {
- final String[] parsed = rawEvent.split(" ");
- if (parsed.length < 2) {
- throw new IllegalArgumentException("Insufficient arguments");
- }
-
- int skiplength = 0;
-
- final int code;
- try {
- code = Integer.parseInt(parsed[0]);
- skiplength = parsed[0].length() + 1;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("problem parsing code", e);
- }
-
- int cmdNumber = -1;
- if (isClassUnsolicited(code) == false) {
- if (parsed.length < 3) {
- throw new IllegalArgumentException("Insufficient arguemnts");
- }
- try {
- cmdNumber = Integer.parseInt(parsed[1]);
- skiplength += parsed[1].length() + 1;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("problem parsing cmdNumber", e);
- }
- }
-
- String logMessage = rawEvent;
- if (parsed.length > 2 && parsed[2].equals(SENSITIVE_MARKER)) {
- skiplength += parsed[2].length() + 1;
- logMessage = parsed[0] + " " + parsed[1] + " {}";
- }
-
- final String message = rawEvent.substring(skiplength);
-
- return new NativeDaemonEvent(cmdNumber, code, message, rawEvent, logMessage, fdList);
- }
-
- /**
- * Filter the given {@link NativeDaemonEvent} list, returning
- * {@link #getMessage()} for any events matching the requested code.
- */
- public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
- final ArrayList<String> result = new ArrayList<>();
- for (NativeDaemonEvent event : events) {
- if (event.getCode() == matchCode) {
- result.add(event.getMessage());
- }
- }
- return result.toArray(new String[result.size()]);
- }
-
- /**
- * Find the Nth field of the event.
- *
- * This ignores and code or cmdNum, the first return value is given for N=0.
- * Also understands "\"quoted\" multiword responses" and tries them as a single field
- */
- public String getField(int n) {
- if (mParsed == null) {
- mParsed = unescapeArgs(mRawEvent);
- }
- n += 2; // skip code and command#
- if (n > mParsed.length) return null;
- return mParsed[n];
- }
-
- public static String[] unescapeArgs(String rawEvent) {
- final boolean DEBUG_ROUTINE = false;
- final String LOGTAG = "unescapeArgs";
- final ArrayList<String> parsed = new ArrayList<String>();
- final int length = rawEvent.length();
- int current = 0;
- int wordEnd = -1;
- boolean quoted = false;
-
- if (DEBUG_ROUTINE) Log.e(LOGTAG, "parsing '" + rawEvent + "'");
- if (rawEvent.charAt(current) == '\"') {
- quoted = true;
- current++;
- }
- while (current < length) {
- // find the end of the word
- char terminator = quoted ? '\"' : ' ';
- wordEnd = current;
- while (wordEnd < length && rawEvent.charAt(wordEnd) != terminator) {
- if (rawEvent.charAt(wordEnd) == '\\') {
- // skip the escaped char
- ++wordEnd;
- }
- ++wordEnd;
- }
- if (wordEnd > length) wordEnd = length;
- String word = rawEvent.substring(current, wordEnd);
- current += word.length();
- if (!quoted) {
- word = word.trim();
- } else {
- current++; // skip the trailing quote
- }
- // unescape stuff within the word
- word = word.replace("\\\\", "\\");
- word = word.replace("\\\"", "\"");
-
- if (DEBUG_ROUTINE) Log.e(LOGTAG, "found '" + word + "'");
- parsed.add(word);
-
- // find the beginning of the next word - either of these options
- int nextSpace = rawEvent.indexOf(' ', current);
- int nextQuote = rawEvent.indexOf(" \"", current);
- if (DEBUG_ROUTINE) {
- Log.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
- }
- if (nextQuote > -1 && nextQuote <= nextSpace) {
- quoted = true;
- current = nextQuote + 2;
- } else {
- quoted = false;
- if (nextSpace > -1) {
- current = nextSpace + 1;
- }
- } // else we just start the next word after the current and read til the end
- if (DEBUG_ROUTINE) {
- Log.e(LOGTAG, "next loop - current=" + current
- + ", length=" + length + ", quoted=" + quoted);
- }
- }
- return parsed.toArray(new String[parsed.size()]);
- }
-}
diff --git a/service-t/src/com/android/server/NativeDaemonTimeoutException.java b/service-t/src/com/android/server/NativeDaemonTimeoutException.java
deleted file mode 100644
index 658f7d6..0000000
--- a/service-t/src/com/android/server/NativeDaemonTimeoutException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 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.server;
-
-/**
- * An exception that indicates there was a timeout with a
- * {@link NativeDaemonConnector} operation.
- */
-public class NativeDaemonTimeoutException extends NativeDaemonConnectorException {
- public NativeDaemonTimeoutException(String command, NativeDaemonEvent event) {
- super(command, event);
- }
-}
-
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index a0bfb4a..ccc2776 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -231,6 +231,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
index ce955fd..b06c8aa 100644
--- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
+++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java
@@ -29,7 +29,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkSpecifier;
import android.net.TelephonyNetworkSpecifier;
-import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -94,11 +93,7 @@
@NonNull final TelephonyManager t) {
mContext = c;
mTelephonyManager = t;
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
- mTelephonyManagerShim = new TelephonyManagerShimImpl(mTelephonyManager);
- } else {
- mTelephonyManagerShim = null;
- }
+ mTelephonyManagerShim = TelephonyManagerShimImpl.newInstance(mTelephonyManager);
mThread = new HandlerThread(TAG);
mThread.start();
mHandler = new Handler(mThread.getLooper()) {};
@@ -192,36 +187,30 @@
private void addCarrierPrivilegesListener(int logicalSlotIndex, Executor executor,
CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.addCarrierPrivilegesListener(
logicalSlotIndex, executor, listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "addCarrierPrivilegesListener API is not available");
}
}
private void removeCarrierPrivilegesListener(CarrierPrivilegesListenerShim listener) {
- if (mTelephonyManagerShim == null) {
- return;
- }
try {
mTelephonyManagerShim.removeCarrierPrivilegesListener(listener);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "removeCarrierPrivilegesListener API is not available");
}
}
private String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
- if (mTelephonyManagerShim == null) {
- return null;
- }
try {
return mTelephonyManagerShim.getCarrierServicePackageNameForLogicalSlot(
logicalSlotIndex);
} catch (UnsupportedApiLevelException unsupportedApiLevelException) {
+ // Should not happen since CarrierPrivilegeAuthenticator is only used on T+
Log.e(TAG, "getCarrierServicePackageNameForLogicalSlot API is not available");
}
return null;
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 2885ba7..62b3add 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -127,9 +127,17 @@
@GuardedBy("this")
private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
+ // Store PackageManager for each user.
+ // Keys are users, Values are PackageManagers which get from each user.
@GuardedBy("this")
private final Map<UserHandle, PackageManager> mUsersPackageManager = new ArrayMap<>();
+ // Store appIds traffic permissions for each user.
+ // Keys are users, Values are SparseArrays where each entry maps an appId to the permissions
+ // that appId has within that user.
+ @GuardedBy("this")
+ private final Map<UserHandle, SparseIntArray> mUsersTrafficPermissions = new ArrayMap<>();
+
private static final int SYSTEM_APPID = SYSTEM_UID;
private static final int MAX_PERMISSION_UPDATE_LOGS = 40;
@@ -292,14 +300,24 @@
sendUidsNetworkPermission(uids, true /* add */);
}
- private void updateAppIdsTrafficPermission(final SparseIntArray appIds,
- final SparseIntArray extraAppIds) {
- for (int i = 0; i < extraAppIds.size(); i++) {
- final int appId = extraAppIds.keyAt(i);
- final int permission = extraAppIds.valueAt(i);
- appIds.put(appId, appIds.get(appId) | permission);
+ /**
+ * Calculates permissions for appIds.
+ * Maps each appId to the union of all traffic permissions that the appId has in all users.
+ *
+ * @return The appIds traffic permissions.
+ */
+ private synchronized SparseIntArray makeAppIdsTrafficPermForAllUsers() {
+ final SparseIntArray appIds = new SparseIntArray();
+ // Check appIds permissions from each user.
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userAppIds = mUsersTrafficPermissions.get(user);
+ for (int i = 0; i < userAppIds.size(); i++) {
+ final int appId = userAppIds.keyAt(i);
+ final int permission = userAppIds.valueAt(i);
+ appIds.put(appId, appIds.get(appId) | permission);
+ }
}
- sendAppIdsTrafficPermission(appIds);
+ return appIds;
}
private SparseIntArray getSystemTrafficPerm() {
@@ -363,6 +381,10 @@
// mUidsAllowedOnRestrictedNetworks.
updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+ // Read system traffic permissions when a user removed and put them to USER_ALL because they
+ // are not specific to any particular user.
+ mUsersTrafficPermissions.put(UserHandle.ALL, getSystemTrafficPerm());
+
final List<UserHandle> usrs = mUserManager.getUserHandles(true /* excludeDying */);
// Update netd permissions for all users.
for (UserHandle user : usrs) {
@@ -487,9 +509,16 @@
final SparseIntArray uids = makeUidsNetworkPerm(apps);
updateUidsNetworkPermission(uids);
- // App ids traffic permission
- final SparseIntArray appIds = makeAppIdsTrafficPerm(apps);
- updateAppIdsTrafficPermission(appIds, getSystemTrafficPerm());
+ // Add new user appIds permissions.
+ final SparseIntArray addedUserAppIds = makeAppIdsTrafficPerm(apps);
+ mUsersTrafficPermissions.put(user, addedUserAppIds);
+ // Generate appIds from all users and send result to netd.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user added
+ mPermissionUpdateLogs.log("New user(" + user.getIdentifier() + ") added: nPerm uids="
+ + uids + ", tPerm appIds=" + addedUserAppIds);
}
/**
@@ -502,6 +531,7 @@
public synchronized void onUserRemoved(@NonNull UserHandle user) {
mUsers.remove(user);
+ // Remove uids network permissions that belongs to the user.
final SparseIntArray removedUids = new SparseIntArray();
final SparseIntArray allUids = mUidToNetworkPerm.clone();
for (int i = 0; i < allUids.size(); i++) {
@@ -512,6 +542,27 @@
}
}
sendUidsNetworkPermission(removedUids, false /* add */);
+
+ // Remove appIds traffic permission that belongs to the user
+ final SparseIntArray removedUserAppIds = mUsersTrafficPermissions.remove(user);
+ // Generate appIds from left users.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ // Clear permission on those appIds belong to this user only, set the permission to
+ // PERMISSION_UNINSTALLED.
+ if (removedUserAppIds != null) {
+ for (int i = 0; i < removedUserAppIds.size(); i++) {
+ final int appId = removedUserAppIds.keyAt(i);
+ // Need to clear permission if the removed appId is not found in the array.
+ if (appIds.indexOfKey(appId) < 0) {
+ appIds.put(appId, PERMISSION_UNINSTALLED);
+ }
+ }
+ }
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user removed
+ mPermissionUpdateLogs.log("User(" + user.getIdentifier() + ") removed: nPerm uids="
+ + removedUids + ", tPerm appIds=" + removedUserAppIds);
}
/**
@@ -598,6 +649,39 @@
}
}
+ private synchronized void updateAppIdTrafficPermission(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final int uidTrafficPerm = getTrafficPermissionForUid(uid);
+ final SparseIntArray userTrafficPerms =
+ mUsersTrafficPermissions.get(UserHandle.getUserHandleForUid(uid));
+ if (userTrafficPerms == null) {
+ Log.wtf(TAG, "Can't get user traffic permission from uid=" + uid);
+ return;
+ }
+ // Do not put PERMISSION_UNINSTALLED into the array. If no package left on the uid
+ // (PERMISSION_UNINSTALLED), remove the appId from the array. Otherwise, update the latest
+ // permission to the appId.
+ if (uidTrafficPerm == PERMISSION_UNINSTALLED) {
+ userTrafficPerms.delete(appId);
+ } else {
+ userTrafficPerms.put(appId, uidTrafficPerm);
+ }
+ }
+
+ private synchronized int getAppIdTrafficPermission(int appId) {
+ int permission = PERMISSION_NONE;
+ boolean installed = false;
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userApps = mUsersTrafficPermissions.get(user);
+ final int appIdx = userApps.indexOfKey(appId);
+ if (appIdx >= 0) {
+ permission |= userApps.valueAt(appIdx);
+ installed = true;
+ }
+ }
+ return installed ? permission : PERMISSION_UNINSTALLED;
+ }
+
/**
* Called when a package is added.
*
@@ -607,9 +691,12 @@
* @hide
*/
public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestPermissionForUid(uid, currentPermission, packageName);
@@ -633,10 +720,12 @@
// package can bypass VPN.
updateVpnUid(uid, true /* add */);
mAllApps.add(appId);
+
+ // Log package added.
mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
}
private int highestUidNetworkPermission(int uid) {
@@ -664,9 +753,12 @@
* @hide
*/
public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
// If the newly-removed package falls within some VPN's uid range, update Netd with it.
// This needs to happen before the mUidToNetworkPerm update below, since
@@ -680,10 +772,13 @@
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestUidNetworkPermission(uid);
+
+ // Log package removed.
mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
+
if (permission != currentPermission) {
final SparseIntArray apps = new SparseIntArray();
int sdkSandboxUid = -1;
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 7c8e710..d4898b2 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -18,6 +18,8 @@
</target_preparer>
<option name="test-tag" value="ConnectivityCoverageTests" />
+ <!-- Tethering/Connectivity is a SDK 30+ module -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.connectivity.tests.coverage" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d40bc9f..9055861 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -590,6 +590,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedLinkPropertiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
@@ -675,6 +676,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedNetworkCapabilitiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index 9cd8418..e8add6b 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -146,6 +146,9 @@
@After
fun tearDown() {
+ if (!kernelIsAtLeast(5, 4)) {
+ return;
+ }
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { cm.unregisterNetworkCallback(it) }
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 2737258..30e0015 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -17,7 +17,9 @@
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
+import android.net.InetAddresses
import android.net.IpConfiguration
+import android.net.MacAddress
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.platform.test.annotations.AppModeFull
@@ -32,6 +34,7 @@
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import android.content.Context
import org.junit.runner.RunWith
import kotlin.test.assertNull
import kotlin.test.fail
@@ -46,10 +49,15 @@
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_CLIENT
import com.android.networkstack.apishim.common.EthernetManagerShim.ROLE_NONE
import com.android.networkstack.apishim.EthernetManagerShimImpl
+import com.android.testutils.RouterAdvertisementResponder
+import com.android.testutils.TapPacketReader
+import com.android.testutils.waitForIdle
+import java.net.Inet6Address
import java.util.concurrent.Executor
import kotlin.test.assertFalse
import kotlin.test.assertEquals
import kotlin.test.assertTrue
+import java.net.NetworkInterface
private const val TIMEOUT_MS = 1000L
private const val NO_CALLBACK_TIMEOUT_MS = 200L
@@ -66,9 +74,40 @@
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
- private val createdIfaces = ArrayList<TestNetworkInterface>()
+ private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<InterfaceStateListener>()
+ private class EthernetTestInterface(
+ context: Context,
+ private val handler: Handler
+ ) {
+ private val tapInterface: TestNetworkInterface
+ private val packetReader: TapPacketReader
+ private val raResponder: RouterAdvertisementResponder
+ val interfaceName get() = tapInterface.interfaceName
+
+ init {
+ tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
+ val tnm = context.getSystemService(TestNetworkManager::class.java)
+ tnm.createTapInterface(false /* bringUp */)
+ }
+ val mtu = NetworkInterface.getByName(tapInterface.interfaceName).getMTU()
+ packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
+ raResponder = RouterAdvertisementResponder(packetReader)
+ raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
+ InetAddresses.parseNumericAddress("fe80::abcd") as Inet6Address)
+
+ packetReader.startAsyncForTest()
+ raResponder.start()
+ }
+
+ fun destroy() {
+ raResponder.stop()
+ handler.post({ packetReader.stop() })
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+ }
+
private open class EthernetStateListener private constructor(
private val history: ArrayTrackRecord<CallbackEntry>
) : InterfaceStateListener,
@@ -101,7 +140,7 @@
return event as T
}
- fun expectCallback(iface: TestNetworkInterface, state: Int, role: Int) {
+ fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
expectCallback(InterfaceStateChanged(iface.interfaceName, state, role,
if (state != STATE_ABSENT) DEFAULT_IP_CONFIGURATION else null))
}
@@ -116,23 +155,55 @@
}
}
+ @Before
+ fun setUp() {
+ setIncludeTestInterfaces(true)
+ }
+
+ @After
+ fun tearDown() {
+ setIncludeTestInterfaces(false)
+ for (iface in createdIfaces) {
+ iface.destroy()
+ }
+ for (listener in addedListeners) {
+ em.removeInterfaceStateListener(listener)
+ }
+ }
+
+ private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
+ em.addInterfaceStateListener(executor, listener)
+ addedListeners.add(listener)
+ }
+
+ private fun createInterface(): EthernetTestInterface {
+ return EthernetTestInterface(context, Handler(Looper.getMainLooper()))
+ }
+
+ private fun setIncludeTestInterfaces(value: Boolean) {
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(value)
+ }
+ }
+
+ private fun removeInterface(iface: EthernetTestInterface) {
+ iface.destroy()
+ createdIfaces.remove(iface)
+ }
+
@Test
public fun testCallbacks() {
val executor = HandlerExecutor(Handler(Looper.getMainLooper()))
// If an interface exists when the callback is registered, it is reported on registration.
- val iface = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
- }
+ val iface = createInterface()
val listener = EthernetStateListener()
addInterfaceStateListener(executor, listener)
listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
// If an interface appears, existing callbacks see it.
// TODO: fix the up/up/down/up callbacks and only send down/up.
- val iface2 = runAsShell(MANAGE_TEST_NETWORKS) {
- createInterface()
- }
+ val iface2 = createInterface()
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
@@ -149,66 +220,25 @@
listener.assertNoCallback()
}
- @Before
- fun setUp() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(true)
- }
- }
-
- @After
- fun tearDown() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- em.setIncludeTestInterfaces(false)
- for (iface in createdIfaces) {
- if (iface.fileDescriptor.fileDescriptor.valid()) iface.fileDescriptor.close()
- }
- for (listener in addedListeners) {
- em.removeInterfaceStateListener(listener)
- }
- }
- }
-
- private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
- em.addInterfaceStateListener(executor, listener)
- addedListeners.add(listener)
- }
-
- private fun createInterface(): TestNetworkInterface {
- val tnm = context.getSystemService(TestNetworkManager::class.java)
- return tnm.createTapInterface(false /* bringUp */).also { createdIfaces.add(it) }
- }
-
- private fun removeInterface(iface: TestNetworkInterface) {
- iface.fileDescriptor.close()
- createdIfaces.remove(iface)
- }
-
- private fun doTestGetInterfaceList() {
- em.setIncludeTestInterfaces(true)
+ @Test
+ public fun testGetInterfaceList() {
+ setIncludeTestInterfaces(true)
// Create two test interfaces and check the return list contains the interface names.
val iface1 = createInterface()
val iface2 = createInterface()
var ifaces = em.getInterfaceList()
assertTrue(ifaces.size > 0)
- assertTrue(ifaces.contains(iface1.getInterfaceName()))
- assertTrue(ifaces.contains(iface2.getInterfaceName()))
+ assertTrue(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
// Remove one existing test interface and check the return list doesn't contain the
// removed interface name.
removeInterface(iface1)
ifaces = em.getInterfaceList()
- assertFalse(ifaces.contains(iface1.getInterfaceName()))
- assertTrue(ifaces.contains(iface2.getInterfaceName()))
+ assertFalse(ifaces.contains(iface1.interfaceName))
+ assertTrue(ifaces.contains(iface2.interfaceName))
removeInterface(iface2)
}
-
- @Test
- public fun testGetInterfaceList() {
- runAsShell(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS) {
- doTestGetInterfaceList()
- }
- }
}
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 0c4c370..9590f88 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -54,15 +55,20 @@
import android.os.Build;
import android.os.Process;
import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
+import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
+import com.android.networkstack.apishim.VpnManagerShimImpl;
import com.android.networkstack.apishim.common.Ikev2VpnProfileBuilderShim;
import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.apishim.common.VpnManagerShim;
+import com.android.networkstack.apishim.common.VpnProfileStateShim;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -93,8 +99,10 @@
@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)")
public class Ikev2VpnTest {
private static final String TAG = Ikev2VpnTest.class.getSimpleName();
+
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
// Test vectors for IKE negotiation in test mode.
private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
"46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
@@ -184,6 +192,8 @@
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
private static final long TIMEOUT_MS = 15_000;
+ private VpnManagerShim mVmShim = VpnManagerShimImpl.newInstance(sContext);
+
private final X509Certificate mServerRootCa;
private final CertificateAndKey mUserCertKey;
@@ -457,7 +467,7 @@
}
private void checkStartStopVpnProfileBuildsNetworks(@NonNull IkeTunUtils tunUtils,
- boolean testIpv6, boolean requiresValidation)
+ boolean testIpv6, boolean requiresValidation, boolean testSessionKey)
throws Exception {
String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
@@ -476,7 +486,20 @@
.clearCapabilities().addTransportType(TRANSPORT_VPN).build();
sCM.registerNetworkCallback(nr, cb);
- sVpnMgr.startProvisionedVpnProfile();
+ if (testSessionKey) {
+ // testSessionKey will never be true if running on <T
+ // startProvisionedVpnProfileSession() should return a non-null & non-empty random UUID.
+ final String sessionId = mVmShim.startProvisionedVpnProfileSession();
+ assertFalse(TextUtils.isEmpty(sessionId));
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTING, profileState.getState());
+ assertEquals(sessionId, profileState.getSessionId());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ } else {
+ sVpnMgr.startProvisionedVpnProfile();
+ }
// Inject IKE negotiation
int expectedMsgId = 0;
@@ -489,6 +512,14 @@
final Network vpnNetwork = cb.expectCallback(CallbackEntry.AVAILABLE, anyNetwork())
.getNetwork();
+ if (testSessionKey) {
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTED, profileState.getState());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ }
+
cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS, caps -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasCapability(NET_CAPABILITY_INTERNET)
&& !caps.hasCapability(NET_CAPABILITY_VALIDATED)
@@ -519,16 +550,20 @@
private class VerifyStartStopVpnProfileTest implements TestNetworkRunnable.Test {
private final boolean mTestIpv6Only;
private final boolean mRequiresValidation;
+ private final boolean mTestSessionKey;
/**
* Constructs the test
*
* @param testIpv6Only if true, builds a IPv6-only test; otherwise builds a IPv4-only test
* @param requiresValidation whether this VPN should request platform validation
+ * @param testSessionKey if true, start VPN by calling startProvisionedVpnProfileSession()
*/
- VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation) {
+ VerifyStartStopVpnProfileTest(boolean testIpv6Only, boolean requiresValidation,
+ boolean testSessionKey) {
mTestIpv6Only = testIpv6Only;
mRequiresValidation = requiresValidation;
+ mTestSessionKey = testSessionKey;
}
@Override
@@ -537,7 +572,7 @@
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
checkStartStopVpnProfileBuildsNetworks(
- tunUtils, mTestIpv6Only, mRequiresValidation);
+ tunUtils, mTestIpv6Only, mRequiresValidation, mTestSessionKey);
}
@Override
@@ -561,10 +596,14 @@
// Requires shell permission to update appops.
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, false)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */)));
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(false, true)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */)));
}
@Test
@@ -573,9 +612,31 @@
// Requires shell permission to update appops.
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, false)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, false /* requiresValidation */,
+ false /* testSessionKey */)));
runWithShellPermissionIdentity(
- new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(true, true)));
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, true /* requiresValidation */,
+ false /* testSessionKey */)));
+ }
+
+ @IgnoreUpTo(SC_V2)
+ @Test
+ public void testStartProvisionedVpnProfileSession() throws Exception {
+ assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
+ assumeTrue(TestUtils.shouldTestTApis());
+
+ // Requires shell permission to update appops.
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ false /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */)));
+
+ runWithShellPermissionIdentity(
+ new TestNetworkRunnable(new VerifyStartStopVpnProfileTest(
+ true /* testIpv6Only */, false /* requiresValidation */,
+ true /* testSessionKey */)));
}
private static class CertificateAndKey {
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index 9c286d8..a8d908a 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -9,8 +9,8 @@
"mts-tethering",
"vts",
],
+ test_config_template: "AndroidTestTemplate.xml",
min_sdk_version: "31",
- require_root: true,
tidy: false,
srcs: [
"connectivity_native_test.cpp",
@@ -30,5 +30,4 @@
"libutils",
],
compile_multilib: "first",
- defaults: ["connectivity-mainline-presubmit-cc-defaults"],
}
diff --git a/tests/native/AndroidTestTemplate.xml b/tests/native/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44e35a9
--- /dev/null
+++ b/tests/native/AndroidTestTemplate.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Configuration for connectivity {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 4c63cba..545f7b9 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -76,7 +76,6 @@
"java/com/android/server/IpSecServiceParameterizedTest.java",
"java/com/android/server/IpSecServiceRefcountedResourceTest.java",
"java/com/android/server/IpSecServiceTest.java",
- "java/com/android/server/NativeDaemonConnectorTest.java",
"java/com/android/server/NetworkManagementServiceTest.java",
"java/com/android/server/NsdServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
diff --git a/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java b/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
deleted file mode 100644
index e2253a2..0000000
--- a/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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.server;
-
-import static com.android.server.NativeDaemonConnector.appendEscaped;
-import static com.android.server.NativeDaemonConnector.makeCommand;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-
-import com.android.server.NativeDaemonConnector.SensitiveArg;
-
-/**
- * Tests for {@link NativeDaemonConnector}.
- */
-@MediumTest
-public class NativeDaemonConnectorTest extends AndroidTestCase {
- private static final String TAG = "NativeDaemonConnectorTest";
-
- public void testArgumentNormal() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "");
- assertEquals("", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo");
- assertEquals("foo", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\"bar");
- assertEquals("foo\\\"bar", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\\bar\\\"baz");
- assertEquals("foo\\\\bar\\\\\\\"baz", builder.toString());
- }
-
- public void testArgumentWithSpaces() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "foo bar");
- assertEquals("\"foo bar\"", builder.toString());
-
- builder.setLength(0);
- appendEscaped(builder, "foo\"bar\\baz foo");
- assertEquals("\"foo\\\"bar\\\\baz foo\"", builder.toString());
- }
-
- public void testArgumentWithUtf() throws Exception {
- final StringBuilder builder = new StringBuilder();
-
- builder.setLength(0);
- appendEscaped(builder, "caf\u00E9 c\u00F6ffee");
- assertEquals("\"caf\u00E9 c\u00F6ffee\"", builder.toString());
- }
-
- public void testSensitiveArgs() throws Exception {
- final StringBuilder rawBuilder = new StringBuilder();
- final StringBuilder logBuilder = new StringBuilder();
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", "bar", "baz");
- assertEquals("1 foo bar baz\0", rawBuilder.toString());
- assertEquals("1 foo bar baz", logBuilder.toString());
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("bar"), "baz");
- assertEquals("1 foo bar baz\0", rawBuilder.toString());
- assertEquals("1 foo [scrubbed] baz", logBuilder.toString());
-
- rawBuilder.setLength(0);
- logBuilder.setLength(0);
- makeCommand(rawBuilder, logBuilder, 1, "foo", new SensitiveArg("foo bar"), "baz baz",
- new SensitiveArg("wat"));
- assertEquals("1 foo \"foo bar\" \"baz baz\" wat\0", rawBuilder.toString());
- assertEquals("1 foo [scrubbed] \"baz baz\" [scrubbed]", logBuilder.toString());
- }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6b379e8..fb821c3 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -117,23 +117,32 @@
public class PermissionMonitorTest {
private static final int MOCK_USER_ID1 = 0;
private static final int MOCK_USER_ID2 = 1;
+ private static final int MOCK_USER_ID3 = 2;
private static final UserHandle MOCK_USER1 = UserHandle.of(MOCK_USER_ID1);
private static final UserHandle MOCK_USER2 = UserHandle.of(MOCK_USER_ID2);
+ private static final UserHandle MOCK_USER3 = UserHandle.of(MOCK_USER_ID3);
private static final int MOCK_APPID1 = 10001;
private static final int MOCK_APPID2 = 10086;
+ private static final int MOCK_APPID3 = 10110;
private static final int SYSTEM_APPID1 = 1100;
private static final int SYSTEM_APPID2 = 1108;
private static final int VPN_APPID = 10002;
private static final int MOCK_UID11 = MOCK_USER1.getUid(MOCK_APPID1);
private static final int MOCK_UID12 = MOCK_USER1.getUid(MOCK_APPID2);
+ private static final int MOCK_UID13 = MOCK_USER1.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID11 = MOCK_USER1.getUid(SYSTEM_APPID1);
private static final int VPN_UID = MOCK_USER1.getUid(VPN_APPID);
private static final int MOCK_UID21 = MOCK_USER2.getUid(MOCK_APPID1);
private static final int MOCK_UID22 = MOCK_USER2.getUid(MOCK_APPID2);
+ private static final int MOCK_UID23 = MOCK_USER2.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID21 = MOCK_USER2.getUid(SYSTEM_APPID1);
+ private static final int MOCK_UID31 = MOCK_USER3.getUid(MOCK_APPID1);
+ private static final int MOCK_UID32 = MOCK_USER3.getUid(MOCK_APPID2);
+ private static final int MOCK_UID33 = MOCK_USER3.getUid(MOCK_APPID3);
private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
+ private static final String MOCK_PACKAGE3 = "appName3";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
private static final String PARTITION_SYSTEM = "system";
@@ -191,6 +200,7 @@
mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
}
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
@@ -283,6 +293,18 @@
mPermissionMonitor.onPackageAdded(packageName, uid);
}
+ private void removePackage(String packageName, int uid) {
+ final String[] oldPackages = mPackageManager.getPackagesForUid(uid);
+ // If the package isn't existed, no need to remove it.
+ if (!CollectionUtils.contains(oldPackages, packageName)) return;
+
+ // Remove the package if this uid is shared with other packages.
+ final String[] newPackages = Arrays.stream(oldPackages).filter(e -> !e.equals(packageName))
+ .toArray(String[]::new);
+ doReturn(newPackages).when(mPackageManager).getPackagesForUid(eq(uid));
+ mPermissionMonitor.onPackageRemoved(packageName, uid);
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = systemPackageInfoWithPermissions();
@@ -791,6 +813,7 @@
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11);
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
mPermissionMonitor.startMonitoring();
final Set<UidRange> vpnRange = Set.of(UidRange.createForUser(MOCK_USER1),
@@ -881,7 +904,7 @@
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
- // Install another package with the same uid and no permissions should not cause the app id
+ // Install another package with the same uid and no permissions should not cause the appId
// to lose permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
@@ -1249,4 +1272,211 @@
assertTrue(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_NETWORK));
assertFalse(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_SYSTEM));
}
+
+ private void prepareMultiUserPackages() {
+ // MOCK_USER1 has installed 3 packages
+ // mockApp1 has no permission and share MOCK_APPID1.
+ // mockApp2 has INTERNET permission and share MOCK_APPID2.
+ // mockApp3 has UPDATE_DEVICE_STATS permission and share MOCK_APPID3.
+ final List<PackageInfo> pkgs1 = List.of(
+ buildPackageInfo("mockApp1", MOCK_UID11),
+ buildPackageInfo("mockApp2", MOCK_UID12, INTERNET),
+ buildPackageInfo("mockApp3", MOCK_UID13, UPDATE_DEVICE_STATS));
+
+ // MOCK_USER2 has installed 2 packages
+ // mockApp4 has UPDATE_DEVICE_STATS permission and share MOCK_APPID1.
+ // mockApp5 has INTERNET permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs2 = List.of(
+ buildPackageInfo("mockApp4", MOCK_UID21, UPDATE_DEVICE_STATS),
+ buildPackageInfo("mockApp5", MOCK_UID23, INTERNET));
+
+ // MOCK_USER3 has installed 1 packages
+ // mockApp6 has UPDATE_DEVICE_STATS permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs3 = List.of(
+ buildPackageInfo("mockApp6", MOCK_UID32, UPDATE_DEVICE_STATS));
+
+ doReturn(pkgs1).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID1));
+ doReturn(pkgs2).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID2));
+ doReturn(pkgs3).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID3));
+ }
+
+ private void addUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserAdded(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ private void removeUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserRemoved(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_UserAddedRemoved() {
+ prepareMultiUserPackages();
+
+ // Add MOCK_USER1 and verify the permissions with each appIds.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Add MOCK_USER2 and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_INTERNET, PERMISSION_TRAFFIC_ALL);
+
+ // Add MOCK_USER3 and verify the permissions upgrade on MOCK_APPID2.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_TRAFFIC_ALL, PERMISSION_TRAFFIC_ALL);
+
+ // Remove MOCK_USER2 and verify the permissions downgrade on MOCK_APPID1 & MOCK_APPID3.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_NONE, PERMISSION_TRAFFIC_ALL,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Remove MOCK_USER1 and verify the permissions downgrade on all appIds.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_UNINSTALLED,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_UNINSTALLED);
+
+ // Add MOCK_USER2 back and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_INTERNET);
+
+ // Remove MOCK_USER3 and verify the permissions downgrade on MOCK_APPID2.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UNINSTALLED, PERMISSION_INTERNET);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageAdded() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ final int[] netdPermissions = {PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_TRAFFIC_ALL};
+ final String[][] grantPermissions = {new String[]{}, new String[]{INTERNET},
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{INTERNET, UPDATE_DEVICE_STATS}};
+
+ // Verify that the permission combination is expected when same appId package is installed
+ // on another user. List the expected permissions below.
+ // NONE + NONE = NONE
+ // NONE + INTERNET = INTERNET
+ // NONE + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // NONE + ALL = ALL
+ // INTERNET + NONE = INTERNET
+ // INTERNET + INTERNET = INTERNET
+ // INTERNET + UPDATE_DEVICE_STATS = ALL
+ // INTERNET + ALL = ALL
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + INTERNET = ALL
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + ALL = ALL
+ // ALL + NONE = ALL
+ // ALL + INTERNET = ALL
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ // ALL + ALL = ALL
+ for (int i = 0, num = 0; i < netdPermissions.length; i++) {
+ final int current = netdPermissions[i];
+ final String[] user1Perm = grantPermissions[i];
+ for (int j = 0; j < netdPermissions.length; j++) {
+ final int appId = MOCK_APPID1 + num;
+ final int added = netdPermissions[j];
+ final String[] user2Perm = grantPermissions[j];
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(current, appId);
+
+ // Add package which share the same appId on MOCK_USER2, and verify the permission
+ // has combined.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm((current | added), appId);
+ num++;
+ }
+ }
+ }
+
+ private void verifyAppIdPermissionsAfterPackageRemoved(int appId, int expectedPerm,
+ String[] user1Perm, String[] user2Perm) throws Exception {
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Add two packages which share the same appId and don't declare permission on
+ // MOCK_USER2. Verify the permission has no change.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ addPackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove one packages from MOCK_USER2. Verify the permission has no change too.
+ removePackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove last packages from MOCK_USER2. Verify the permission has still no change.
+ removePackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageRemoved() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ int appId = MOCK_APPID1;
+ // Verify that the permission combination is expected when same appId package is removed on
+ // another user. List the expected permissions below.
+ /***** NONE *****/
+ // NONE + NONE = NONE
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_NONE, new String[]{}, new String[]{});
+
+ /***** INTERNET *****/
+ // INTERNET + NONE = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{});
+
+ // INTERNET + INTERNET = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{INTERNET});
+
+ /***** UPDATE_DEVICE_STATS *****/
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{});
+
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ /***** ALL *****/
+ // ALL + NONE = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{});
+
+ // ALL + INTERNET = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{INTERNET});
+
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ // ALL + ALL = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS},
+ new String[]{INTERNET, UPDATE_DEVICE_STATS});
+
+ /***** UNINSTALL *****/
+ // UNINSTALL + UNINSTALL = UNINSTALL
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId, PERMISSION_NONE, new String[]{}, new String[]{});
+ removePackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, appId);
+ }
}