blob: 33cc1b7dc8c2ea5385470bd17649dfb508872b2a [file] [log] [blame]
/*
* Copyright (C) 2015 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.media.cts;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.ContentResolver;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.provider.Settings;
import com.google.android.collect.Lists;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
public class Utils {
private static final String TAG = "CtsMediaTestUtil";
private static final int TEST_TIMING_TOLERANCE_MS = 50;
public static void enableAppOps(String packageName, String operation,
Instrumentation instrumentation) {
setAppOps(packageName, operation, instrumentation, true);
}
public static void disableAppOps(String packageName, String operation,
Instrumentation instrumentation) {
setAppOps(packageName, operation, instrumentation, false);
}
public static String convertStreamToString(InputStream is) {
try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
return scanner.hasNext() ? scanner.next() : "";
}
}
private static void setAppOps(String packageName, String operation,
Instrumentation instrumentation, boolean enable) {
StringBuilder cmd = new StringBuilder();
cmd.append("appops set ");
cmd.append(packageName);
cmd.append(" ");
cmd.append(operation);
cmd.append(enable ? " allow" : " deny");
instrumentation.getUiAutomation().executeShellCommand(cmd.toString());
StringBuilder query = new StringBuilder();
query.append("appops get ");
query.append(packageName);
query.append(" ");
query.append(operation);
String queryStr = query.toString();
String expectedResult = enable ? "allow" : "deny";
String result = "";
while(!result.contains(expectedResult)) {
ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(
queryStr);
InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
result = convertStreamToString(inputStream);
}
}
protected static void toggleNotificationPolicyAccess(String packageName,
Instrumentation instrumentation, boolean on) throws IOException {
// Read the setting listing the package allowed to manage notification policy configuration
String alreadyEnabledServices = querryNotificationPolicyAccessPakages(instrumentation);
// The property is a list of : separated package
List<String> enabledServices = Lists.newArrayList(alreadyEnabledServices.split(":"));
// Actually add or remove the package from the list
if (on) {
// Only add the package if it is not already in the list
if (!enabledServices.contains(packageName)) {
enabledServices.add(packageName);
setNotificationPolicyAccessPackages(enabledServices, instrumentation);
}
} else {
// Remove all instance of the package in the list
if (enabledServices.removeIf(packageName::equals)) {
// Only update the settings if there was a change
setNotificationPolicyAccessPackages(enabledServices, instrumentation);
}
}
}
/** Read the setting listing the package allowed to manage notification policy configuration */
private static String querryNotificationPolicyAccessPakages(Instrumentation instrumentation) {
ContentResolver cr = instrumentation.getContext().getContentResolver();
String enabledService = Settings.Secure.getString(
cr,Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES);
// A non existing property is equivalent to no package listed
if (enabledService == null) {
enabledService = "";
}
return enabledService;
}
private static void setNotificationPolicyAccessPackages(final List<String> enabledServicesList,
final Instrumentation instrumentation) throws IOException {
// Format the list back to a string
String enabledServices = String.join(":", enabledServicesList);
// If the list is empty, remove the property by setting it to null
String enabledServicesStrOrNull = enabledServices.isEmpty() ? "null" : enabledServices;
// Write back the property to the settings database
String command = "settings --user cur put secure "
+ Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES + " "
+ enabledServicesStrOrNull;
// Get permission to enable accessibility
UiAutomation uiAutomation = instrumentation.getUiAutomation();
// Execute command
try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
Assert.assertNotNull("Failed to execute shell command: " + command, fd);
// Wait for the command to finish by reading until EOF
try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
byte[] buffer = new byte[4096];
while (in.read(buffer) > 0) {}
} catch (IOException e) {
throw new IOException("Could not read stdout of command: " + command, e);
}
} finally {
uiAutomation.destroy();
}
// Read the settings again to make sure it is updated
String nowEnabledServices = querryNotificationPolicyAccessPakages(instrumentation);
Assert.assertEquals("Wrote setting should be the same as the read one",
enabledServices, nowEnabledServices);
}
/**
* Assert that a media playback is started and an active {@link AudioPlaybackConfiguration}
* is created once. The playback will be stopped immediately after that.
* <p>For a media session to receive media button events, an actual playback is needed.
*/
static void assertMediaPlaybackStarted(Context context) {
final AudioManager am = new AudioManager(context);
final HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback();
MediaPlayer mediaPlayer = null;
try {
final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size();
final Handler handler = new Handler(handlerThread.getLooper());
am.registerAudioPlaybackCallback(callback, handler);
mediaPlayer = MediaPlayer.create(context, R.raw.sine1khzs40dblong);
mediaPlayer.start();
if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS)
|| callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) {
Assert.fail("Failed to create an active AudioPlaybackConfiguration");
}
} catch (InterruptedException e) {
Assert.fail("Failed to create an active AudioPlaybackConfiguration");
} finally {
am.unregisterAudioPlaybackCallback(callback);
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
handlerThread.quitSafely();
}
}
private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private int mActiveConfigSize;
@Override
public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
// For non-framework apps, only anonymized active AudioPlaybackCallbacks will be
// notified.
mActiveConfigSize = configs.size();
mCountDownLatch.countDown();
}
}
}