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.content.res.Resources; 24 import android.media.AudioManager; 25 import android.media.AudioPlaybackConfiguration; 26 import android.media.MediaPlayer; 27 import android.media.session.MediaSessionManager.RemoteUserInfo; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.ParcelFileDescriptor; 32 import android.os.UserHandle; 33 import android.util.Log; 34 35 import com.android.compatibility.common.util.AmUtils; 36 37 import junit.framework.Assert; 38 39 import java.io.File; 40 import java.io.FileInputStream; 41 import java.io.FileWriter; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.util.List; 45 import java.util.Scanner; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.TimeUnit; 48 49 public class Utils { 50 private static final String TAG = "CtsMediaTestUtil"; 51 private static final int TEST_TIMING_TOLERANCE_MS = 500; 52 private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path"; 53 54 public static final Uri RINGTONE_TEST_URI = Uri.parse("content://cts/ringtone/"); 55 enableAppOps(String packageName, String operation, Instrumentation instrumentation)56 public static void enableAppOps(String packageName, String operation, 57 Instrumentation instrumentation) { 58 setAppOps(packageName, operation, instrumentation, true); 59 } 60 disableAppOps(String packageName, String operation, Instrumentation instrumentation)61 public static void disableAppOps(String packageName, String operation, 62 Instrumentation instrumentation) { 63 setAppOps(packageName, operation, instrumentation, false); 64 } 65 convertStreamToString(InputStream is)66 public static String convertStreamToString(InputStream is) { 67 try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) { 68 return scanner.hasNext() ? scanner.next() : ""; 69 } 70 } 71 setAppOps(String packageName, String operation, Instrumentation instrumentation, boolean enable)72 private static void setAppOps(String packageName, String operation, 73 Instrumentation instrumentation, boolean enable) { 74 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 75 StringBuilder cmd = new StringBuilder(); 76 cmd.append("appops set --user "); 77 cmd.append(UserHandle.myUserId()); 78 cmd.append(" "); 79 cmd.append(packageName); 80 cmd.append(" "); 81 cmd.append(operation); 82 cmd.append(enable ? " allow" : " deny"); 83 try (InputStream inputStream = 84 new ParcelFileDescriptor.AutoCloseInputStream( 85 uiAutomation.executeShellCommand(cmd.toString()))) { 86 String result = convertStreamToString(inputStream); 87 if (!result.isEmpty()) { 88 Log.e(TAG, result); 89 return; 90 } 91 } catch (IOException e) { 92 Log.w(TAG, "Failure closing ParcelFileDescriptor"); 93 } 94 95 StringBuilder query = new StringBuilder(); 96 query.append("appops get "); 97 query.append(packageName); 98 query.append(" "); 99 query.append(operation); 100 String queryStr = query.toString(); 101 102 String expectedResult = enable ? "allow" : "deny"; 103 try (InputStream inputStream = 104 new ParcelFileDescriptor.AutoCloseInputStream( 105 uiAutomation.executeShellCommand(queryStr.toString()))) { 106 if (!convertStreamToString(inputStream).contains(expectedResult)) { 107 Log.w(TAG, "setAppOps did not return " + expectedResult); 108 } 109 } catch (IOException e) { 110 Log.w(TAG, "Failure closing ParcelFileDescriptor"); 111 } 112 } 113 toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)114 public static void toggleNotificationPolicyAccess(String packageName, 115 Instrumentation instrumentation, boolean on) throws IOException { 116 117 int userId = instrumentation.getTargetContext().getUserId(); 118 String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName 119 + " " + userId; 120 121 // Get permission to enable accessibility 122 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 123 // Execute command 124 try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) { 125 Assert.assertNotNull("Failed to execute shell command: " + command, fd); 126 // Wait for the command to finish by reading until EOF 127 try (InputStream in = new FileInputStream(fd.getFileDescriptor())) { 128 byte[] buffer = new byte[4096]; 129 while (in.read(buffer) > 0) {} 130 } catch (IOException e) { 131 throw new IOException("Could not read stdout of command: " + command, e); 132 } 133 } finally { 134 uiAutomation.destroy(); 135 } 136 137 AmUtils.waitForBroadcastBarrier(); 138 139 NotificationManager nm = (NotificationManager) instrumentation.getContext() 140 .getSystemService(Context.NOTIFICATION_SERVICE); 141 Assert.assertEquals("Notification Policy Access Grant is " 142 + nm.isNotificationPolicyAccessGranted() + " not " + on + " for " 143 + packageName, on, nm.isNotificationPolicyAccessGranted()); 144 } 145 compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b)146 public static boolean compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b) { 147 if (a == null && b == null) { 148 return true; 149 } else if (a == null || b == null) { 150 return false; 151 } 152 return a.getPackageName().equals(b.getPackageName()) 153 && a.getPid() == b.getPid() 154 && a.getUid() == b.getUid(); 155 } 156 157 /** 158 * Assert that a media playback is started and an active {@link AudioPlaybackConfiguration} 159 * is created once. The playback will be stopped immediately after that. 160 * <p>For a media session to receive media button events, an actual playback is needed. 161 */ assertMediaPlaybackStarted(Context context)162 public static void assertMediaPlaybackStarted(Context context) { 163 final AudioManager am = new AudioManager(context); 164 final HandlerThread handlerThread = new HandlerThread(TAG); 165 handlerThread.start(); 166 final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback(); 167 MediaPlayer mediaPlayer = null; 168 169 try { 170 final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size(); 171 final Handler handler = new Handler(handlerThread.getLooper()); 172 173 am.registerAudioPlaybackCallback(callback, handler); 174 mediaPlayer = MediaPlayer.create(context, R.raw.sine1khzs40dblong); 175 mediaPlayer.start(); 176 if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS) 177 || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) { 178 Assert.fail("Failed to create an active AudioPlaybackConfiguration"); 179 } 180 } catch (InterruptedException e) { 181 Assert.fail("Failed to create an active AudioPlaybackConfiguration"); 182 } finally { 183 am.unregisterAudioPlaybackCallback(callback); 184 if (mediaPlayer != null) { 185 mediaPlayer.stop(); 186 mediaPlayer.release(); 187 mediaPlayer = null; 188 } 189 handlerThread.quitSafely(); 190 } 191 } 192 193 /** 194 * Gets the {@link com.android.internal.R.bool#config_ringtoneVibrationSettingsSupported} value. 195 * @return {@code true} If the device supports ringtone vibration settings. 196 */ isRingtoneVibrationSupported(Context context)197 public static boolean isRingtoneVibrationSupported(Context context) { 198 try { 199 int resId = Resources.getSystem().getIdentifier( 200 "config_ringtoneVibrationSettingsSupported", "bool", "android"); 201 return context.getResources().getBoolean(resId); 202 } catch (Resources.NotFoundException e) { 203 Log.w(TAG, "Unable to read system resource " + e.getMessage()); 204 return false; 205 } 206 } 207 208 /** 209 * Returns a temp file which includes predefined the {@link android.os.VibrationEffect} 210 * information. 211 */ getTestVibrationFile()212 public static File getTestVibrationFile() throws IOException { 213 File tempFile = File.createTempFile("test_vibration_file", ".xml"); 214 FileWriter writer = new FileWriter(tempFile); 215 writer.write("<vibration-effect>\n" 216 + " <waveform-effect>\n" 217 + " <!-- PRIMING -->\n" 218 + " <waveform-entry durationMs=\"0\" amplitude=\"0\"/>\n" 219 + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" 220 + " <waveform-entry durationMs=\"250\" amplitude=\"100\"/>\n" 221 + " <waveform-entry durationMs=\"12\" amplitude=\"255\"/>\n" 222 + " <waveform-entry durationMs=\"500\" amplitude=\"100\"/>\n" 223 + " </waveform-effect>\n" 224 + "</vibration-effect>"); 225 writer.close(); 226 return tempFile; 227 } 228 229 private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback { 230 private final CountDownLatch mCountDownLatch = new CountDownLatch(1); 231 private int mActiveConfigSize; 232 233 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs)234 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 235 // For non-framework apps, only anonymized active AudioPlaybackCallbacks will be 236 // notified. 237 mActiveConfigSize = configs.size(); 238 mCountDownLatch.countDown(); 239 } 240 } 241 } 242