Merge cherrypicks of ['googleplex-android-review.googlesource.com/27140479', 'googleplex-android-review.googlesource.com/27053555', 'googleplex-android-review.googlesource.com/27250425', 'googleplex-android-review.googlesource.com/27094197', 'googleplex-android-review.googlesource.com/27431144', 'googleplex-android-review.googlesource.com/27263999', 'googleplex-android-review.googlesource.com/27801400'] into 24Q2-release.
Change-Id: Ibeca3b997ee82d1d30cd8872a3ff99b405c8a93d
diff --git a/core/java/android/app/AppOpInfo.java b/core/java/android/app/AppOpInfo.java
index 5268ec4..a0f0cca 100644
--- a/core/java/android/app/AppOpInfo.java
+++ b/core/java/android/app/AppOpInfo.java
@@ -88,7 +88,7 @@
/**
* This specifies whether each option is only allowed to be read
- * by apps with manage appops permission.
+ * by apps with privileged appops permission.
*/
public final boolean restrictRead;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2afc78c..62f9b59 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3268,7 +3268,7 @@
}
/**
- * Retrieve whether the op can be read by apps with manage appops permission.
+ * Retrieve whether the op can be read by apps with privileged appops permission.
* @hide
*/
public static boolean opRestrictsRead(int op) {
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 2077d73..ba45d17 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
@@ -29,6 +29,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
@@ -109,6 +110,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]. */
@@ -134,6 +136,7 @@
footerActionsInteractor,
falsingManager,
globalActionsDialogLite,
+ activityStarter,
showPowerButton,
)
}
@@ -145,6 +148,7 @@
footerActionsInteractor: FooterActionsInteractor,
falsingManager: FalsingManager,
globalActionsDialogLite: GlobalActionsDialogLite,
+ activityStarter: ActivityStarter,
showPowerButton: Boolean,
): FooterActionsViewModel {
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
@@ -169,7 +173,14 @@
return
}
- footerActionsInteractor.showForegroundServicesDialog(expandable)
+ activityStarter.dismissKeyguardThenExecute(
+ {
+ footerActionsInteractor.showForegroundServicesDialog(expandable)
+ false /* if the dismiss should be deferred */
+ },
+ null /* cancelAction */,
+ true /* afterKeyguardGone */
+ )
}
fun onUserSwitcherClicked(expandable: Expandable) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 23d657d..89bb16c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -27,5 +27,53 @@
val Kosmos.qsEventLogger: QsEventLoggerFake by
Kosmos.Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) }
-var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
-var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory>()
+
+val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
+
+val Kosmos.footerActionsController by Fixture {
+ FooterActionsController(
+ fgsManagerController = fgsManagerController,
+ )
+}
+
+val Kosmos.qsSecurityFooterUtils by Fixture {
+ QSSecurityFooterUtils(
+ applicationContext,
+ devicePolicyManager,
+ userTracker,
+ fakeExecutorHandler,
+ activityStarter,
+ securityController,
+ looper,
+ dialogTransitionAnimator,
+ )
+}
+
+val Kosmos.footerActionsInteractor by Fixture {
+ FooterActionsInteractorImpl(
+ activityStarter = activityStarter,
+ metricsLogger = metricsLogger,
+ uiEventLogger = uiEventLogger,
+ deviceProvisionedController = deviceProvisionedController,
+ qsSecurityFooterUtils = qsSecurityFooterUtils,
+ fgsManagerController = fgsManagerController,
+ userSwitcherInteractor = userSwitcherInteractor,
+ securityRepository = securityRepository,
+ foregroundServicesRepository = foregroundServicesRepository,
+ userSwitcherRepository = userSwitcherRepository,
+ broadcastDispatcher = broadcastDispatcher,
+ bgDispatcher = testDispatcher,
+ )
+}
+
+val Kosmos.footerActionsViewModelFactory by Fixture {
+ FooterActionsViewModel.Factory(
+ context = applicationContext,
+ falsingManager = falsingManager,
+ footerActionsInteractor = footerActionsInteractor,
+ globalActionsDialogLiteProvider = { mock() },
+ activityStarter,
+ showPowerButton = true,
+ )
+}
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 cddb007..cde5d4e 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) {
settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled)
@@ -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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index fb62785..3908746 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1561,19 +1561,29 @@
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops,
String persistentDeviceId) {
ArrayList<AppOpsManager.OpEntry> resOps = null;
+ boolean shouldReturnRestrictedAppOps = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
if (ops == null) {
resOps = new ArrayList<>();
- for (int j=0; j<pkgOps.size(); j++) {
+ for (int j = 0; j < pkgOps.size(); j++) {
Op curOp = pkgOps.valueAt(j);
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
} else {
- for (int j=0; j<ops.length; j++) {
+ for (int j = 0; j < ops.length; j++) {
Op curOp = pkgOps.get(ops[j]);
if (curOp != null) {
if (resOps == null) {
resOps = new ArrayList<>();
}
+ if (opRestrictsRead(curOp.op) && !shouldReturnRestrictedAppOps) {
+ continue;
+ }
resOps.add(getOpEntryForResult(curOp, persistentDeviceId));
}
}
@@ -4226,10 +4236,21 @@
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
- // Enforce manage appops permission if it's a restricted read op.
+ // Enforce privileged appops permission if it's a restricted read op.
if (opRestrictsRead(op)) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ if (!(mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED || mContext.checkPermission(
+ Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("verifyIncomingOp: uid " + Binder.getCallingUid()
+ + " does not have any of {MANAGE_APPOPS, GET_APP_OPS_STATS, "
+ + "MANAGE_APP_OPS_MODES}");
+ }
}
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 981c4c0..8655b3e 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -99,6 +99,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;
@@ -108,6 +109,9 @@
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.uri.GrantUri;
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;
/**
@@ -123,6 +127,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;
@@ -946,6 +957,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);
@@ -960,6 +972,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);
@@ -1012,6 +1025,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.
*
@@ -1035,6 +1061,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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 18ac0e7..852a145 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3065,12 +3065,13 @@
}
void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) {
+ final int baseType = getBaseType();
if (mSession.mCanAddInternalSystemWindow
- || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) {
+ || (!isSystemAlertWindowType(baseType) && baseType != TYPE_TOAST)) {
return;
}
- if (mAttrs.type == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay()
+ if (baseType == TYPE_APPLICATION_OVERLAY && mAttrs.isSystemApplicationOverlay()
&& mSession.mCanCreateSystemApplicationOverlay) {
return;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 42ac998..ad50116 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -357,8 +357,7 @@
@Override
boolean shouldWrite() {
- return (mDeviceOwner != null) || (mSystemUpdatePolicy != null)
- || (mSystemUpdateInfo != null);
+ return true;
}
@Override
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 03b695d..2594987 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1235,6 +1235,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 2ff21ad..93809bb 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.Manifest;
@@ -43,6 +45,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;
@@ -939,13 +942,24 @@
}
/**
- * @return true if any application in foreground have set restrict_usb_overlay_activities as
- * true in manifest file. The application needs to have MANAGE_USB permission.
+ * @return true if the user has not finished the setup process or if there are any
+ * foreground applications with MANAGE_USB permission and restrict_usb_overlay_activities
+ * enabled in the manifest file.
*/
private boolean shouldRestrictOverlayActivities() {
if (!Flags.allowRestrictionOfOverlayActivities()) return false;
+ 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;
+ }
+
List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager
.getRunningAppProcesses();
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index a16a7ea..125a825 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -42,6 +42,9 @@
"libmultiplejvmtiagentsinterferenceagent",
"libstaticjvmtiagent",
],
+ libs: [
+ "android.test.mock",
+ ],
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
index 4780d8a..87b26a6 100644
--- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.usbtest;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
+
import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES;
import static org.mockito.ArgumentMatchers.any;
@@ -32,16 +34,20 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.Property;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.hardware.usb.UsbDevice;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.usb.UsbHandlerManager;
import com.android.server.usb.UsbProfileGroupSettingsManager;
import com.android.server.usb.UsbSettingsManager;
@@ -69,6 +75,7 @@
public class UsbProfileGroupSettingsManagerTest {
private static final String TEST_PACKAGE_NAME = "testPkg";
+
@Mock
private Context mContext;
@Mock
@@ -85,43 +92,78 @@
private UserManager mUserManager;
@Mock
private UsbUserSettingsManager mUsbUserSettingsManager;
- @Mock private Property mProperty;
- private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo;
- private PackageInfo mPackageInfo;
- private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+ @Mock
+ private Property mRestrictUsbOverlayActivitiesProperty;
+ @Mock
+ private UsbDevice mUsbDevice;
+
+ private MockContentResolver mContentResolver;
private MockitoSession mStaticMockSession;
+ private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- mRunningAppProcessInfo = new ActivityManager.RunningAppProcessInfo();
- mRunningAppProcessInfo.pkgList = new String[]{TEST_PACKAGE_NAME};
- mPackageInfo = new PackageInfo();
- mPackageInfo.packageName = TEST_PACKAGE_NAME;
- mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
-
- when(mContext.getPackageManager()).thenReturn(mPackageManager);
- when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
- when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
- when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
- .thenReturn(mContext);
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
-
- mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(mContext, mUserHandle,
- mUsbSettingsManager, mUsbHandlerManager);
-
mStaticMockSession = ExtendedMockito.mockitoSession()
.mockStatic(Flags.class)
.strictness(Strictness.WARN)
.startMocking();
- when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
- when(mPackageManager.getProperty(eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES),
- eq(TEST_PACKAGE_NAME))).thenReturn(mProperty);
+ when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
when(mUserManager.getEnabledProfiles(anyInt()))
.thenReturn(List.of(Mockito.mock(UserInfo.class)));
- when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
+
+ mContentResolver = new MockContentResolver();
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+ when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
+ .thenReturn(mContext);
+
+ mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(
+ mContext, mUserHandle, mUsbSettingsManager, mUsbHandlerManager);
+
+ setupDefaultConfiguration();
+ }
+
+ /**
+ * Setups the following configuration
+ *
+ * <ul>
+ * <li>Flag is enabled
+ * <li>Device setup has completed
+ * <li>There is a foreground activity with MANAGE_USB permission
+ * <li>The foreground activity has PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES enabled
+ * </ul>
+ */
+ private void setupDefaultConfiguration() throws NameNotFoundException {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+
+ Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1);
+
+ ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo =
+ new ActivityManager.RunningAppProcessInfo();
+ mRunningAppProcessInfo.pkgList = new String[] { TEST_PACKAGE_NAME };
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+
+ PackageInfo mPackageInfo = new PackageInfo();
+ mPackageInfo.packageName = TEST_PACKAGE_NAME;
+ mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+ when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
+ when(mPackageManager.getPackagesHoldingPermissions(
+ new String[] { android.Manifest.permission.MANAGE_USB },
+ PackageManager.MATCH_SYSTEM_ONLY))
+ .thenReturn(List.of(mPackageInfo));
+
+ when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(true);
+ when(mPackageManager.getProperty(
+ eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES), eq(TEST_PACKAGE_NAME)))
+ .thenReturn(mRestrictUsbOverlayActivitiesProperty);
}
@After
@@ -130,66 +172,59 @@
}
@Test
- public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() {
- when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
+ verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_noForegroundActivity_resolveActivityCalled() {
when(mActivityManager.getRunningAppProcesses()).thenReturn(new ArrayList<>());
- when(mPackageManager.getPackagesHoldingPermissions(
- new String[]{android.Manifest.permission.MANAGE_USB},
- PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
- UsbDevice device = Mockito.mock(UsbDevice.class);
- mUsbProfileGroupSettingsManager.deviceAttached(device);
+
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
}
@Test
public void testDeviceAttached_noForegroundActivityWithUsbPermission_resolveActivityCalled() {
- when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
- when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
when(mPackageManager.getPackagesHoldingPermissions(
- new String[]{android.Manifest.permission.MANAGE_USB},
- PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(new ArrayList<>());
- UsbDevice device = Mockito.mock(UsbDevice.class);
- mUsbProfileGroupSettingsManager.deviceAttached(device);
+ new String[] { android.Manifest.permission.MANAGE_USB },
+ PackageManager.MATCH_SYSTEM_ONLY))
+ .thenReturn(new ArrayList<>());
+
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
}
@Test
- public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
- when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
- when(mProperty.getBoolean()).thenReturn(true);
- when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
- when(mPackageManager.getPackagesHoldingPermissions(
- new String[]{android.Manifest.permission.MANAGE_USB},
- PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
- UsbDevice device = Mockito.mock(UsbDevice.class);
- mUsbProfileGroupSettingsManager.deviceAttached(device);
- verify(mUsbUserSettingsManager, times(0))
- .queryIntentActivities(any(Intent.class));
- }
+ public void testDeviceAttached_restricUsbOverlayPropertyDisabled_resolveActivityCalled() {
+ when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false);
- @Test
- public void testDeviceAttached_foregroundActivityWithoutManifestField_resolveActivityCalled() {
- when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
- when(mProperty.getBoolean()).thenReturn(false);
- when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
- when(mPackageManager.getPackagesHoldingPermissions(
- new String[]{android.Manifest.permission.MANAGE_USB},
- PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
- UsbDevice device = Mockito.mock(UsbDevice.class);
- mUsbProfileGroupSettingsManager.deviceAttached(device);
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
}
@Test
- public void testDeviceAttached_flagFalseForegroundActivity_resolveActivityCalled() {
+ public void testDeviceAttached_flagFalse_resolveActivityCalled() {
when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(false);
- when(mProperty.getBoolean()).thenReturn(true);
- when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
- when(mPackageManager.getPackagesHoldingPermissions(
- new String[]{android.Manifest.permission.MANAGE_USB},
- PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
- UsbDevice device = Mockito.mock(UsbDevice.class);
- mUsbProfileGroupSettingsManager.deviceAttached(device);
+
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
}
+
+ @Test
+ public void
+ testDeviceAttached_setupNotCompleteAndNoBlockingActivities_resolveActivityNotCalled() {
+ when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false);
+ Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 0);
+
+ mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice);
+
+ verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class));
+ }
}