Avoiding overlay activities when usb device is attached.
Adding property android.app.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES
in manifest. This property will be honored only for applications which
have MANAGE_USB permission(i.e only priviledged applications). If this
property is set on application which is in foreground when usb device is
attached then, usb flow would not resolveActivity.
If there is no application in foreground with this field set,
then usb attachment will start overlay activities.
Bug: 307231174
Test: Manually tested the flow.
Test: atest UsbManagerTests
Change-Id: Ica4f557ec5eeb0887373f5bd1a44f05f1bdfeecd
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3a0a6ab..e8ffe54 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -34,8 +34,20 @@
"android.hardware.usb-V1.2-java",
"android.hardware.usb-V1.3-java",
"android.hardware.usb-V3-java",
+ "usb_flags_lib",
],
lint: {
baseline_filename: "lint-baseline.xml",
},
}
+
+aconfig_declarations {
+ name: "usb_flags",
+ package: "com.android.server.usb.flags",
+ srcs: ["**/usb_flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "usb_flags_lib",
+ aconfig_declarations: "usb_flags",
+}
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index f311274..d83ff1f 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -37,7 +37,7 @@
*
* @hide
*/
-class UsbHandlerManager {
+public class UsbHandlerManager {
private static final String LOG_TAG = UsbHandlerManager.class.getSimpleName();
private final Context mContext;
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index f916660..2ff21ad 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -18,6 +18,7 @@
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -62,6 +63,7 @@
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.usb.flags.Flags;
import com.android.server.utils.EventLogger;
import libcore.io.IoUtils;
@@ -80,8 +82,20 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
-class UsbProfileGroupSettingsManager {
+public class UsbProfileGroupSettingsManager {
+ /**
+ * <application> level property that an app can specify to restrict any overlaying of
+ * activities when usb device is attached.
+ *
+ *
+ * <p>This should only be set by privileged apps having {@link Manifest.permission#MANAGE_USB}
+ * permission.
+ * @hide
+ */
+ public static final String PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES =
+ "android.app.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES";
private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -101,6 +115,8 @@
private final PackageManager mPackageManager;
+ private final ActivityManager mActivityManager;
+
private final UserManager mUserManager;
private final @NonNull UsbSettingsManager mSettingsManager;
@@ -224,7 +240,7 @@
* @param settingsManager The settings manager of the service
* @param usbResolveActivityManager The resovle activity manager of the service
*/
- UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
+ public UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
@NonNull UsbSettingsManager settingsManager,
@NonNull UsbHandlerManager usbResolveActivityManager) {
if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
@@ -238,6 +254,7 @@
mContext = context;
mPackageManager = context.getPackageManager();
+ mActivityManager = context.getSystemService(ActivityManager.class);
mSettingsManager = settingsManager;
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -895,7 +912,10 @@
// Send broadcast to running activities with registered intent
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- resolveActivity(intent, device, true /* showMtpNotification */);
+ //resolving activities only if there is no foreground activity restricting it.
+ if (!shouldRestrictOverlayActivities()) {
+ resolveActivity(intent, device, true /* showMtpNotification */);
+ }
}
private void resolveActivity(Intent intent, UsbDevice device, boolean showMtpNotification) {
@@ -918,6 +938,63 @@
resolveActivity(intent, matches, defaultActivity, device, null);
}
+ /**
+ * @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.
+ */
+ private boolean shouldRestrictOverlayActivities() {
+
+ if (!Flags.allowRestrictionOfOverlayActivities()) return false;
+
+ List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager
+ .getRunningAppProcesses();
+
+ List<String> filteredAppProcessInfos = new ArrayList<>();
+ boolean shouldRestrictOverlayActivities;
+
+ //filtering out applications in foreground.
+ for (ActivityManager.RunningAppProcessInfo processInfo : appProcessInfos) {
+ if (processInfo.importance <= ActivityManager
+ .RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ filteredAppProcessInfos.addAll(List.of(processInfo.pkgList));
+ }
+ }
+
+ if (DEBUG) Slog.d(TAG, "packages in foreground : " + filteredAppProcessInfos);
+
+ List<String> packagesHoldingManageUsbPermission =
+ mPackageManager.getPackagesHoldingPermissions(
+ new String[]{android.Manifest.permission.MANAGE_USB},
+ PackageManager.MATCH_SYSTEM_ONLY).stream()
+ .map(packageInfo -> packageInfo.packageName).collect(Collectors.toList());
+
+ //retaining only packages that hold the required permission
+ filteredAppProcessInfos.retainAll(packagesHoldingManageUsbPermission);
+
+ if (DEBUG) {
+ Slog.d(TAG, "packages in foreground with required permission : "
+ + filteredAppProcessInfos);
+ }
+
+ shouldRestrictOverlayActivities = filteredAppProcessInfos.stream().anyMatch(pkg -> {
+ try {
+ return mPackageManager.getProperty(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES, pkg)
+ .getBoolean();
+ } catch (NameNotFoundException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "property PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES "
+ + "not present for " + pkg);
+ }
+ return false;
+ }
+ });
+
+ if (shouldRestrictOverlayActivities) {
+ Slog.d(TAG, "restricting starting of usb overlay activities");
+ }
+ return shouldRestrictOverlayActivities;
+ }
+
public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
final Intent intent = createDeviceAttachedIntent(device);
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 8e53ff4..0b854a8 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -33,7 +33,7 @@
/**
* Maintains all {@link UsbUserSettingsManager} for all users.
*/
-class UsbSettingsManager {
+public class UsbSettingsManager {
private static final String LOG_TAG = UsbSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -70,7 +70,7 @@
*
* @return The settings for the user
*/
- @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
+ public @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
synchronized (mSettingsByUser) {
UsbUserSettingsManager settings = mSettingsByUser.get(userId);
if (settings == null) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index c2b8d01..be729c5 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -49,7 +49,7 @@
import java.util.ArrayList;
import java.util.List;
-class UsbUserSettingsManager {
+public class UsbUserSettingsManager {
private static final String TAG = UsbUserSettingsManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -81,7 +81,7 @@
*
* @return The resolve infos of the activities that can handle the intent
*/
- List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
+ public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
return mPackageManager.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
mUser.getIdentifier());
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
new file mode 100644
index 0000000..ea6e502
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.usb.flags"
+
+flag {
+ name: "allow_restriction_of_overlay_activities"
+ namespace: "usb"
+ description: "This flag controls the restriction of usb overlay activities"
+ bug: "307231174"
+}
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index c02d8e9..70c7dad 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -29,12 +29,17 @@
static_libs: [
"frameworks-base-testutils",
"androidx.test.rules",
- "mockito-target-inline-minus-junit4",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"truth",
"UsbManagerTestLib",
],
- jni_libs: ["libdexmakerjvmtiagent"],
+ jni_libs: [
+ // Required for ExtendedMockito
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
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
new file mode 100644
index 0000000..4780d8a
--- /dev/null
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 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.usbtest;
+
+import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+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 androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.usb.UsbHandlerManager;
+import com.android.server.usb.UsbProfileGroupSettingsManager;
+import com.android.server.usb.UsbSettingsManager;
+import com.android.server.usb.UsbUserSettingsManager;
+import com.android.server.usb.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.usb.UsbProfileGroupSettingsManager}.
+ * Note: MUST claim MANAGE_USB permission in Manifest
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbProfileGroupSettingsManagerTest {
+
+ private static final String TEST_PACKAGE_NAME = "testPkg";
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ActivityManager mActivityManager;
+ @Mock
+ private UserHandle mUserHandle;
+ @Mock
+ private UsbSettingsManager mUsbSettingsManager;
+ @Mock
+ private UsbHandlerManager mUsbHandlerManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UsbUserSettingsManager mUsbUserSettingsManager;
+ @Mock private Property mProperty;
+ private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo;
+ private PackageInfo mPackageInfo;
+ private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+ private MockitoSession mStaticMockSession;
+
+ @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(mUserManager.getEnabledProfiles(anyInt()))
+ .thenReturn(List.of(Mockito.mock(UserInfo.class)));
+ when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() {
+ when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+ 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);
+ 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);
+ 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));
+ }
+
+ @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);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+
+ @Test
+ public void testDeviceAttached_flagFalseForegroundActivity_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);
+ verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+ }
+}