diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 0b2b5b1f0ec48..f25ce76d93655 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1222,13 +1222,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeInt(lockTaskLaunchMode); if (windowLayout != null) { dest.writeInt(1); - dest.writeInt(windowLayout.width); - dest.writeFloat(windowLayout.widthFraction); - dest.writeInt(windowLayout.height); - dest.writeFloat(windowLayout.heightFraction); - dest.writeInt(windowLayout.gravity); - dest.writeInt(windowLayout.minWidth); - dest.writeInt(windowLayout.minHeight); + windowLayout.writeToParcel(dest); } else { dest.writeInt(0); } @@ -1372,8 +1366,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @attr ref android.R.styleable#AndroidManifestLayout_minHeight */ public static final class WindowLayout { - public WindowLayout(int width, float widthFraction, int height, float heightFraction, int gravity, - int minWidth, int minHeight) { + public WindowLayout(int width, float widthFraction, int height, float heightFraction, + int gravity, int minWidth, int minHeight) { this.width = width; this.widthFraction = widthFraction; this.height = height; @@ -1392,6 +1386,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { gravity = source.readInt(); minWidth = source.readInt(); minHeight = source.readInt(); + windowLayoutAffinity = source.readString(); } /** @@ -1457,6 +1452,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ public final int minHeight; + /** + * Affinity of window layout parameters. Activities with the same UID and window layout + * affinity will share the same window dimension record. + * @hide + */ + public String windowLayoutAffinity; + /** * Returns if this {@link WindowLayout} has specified bounds. * @hide @@ -1464,5 +1466,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public boolean hasSpecifiedSize() { return width >= 0 || height >= 0 || widthFraction >= 0 || heightFraction >= 0; } + + /** @hide */ + public void writeToParcel(Parcel dest) { + dest.writeInt(width); + dest.writeFloat(widthFraction); + dest.writeInt(height); + dest.writeFloat(heightFraction); + dest.writeInt(gravity); + dest.writeInt(minWidth); + dest.writeInt(minHeight); + dest.writeString(windowLayoutAffinity); + } } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index edf313467b765..18f13431c09a3 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -55,7 +55,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; @@ -209,6 +208,8 @@ public class PackageParser { public static final String TAG_USES_SPLIT = "uses-split"; public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; + public static final String METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY = + "android.activity_window_layout_affinity"; /** * Bit mask of all the valid bits that can be set in recreateOnConfigChanges. @@ -4560,6 +4561,8 @@ public class PackageParser { } } + resolveWindowLayout(a); + if (!setExported) { a.info.exported = a.intents.size() > 0; } @@ -4726,6 +4729,35 @@ public class PackageParser { height, heightFraction, gravity, minWidth, minHeight); } + /** + * Resolves values in {@link ActivityInfo.WindowLayout}. + * + *

{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in + * Android R and some variants of pre-R. + */ + private void resolveWindowLayout(Activity activity) { + // There isn't a metadata for us to fall back. Whatever is in layout is correct. + if (activity.metaData == null + || !activity.metaData.containsKey(METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) { + return; + } + + final ActivityInfo aInfo = activity.info; + // Layout already specifies a value. We should just use that one. + if (aInfo.windowLayout != null && aInfo.windowLayout.windowLayoutAffinity != null) { + return; + } + + String windowLayoutAffinity = activity.metaData.getString( + METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY); + if (aInfo.windowLayout == null) { + aInfo.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */, + -1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */, + Gravity.NO_GRAVITY, -1 /* minWidth */, -1 /* minHeight */); + } + aInfo.windowLayout.windowLayoutAffinity = windowLayoutAffinity; + } + private Activity parseActivityAlias(Package owner, Resources res, XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs) diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java index d32171dfe9628..4c93d09503884 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java @@ -285,14 +285,8 @@ public class ParsedActivity extends ParsedMainComponent { dest.writeBundle(this.metaData); if (windowLayout != null) { - dest.writeBoolean(true); - dest.writeInt(windowLayout.width); - dest.writeFloat(windowLayout.widthFraction); - dest.writeInt(windowLayout.height); - dest.writeFloat(windowLayout.heightFraction); - dest.writeInt(windowLayout.gravity); - dest.writeInt(windowLayout.minWidth); - dest.writeInt(windowLayout.minHeight); + dest.writeInt(1); + windowLayout.writeToParcel(dest); } else { dest.writeBoolean(false); } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index 1dcf262d46bae..6e5c51a22025b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -17,16 +17,17 @@ package android.content.pm.parsing.component; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - import static android.content.pm.parsing.component.ComponentParseUtils.flag; import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.content.pm.ActivityInfo; import android.content.pm.PackageParser; - import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -42,9 +43,6 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; -import android.content.pm.parsing.ParsingPackageUtils; -import android.content.pm.parsing.result.ParseInput; -import android.content.pm.parsing.result.ParseResult; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -379,6 +377,12 @@ public class ParsedActivityUtils { } } + ParseResult layoutResult = resolveWindowLayout(activity, input); + if (layoutResult.isError()) { + return input.error(layoutResult); + } + activity.windowLayout = layoutResult.getResult(); + if (!setExported) { activity.exported = activity.getIntents().size() > 0; } @@ -481,4 +485,35 @@ public class ParsedActivityUtils { sw.recycle(); } } + + /** + * Resolves values in {@link ActivityInfo.WindowLayout}. + * + *

{@link ActivityInfo.WindowLayout#windowLayoutAffinity} has a fallback metadata used in + * Android R and some variants of pre-R. + */ + private static ParseResult resolveWindowLayout( + ParsedActivity activity, ParseInput input) { + // There isn't a metadata for us to fall back. Whatever is in layout is correct. + if (activity.metaData == null || !activity.metaData.containsKey( + PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) { + return input.success(activity.windowLayout); + } + + // Layout already specifies a value. We should just use that one. + if (activity.windowLayout != null && activity.windowLayout.windowLayoutAffinity != null) { + return input.success(activity.windowLayout); + } + + String windowLayoutAffinity = activity.metaData.getString( + PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY); + ActivityInfo.WindowLayout layout = activity.windowLayout; + if (layout == null) { + layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */, + -1 /* height */, -1 /* heightFraction */, Gravity.NO_GRAVITY, + -1 /* minWidth */, -1 /* minHeight */); + } + layout.windowLayoutAffinity = windowLayoutAffinity; + return input.success(layout); + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1937662997aee..034ab52a2f148 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1589,6 +1589,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.taskAffinity = uid + ":" + info.taskAffinity; } taskAffinity = info.taskAffinity; + if (info.windowLayout != null && info.windowLayout.windowLayoutAffinity != null + && !info.windowLayout.windowLayoutAffinity.startsWith(uid)) { + info.windowLayout.windowLayoutAffinity = + uid + ":" + info.windowLayout.windowLayoutAffinity; + } stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0; nonLocalizedLabel = aInfo.nonLocalizedLabel; labelRes = aInfo.labelRes; diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index 660706e4139bd..9371c0eec26fb 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -16,7 +16,9 @@ package com.android.server.wm; +import android.annotation.Nullable; import android.content.ComponentName; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManagerInternal; import android.graphics.Rect; import android.os.Environment; @@ -87,9 +89,17 @@ class LaunchParamsPersister { * launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata * that are stable across reboots. */ - private final SparseArray> mMap = + private final SparseArray> mLaunchParamsMap = new SparseArray<>(); + /** + * A map from {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} to + * activity's component name for reverse queries from window layout affinities to activities. + * Used to decide if we should use another activity's record with the same affinity. + */ + private final ArrayMap> mWindowLayoutAffinityMap = + new ArrayMap<>(); + LaunchParamsPersister(PersisterQueue persisterQueue, ActivityStackSupervisor supervisor) { this(persisterQueue, supervisor, Environment::getDataSystemCeDirectory); } @@ -112,7 +122,7 @@ class LaunchParamsPersister { } void onCleanupUser(int userId) { - mMap.remove(userId); + mLaunchParamsMap.remove(userId); } private void loadLaunchParams(int userId) { @@ -128,7 +138,7 @@ class LaunchParamsPersister { final File[] paramsFiles = launchParamsFolder.listFiles(); final ArrayMap map = new ArrayMap<>(paramsFiles.length); - mMap.put(userId, map); + mLaunchParamsMap.put(userId, map); for (File paramsFile : paramsFiles) { if (!paramsFile.isFile()) { @@ -179,10 +189,12 @@ class LaunchParamsPersister { continue; } - params.restoreFromXml(parser); + params.restore(paramsFile, parser); } map.put(name, params); + addComponentNameToLaunchParamAffinityMapIfNotNull( + name, params.mWindowLayoutAffinity); } catch (Exception e) { Slog.w(TAG, "Failed to restore launch params for " + name, e); filesToDelete.add(paramsFile); @@ -204,19 +216,17 @@ class LaunchParamsPersister { final ComponentName name = task.realActivity; final int userId = task.mUserId; PersistableLaunchParams params; - ArrayMap map = mMap.get(userId); + ArrayMap map = mLaunchParamsMap.get(userId); if (map == null) { map = new ArrayMap<>(); - mMap.put(userId, map); + mLaunchParamsMap.put(userId, map); } - params = map.get(name); - if (params == null) { - params = new PersistableLaunchParams(); - map.put(name, params); - } + params = map.computeIfAbsent(name, componentName -> new PersistableLaunchParams()); final boolean changed = saveTaskToLaunchParam(task, display, params); + addComponentNameToLaunchParamAffinityMapIfNotNull(name, params.mWindowLayoutAffinity); + if (changed) { mPersisterQueue.updateLastOrAddItem( new LaunchParamsWriteQueueItem(userId, name, params), @@ -243,19 +253,63 @@ class LaunchParamsPersister { params.mBounds.setEmpty(); } + String launchParamAffinity = task.mWindowLayoutAffinity; + changed |= Objects.equals(launchParamAffinity, params.mWindowLayoutAffinity); + params.mWindowLayoutAffinity = launchParamAffinity; + + if (changed) { + params.mTimestamp = System.currentTimeMillis(); + } + return changed; } + private void addComponentNameToLaunchParamAffinityMapIfNotNull( + ComponentName name, String launchParamAffinity) { + if (launchParamAffinity == null) { + return; + } + mWindowLayoutAffinityMap.computeIfAbsent(launchParamAffinity, affinity -> new ArraySet<>()) + .add(name); + } + void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) { final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent; final int userId = task != null ? task.mUserId : activity.mUserId; + final String windowLayoutAffinity; + if (task != null) { + windowLayoutAffinity = task.mWindowLayoutAffinity; + } else { + ActivityInfo.WindowLayout layout = activity.info.windowLayout; + windowLayoutAffinity = layout == null ? null : layout.windowLayoutAffinity; + } outParams.reset(); - Map map = mMap.get(userId); + Map map = mLaunchParamsMap.get(userId); if (map == null) { return; } - final PersistableLaunchParams persistableParams = map.get(name); + + // First use its own record as a reference. + PersistableLaunchParams persistableParams = map.get(name); + // Next we'll compare these params against all existing params with the same affinity and + // use the newest one. + if (windowLayoutAffinity != null + && mWindowLayoutAffinityMap.get(windowLayoutAffinity) != null) { + ArraySet candidates = mWindowLayoutAffinityMap.get(windowLayoutAffinity); + for (int i = 0; i < candidates.size(); ++i) { + ComponentName candidate = candidates.valueAt(i); + final PersistableLaunchParams candidateParams = map.get(candidate); + if (candidateParams == null) { + continue; + } + + if (persistableParams == null + || candidateParams.mTimestamp > persistableParams.mTimestamp) { + persistableParams = candidateParams; + } + } + } if (persistableParams == null) { return; @@ -272,10 +326,10 @@ class LaunchParamsPersister { void removeRecordForPackage(String packageName) { final List fileToDelete = new ArrayList<>(); - for (int i = 0; i < mMap.size(); ++i) { - int userId = mMap.keyAt(i); + for (int i = 0; i < mLaunchParamsMap.size(); ++i) { + int userId = mLaunchParamsMap.keyAt(i); final File launchParamsFolder = getLaunchParamFolder(userId); - ArrayMap map = mMap.valueAt(i); + ArrayMap map = mLaunchParamsMap.valueAt(i); for (int j = map.size() - 1; j >= 0; --j) { final ComponentName name = map.keyAt(j); if (name.getPackageName().equals(packageName)) { @@ -409,6 +463,7 @@ class LaunchParamsPersister { private static final String ATTR_WINDOWING_MODE = "windowing_mode"; private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id"; private static final String ATTR_BOUNDS = "bounds"; + private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity"; /** The bounds within the parent container. */ final Rect mBounds = new Rect(); @@ -419,14 +474,29 @@ class LaunchParamsPersister { /** The windowing mode to be in. */ int mWindowingMode; + /** + * Last {@link android.content.pm.ActivityInfo.WindowLayout#windowLayoutAffinity} of the + * window. + */ + @Nullable String mWindowLayoutAffinity; + + /** + * Timestamp from {@link System#currentTimeMillis()} when this record is captured, or last + * modified time when the record is restored from storage. + */ + long mTimestamp; + void saveToXml(XmlSerializer serializer) throws IOException { serializer.attribute(null, ATTR_DISPLAY_UNIQUE_ID, mDisplayUniqueId); serializer.attribute(null, ATTR_WINDOWING_MODE, Integer.toString(mWindowingMode)); serializer.attribute(null, ATTR_BOUNDS, mBounds.flattenToString()); + if (mWindowLayoutAffinity != null) { + serializer.attribute(null, ATTR_WINDOW_LAYOUT_AFFINITY, mWindowLayoutAffinity); + } } - void restoreFromXml(XmlPullParser parser) { + void restore(File xmlFile, XmlPullParser parser) { for (int i = 0; i < parser.getAttributeCount(); ++i) { final String attrValue = parser.getAttributeValue(i); switch (parser.getAttributeName(i)) { @@ -443,16 +513,28 @@ class LaunchParamsPersister { } break; } + case ATTR_WINDOW_LAYOUT_AFFINITY: + mWindowLayoutAffinity = attrValue; + break; } } + + // The modified time could be a few seconds later than the timestamp when the record is + // captured, which is a good enough estimate to the capture time after a reboot or a + // user switch. + mTimestamp = xmlFile.lastModified(); } @Override public String toString() { final StringBuilder builder = new StringBuilder("PersistableLaunchParams{"); - builder.append("windowingMode=" + mWindowingMode); + builder.append(" windowingMode=" + mWindowingMode); builder.append(" displayUniqueId=" + mDisplayUniqueId); builder.append(" bounds=" + mBounds); + if (mWindowLayoutAffinity != null) { + builder.append(" launchParamsAffinity=" + mWindowLayoutAffinity); + } + builder.append(" timestamp=" + mTimestamp); builder.append(" }"); return builder.toString(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8c7a68bd81656..5ab5fbcf693da 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -199,6 +199,7 @@ class Task extends WindowContainer { private static final String ATTR_MIN_WIDTH = "min_width"; private static final String ATTR_MIN_HEIGHT = "min_height"; private static final String ATTR_PERSIST_TASK_VERSION = "persist_task_version"; + private static final String ATTR_WINDOW_LAYOUT_AFFINITY = "window_layout_affinity"; // Current version of the task record we persist. Used to check if we need to run any upgrade // code. @@ -233,6 +234,8 @@ class Task extends WindowContainer { String affinity; // The affinity name for this task, or null; may change identity. String rootAffinity; // Initial base affinity, or null; does not change from initial root. + String mWindowLayoutAffinity; // Launch param affinity of this task or null. Used when saving + // launch params of this task. IVoiceInteractionSession voiceSession; // Voice interaction session driving task IVoiceInteractor voiceInteractor; // Associated interactor to provide to app Intent intent; // The original intent that started the task. Note that this value can @@ -1001,6 +1004,8 @@ class Task extends WindowContainer { origActivity = new ComponentName(info.packageName, info.name); } } + mWindowLayoutAffinity = + info.windowLayout == null ? null : info.windowLayout.windowLayoutAffinity; final int intentFlags = intent == null ? 0 : intent.getFlags(); if ((intentFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { @@ -3410,6 +3415,9 @@ class Task extends WindowContainer { pw.println(); } } + if (mWindowLayoutAffinity != null) { + pw.print(prefix); pw.print("windowLayoutAffinity="); pw.println(mWindowLayoutAffinity); + } if (voiceSession != null || voiceInteractor != null) { pw.print(prefix); pw.print("VOICE: session=0x"); pw.print(Integer.toHexString(System.identityHashCode(voiceSession))); @@ -3591,6 +3599,9 @@ class Task extends WindowContainer { } else if (rootAffinity != null) { out.attribute(null, ATTR_ROOT_AFFINITY, rootAffinity != null ? rootAffinity : "@"); } + if (mWindowLayoutAffinity != null) { + out.attribute(null, ATTR_WINDOW_LAYOUT_AFFINITY, mWindowLayoutAffinity); + } out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset)); out.attribute(null, ATTR_AUTOREMOVERECENTS, String.valueOf(autoRemoveRecents)); out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode)); @@ -3748,6 +3759,7 @@ class Task extends WindowContainer { String affinity = null; String rootAffinity = null; boolean hasRootAffinity = false; + String windowLayoutAffinity = null; boolean rootHasReset = false; boolean autoRemoveRecents = false; boolean askedCompatMode = false; @@ -3800,6 +3812,9 @@ class Task extends WindowContainer { rootAffinity = attrValue; hasRootAffinity = true; break; + case ATTR_WINDOW_LAYOUT_AFFINITY: + windowLayoutAffinity = attrValue; + break; case ATTR_ROOTHASRESET: rootHasReset = Boolean.parseBoolean(attrValue); break; @@ -3955,6 +3970,7 @@ class Task extends WindowContainer { realActivitySuspended, userSetupComplete, minWidth, minHeight, null /*stack*/); task.mLastNonFullscreenBounds = lastNonFullscreenBounds; task.setBounds(lastNonFullscreenBounds); + task.mWindowLayoutAffinity = windowLayoutAffinity; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { task.addChild(activities.get(activityNdx)); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 12d89de8e9ad1..8f3ff52b80185 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -37,8 +37,8 @@ import android.graphics.Rect; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; -import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.LocalServices; import com.android.server.pm.PackageList; @@ -72,6 +72,10 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { ComponentName.createRelative("com.android.foo", ".BarActivity"); private static final ComponentName ALTERNATIVE_COMPONENT = ComponentName.createRelative("com.android.foo", ".AlternativeBarActivity"); + private static final String TEST_WINDOW_LAYOUT_AFFINITY = "135:.Affinity"; + private static final String TEST_ALTERNATIVE_WINDOW_LAYOUT_AFFINITY = + "246:.AlternativeAffinity"; + private static final String TEST_DIFFERENT_AFFINITY_WITH_SAME_UID = "135:.DifferentAffinity"; private static final int TEST_WINDOWING_MODE = WINDOWING_MODE_FREEFORM; private static final Rect TEST_BOUNDS = new Rect(100, 200, 300, 400); @@ -99,11 +103,12 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { public void setUp() throws Exception { mPersisterQueue = new TestPersisterQueue(); - final File cacheFolder = InstrumentationRegistry.getContext().getCacheDir(); + final File cacheFolder = + InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(); mFolder = new File(cacheFolder, "launch_params_tests"); deleteRecursively(mFolder); - mDisplayUniqueId = "test:" + Integer.toString(sNextUniqueId++); + mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mService, 1000, 1500) .setUniqueId(mDisplayUniqueId).build(); when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) @@ -210,6 +215,61 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { assertTrue("Result should be empty.", mResult.isEmpty()); } + @Test + public void testUsesRecordWithSameWindowLayoutAffinityInSameInstance_NoPreviousRecord() { + mTestTask.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId); + assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode); + assertEquals(TEST_BOUNDS, mResult.mBounds); + } + + @Test + public void testUsesRecordWithSameWindowLayoutAffinityInSameInstance_HasOldPreviousRecord() + throws Exception { + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTaskWithDifferentComponent); + + Thread.sleep(1); // Sleep 1ms so that the timestamp can for sure increase. + + mTestTask.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + + mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId); + assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode); + assertEquals(TEST_BOUNDS, mResult.mBounds); + } + + @Test + public void testReturnsEmptyLaunchParamsUidInLaunchAffinityMismatch() { + mTestTask.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_DIFFERENT_AFFINITY_WITH_SAME_UID; + mResult.mWindowingMode = WINDOWING_MODE_FULLSCREEN; + mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertTrue("Result must be empty.", mResult.isEmpty()); + } + + @Test + public void testReturnsEmptyLaunchParamsWindowLayoutAffinityMismatch() { + mTestTask.affinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_ALTERNATIVE_WINDOW_LAYOUT_AFFINITY; + mResult.mWindowingMode = WINDOWING_MODE_FULLSCREEN; + mTarget.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertTrue("Result must be empty.", mResult.isEmpty()); + } + @Test public void testSavesAndRestoresLaunchParamsAcrossInstances() { mTarget.saveTask(mTestTask); @@ -227,6 +287,52 @@ public class LaunchParamsPersisterTests extends ActivityTestsBase { assertEquals(TEST_BOUNDS, mResult.mBounds); } + @Test + public void testUsesRecordWithSameWindowLayoutAffinityAcrossInstances_NoPreviousRecord() { + mTestTask.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + mPersisterQueue.flush(); + + final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor, + mUserFolderGetter); + target.onSystemReady(); + target.onUnlockUser(TEST_USER_ID); + + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + target.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId); + assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode); + assertEquals(TEST_BOUNDS, mResult.mBounds); + } + + @Test + public void testUsesRecordWithSameWindowLayoutAffinityAcrossInstances_HasOldPreviousRecord() + throws Exception { + mTaskWithDifferentComponent.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTaskWithDifferentComponent); + mPersisterQueue.flush(); + + // Sleep 1s because many file systems only save last modified time as precise as 1s so we + // can for sure know the last modified time is different. + Thread.sleep(1000); + + mTestTask.mWindowLayoutAffinity = TEST_WINDOW_LAYOUT_AFFINITY; + mTarget.saveTask(mTestTask); + mPersisterQueue.flush(); + + final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor, + mUserFolderGetter); + target.onSystemReady(); + target.onUnlockUser(TEST_USER_ID); + + target.getLaunchParams(mTaskWithDifferentComponent, null, mResult); + + assertEquals(mTestDisplay.mDisplayId, mResult.mPreferredDisplayId); + assertEquals(TEST_WINDOWING_MODE, mResult.mWindowingMode); + assertEquals(TEST_BOUNDS, mResult.mBounds); + } + @Test public void testClearsRecordsOfTheUserOnUserCleanUp() { mTarget.saveTask(mTestTask);