Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2c34ec91 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add resizing tasks synchronously flicker tests"

parents a418d091 dc1d359a
Loading
Loading
Loading
Loading
+15 −2
Original line number Diff line number Diff line
@@ -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
+28 −4
Original line number Diff line number Diff line
@@ -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>
+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>
+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
+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.
@@ -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