diff options
| author | 2024-02-12 04:46:04 +0000 | |
|---|---|---|
| committer | 2024-02-12 04:46:04 +0000 | |
| commit | aaa6244b08193d43f23dd414ebc37498c29e9bea (patch) | |
| tree | 7c2e6d54a49917e8f1fe8b3de439c27a99711e7e | |
| parent | ace52242eb6fcbe7dc6e67b2262497300469d140 (diff) | |
| parent | 3e5c69374d03791ad964538e64377a6126b66ecc (diff) | |
Merge "Avoiding overlay activities when usb device is attached." into main
8 files changed, 307 insertions, 10 deletions
diff --git a/services/usb/Android.bp b/services/usb/Android.bp index 3a0a6abb2307..e8ffe541d641 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -34,8 +34,20 @@ java_library_static { "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 f3112743bcf2..d83ff1fbc381 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 @@ import java.util.ArrayList; * * @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 f91666081e82..2ff21ad40558 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 @@ package com.android.server.usb; 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.XmlUtils; 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.HashMap; 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 @@ class UsbProfileGroupSettingsManager { private final PackageManager mPackageManager; + private final ActivityManager mActivityManager; + private final UserManager mUserManager; private final @NonNull UsbSettingsManager mSettingsManager; @@ -224,7 +240,7 @@ class UsbProfileGroupSettingsManager { * @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 @@ class UsbProfileGroupSettingsManager { mContext = context; mPackageManager = context.getPackageManager(); + mActivityManager = context.getSystemService(ActivityManager.class); mSettingsManager = settingsManager; mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); @@ -895,7 +912,10 @@ class UsbProfileGroupSettingsManager { // 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 @@ class UsbProfileGroupSettingsManager { 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 8e53ff412f0a..0b854a8440a5 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 @@ import java.util.List; /** * 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 @@ class UsbSettingsManager { * * @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 c2b8d0109e68..be729c562f64 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 org.xmlpull.v1.XmlPullParser; 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 @@ class UsbUserSettingsManager { * * @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 000000000000..ea6e50221cd0 --- /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 c02d8e96abb0..70c7dad2fd55 100644 --- a/tests/UsbManagerTests/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -29,12 +29,17 @@ android_test { 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 000000000000..4780d8a610e8 --- /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)); + } +} |