| /* |
| * Copyright (C) 2019 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 android.host.systemui; |
| |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.server.wm.DisplayContentProto; |
| import com.android.server.wm.IdentifierProto; |
| import com.android.server.wm.RootWindowContainerProto; |
| import com.android.server.wm.WindowManagerServiceDumpProto; |
| import com.android.server.wm.WindowStateProto; |
| import com.android.server.wm.WindowTokenProto; |
| import com.android.tradefed.device.CollectingByteOutputReceiver; |
| import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; |
| import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; |
| |
| import org.junit.After; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| @RunWith(DeviceJUnit4ClassRunner.class) |
| public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test { |
| private static final String SHELL_AM_START_FG_SERVICE = |
| "am start-foreground-service -n %s -a %s"; |
| private static final String SHELL_AM_FORCE_STOP = |
| "am force-stop %s"; |
| private static final String SHELL_DUMPSYS_WINDOW = "dumpsys window --proto"; |
| private static final String SHELL_PID_OF = "pidof %s"; |
| |
| private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; |
| |
| private static final String AUDIO_RECORDER_AR_PACKAGE_NAME = |
| "android.systemui.cts.audiorecorder.audiorecord"; |
| private static final String AUDIO_RECORDER_MR_PACKAGE_NAME = |
| "android.systemui.cts.audiorecorder.mediarecorder"; |
| private static final String AUDIO_RECORDER_AR_SERVICE_COMPONENT = |
| AUDIO_RECORDER_AR_PACKAGE_NAME + "/.AudioRecorderService"; |
| private static final String AUDIO_RECORDER_MR_SERVICE_COMPONENT = |
| AUDIO_RECORDER_MR_PACKAGE_NAME + "/.AudioRecorderService"; |
| private static final String AUDIO_RECORDER_ACTION_START = |
| "android.systemui.cts.audiorecorder.ACTION_START"; |
| private static final String AUDIO_RECORDER_ACTION_STOP = |
| "android.systemui.cts.audiorecorder.ACTION_STOP"; |
| private static final String AUDIO_RECORDER_ACTION_THROW = |
| "android.systemui.cts.audiorecorder.ACTION_THROW"; |
| |
| private static final String SHELL_AR_START_REC = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_AR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_START); |
| private static final String SHELL_AR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_AR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_STOP); |
| private static final String SHELL_MR_START_REC = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_MR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_START); |
| private static final String SHELL_MR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_MR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_STOP); |
| private static final String SHELL_AR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP, |
| AUDIO_RECORDER_AR_PACKAGE_NAME); |
| private static final String SHELL_MR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP, |
| AUDIO_RECORDER_MR_PACKAGE_NAME); |
| private static final String SHELL_AR_THROW = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_AR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_THROW); |
| private static final String SHELL_MR_THROW = String.format(SHELL_AM_START_FG_SERVICE, |
| AUDIO_RECORDER_MR_SERVICE_COMPONENT, |
| AUDIO_RECORDER_ACTION_THROW); |
| |
| private static final String WINDOW_TITLE_MIC_INDICATOR = "MicrophoneCaptureIndicator"; |
| |
| private static final long ONE_SECOND = 1000L; |
| private static final long THREE_SECONDS = 3 * ONE_SECOND; |
| private static final long FIVE_SECONDS = 5 * ONE_SECOND; |
| private static final long THREE_HUNDRED_MILLISECONDS = (long) (0.3 * ONE_SECOND); |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingAudioRecordApi() throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC, |
| SHELL_AR_STOP_REC); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingMediaRecorderApi() throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC, |
| SHELL_MR_STOP_REC); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndForceStopped() |
| throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC, |
| SHELL_AR_FORCE_STOP); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndForceStopped() |
| throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC, |
| SHELL_MR_FORCE_STOP); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndCrashed() throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC, |
| SHELL_AR_THROW); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndCrashed() throws Exception { |
| runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC, |
| SHELL_MR_THROW); |
| } |
| |
| @Test |
| public void testIndicatorShownWhileRecordingUsingBothApisSimultaneously() throws Exception { |
| assumeTrue("Not running on a Leanback (TV) device", |
| getDevice().hasFeature(FEATURE_LEANBACK_ONLY)); |
| |
| // Check that the indicator isn't shown initially |
| assertIndicatorInvisible(); |
| |
| // Start recording using MediaRecorder API |
| getDevice().executeShellCommand(SHELL_MR_START_REC); |
| |
| // Wait for the application to be launched |
| waitForProcessToComeAlive(AUDIO_RECORDER_MR_PACKAGE_NAME); |
| |
| // Wait for a second, and then check that the indicator is shown |
| Thread.sleep(ONE_SECOND); |
| assertIndicatorVisible(); |
| |
| // Start recording using AudioRecord API |
| getDevice().executeShellCommand(SHELL_AR_START_REC); |
| |
| // Wait for the application to be launched |
| waitForProcessToComeAlive(AUDIO_RECORDER_AR_PACKAGE_NAME); |
| |
| // Check that the indicator is still shown |
| assertIndicatorVisible(); |
| |
| // Check 3 more times that the indicator remains shown |
| for (int i = 0; i < 3; i++) { |
| Thread.sleep(ONE_SECOND); |
| assertIndicatorVisible(); |
| } |
| |
| // Stop recording using MediaRecorder API |
| getDevice().executeShellCommand(SHELL_MR_STOP_REC); |
| |
| // check that the indicator is still shown |
| assertIndicatorVisible(); |
| |
| // Check 3 more times that the indicator remains shown |
| for (int i = 0; i < 3; i++) { |
| Thread.sleep(ONE_SECOND); |
| assertIndicatorVisible(); |
| } |
| |
| // Stop recording using AudioRecord API |
| getDevice().executeShellCommand(SHELL_AR_STOP_REC); |
| |
| // Wait for five seconds and make sure that the indicator is not shown |
| Thread.sleep(FIVE_SECONDS); |
| assertIndicatorInvisible(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| // Kill both apps |
| getDevice().executeShellCommand(SHELL_AR_FORCE_STOP); |
| getDevice().executeShellCommand(SHELL_MR_FORCE_STOP); |
| } |
| |
| private void runSimpleStartStopTestRoutine(String packageName, String startCommand, |
| String stopCommand) throws Exception { |
| assumeTrue("Not running on a Leanback (TV) device", |
| getDevice().hasFeature(FEATURE_LEANBACK_ONLY)); |
| |
| // Check that the indicator isn't shown initially |
| assertIndicatorInvisible(); |
| |
| // Start recording using AudioRecord API |
| getDevice().executeShellCommand(startCommand); |
| |
| // Wait for the application to be launched |
| waitForProcessToComeAlive(packageName); |
| |
| // Wait for a second, and then check that the indicator is shown, repeat 2 more times |
| for (int i = 0; i < 3; i++) { |
| Thread.sleep(ONE_SECOND); |
| assertIndicatorVisible(); |
| } |
| |
| // Stop recording (this may either send a command to the app to stop recording or command |
| // to crash or force-stop the app) |
| getDevice().executeShellCommand(stopCommand); |
| |
| // Wait for five seconds and make sure that the indicator is not shown |
| Thread.sleep(FIVE_SECONDS); |
| assertIndicatorInvisible(); |
| } |
| |
| private void waitForProcessToComeAlive(String appPackageName) throws Exception { |
| final String pidofCommand = String.format(SHELL_PID_OF, appPackageName); |
| |
| long waitTime = 0; |
| while (waitTime < THREE_SECONDS) { |
| Thread.sleep(THREE_HUNDRED_MILLISECONDS); |
| |
| final String pid = getDevice().executeShellCommand(pidofCommand).trim(); |
| if (!pid.isEmpty()) { |
| // Process is running |
| return; |
| } |
| waitTime += THREE_HUNDRED_MILLISECONDS; |
| } |
| |
| fail("The process for " + appPackageName |
| + " should have come alive within 3 secs of launching the app."); |
| } |
| |
| private void assertIndicatorVisible() throws Exception { |
| final WindowStateProto window = getMicCaptureIndicatorWindow(true); |
| |
| assertNotNull("\"MicrophoneCaptureIndicator\" window does not exist", window); |
| assertTrue("\"MicrophoneCaptureIndicator\" window is not visible", |
| window.getIsVisible()); |
| assertTrue("\"MicrophoneCaptureIndicator\" window is not on screen", |
| window.getIsOnScreen()); |
| } |
| |
| private void assertIndicatorInvisible() throws Exception { |
| final WindowStateProto window = getMicCaptureIndicatorWindow(false); |
| if (window == null) { |
| // If window is not present, that's fine, there is no need to check anything else. |
| return; |
| } |
| |
| assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be visible", |
| window.getIsVisible()); |
| assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be present on screen", |
| window.getIsOnScreen()); |
| } |
| |
| private WindowStateProto getMicCaptureIndicatorWindow(boolean aboveAppOnly) throws Exception { |
| final WindowManagerServiceDumpProto dump = getDump(); |
| final RootWindowContainerProto root = dump.getRootWindowContainer(); |
| final List<DisplayContentProto> displays = root.getDisplaysList(); |
| for (DisplayContentProto display : displays) { |
| final List<WindowTokenProto> tokens; |
| if (aboveAppOnly) { |
| tokens = display.getAboveAppWindowsList(); |
| } else { |
| tokens = new ArrayList<>(); |
| tokens.addAll(display.getAboveAppWindowsList()); |
| tokens.addAll(display.getImeWindowsList()); |
| tokens.addAll(display.getBelowAppWindowsList()); |
| } |
| |
| for (WindowTokenProto token : tokens) { |
| for (WindowStateProto window : token.getWindowsList()) { |
| final IdentifierProto identifier = window.getIdentifier(); |
| final String title = identifier.getTitle(); |
| if (WINDOW_TITLE_MIC_INDICATOR.equals(title)) { |
| return window; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private WindowManagerServiceDumpProto getDump() throws Exception { |
| final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); |
| getDevice().executeShellCommand(SHELL_DUMPSYS_WINDOW, receiver); |
| return WindowManagerServiceDumpProto.parser().parseFrom(receiver.getOutput()); |
| } |
| } |