Loading core/java/android/content/pm/LauncherActivityInfo.java +8 −0 Original line number Diff line number Diff line Loading @@ -212,6 +212,14 @@ public class LauncherActivityInfo { return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser()); } /** * Returns whether this activity supports (and can be launched in) multiple instances. * @hide */ public boolean supportsMultiInstance() { return mInternal.supportsMultiInstance(); } /** * Check whether the {@code sequence} is visible to the user or not. * <p> Loading core/java/android/content/pm/LauncherActivityInfoInternal.java +12 −1 Original line number Diff line number Diff line Loading @@ -32,19 +32,24 @@ public class LauncherActivityInfoInternal implements Parcelable { @NonNull private ComponentName mComponentName; @NonNull private IncrementalStatesInfo mIncrementalStatesInfo; @NonNull private UserHandle mUser; private boolean mSupportsMultiInstance; /** * @param info ActivityInfo from which to create the LauncherActivityInfo. * @param incrementalStatesInfo The package's states. * @param user The user the activity info belongs to. * @param supportsMultiInstance Whether the activity supports multi-instance as declared in its * app manifest */ public LauncherActivityInfoInternal(@NonNull ActivityInfo info, @NonNull IncrementalStatesInfo incrementalStatesInfo, @NonNull UserHandle user) { @NonNull UserHandle user, boolean supportsMultiInstance) { mActivityInfo = info; mComponentName = new ComponentName(info.packageName, info.name); mIncrementalStatesInfo = incrementalStatesInfo; mUser = user; mSupportsMultiInstance = supportsMultiInstance; } public LauncherActivityInfoInternal(Parcel source) { Loading @@ -52,6 +57,7 @@ public class LauncherActivityInfoInternal implements Parcelable { mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name); mIncrementalStatesInfo = source.readTypedObject(IncrementalStatesInfo.CREATOR); mUser = source.readTypedObject(UserHandle.CREATOR); mSupportsMultiInstance = source.readBoolean(); } public ComponentName getComponentName() { Loading @@ -70,6 +76,10 @@ public class LauncherActivityInfoInternal implements Parcelable { return mIncrementalStatesInfo; } public boolean supportsMultiInstance() { return mSupportsMultiInstance; } @Override public int describeContents() { return 0; Loading @@ -80,6 +90,7 @@ public class LauncherActivityInfoInternal implements Parcelable { dest.writeTypedObject(mActivityInfo, flags); dest.writeTypedObject(mIncrementalStatesInfo, flags); dest.writeTypedObject(mUser, flags); dest.writeBoolean(mSupportsMultiInstance); } public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR = Loading services/core/java/com/android/server/pm/LauncherAppsService.java +35 −5 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static android.content.PermissionChecker.checkCallingOrSelfPermissionForP import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI; import static com.android.server.pm.PackageArchiver.isArchivingEnabled; Loading Loading @@ -118,7 +119,6 @@ import android.window.IDumpCallback; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; Loading Loading @@ -850,7 +850,9 @@ public class LauncherAppsService extends SystemService { // package does not exist; should not happen return null; } return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user); return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user, supportsMultiInstance(mIPM, activityInfo.getComponentName(), user.getIdentifier())); } finally { Binder.restoreCallingIdentity(ident); } Loading Loading @@ -941,7 +943,7 @@ public class LauncherAppsService extends SystemService { archiveState.getActivityInfos(); for (int j = 0; j < archiveActivityInfoList.size(); j++) { launcherActivityList.add( constructLauncherActivityInfoForArchivedApp( constructLauncherActivityInfoForArchivedApp(mIPM, user, applicationInfo, archiveActivityInfoList.get(j))); } } Loading @@ -949,6 +951,7 @@ public class LauncherAppsService extends SystemService { } private static LauncherActivityInfoInternal constructLauncherActivityInfoForArchivedApp( IPackageManager pm, UserHandle user, ApplicationInfo applicationInfo, ArchiveState.ArchiveActivityInfo archiveActivityInfo) { Loading @@ -964,7 +967,9 @@ public class LauncherAppsService extends SystemService { activityInfo, new IncrementalStatesInfo( false /* isLoading */, 0 /* progress */, 0 /* loadingCompletedTime */), user); user, supportsMultiInstance(pm, activityInfo.getComponentName(), user.getIdentifier())); } @NonNull Loading Loading @@ -1025,7 +1030,9 @@ public class LauncherAppsService extends SystemService { continue; } results.add(new LauncherActivityInfoInternal(ri.activityInfo, incrementalStatesInfo, user)); incrementalStatesInfo, user, supportsMultiInstance(mIPM, ri.activityInfo.getComponentName(), user.getIdentifier()))); } return results; } Loading Loading @@ -1660,6 +1667,29 @@ public class LauncherAppsService extends SystemService { } } /** * Returns whether the specified activity info has the multi-instance property declared. */ @VisibleForTesting static boolean supportsMultiInstance(@NonNull IPackageManager pm, @NonNull ComponentName component, int userId) { try { // Try to get the property for the component return pm.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component.getPackageName(), component.getClassName(), userId).getBoolean(); } catch (Exception e) { try { // Fallback to the property for the app return pm.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component.getPackageName(), null, userId).getBoolean(); } catch (Exception e2) { return false; } } } @Override public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) { if (!canAccessProfile(user.getIdentifier(), Loading services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.pm import android.app.ActivityTaskManager import android.content.ComponentName import android.content.pm.IPackageManager import android.content.pm.PackageManager import android.os.RemoteException import android.platform.test.annotations.Postsubmit import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.LauncherAppsService.LauncherAppsImpl.supportsMultiInstance import org.junit.Assert.assertEquals import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever /** * Unit tests for LauncherAppsService * Run: atest LauncherAppsServiceTest */ @Postsubmit @RunWith(AndroidJUnit4::class) class LauncherAppsServiceTest { val pm = mock<IPackageManager>() @Before fun setup() { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow( InstrumentationRegistry.getInstrumentation().getTargetContext())) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_activityPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val activityProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenReturn(activityProp) val appProp = PackageManager.Property("", false, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect activity property to override application property assertEquals(true, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val activityProp = PackageManager.Property("", false, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect activity property to override application property assertEquals(false, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) val appProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect fall through to app property assertEquals(true, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_noActivityOrAppProperty() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) assertEquals(false, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } companion object { val TEST_PACKAGE = "com.android.server.pm" val TEST_ACTIVITY = "TestActivity" val TEST_OTHER_USER = 1234 } } No newline at end of file Loading
core/java/android/content/pm/LauncherActivityInfo.java +8 −0 Original line number Diff line number Diff line Loading @@ -212,6 +212,14 @@ public class LauncherActivityInfo { return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser()); } /** * Returns whether this activity supports (and can be launched in) multiple instances. * @hide */ public boolean supportsMultiInstance() { return mInternal.supportsMultiInstance(); } /** * Check whether the {@code sequence} is visible to the user or not. * <p> Loading
core/java/android/content/pm/LauncherActivityInfoInternal.java +12 −1 Original line number Diff line number Diff line Loading @@ -32,19 +32,24 @@ public class LauncherActivityInfoInternal implements Parcelable { @NonNull private ComponentName mComponentName; @NonNull private IncrementalStatesInfo mIncrementalStatesInfo; @NonNull private UserHandle mUser; private boolean mSupportsMultiInstance; /** * @param info ActivityInfo from which to create the LauncherActivityInfo. * @param incrementalStatesInfo The package's states. * @param user The user the activity info belongs to. * @param supportsMultiInstance Whether the activity supports multi-instance as declared in its * app manifest */ public LauncherActivityInfoInternal(@NonNull ActivityInfo info, @NonNull IncrementalStatesInfo incrementalStatesInfo, @NonNull UserHandle user) { @NonNull UserHandle user, boolean supportsMultiInstance) { mActivityInfo = info; mComponentName = new ComponentName(info.packageName, info.name); mIncrementalStatesInfo = incrementalStatesInfo; mUser = user; mSupportsMultiInstance = supportsMultiInstance; } public LauncherActivityInfoInternal(Parcel source) { Loading @@ -52,6 +57,7 @@ public class LauncherActivityInfoInternal implements Parcelable { mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name); mIncrementalStatesInfo = source.readTypedObject(IncrementalStatesInfo.CREATOR); mUser = source.readTypedObject(UserHandle.CREATOR); mSupportsMultiInstance = source.readBoolean(); } public ComponentName getComponentName() { Loading @@ -70,6 +76,10 @@ public class LauncherActivityInfoInternal implements Parcelable { return mIncrementalStatesInfo; } public boolean supportsMultiInstance() { return mSupportsMultiInstance; } @Override public int describeContents() { return 0; Loading @@ -80,6 +90,7 @@ public class LauncherActivityInfoInternal implements Parcelable { dest.writeTypedObject(mActivityInfo, flags); dest.writeTypedObject(mIncrementalStatesInfo, flags); dest.writeTypedObject(mUser, flags); dest.writeBoolean(mSupportsMultiInstance); } public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR = Loading
services/core/java/com/android/server/pm/LauncherAppsService.java +35 −5 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static android.content.PermissionChecker.checkCallingOrSelfPermissionForP import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI; import static com.android.server.pm.PackageArchiver.isArchivingEnabled; Loading Loading @@ -118,7 +119,6 @@ import android.window.IDumpCallback; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; Loading Loading @@ -850,7 +850,9 @@ public class LauncherAppsService extends SystemService { // package does not exist; should not happen return null; } return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user); return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user, supportsMultiInstance(mIPM, activityInfo.getComponentName(), user.getIdentifier())); } finally { Binder.restoreCallingIdentity(ident); } Loading Loading @@ -941,7 +943,7 @@ public class LauncherAppsService extends SystemService { archiveState.getActivityInfos(); for (int j = 0; j < archiveActivityInfoList.size(); j++) { launcherActivityList.add( constructLauncherActivityInfoForArchivedApp( constructLauncherActivityInfoForArchivedApp(mIPM, user, applicationInfo, archiveActivityInfoList.get(j))); } } Loading @@ -949,6 +951,7 @@ public class LauncherAppsService extends SystemService { } private static LauncherActivityInfoInternal constructLauncherActivityInfoForArchivedApp( IPackageManager pm, UserHandle user, ApplicationInfo applicationInfo, ArchiveState.ArchiveActivityInfo archiveActivityInfo) { Loading @@ -964,7 +967,9 @@ public class LauncherAppsService extends SystemService { activityInfo, new IncrementalStatesInfo( false /* isLoading */, 0 /* progress */, 0 /* loadingCompletedTime */), user); user, supportsMultiInstance(pm, activityInfo.getComponentName(), user.getIdentifier())); } @NonNull Loading Loading @@ -1025,7 +1030,9 @@ public class LauncherAppsService extends SystemService { continue; } results.add(new LauncherActivityInfoInternal(ri.activityInfo, incrementalStatesInfo, user)); incrementalStatesInfo, user, supportsMultiInstance(mIPM, ri.activityInfo.getComponentName(), user.getIdentifier()))); } return results; } Loading Loading @@ -1660,6 +1667,29 @@ public class LauncherAppsService extends SystemService { } } /** * Returns whether the specified activity info has the multi-instance property declared. */ @VisibleForTesting static boolean supportsMultiInstance(@NonNull IPackageManager pm, @NonNull ComponentName component, int userId) { try { // Try to get the property for the component return pm.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component.getPackageName(), component.getClassName(), userId).getBoolean(); } catch (Exception e) { try { // Fallback to the property for the app return pm.getPropertyAsUser( PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component.getPackageName(), null, userId).getBoolean(); } catch (Exception e2) { return false; } } } @Override public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) { if (!canAccessProfile(user.getIdentifier(), Loading
services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.pm import android.app.ActivityTaskManager import android.content.ComponentName import android.content.pm.IPackageManager import android.content.pm.PackageManager import android.os.RemoteException import android.platform.test.annotations.Postsubmit import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.LauncherAppsService.LauncherAppsImpl.supportsMultiInstance import org.junit.Assert.assertEquals import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever /** * Unit tests for LauncherAppsService * Run: atest LauncherAppsServiceTest */ @Postsubmit @RunWith(AndroidJUnit4::class) class LauncherAppsServiceTest { val pm = mock<IPackageManager>() @Before fun setup() { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow( InstrumentationRegistry.getInstrumentation().getTargetContext())) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_activityPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val activityProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenReturn(activityProp) val appProp = PackageManager.Property("", false, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect activity property to override application property assertEquals(true, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) val activityProp = PackageManager.Property("", false, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenReturn(activityProp) val appProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect activity property to override application property assertEquals(false, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) val appProp = PackageManager.Property("", true, "", "") whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenReturn(appProp) // Expect fall through to app property assertEquals(true, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } @Test @Throws(RemoteException::class) fun supportsMultiInstanceSplit_noActivityOrAppProperty() { val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(component.className), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) whenever(pm.getPropertyAsUser(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.packageName), eq(null), eq(TEST_OTHER_USER))) .thenThrow(RemoteException()) assertEquals(false, supportsMultiInstance(pm, component, TEST_OTHER_USER)) } companion object { val TEST_PACKAGE = "com.android.server.pm" val TEST_ACTIVITY = "TestActivity" val TEST_OTHER_USER = 1234 } } No newline at end of file