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