Merge "Hide SAW subwindows" into udc-dev
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 6952e5d..f7a22f5 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -3,3 +3,4 @@
include /services/core/java/com/android/server/display/OWNERS
quxiangfang@google.com
+donpaul@google.com
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 993e4e7..765901a 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -93,6 +93,9 @@
throw ex;
}
+ if (peer.getUid() != Process.SYSTEM_UID) {
+ throw new ZygoteSecurityException("Only system UID is allowed to connect to Zygote.");
+ }
isEof = false;
}
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 2b5b8f7..b586b9c 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -353,6 +353,18 @@
return result;
}
+static uid_t getSocketPeerUid(int socket, const std::function<void(const std::string&)>& fail_fn) {
+ struct ucred credentials;
+ socklen_t cred_size = sizeof credentials;
+ if (getsockopt(socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
+ || cred_size != sizeof credentials) {
+ fail_fn(CREATE_ERROR("Failed to get socket credentials, %s",
+ strerror(errno)));
+ }
+
+ return credentials.uid;
+}
+
// Read all lines from the current command into the buffer, and then reset the buffer, so
// we will start reading again at the beginning of the command, starting with the argument
// count. And we don't need access to the fd to do so.
@@ -412,19 +424,12 @@
fail_fn_z("Failed to retrieve session socket timeout");
}
- struct ucred credentials;
- socklen_t cred_size = sizeof credentials;
- if (getsockopt(n_buffer->getFd(), SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1
- || cred_size != sizeof credentials) {
- fail_fn_1(CREATE_ERROR("ForkRepeatedly failed to get initial credentials, %s",
- strerror(errno)));
+ uid_t peerUid = getSocketPeerUid(session_socket, fail_fn_1);
+ if (peerUid != static_cast<uid_t>(expected_uid)) {
+ return JNI_FALSE;
}
-
bool first_time = true;
do {
- if (credentials.uid != expected_uid) {
- return JNI_FALSE;
- }
n_buffer->readAllLines(first_time ? fail_fn_1 : fail_fn_n);
n_buffer->reset();
int pid = zygote::forkApp(env, /* no pipe FDs */ -1, -1, session_socket_fds,
@@ -454,30 +459,56 @@
// Clear buffer and get count from next command.
n_buffer->clear();
for (;;) {
+ bool valid_session_socket = true;
// Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */));
if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
if (n_buffer->getCount(fail_fn_z) != 0) {
break;
- } // else disconnected;
+ } else {
+ // Session socket was disconnected
+ valid_session_socket = false;
+ close(session_socket);
+ }
} else if (poll_res == 0 || (fd_structs[ZYGOTE_IDX].revents & POLLIN) == 0) {
fail_fn_z(
CREATE_ERROR("Poll returned with no descriptors ready! Poll returned %d", poll_res));
}
- // We've now seen either a disconnect or connect request.
- close(session_socket);
- int new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ int new_fd = -1;
+ do {
+ // We've now seen either a disconnect or connect request.
+ new_fd = TEMP_FAILURE_RETRY(accept(zygote_socket_fd, nullptr, nullptr));
+ if (new_fd == -1) {
+ fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ }
+ uid_t newPeerUid = getSocketPeerUid(new_fd, fail_fn_1);
+ if (newPeerUid != static_cast<uid_t>(expected_uid)) {
+ ALOGW("Dropping new connection with a mismatched uid %d\n", newPeerUid);
+ close(new_fd);
+ new_fd = -1;
+ } else {
+ // If we still have a valid session socket, close it now
+ if (valid_session_socket) {
+ close(session_socket);
+ }
+ valid_session_socket = true;
+ }
+ } while (!valid_session_socket);
+
+ // At this point we either have a valid new connection (new_fd > 0), or
+ // an existing session socket we can poll on
if (new_fd == -1) {
- fail_fn_z(CREATE_ERROR("Accept(%d) failed: %s", zygote_socket_fd, strerror(errno)));
+ // The new connection wasn't valid, and we still have an old one; retry polling
+ continue;
}
if (new_fd != session_socket) {
- // Move new_fd back to the old value, so that we don't have to change Java-level data
- // structures to reflect a change. This implicitly closes the old one.
- if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
- fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
- new_fd, session_socket, strerror(errno)));
- }
- close(new_fd); // On Linux, fd is closed even if EINTR is returned.
+ // Move new_fd back to the old value, so that we don't have to change Java-level data
+ // structures to reflect a change. This implicitly closes the old one.
+ if (TEMP_FAILURE_RETRY(dup2(new_fd, session_socket)) != session_socket) {
+ fail_fn_z(CREATE_ERROR("Failed to move fd %d to %d: %s",
+ new_fd, session_socket, strerror(errno)));
+ }
+ close(new_fd); // On Linux, fd is closed even if EINTR is returned.
}
// If we ever return, we effectively reuse the old Java ZygoteConnection.
// None of its state needs to change.
@@ -489,13 +520,6 @@
fail_fn_z(CREATE_ERROR("Failed to set send timeout for socket %d: %s",
session_socket, strerror(errno)));
}
- if (getsockopt(session_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &cred_size) == -1) {
- fail_fn_z(CREATE_ERROR("ForkMany failed to get credentials: %s", strerror(errno)));
- }
- if (cred_size != sizeof credentials) {
- fail_fn_z(CREATE_ERROR("ForkMany credential size = %d, should be %d",
- cred_size, static_cast<int>(sizeof credentials)));
- }
}
first_time = false;
} while (n_buffer->isSimpleForkCommand(minUid, fail_fn_n));
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index 025e831..da29828 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -24,6 +24,8 @@
import static android.util.XmlTest.doVerifyWrite;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.os.PersistableBundle;
@@ -41,12 +43,15 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@RunWith(AndroidJUnit4.class)
public class BinaryXmlTest {
+ private static final int MAX_UNSIGNED_SHORT = 65_535;
+
/**
* Verify that we can write and read large numbers of interned
* {@link String} values.
@@ -170,4 +175,49 @@
}
}
}
+
+ @Test
+ public void testAttributeBytes_BinaryDataOverflow() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1];
+ assertThrows(IOException.class,
+ () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex",
+ testBytes));
+
+ assertThrows(IOException.class,
+ () -> out.attributeBytesBase64(/* namespace */ null, /* name */
+ "attributeBytesBase64", testBytes));
+ }
+
+ @Test
+ public void testAttributeBytesHex_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesHex fails with exception: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAttributeBytesBase64_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64",
+ testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesBase64 fails with exception: " + e.toString());
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index b3596a2..b9258ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -30,6 +30,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
@@ -54,6 +55,7 @@
private val footerActionsInteractor: FooterActionsInteractor,
private val falsingManager: FalsingManager,
private val globalActionsDialogLite: GlobalActionsDialogLite,
+ private val activityStarter: ActivityStarter,
showPowerButton: Boolean,
) {
/** The context themed with the Quick Settings colors. */
@@ -222,7 +224,14 @@
return
}
- footerActionsInteractor.showForegroundServicesDialog(expandable)
+ activityStarter.dismissKeyguardThenExecute(
+ {
+ footerActionsInteractor.showForegroundServicesDialog(expandable)
+ false /* if the dismiss should be deferred */
+ },
+ null /* cancelAction */,
+ true /* afterKeyguardGone */
+ )
}
private fun onUserSwitcherClicked(expandable: Expandable) {
@@ -283,6 +292,7 @@
private val falsingManager: FalsingManager,
private val footerActionsInteractor: FooterActionsInteractor,
private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
+ private val activityStarter: ActivityStarter,
@Named(PM_LITE_ENABLED) private val showPowerButton: Boolean,
) {
/** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */
@@ -308,6 +318,7 @@
footerActionsInteractor,
falsingManager,
globalActionsDialogLite,
+ activityStarter,
showPowerButton,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 1a893f8..a94acf9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -68,6 +68,7 @@
private val testableLooper: TestableLooper,
private val scheduler: TestCoroutineScheduler,
) {
+ private val mockActivityStarter: ActivityStarter = mock<ActivityStarter>()
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId)
@@ -90,13 +91,14 @@
footerActionsInteractor,
falsingManager,
globalActionsDialogLite,
+ mockActivityStarter,
showPowerButton,
)
}
/** Create a [FooterActionsInteractor] to be used in tests. */
fun footerActionsInteractor(
- activityStarter: ActivityStarter = mock(),
+ activityStarter: ActivityStarter = mockActivityStarter,
metricsLogger: MetricsLogger = FakeMetricsLogger(),
uiEventLogger: UiEventLogger = UiEventLoggerFake(),
deviceProvisionedController: DeviceProvisionedController = mock(),
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index aafff2c..f256756 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -96,6 +96,7 @@
import android.window.SizeConfigurationBuckets;
import android.window.TransitionInfo;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.common.ProtoLog;
@@ -104,6 +105,9 @@
import com.android.server.pm.KnownPackages;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.uri.NeededUriGrants;
+import com.android.server.utils.quota.Categorizer;
+import com.android.server.utils.quota.Category;
+import com.android.server.utils.quota.CountQuotaTracker;
import com.android.server.vr.VrManagerInternal;
/**
@@ -119,6 +123,13 @@
private final ActivityTaskSupervisor mTaskSupervisor;
private final Context mContext;
+ // Prevent malicious app abusing the Activity#setPictureInPictureParams API
+ @VisibleForTesting CountQuotaTracker mSetPipAspectRatioQuotaTracker;
+ // Limit to 60 times / minute
+ private static final int SET_PIP_ASPECT_RATIO_LIMIT = 60;
+ // The timeWindowMs here can not be smaller than QuotaTracker#MIN_WINDOW_SIZE_MS
+ private static final long SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS = 60_000;
+
/** Wrapper around VoiceInteractionServiceManager. */
private AssistUtils mAssistUtils;
@@ -869,6 +880,7 @@
public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) {
final long origId = Binder.clearCallingIdentity();
try {
+ ensureSetPipAspectRatioQuotaTracker();
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"enterPictureInPictureMode", token, params);
@@ -883,6 +895,7 @@
public void setPictureInPictureParams(IBinder token, final PictureInPictureParams params) {
final long origId = Binder.clearCallingIdentity();
try {
+ ensureSetPipAspectRatioQuotaTracker();
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"setPictureInPictureParams", token, params);
@@ -935,6 +948,19 @@
}
/**
+ * Initialize the {@link #mSetPipAspectRatioQuotaTracker} if applicable, which should happen
+ * out of {@link #mGlobalLock} to avoid deadlock (AM lock is used in QuotaTrack ctor).
+ */
+ private void ensureSetPipAspectRatioQuotaTracker() {
+ if (mSetPipAspectRatioQuotaTracker == null) {
+ mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext,
+ Categorizer.SINGLE_CATEGORIZER);
+ mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS);
+ }
+ }
+
+ /**
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
*
@@ -958,6 +984,19 @@
+ ": Current activity does not support picture-in-picture.");
}
+ // Rate limit how frequent an app can request aspect ratio change via
+ // Activity#setPictureInPictureParams
+ final int userId = UserHandle.getCallingUserId();
+ if (r.pictureInPictureArgs.hasSetAspectRatio()
+ && params.hasSetAspectRatio()
+ && !r.pictureInPictureArgs.getAspectRatio().equals(
+ params.getAspectRatio())
+ && !mSetPipAspectRatioQuotaTracker.noteEvent(
+ userId, r.packageName, "setPipAspectRatio")) {
+ throw new IllegalStateException(caller
+ + ": Too many PiP aspect ratio change requests from " + r.packageName);
+ }
+
final float minAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
final float maxAspectRatio = mContext.getResources().getFloat(
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 600681f..92135e4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1104,6 +1104,12 @@
assertNotNull(o.mInfo);
assertNotNull(o.mInfo.pictureInPictureParams);
+ // Bypass the quota check, which causes NPE in current test setup.
+ if (mWm.mAtmService.mActivityClientController.mSetPipAspectRatioQuotaTracker != null) {
+ mWm.mAtmService.mActivityClientController.mSetPipAspectRatioQuotaTracker
+ .setEnabled(false);
+ }
+
final PictureInPictureParams p2 = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(3, 4)).build();
mWm.mAtmService.mActivityClientController.setPictureInPictureParams(record.token, p2);
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index f916660..47f5656 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -16,6 +16,8 @@
package com.android.server.usb;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import android.annotation.NonNull;
@@ -42,6 +44,7 @@
import android.os.Environment;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.service.usb.UsbProfileGroupSettingsManagerProto;
import android.service.usb.UsbSettingsAccessoryPreferenceProto;
import android.service.usb.UsbSettingsDevicePreferenceProto;
@@ -914,10 +917,28 @@
return;
}
+ if (shouldRestrictOverlayActivities()) {
+ return;
+ }
+
// Start activity with registered intent
resolveActivity(intent, matches, defaultActivity, device, null);
}
+ private boolean shouldRestrictOverlayActivities() {
+ if (Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ USER_SETUP_COMPLETE,
+ /* defaultValue= */ 1,
+ UserHandle.CURRENT.getIdentifier())
+ == 0) {
+ Slog.d(TAG, "restricting usb overlay activities as setup is not complete");
+ return true;
+ }
+
+ return false;
+ }
+
public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
final Intent intent = createDeviceAttachedIntent(device);