• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2024 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 android.media.cujcommon.cts;
18 
19 import android.app.Instrumentation;
20 import android.os.Looper;
21 import android.os.SystemClock;
22 import android.util.DisplayMetrics;
23 import android.util.Log;
24 import android.view.MotionEvent;
25 import android.view.MotionEvent.PointerCoords;
26 import android.view.MotionEvent.PointerProperties;
27 import android.view.ScaleGestureDetector;
28 
29 import androidx.annotation.NonNull;
30 import androidx.media3.common.Player;
31 
32 import java.time.Duration;
33 
34 public class PinchToZoomTestPlayerListener extends PlayerListener {
35 
36   private static final String TAG = PinchToZoomTestPlayerListener.class.getSimpleName();
37   private static final Duration ZOOM_IN_DURATION = Duration.ofSeconds(4);
38   private static final int PINCH_STEP_COUNT = 10;
39   private static final float SPAN_GAP = 50.0f;
40   private static final float LEFT_MARGIN_WIDTH_FACTOR = 0.1f;
41   private static final float RIGHT_MARGIN_WIDTH_FACTOR = 0.9f;
42 
43   private int mXStart;
44   private int mYStart;
45   private int mWidth;
46   private int mHeight;
47   private float mStepSize;
48 
PinchToZoomTestPlayerListener(Duration sendMessagePosition)49   public PinchToZoomTestPlayerListener(Duration sendMessagePosition) {
50     super();
51     this.mSendMessagePosition = sendMessagePosition;
52   }
53 
54   /**
55    * Return a new pointer of the display.
56    *
57    * @param x x coordinate of the pointer
58    * @param y y coordinate of the pointer
59    */
getDisplayPointer(float x, float y)60   PointerCoords getDisplayPointer(float x, float y) {
61     PointerCoords pointerCoords = new PointerCoords();
62     pointerCoords.x = x;
63     pointerCoords.y = y;
64     pointerCoords.pressure = 1;
65     pointerCoords.size = 1;
66     return pointerCoords;
67   }
68 
69   @Override
getTestType()70   public TestType getTestType() {
71     return TestType.PINCH_TO_ZOOM_TEST;
72   }
73 
74   @Override
onEventsPlaybackStateChanged(@onNull Player player)75   public void onEventsPlaybackStateChanged(@NonNull Player player) {
76     if (mExpectedTotalTime == 0 && player.getPlaybackState() == Player.STATE_READY) {
77       // At the first media transition player is not ready. So, add duration of
78       // first clip when player is ready
79       mExpectedTotalTime += player.getDuration();
80       // Register scale gesture detector
81       mActivity.mScaleGestureDetector = new ScaleGestureDetector(mActivity,
82           new ScaleGestureListener(mActivity.mExoplayerView));
83       // Adjust the touch input region.
84       setInputRegionSize();
85     }
86   }
87 
88   @Override
onEventsMediaItemTransition(@onNull Player player)89   public void onEventsMediaItemTransition(@NonNull Player player) {
90     mActivity.mPlayer.createMessage((messageType, payload) -> {
91           // Programmatically pinch and zoom in
92           pinchAndZoom(true /* zoomIn */);
93         }).setLooper(Looper.getMainLooper()).setPosition(mSendMessagePosition.toMillis())
94         .setDeleteAfterDelivery(true)
95         .send();
96     mActivity.mPlayer.createMessage((messageType, payload) -> {
97           // Programmatically pinch and zoom out
98           pinchAndZoom(false /* zoomOut */);
99         }).setLooper(Looper.getMainLooper())
100         .setPosition(mSendMessagePosition.plus(ZOOM_IN_DURATION).toMillis())
101         .setDeleteAfterDelivery(true)
102         .send();
103   }
104 
105   /** Adjusts the touchable region size, based on the main activity's display metrics. */
setInputRegionSize()106   private void setInputRegionSize() {
107     int[] loc = new int[2];
108     mActivity.getWindow().getDecorView().getRootView().getLocationOnScreen(loc);
109     mXStart = loc[0];
110     mYStart = loc[1];
111     DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
112     mWidth = displayMetrics.widthPixels;
113     mHeight = displayMetrics.heightPixels;
114     mStepSize = (RIGHT_MARGIN_WIDTH_FACTOR * mWidth - LEFT_MARGIN_WIDTH_FACTOR * mWidth
115             - 2 * SPAN_GAP) / (2 * PINCH_STEP_COUNT);
116     Log.i(TAG, "Set the touchable region: x = " + mXStart + ", y = " + mYStart
117             + ", width=" + mWidth + ", height=" + mHeight + ", stepSize=" + mStepSize);
118   }
119 
120   /**
121    * Create a new MotionEvent, filling in all of the basic values that define the motion. Then,
122    * dispatch a pointer event into a window owned by the instrumented application.
123    *
124    * @param inst              An instance of {@link Instrumentation} for sending pointer event.
125    * @param action            The kind of action being performed.
126    * @param pointerCount      The number of pointers that will be in this event.
127    * @param pointerProperties An array of <em>pointerCount</em> values providing a
128    *                          {@link PointerProperties} property object for each pointer, which must
129    *                          include the pointer identifier.
130    * @param pointerCoords     An array of <em>pointerCount</em> values providing a
131    *                          {@link PointerCoords} coordinate object for each pointer.
132    */
obtainAndSendPointerEvent(Instrumentation inst, int action, int pointerCount, PointerProperties[] pointerProperties, PointerCoords[] pointerCoords)133   void obtainAndSendPointerEvent(Instrumentation inst, int action, int pointerCount,
134       PointerProperties[] pointerProperties, PointerCoords[] pointerCoords) {
135     MotionEvent pointerMotionEvent = MotionEvent.obtain(SystemClock.uptimeMillis() /* downTime */,
136         SystemClock.uptimeMillis() /* eventTime */, action, pointerCount, pointerProperties,
137         pointerCoords, 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */,
138         1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, 0 /* source */,
139         mActivity.getDisplayId(), 0 /* flags */);
140     inst.sendPointerSync(pointerMotionEvent);
141   }
142 
143   /**
144    * Return array of two PointerCoords.
145    *
146    * @param isZoomIn  True for zoom in.
147    */
getPointerCoords(boolean isZoomIn)148   PointerCoords[] getPointerCoords(boolean isZoomIn) {
149     PointerCoords leftPointerStartCoords;
150     PointerCoords rightPointerStartCoords;
151     float midDisplayHeight = mYStart + mHeight / 2.0f;
152     if (isZoomIn) {
153       float midDisplayWidth = mXStart + mWidth / 2.0f;
154       // During zoom in, start pinching from middle of the display towards the end.
155       leftPointerStartCoords = getDisplayPointer(midDisplayWidth - SPAN_GAP, midDisplayHeight);
156       rightPointerStartCoords = getDisplayPointer(midDisplayWidth + SPAN_GAP, midDisplayHeight);
157     } else {
158       // During zoom out, start pinching from end of the display towards the middle.
159       leftPointerStartCoords = getDisplayPointer(mXStart + LEFT_MARGIN_WIDTH_FACTOR * mWidth,
160           midDisplayHeight);
161       rightPointerStartCoords = getDisplayPointer(mXStart + RIGHT_MARGIN_WIDTH_FACTOR * mWidth,
162           midDisplayHeight);
163     }
164     return new PointerCoords[]{leftPointerStartCoords, rightPointerStartCoords};
165   }
166 
167   /**
168    * Return array of two PointerProperties.
169    */
getPointerProperties()170   PointerProperties[] getPointerProperties() {
171     PointerProperties defaultPointerProperties = new PointerProperties();
172     defaultPointerProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
173     PointerProperties leftPointerProperties = new PointerProperties(defaultPointerProperties);
174     leftPointerProperties.id = 0;
175     PointerProperties rightPointerProperties = new PointerProperties(defaultPointerProperties);
176     rightPointerProperties.id = 1;
177     return new PointerProperties[]{leftPointerProperties, rightPointerProperties};
178   }
179 
180   /**
181    * Simulate pinch gesture to zoom in and zoom out.
182    *
183    * @param isZoomIn  True for zoom in.
184    */
pinchAndZoom(boolean isZoomIn)185   private void pinchAndZoom(boolean isZoomIn) {
186     new Thread(() -> {
187       try {
188         PointerCoords[] pointerCoords = getPointerCoords(isZoomIn);
189         PointerProperties[] pointerProperties = getPointerProperties();
190 
191         Instrumentation inst = new Instrumentation();
192         // Pinch In
193         obtainAndSendPointerEvent(inst, MotionEvent.ACTION_DOWN, 1 /* pointerCount*/,
194             pointerProperties, pointerCoords);
195         obtainAndSendPointerEvent(inst, MotionEvent.ACTION_POINTER_DOWN + (pointerProperties[1].id
196                 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 2 /* pointerCount */, pointerProperties,
197             pointerCoords);
198 
199         for (int i = 0; i < PINCH_STEP_COUNT; i++) {
200           if (isZoomIn) {
201             pointerCoords[0].x -= mStepSize;
202             pointerCoords[1].x += mStepSize;
203           } else {
204             pointerCoords[0].x += mStepSize;
205             pointerCoords[1].x -= mStepSize;
206           }
207           obtainAndSendPointerEvent(inst, MotionEvent.ACTION_MOVE, 2 /* pointerCount */,
208               pointerProperties, pointerCoords);
209         }
210 
211         // Pinch Out
212         obtainAndSendPointerEvent(inst, MotionEvent.ACTION_POINTER_UP + (pointerProperties[1].id
213                 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 2 /* pointerCount */, pointerProperties,
214             pointerCoords);
215         obtainAndSendPointerEvent(inst, MotionEvent.ACTION_UP, 1 /* pointerCount */,
216             pointerProperties, pointerCoords);
217       } catch (Exception e) {
218         throw new RuntimeException(e);
219       }
220     }).start();
221   }
222 }
223