summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chung <winsonc@google.com> 2025-02-06 08:17:55 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-02-06 08:17:55 -0800
commitd7b5186b583fe03b389d627d1bb1bf51a5f2ef66 (patch)
treeb006b656719fa06936b6268a929474c4dc5ed742
parente80f1b9a275793715dd7279e2ab352ed56e72e94 (diff)
parentf45aac8e78412523166a3dc5e9fa0b935e4ad086 (diff)
Merge "Pass multi-instance state via LauncherActivityInfo" into main
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java8
-rw-r--r--core/java/android/content/pm/LauncherActivityInfoInternal.java13
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt123
4 files changed, 178 insertions, 6 deletions
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index bb91a37825e6..760224f92acb 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -213,6 +213,14 @@ public class LauncherActivityInfo {
}
/**
+ * 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>
* Return {@code false} when one of these conditions are satisfied:
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 5aac97d784b3..3bb38799a211 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -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) {
@@ -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() {
@@ -70,6 +76,10 @@ public class LauncherActivityInfoInternal implements Parcelable {
return mIncrementalStatesInfo;
}
+ public boolean supportsMultiInstance() {
+ return mSupportsMultiInstance;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -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 =
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 4c959fafaa8d..2dd679818ada 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -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;
@@ -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;
@@ -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);
}
@@ -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)));
}
}
@@ -949,6 +951,7 @@ public class LauncherAppsService extends SystemService {
}
private static LauncherActivityInfoInternal constructLauncherActivityInfoForArchivedApp(
+ IPackageManager pm,
UserHandle user,
ApplicationInfo applicationInfo,
ArchiveState.ArchiveActivityInfo archiveActivityInfo) {
@@ -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
@@ -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;
}
@@ -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(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt b/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt
new file mode 100644
index 000000000000..aa1487b10a7b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/LauncherAppsServiceTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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