1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.fail;
23 
24 import android.app.Activity;
25 import android.app.Instrumentation;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.ActivityInfo;
30 import android.content.res.Configuration;
31 import android.os.Parcel;
32 import android.platform.test.annotations.Presubmit;
33 import android.util.Log;
34 import android.view.SurfaceControl;
35 import android.view.SurfaceHolder;
36 import android.view.SurfaceView;
37 
38 import androidx.annotation.NonNull;
39 import androidx.test.filters.SmallTest;
40 import androidx.test.runner.AndroidJUnit4;
41 
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 
45 import java.util.concurrent.atomic.AtomicInteger;
46 
47 /**
48  * Class for testing {@link SurfaceControl}.
49  *
50  * Build/Install/Run:
51  *  atest WmTests:SurfaceControlTests
52  */
53 @Presubmit
54 @RunWith(AndroidJUnit4.class)
55 public class SurfaceControlTests {
56 
57     @SmallTest
58     @Test
testUseValidSurface()59     public void testUseValidSurface() {
60         SurfaceControl sc = buildTestSurface();
61         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
62         t.setVisibility(sc, false);
63         sc.release();
64     }
65 
66     @SmallTest
67     @Test
testUseInvalidSurface()68     public void testUseInvalidSurface() {
69         SurfaceControl sc = buildTestSurface();
70         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
71         sc.release();
72         try {
73             t.setVisibility(sc, false);
74             fail("Expected exception from updating invalid surface");
75         } catch (Exception e) {
76             // Expected exception
77         }
78     }
79 
80     @SmallTest
81     @Test
testUseInvalidSurface_debugEnabled()82     public void testUseInvalidSurface_debugEnabled() {
83         SurfaceControl sc = buildTestSurface();
84         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
85         try {
86             SurfaceControl.setDebugUsageAfterRelease(true);
87             sc.release();
88             try {
89                 t.setVisibility(sc, false);
90                 fail("Expected exception from updating invalid surface");
91             } catch (IllegalStateException ise) {
92                 assertNotNull(ise.getCause());
93             } catch (Exception e) {
94                 fail("Expected IllegalStateException with cause");
95             }
96         } finally {
97             SurfaceControl.setDebugUsageAfterRelease(false);
98         }
99     }
100 
101     @SmallTest
102     @Test
testWriteInvalidSurface_debugEnabled()103     public void testWriteInvalidSurface_debugEnabled() {
104         SurfaceControl sc = buildTestSurface();
105         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
106         Parcel p = Parcel.obtain();
107         try {
108             SurfaceControl.setDebugUsageAfterRelease(true);
109             sc.release();
110             try {
111                 sc.writeToParcel(p, 0 /* flags */);
112                 fail("Expected exception from writing invalid surface to parcel");
113             } catch (IllegalStateException ise) {
114                 assertNotNull(ise.getCause());
115             } catch (Exception e) {
116                 fail("Expected IllegalStateException with cause");
117             }
118         } finally {
119             SurfaceControl.setDebugUsageAfterRelease(false);
120             p.recycle();
121         }
122     }
123 
124     @Test
testSurfaceChangedOnRotation()125     public void testSurfaceChangedOnRotation() {
126         final Instrumentation instrumentation = getInstrumentation();
127         final Context context = instrumentation.getContext();
128         final Intent intent = new Intent().setComponent(
129                 new ComponentName(context, ActivityOptionsTest.MainActivity.class))
130                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
131         final Activity activity = instrumentation.startActivitySync(intent);
132         final SurfaceView sv = new SurfaceView(activity);
133         final AtomicInteger surfaceChangedCount = new AtomicInteger();
134         instrumentation.runOnMainSync(() -> activity.setContentView(sv));
135         sv.getHolder().addCallback(new SurfaceHolder.Callback() {
136             @Override
137             public void surfaceCreated(@NonNull SurfaceHolder holder) {
138             }
139             @Override
140             public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
141                     int height) {
142                 surfaceChangedCount.getAndIncrement();
143                 Log.i("surfaceChanged", "width=" + width + " height=" + height
144                         + " getTransformHint="
145                         + sv.getViewRootImpl().getSurfaceControl().getTransformHint());
146             }
147             @Override
148             public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
149             }
150         });
151         final int rotation = activity.getResources().getConfiguration()
152                 .windowConfiguration.getRotation();
153         activity.setRequestedOrientation(activity.getResources().getConfiguration().orientation
154                 == Configuration.ORIENTATION_PORTRAIT
155                 ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
156                 : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
157         instrumentation.getUiAutomation().syncInputTransactions();
158         instrumentation.waitForIdleSync();
159         final int newRotation = activity.getResources().getConfiguration()
160                 .windowConfiguration.getRotation();
161         if (rotation == newRotation) {
162             // The device might not support requested orientation.
163             activity.finishAndRemoveTask();
164             return;
165         }
166         final int count = surfaceChangedCount.get();
167         activity.moveTaskToBack(true /* nonRoot */);
168         instrumentation.getUiAutomation().syncInputTransactions();
169         context.startActivity(intent);
170         instrumentation.getUiAutomation().syncInputTransactions();
171         final int countAfterToFront = count - surfaceChangedCount.get();
172         activity.finishAndRemoveTask();
173 
174         // The first count is triggered from creation, so the target number is 2.
175         if (count > 2) {
176             fail("More than once surfaceChanged for rotation change: " + count);
177         }
178         if (countAfterToFront > 1) {
179             fail("More than once surfaceChanged for app transition with rotation change: "
180                     + countAfterToFront);
181         }
182     }
183 
buildTestSurface()184     private SurfaceControl buildTestSurface() {
185         return new SurfaceControl.Builder()
186                 .setContainerLayer()
187                 .setName("SurfaceControlTests")
188                 .setCallsite("SurfaceControlTests")
189                 .build();
190     }
191 }
192