1 /* 2 * Copyright (C) 2015 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.cts; 18 19 import android.app.Instrumentation; 20 import android.app.NotificationManager; 21 import android.app.UiAutomation; 22 import android.content.Context; 23 import android.media.AudioManager; 24 import android.media.AudioPlaybackConfiguration; 25 import android.media.MediaPlayer; 26 import android.media.session.MediaSessionManager.RemoteUserInfo; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.ParcelFileDescriptor; 32 import android.platform.test.annotations.AppModeFull; 33 import android.util.Log; 34 import androidx.test.platform.app.InstrumentationRegistry; 35 import java.io.File; 36 import java.io.FileInputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.util.List; 40 import java.util.Scanner; 41 import java.util.concurrent.CountDownLatch; 42 import java.util.concurrent.TimeUnit; 43 44 import junit.framework.Assert; 45 46 public class Utils { 47 private static final String TAG = "CtsMediaTestUtil"; 48 private static final int TEST_TIMING_TOLERANCE_MS = 500; 49 private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path"; 50 enableAppOps(String packageName, String operation, Instrumentation instrumentation)51 public static void enableAppOps(String packageName, String operation, 52 Instrumentation instrumentation) { 53 setAppOps(packageName, operation, instrumentation, true); 54 } 55 disableAppOps(String packageName, String operation, Instrumentation instrumentation)56 public static void disableAppOps(String packageName, String operation, 57 Instrumentation instrumentation) { 58 setAppOps(packageName, operation, instrumentation, false); 59 } 60 convertStreamToString(InputStream is)61 public static String convertStreamToString(InputStream is) { 62 try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) { 63 return scanner.hasNext() ? scanner.next() : ""; 64 } 65 } 66 getMediaPath()67 public static String getMediaPath() { 68 Bundle bundle = InstrumentationRegistry.getArguments(); 69 String mediaPath = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY); 70 Log.i(TAG, "Media Path value is: " + mediaPath); 71 72 if (mediaPath != null && !mediaPath.isEmpty()) { 73 if (mediaPath.startsWith("http") || mediaPath.startsWith("file")) { 74 return mediaPath; 75 } 76 // Otherwise, assume a file path that is not already Uri formatted 77 return Uri.fromFile(new File(mediaPath)).toString(); 78 } 79 return "https://storage.googleapis.com/wvmedia"; 80 } 81 setAppOps(String packageName, String operation, Instrumentation instrumentation, boolean enable)82 private static void setAppOps(String packageName, String operation, 83 Instrumentation instrumentation, boolean enable) { 84 StringBuilder cmd = new StringBuilder(); 85 cmd.append("appops set "); 86 cmd.append(packageName); 87 cmd.append(" "); 88 cmd.append(operation); 89 cmd.append(enable ? " allow" : " deny"); 90 instrumentation.getUiAutomation().executeShellCommand(cmd.toString()); 91 92 StringBuilder query = new StringBuilder(); 93 query.append("appops get "); 94 query.append(packageName); 95 query.append(" "); 96 query.append(operation); 97 String queryStr = query.toString(); 98 99 String expectedResult = enable ? "allow" : "deny"; 100 String result = ""; 101 while(!result.contains(expectedResult)) { 102 ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand( 103 queryStr); 104 InputStream inputStream = new FileInputStream(pfd.getFileDescriptor()); 105 result = convertStreamToString(inputStream); 106 } 107 } 108 toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)109 protected static void toggleNotificationPolicyAccess(String packageName, 110 Instrumentation instrumentation, boolean on) throws IOException { 111 112 String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName; 113 114 // Get permission to enable accessibility 115 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 116 // Execute command 117 try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) { 118 Assert.assertNotNull("Failed to execute shell command: " + command, fd); 119 // Wait for the command to finish by reading until EOF 120 try (InputStream in = new FileInputStream(fd.getFileDescriptor())) { 121 byte[] buffer = new byte[4096]; 122 while (in.read(buffer) > 0) {} 123 } catch (IOException e) { 124 throw new IOException("Could not read stdout of command: " + command, e); 125 } 126 } finally { 127 uiAutomation.destroy(); 128 } 129 130 NotificationManager nm = (NotificationManager) instrumentation.getContext() 131 .getSystemService(Context.NOTIFICATION_SERVICE); 132 Assert.assertEquals("Wrote setting should be the same as the read one", on, 133 nm.isNotificationPolicyAccessGranted()); 134 } 135 compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b)136 static boolean compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b) { 137 if (a == null && b == null) { 138 return true; 139 } else if (a == null || b == null) { 140 return false; 141 } 142 return a.getPackageName().equals(b.getPackageName()) 143 && a.getPid() == b.getPid() 144 && a.getUid() == b.getUid(); 145 } 146 147 /** 148 * Assert that a media playback is started and an active {@link AudioPlaybackConfiguration} 149 * is created once. The playback will be stopped immediately after that. 150 * <p>For a media session to receive media button events, an actual playback is needed. 151 */ 152 @AppModeFull(reason = "Instant apps cannot access the SD card") assertMediaPlaybackStarted(Context context)153 static void assertMediaPlaybackStarted(Context context) { 154 final AudioManager am = new AudioManager(context); 155 final HandlerThread handlerThread = new HandlerThread(TAG); 156 handlerThread.start(); 157 final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback(); 158 MediaPlayer mediaPlayer = null; 159 final String mInpPrefix = WorkDir.getMediaDirString(); 160 161 try { 162 final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size(); 163 final Handler handler = new Handler(handlerThread.getLooper()); 164 165 am.registerAudioPlaybackCallback(callback, handler); 166 mediaPlayer = MediaPlayer.create(context, Uri.fromFile(new File(mInpPrefix + 167 "sine1khzm40db.wav"))); 168 mediaPlayer.start(); 169 if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS) 170 || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) { 171 Assert.fail("Failed to create an active AudioPlaybackConfiguration"); 172 } 173 } catch (InterruptedException e) { 174 Assert.fail("Failed to create an active AudioPlaybackConfiguration"); 175 } finally { 176 am.unregisterAudioPlaybackCallback(callback); 177 if (mediaPlayer != null) { 178 mediaPlayer.stop(); 179 mediaPlayer.release(); 180 mediaPlayer = null; 181 } 182 handlerThread.quitSafely(); 183 } 184 } 185 186 private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback { 187 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 188 private int mActiveConfigSize; 189 190 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs)191 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 192 // For non-framework apps, only anonymized active AudioPlaybackCallbacks will be 193 // notified. 194 mActiveConfigSize = configs.size(); 195 mCountDownLatch.countDown(); 196 } 197 } 198 } 199