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