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