Loading tests/TaskOrganizerTest/Android.bp +15 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,20 @@ android_test { name: "TaskOrganizerTest", srcs: ["**/*.java"], srcs: ["**/*.java","**/*.kt"], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, certificate: "platform", static_libs: [ "androidx.appcompat_appcompat", "androidx.test.rules", "androidx.test.runner", "androidx.test.ext.junit", "kotlin-stdlib", "kotlinx-coroutines-android", "flickerlib", "truth-prebuilt", ], } No newline at end of file tests/TaskOrganizerTest/AndroidManifest.xml +28 −4 Original line number Diff line number Diff line Loading @@ -16,17 +16,41 @@ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> <!-- Enable / Disable tracing !--> <uses-permission android:name="android.permission.DUMP" /> <application> <service android:name=".TaskOrganizerPipTest" android:exported="true"> </service> <activity android:name="TaskOrganizerMultiWindowTest" android:label="TaskOrganizer MW Test" android:exported="true"> android:exported="true" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name="TaskOrganizerMultiWindowTest$TestActivity1" android:label="Test Activity 1" android:exported="false" android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" android:taskAffinity="com.android.test.taskembed.task1" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> </intent-filter> </activity> <activity android:name="TaskOrganizerMultiWindowTest$TestActivity2" android:label="Test Activity 2" android:exported="false" android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" android:taskAffinity="com.android.test.taskembed.task2" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> </intent-filter> </activity> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.test.taskembed" android:label="TaskOrganizerTest"> </instrumentation> </manifest> tests/TaskOrganizerTest/AndroidTest.xml 0 → 100644 +35 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2020 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. --> <configuration description="Runs TaskOrganizer Test"> <option name="test-tag" value="TaskOrganizerTest" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="false"/> <option name="test-file-name" value="TaskOrganizerTest.apk"/> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.test.taskembed"/> <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6000s" /> <option name="hidden-api-checks" value="false" /> </test> </configuration> tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.test.taskembed import android.app.Instrumentation import android.graphics.Rect import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import com.android.server.wm.flicker.monitor.LayersTraceMonitor import com.android.server.wm.flicker.monitor.withSFTracing import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat import org.junit.After import org.junit.Before import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import java.util.concurrent.CountDownLatch import org.junit.Assert.assertNotEquals @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class ResizeTasksSyncTest { @get:Rule var scenarioRule: ActivityScenarioRule<TaskOrganizerMultiWindowTest> = ActivityScenarioRule<TaskOrganizerMultiWindowTest>( TaskOrganizerMultiWindowTest::class.java) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @Before fun setup() { val tmpDir = instrumentation.targetContext.dataDir.toPath() LayersTraceMonitor(tmpDir).stop() val firstTaskBounds = Rect(0, 0, 1080, 1000) val secondTaskBounds = Rect(0, 1000, 1080, 2000) lateinit var surfaceReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { surfaceReadyLatch = it.openTaskView(firstTaskBounds, secondTaskBounds) } surfaceReadyLatch.await() } @After fun teardown() { scenarioRule.getScenario().close() } @Test fun testResizeTwoTaskSync() { val firstBounds = Rect(0, 0, 1080, 800) val secondBounds = Rect(0, 1000, 1080, 1800) val trace = withSFTracing(instrumentation, TRACE_FLAGS) { lateinit var resizeReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds) } resizeReadyLatch.await() } // find the frame which match resized buffer size. var frame: Long = -1 loop@ for (trace in trace.entries) { for (layer in trace.flattenedLayers) { if (layer.proto.activeBuffer != null && layer.proto.activeBuffer.width == firstBounds.width() && layer.proto.activeBuffer.height == firstBounds.height()) { frame = layer.proto.currFrame break@loop } } } assertNotEquals(-1, frame) // layer bounds should be related to parent surfaceview. secondBounds.offsetTo(0, 0) // verify buffer size should be changed to expected values. assertThat(trace).layer(FIRST_ACTIVITY, frame).also { it.hasLayerSize(firstBounds) it.hasBufferSize(firstBounds) } assertThat(trace).layer(SECOND_ACTIVITY, frame).also { it.hasLayerSize(secondBounds) it.hasBufferSize(secondBounds) } } companion object { private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL private const val FIRST_ACTIVITY = "Activity1" private const val SECOND_ACTIVITY = "Activity2" } } No newline at end of file tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +124 −131 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * Copyright (C) 2020 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. Loading @@ -16,189 +16,182 @@ package com.android.test.taskembed; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.view.Gravity; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.FrameLayout; import android.widget.TextView; import android.window.TaskOrganizer; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; public class TaskOrganizerMultiWindowTest extends Activity { static class SplitLayout extends LinearLayout implements View.OnTouchListener { View mView1; View mView2; View mDividerView; public boolean onTouch(View v, MotionEvent e) { if (e.getAction() != MotionEvent.ACTION_UP) { return true; } float x = e.getRawX(0); float ratio = (float) x / (float) getWidth() ; ratio = 1-ratio; LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, ratio-0.02f); LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1-ratio-0.02f); updateViewLayout(mView1, lp2); updateViewLayout(mView2, lp1); return true; } SplitLayout(Context c, View v1, View v2) { super(c); LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); LinearLayout.LayoutParams lp3 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 0.04f); lp2.gravity = Gravity.CENTER; setWeightSum(1); mView1 = v1; mView2 = v2; addView(mView1, lp1); mDividerView = new View(getContext()); mDividerView.setBackgroundColor(Color.BLACK); addView(mDividerView, lp2); mDividerView.setOnTouchListener(this); addView(mView2, lp3); } } class ResizingTaskView extends TaskView { final Intent mIntent; boolean launched = false; ResizingTaskView(Context c, Intent i) { super(c); mIntent = i; } @Override public void surfaceChanged(SurfaceHolder h, int format, int width, int height) { if (!launched) { launchOrganizedActivity(mIntent, width, height); launched = true; } else { resizeTask(width, height); } } void resizeTask(int width, int height) { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mWc, new Rect(0, 0, width, height)); try { mOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback); } catch (Exception e) { // Oh well } } } import java.util.concurrent.CountDownLatch; private TaskView mTaskView1; private TaskView mTaskView2; private boolean mGotFirstTask = false; public class TaskOrganizerMultiWindowTest extends Activity { private CountDownLatch mTasksReadyLatch; private CountDownLatch mTasksResizeLatch; class Organizer extends TaskOrganizer { private int receivedTransactions = 0; SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); private int mReceivedTransactions = 0; private SurfaceControl.Transaction mMergedTransaction = new SurfaceControl.Transaction(); WindowContainerTransactionCallback mTransactionCallback = new WindowContainerTransactionCallback() { @Override public void onTransactionReady(int id, SurfaceControl.Transaction t) { mergedTransaction.merge(t); receivedTransactions++; if (receivedTransactions == 2) { mergedTransaction.apply(); receivedTransactions = 0; mMergedTransaction.merge(t); mReceivedTransactions++; if (mReceivedTransactions == 2) { mReceivedTransactions = 0; mMergedTransaction.apply(true); if (mTasksResizeLatch != null) { mTasksResizeLatch.countDown(); } } } }; @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo ti, SurfaceControl leash) { if (!mGotFirstTask) { if (ti.baseActivity == null) { return; } final String clsName = ti.baseActivity.getClassName(); if (clsName.contentEquals(TestActivity1.class.getName())) { mTaskView1.reparentTask(ti.token, leash); mGotFirstTask = true; } else { mOrganizer.setInterceptBackPressedOnTaskRoot(ti.token, true); mTasksReadyLatch.countDown(); } else if (clsName.contentEquals(TestActivity2.class.getName())) { mTaskView2.reparentTask(ti.token, leash); mOrganizer.setInterceptBackPressedOnTaskRoot(ti.token, true); mTasksReadyLatch.countDown(); } } @Override public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { getMainThreadHandler().post(() -> { finish(); }); } } private Organizer mOrganizer = new Organizer(); private FrameLayout mTasksLayout; private TaskView mTaskView1; private TaskView mTaskView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; mOrganizer.registerOrganizer(); mTaskView1 = new ResizingTaskView(this, makeSettingsIntent()); mTaskView2 = new ResizingTaskView(this, makeContactsIntent()); View splitView = new SplitLayout(this, mTaskView1, mTaskView2); mTasksLayout = new FrameLayout(this); setContentView(mTasksLayout); setContentView(splitView); mOrganizer.registerOrganizer(); } @Override protected void onDestroy() { super.onDestroy(); mOrganizer.unregisterOrganizer(); mTasksLayout.removeAllViews(); } private Intent makeActivityIntent(final Class<?> clazz) { Intent intent = new Intent(this, clazz); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } private void addFlags(Intent intent) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); public CountDownLatch openTaskView(Rect firstBounds, Rect secondBounds) { mTasksReadyLatch = new CountDownLatch(2); mTaskView1 = new TaskView(this, mOrganizer, makeActivityIntent(TestActivity1.class)); mTaskView1.setBackgroundColor(Color.DKGRAY); FrameLayout.LayoutParams viewLayout1 = new FrameLayout.LayoutParams(firstBounds.width(), firstBounds.height(), Gravity.TOP | Gravity.LEFT); viewLayout1.setMargins(firstBounds.left, firstBounds.top, 0, 0); mTasksLayout.addView(mTaskView1, viewLayout1); mTaskView2 = new TaskView(this, mOrganizer, makeActivityIntent(TestActivity2.class)); mTaskView2.setBackgroundColor(Color.LTGRAY); FrameLayout.LayoutParams viewLayout2 = new FrameLayout.LayoutParams(secondBounds.width(), secondBounds.height(), Gravity.TOP | Gravity.LEFT); viewLayout2.setMargins(secondBounds.left, secondBounds.top, 0, 0); mTasksLayout.addView(mTaskView2, viewLayout2); return mTasksReadyLatch; } private Intent makeSettingsIntent() { Intent intent = new Intent(); intent.setAction(android.provider.Settings.ACTION_SETTINGS); addFlags(intent); return intent; public CountDownLatch resizeTaskView(Rect firstBounds, Rect secondBounds) { mTasksResizeLatch = new CountDownLatch(1); mTaskView1.resizeTask(firstBounds.width(), firstBounds.height()); mTaskView2.resizeTask(secondBounds.width(), secondBounds.height()); return mTasksResizeLatch; } private Intent makeContactsIntent() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_APP_CONTACTS); addFlags(intent); return intent; static class InstrumentedTextView extends TextView { private final boolean mSlowDraw; InstrumentedTextView(Context context, boolean slowDraw) { super(context); mSlowDraw = slowDraw; } private Bundle makeLaunchOptions(int width, int height) { ActivityOptions o = ActivityOptions.makeBasic(); o.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); o.setLaunchBounds(new Rect(0, 0, width, height)); return o.toBundle(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mSlowDraw) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static class TestActivity1 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; TextView v = new InstrumentedTextView(this, true); v.setText("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); v.setBackgroundColor(Color.RED); v.setTextColor(Color.BLACK); setContentView(v); } } private void launchOrganizedActivity(Intent i, int width, int height) { startActivity(i, makeLaunchOptions(width, height)); public static class TestActivity2 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; TextView v = new InstrumentedTextView(this, false); v.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"); v.setBackgroundColor(Color.GREEN); v.setTextColor(Color.BLACK); setContentView(v); } } } Loading
tests/TaskOrganizerTest/Android.bp +15 −2 Original line number Diff line number Diff line Loading @@ -16,7 +16,20 @@ android_test { name: "TaskOrganizerTest", srcs: ["**/*.java"], srcs: ["**/*.java","**/*.kt"], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", platform_apis: true, certificate: "platform", static_libs: [ "androidx.appcompat_appcompat", "androidx.test.rules", "androidx.test.runner", "androidx.test.ext.junit", "kotlin-stdlib", "kotlinx-coroutines-android", "flickerlib", "truth-prebuilt", ], } No newline at end of file
tests/TaskOrganizerTest/AndroidManifest.xml +28 −4 Original line number Diff line number Diff line Loading @@ -16,17 +16,41 @@ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/> <!-- Enable / Disable tracing !--> <uses-permission android:name="android.permission.DUMP" /> <application> <service android:name=".TaskOrganizerPipTest" android:exported="true"> </service> <activity android:name="TaskOrganizerMultiWindowTest" android:label="TaskOrganizer MW Test" android:exported="true"> android:exported="true" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name="TaskOrganizerMultiWindowTest$TestActivity1" android:label="Test Activity 1" android:exported="false" android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" android:taskAffinity="com.android.test.taskembed.task1" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> </intent-filter> </activity> <activity android:name="TaskOrganizerMultiWindowTest$TestActivity2" android:label="Test Activity 2" android:exported="false" android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" android:taskAffinity="com.android.test.taskembed.task2" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> </intent-filter> </activity> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.test.taskembed" android:label="TaskOrganizerTest"> </instrumentation> </manifest>
tests/TaskOrganizerTest/AndroidTest.xml 0 → 100644 +35 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2020 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. --> <configuration description="Runs TaskOrganizer Test"> <option name="test-tag" value="TaskOrganizerTest" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="false"/> <option name="test-file-name" value="TaskOrganizerTest.apk"/> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> <option name="package" value="com.android.test.taskembed"/> <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" /> <option name="shell-timeout" value="6600s" /> <option name="test-timeout" value="6000s" /> <option name="hidden-api-checks" value="false" /> </test> </configuration>
tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.test.taskembed import android.app.Instrumentation import android.graphics.Rect import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 import com.android.server.wm.flicker.monitor.LayersTraceMonitor import com.android.server.wm.flicker.monitor.withSFTracing import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat import org.junit.After import org.junit.Before import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import java.util.concurrent.CountDownLatch import org.junit.Assert.assertNotEquals @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class ResizeTasksSyncTest { @get:Rule var scenarioRule: ActivityScenarioRule<TaskOrganizerMultiWindowTest> = ActivityScenarioRule<TaskOrganizerMultiWindowTest>( TaskOrganizerMultiWindowTest::class.java) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @Before fun setup() { val tmpDir = instrumentation.targetContext.dataDir.toPath() LayersTraceMonitor(tmpDir).stop() val firstTaskBounds = Rect(0, 0, 1080, 1000) val secondTaskBounds = Rect(0, 1000, 1080, 2000) lateinit var surfaceReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { surfaceReadyLatch = it.openTaskView(firstTaskBounds, secondTaskBounds) } surfaceReadyLatch.await() } @After fun teardown() { scenarioRule.getScenario().close() } @Test fun testResizeTwoTaskSync() { val firstBounds = Rect(0, 0, 1080, 800) val secondBounds = Rect(0, 1000, 1080, 1800) val trace = withSFTracing(instrumentation, TRACE_FLAGS) { lateinit var resizeReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds) } resizeReadyLatch.await() } // find the frame which match resized buffer size. var frame: Long = -1 loop@ for (trace in trace.entries) { for (layer in trace.flattenedLayers) { if (layer.proto.activeBuffer != null && layer.proto.activeBuffer.width == firstBounds.width() && layer.proto.activeBuffer.height == firstBounds.height()) { frame = layer.proto.currFrame break@loop } } } assertNotEquals(-1, frame) // layer bounds should be related to parent surfaceview. secondBounds.offsetTo(0, 0) // verify buffer size should be changed to expected values. assertThat(trace).layer(FIRST_ACTIVITY, frame).also { it.hasLayerSize(firstBounds) it.hasBufferSize(firstBounds) } assertThat(trace).layer(SECOND_ACTIVITY, frame).also { it.hasLayerSize(secondBounds) it.hasBufferSize(secondBounds) } } companion object { private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL private const val FIRST_ACTIVITY = "Activity1" private const val SECOND_ACTIVITY = "Activity2" } } No newline at end of file
tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +124 −131 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * Copyright (C) 2020 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. Loading @@ -16,189 +16,182 @@ package com.android.test.taskembed; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.view.Gravity; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.FrameLayout; import android.widget.TextView; import android.window.TaskOrganizer; import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; public class TaskOrganizerMultiWindowTest extends Activity { static class SplitLayout extends LinearLayout implements View.OnTouchListener { View mView1; View mView2; View mDividerView; public boolean onTouch(View v, MotionEvent e) { if (e.getAction() != MotionEvent.ACTION_UP) { return true; } float x = e.getRawX(0); float ratio = (float) x / (float) getWidth() ; ratio = 1-ratio; LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, ratio-0.02f); LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1-ratio-0.02f); updateViewLayout(mView1, lp2); updateViewLayout(mView2, lp1); return true; } SplitLayout(Context c, View v1, View v2) { super(c); LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); LinearLayout.LayoutParams lp3 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 0.48f); LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.FILL_PARENT, 0.04f); lp2.gravity = Gravity.CENTER; setWeightSum(1); mView1 = v1; mView2 = v2; addView(mView1, lp1); mDividerView = new View(getContext()); mDividerView.setBackgroundColor(Color.BLACK); addView(mDividerView, lp2); mDividerView.setOnTouchListener(this); addView(mView2, lp3); } } class ResizingTaskView extends TaskView { final Intent mIntent; boolean launched = false; ResizingTaskView(Context c, Intent i) { super(c); mIntent = i; } @Override public void surfaceChanged(SurfaceHolder h, int format, int width, int height) { if (!launched) { launchOrganizedActivity(mIntent, width, height); launched = true; } else { resizeTask(width, height); } } void resizeTask(int width, int height) { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mWc, new Rect(0, 0, width, height)); try { mOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback); } catch (Exception e) { // Oh well } } } import java.util.concurrent.CountDownLatch; private TaskView mTaskView1; private TaskView mTaskView2; private boolean mGotFirstTask = false; public class TaskOrganizerMultiWindowTest extends Activity { private CountDownLatch mTasksReadyLatch; private CountDownLatch mTasksResizeLatch; class Organizer extends TaskOrganizer { private int receivedTransactions = 0; SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction(); private int mReceivedTransactions = 0; private SurfaceControl.Transaction mMergedTransaction = new SurfaceControl.Transaction(); WindowContainerTransactionCallback mTransactionCallback = new WindowContainerTransactionCallback() { @Override public void onTransactionReady(int id, SurfaceControl.Transaction t) { mergedTransaction.merge(t); receivedTransactions++; if (receivedTransactions == 2) { mergedTransaction.apply(); receivedTransactions = 0; mMergedTransaction.merge(t); mReceivedTransactions++; if (mReceivedTransactions == 2) { mReceivedTransactions = 0; mMergedTransaction.apply(true); if (mTasksResizeLatch != null) { mTasksResizeLatch.countDown(); } } } }; @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo ti, SurfaceControl leash) { if (!mGotFirstTask) { if (ti.baseActivity == null) { return; } final String clsName = ti.baseActivity.getClassName(); if (clsName.contentEquals(TestActivity1.class.getName())) { mTaskView1.reparentTask(ti.token, leash); mGotFirstTask = true; } else { mOrganizer.setInterceptBackPressedOnTaskRoot(ti.token, true); mTasksReadyLatch.countDown(); } else if (clsName.contentEquals(TestActivity2.class.getName())) { mTaskView2.reparentTask(ti.token, leash); mOrganizer.setInterceptBackPressedOnTaskRoot(ti.token, true); mTasksReadyLatch.countDown(); } } @Override public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { getMainThreadHandler().post(() -> { finish(); }); } } private Organizer mOrganizer = new Organizer(); private FrameLayout mTasksLayout; private TaskView mTaskView1; private TaskView mTaskView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; mOrganizer.registerOrganizer(); mTaskView1 = new ResizingTaskView(this, makeSettingsIntent()); mTaskView2 = new ResizingTaskView(this, makeContactsIntent()); View splitView = new SplitLayout(this, mTaskView1, mTaskView2); mTasksLayout = new FrameLayout(this); setContentView(mTasksLayout); setContentView(splitView); mOrganizer.registerOrganizer(); } @Override protected void onDestroy() { super.onDestroy(); mOrganizer.unregisterOrganizer(); mTasksLayout.removeAllViews(); } private Intent makeActivityIntent(final Class<?> clazz) { Intent intent = new Intent(this, clazz); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } private void addFlags(Intent intent) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); public CountDownLatch openTaskView(Rect firstBounds, Rect secondBounds) { mTasksReadyLatch = new CountDownLatch(2); mTaskView1 = new TaskView(this, mOrganizer, makeActivityIntent(TestActivity1.class)); mTaskView1.setBackgroundColor(Color.DKGRAY); FrameLayout.LayoutParams viewLayout1 = new FrameLayout.LayoutParams(firstBounds.width(), firstBounds.height(), Gravity.TOP | Gravity.LEFT); viewLayout1.setMargins(firstBounds.left, firstBounds.top, 0, 0); mTasksLayout.addView(mTaskView1, viewLayout1); mTaskView2 = new TaskView(this, mOrganizer, makeActivityIntent(TestActivity2.class)); mTaskView2.setBackgroundColor(Color.LTGRAY); FrameLayout.LayoutParams viewLayout2 = new FrameLayout.LayoutParams(secondBounds.width(), secondBounds.height(), Gravity.TOP | Gravity.LEFT); viewLayout2.setMargins(secondBounds.left, secondBounds.top, 0, 0); mTasksLayout.addView(mTaskView2, viewLayout2); return mTasksReadyLatch; } private Intent makeSettingsIntent() { Intent intent = new Intent(); intent.setAction(android.provider.Settings.ACTION_SETTINGS); addFlags(intent); return intent; public CountDownLatch resizeTaskView(Rect firstBounds, Rect secondBounds) { mTasksResizeLatch = new CountDownLatch(1); mTaskView1.resizeTask(firstBounds.width(), firstBounds.height()); mTaskView2.resizeTask(secondBounds.width(), secondBounds.height()); return mTasksResizeLatch; } private Intent makeContactsIntent() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_APP_CONTACTS); addFlags(intent); return intent; static class InstrumentedTextView extends TextView { private final boolean mSlowDraw; InstrumentedTextView(Context context, boolean slowDraw) { super(context); mSlowDraw = slowDraw; } private Bundle makeLaunchOptions(int width, int height) { ActivityOptions o = ActivityOptions.makeBasic(); o.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); o.setLaunchBounds(new Rect(0, 0, width, height)); return o.toBundle(); @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mSlowDraw) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static class TestActivity1 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; TextView v = new InstrumentedTextView(this, true); v.setText("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"); v.setBackgroundColor(Color.RED); v.setTextColor(Color.BLACK); setContentView(v); } } private void launchOrganizedActivity(Intent i, int width, int height) { startActivity(i, makeLaunchOptions(width, height)); public static class TestActivity2 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().getAttributes().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; TextView v = new InstrumentedTextView(this, false); v.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"); v.setBackgroundColor(Color.GREEN); v.setTextColor(Color.BLACK); setContentView(v); } } }